Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion. It also integrates with Vue’s official devtools extension to provide advanced features such as zero-config time-travel debugging and state snapshot export / importĐịnh nghĩ thì có vẻ khó hiểu như vậy nhưng ta có thể hiểu một cách nôm na Vuex là một pattern + library của Vuejs, nó có chức năng như một cái kho chứa tập trung các state của các component trong ứng dụng. Khi chúng ta cần thay đổi gì chỉ cần tương tác trực tiếp với thằng state trên store của Vuex, mà không cần phải thông qua quan hệ giữa các component.
Counter
chứa 2 chức năng là increment
– decrement
và component Result
có chức năng in ra kết quả
increment
hoặc decrement
từ Counter
lên cho App
và sau đó App
sẽ cập nhật và truyền kết quả xuống cho thằng Result
1
2
3
4
|
<script src="/path/to/vue.js"></script>
<script src="/path/to/vuex.js"></script>
|
1
2
3
|
npm install vuex --save
|
1
2
3
|
yarn add vuex
|
package.json
đã cài đặt thành công Vuex ta tạo 1 folder store
và tạo file store.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
result: 0
},
mutations: {
},
getters: {
},
actions: {
},
modules: {
}
});
|
data
chứa các biến của componet thì state ở đây cũng có thể hiểu chính là data của cả ứng dụng. Sử dụng một state duy nhất như thế này sẽ giúp ta đồng bộ được dữ liệu giữa các componet một cách nhanh chóng và chính xác.
1
2
3
4
5
|
state: {
result: 0
}
|
1
2
3
4
5
6
7
8
9
|
export default {
computed: {
result() {
return this.$store.state.result;
}
}
};
|
...Array
) cú pháp này chỉ áp dụng được trong các phiên bản javascript ES6 trở lên thôi nhe.
state
1
2
3
4
5
6
|
state: {
result: 0,
value: 'aaa'
}
|
1
2
3
4
5
6
7
8
9
10
11
|
import { mapState } from "vuex";
export default {
computed: {
localComputed () { /* ... */ },
// mix this into the outer object with the object spread operator
...mapState(["result","value"]),
c
}
};
|
1
2
3
4
5
6
7
8
|
<template>
<div>
<p>this is Result: {{result}}</p>
<p>value: {{value}}</p>
</div>
</template>
|
result
và value
đã có thể lấy ra sử dụng mà không cần phải lấy từng giá trị một nữa. Đừng quên import mapState không lại bảo sao không chạy.
Sử dụng mapState thì có thể lấy ra giá trị nhưng không thể update được đâu, Docs thì không thấy nói update bằng cách này, nhưng mình thấy từ map
mình cứ nghĩ là nó binding 2 chiều nên mình đã thử update state bằng cách này và không thấy được nên chắc nó chỉ để get state thôi.
1
2
3
4
5
6
7
|
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
|
getters
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
|
1
2
3
|
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
|
getters
với nhau:
1
2
3
4
5
6
7
8
9
10
11
|
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
|
1
2
3
|
store.getters.doneTodosCount // -> 1
|
1
2
3
4
5
6
7
|
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// mix the getters into computed with object spread operator
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
|
1
2
3
4
5
6
|
...mapGetters({
// map `this.doneCount` to `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
|
String
chính là tên của hàm mà ta muốn gọi trong mutations, nó sẽ nhận state của store làm tham số đầu tiên:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// mutate state
state.count++
}
}
})
|
1
2
3
|
store.commit('increment')
|
1
2
3
4
5
6
7
8
9
10
|
export default {
name: "counter",
methods: {
increment() {
this.$store.state.count++;
}
}
}
|
1
2
3
4
5
6
7
8
|
// ...
mutations: {
increment (state, n) {
state.count += n
}
}
|
1
2
3
|
store.commit('increment', 10)
|
1
2
3
4
5
6
7
8
|
// ...
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
|
1
2
3
|
store.commit('increment', { amount: 10, total:50 })
|
type
vậy là mutations sẽ hiểu và thực hiện mà không cần thay đổi số tham số của hàm
1
2
3
4
5
6
|
store.commit({
type: 'increment',
amount: 10
})
|
1
2
3
4
5
6
7
|
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
|
1
2
3
4
5
6
7
|
Vue.set(obj, 'newProp', 123)
OR
state.obj = { ...state.obj, newProp: 123 }
|
1
2
3
4
|
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// we can use the ES2015 computed property name feature
// to use a constant as the function name
[SOME_MUTATION] (state) {
// mutate state
}
}
})
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // map `this.increment()` to `this.$store.commit('increment')`
// `mapMutations` also supports payloads:
'incrementBy' // map `this.incrementBy(amount)` to `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // map `this.add()` to `this.$store.commit('increment')`
})
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
|
1
2
3
4
5
6
7
|
actions: {
increment ({ commit }) {
commit('increment')
}
}
|
1
2
3
|
store.dispatch('increment')
|
1
2
3
4
5
6
7
8
9
10
11
12
|
// dispatch with a payload
store.dispatch('incrementAsync', {
amount: 10
})
// dispatch with an object
store.dispatch({
type: 'incrementAsync',
amount: 10
})
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // map `this.increment()` to `this.$store.dispatch('increment')`
// mapActions` also supports payloads:
'incrementBy' // map `this.incrementBy(amount)` to `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // map `this.add()` to `this.$store.dispatch('increment')`
})
}
}
|
store.js
được chứ về sau mỗi biến trong state lại có hàng tá hàm thì rất rối. Thì Vuex đã hỗ trợ một tùy chỉnh đó là modules, ta có thể tách các hàm có chung mục đích ra một file như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> `moduleA`'s state
store.state.b // -> `moduleB`'s state
|
namespaced: true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// module assets
state: { ... }, // module state is already nested and not affected by namespace option
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// nested modules
modules: {
// inherits the namespace from parent module
myPage: {
state: { ... },
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// further nest the namespace
posts: {
namespaced: true,
state: { ... },
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
computed: {
...mapState({
a: state => state.some.nested.module.a,
b: state => state.some.nested.module.b
})
},
methods: {
...mapActions([
'some/nested/module/foo', // -> this['some/nested/module/foo']()
'some/nested/module/bar' // -> this['some/nested/module/bar']()
])
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
])
}
|
createNamespacedHelpers
. Nó sẽ trả về một đối tượng liên kết với các helper mà bạn muốn.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// look up in `some/nested/module`
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// look up in `some/nested/module`
...mapActions([
'foo',
'bar'
])
}
}
|
store.registerModule
1
2
3
4
5
6
7
8
9
10
11
|
// register a module `myModule`
store.registerModule('myModule', {
// ...
})
// register a nested module `nested/myModule`
store.registerModule(['nested', 'myModule'], {
// ...
})
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
├── index.html
├── main.js
├── api
│ └── ... # abstractions for making API requests
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # where we assemble modules and export the store
├── actions.js # root actions
├── mutations.js # root mutations
└── modules
├── cart.js # cart module
└── products.js # products module
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18 |
<! DOCTYPE html> < html > < head > < meta charset = "UTF-8" /> <!-- Put the CSS code here --> </ head > < body > <!-- Put the HTML template code here --> < script > // Put the Vue code here </ script > </ body > </ html > |
<head>
bên dưới:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 |
< style > #app { background-color: yellow; padding: 10px; } #parent { background-color: green; width: 400px; height: 300px; position: relative; padding-left: 5px; } h1 { margin-top: 0; } .child { width: 150px; height: 150px; position:absolute; top: 60px; padding: 0 5px 5px; } .childA { background-color: red; left: 20px; } .childB { background-color: blue; left: 190px; } </ style > |
<script>
, bên phải trên thẻ đóng </body>
, đưa code Vue sau đầy vào:, bên phải trên thẻ đóng . đưa code của Vue bên dưới vào:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 |
Vue.component( 'ChildB' ,{ template:` <div class = "child childB" > <h1> Score: </h1> </div>` }) Vue.component( 'ChildA' ,{ template:` <div class = "child childA" > <h1> Score: </h1> </div>` }) Vue.component( 'Parent' ,{ template:` <div id= "parent" > <childA/> <childB/> <h1> Score: </h1> </div>` }) new Vue ({ el: '#app' }) |
<div>
bao bọc bên ngoài với id="app"
ngay sau thẻ mở <body>
, và đặt component cha vào bên trong:
1
2
3 |
< div id = "app" > < parent /> </ div > |
state
là một đối tượng lưu giữ trạng thái của dữ liệu.mulatations
cũng là một đối tượng chưa những phương thức tác động đến state.getters
có các phương thức được dùng để giả lập việc truy xuất trạng thái, và thực hiện một vài công việc tiền xử lý, nếu cần thiết (tính toán dữ liệu, lọc dữ liệu.v.v).actions
là các phương thức để kích hoạt mutations và xử lý code không đồng bộ.this.$store.commit('increment', 3)
và sau đó, những thay đổi này làm thay đổi trạng thái (score
trở thành 3
). Sau đó, getter tự động cập nhật và chúng xuất các thay đổi trong view của component (với this.$store.getters.score
).
Mutation không thể xử lý code không đồng bộ, vì việc này sẽ không thể tạo bản ghi và theo dấu các thay đổi trong những công cụ gỡ lỗi như Vue DevTools. Để sử dụng logic không đồng bộ, bạn cần đưa nó vào actions. Trong trường hợp này, một component trước tiên sẽ gửi action (this.$store.dispatch('incrementScore', 3000)
ở đây code không đồng bộ được xử lý, và sau đó những action này sẽ cam kết mutation, điều này sẽ thay đổi trạng thái.
ChildB
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14 |
const store = new Vuex.Store({ state: { }, getters: { }, mutations: { }, actions: { } }) |
store
vào đối tượng Vue:
1
2
3
4 |
new Vue ({ el: '#app' , store // register the Vuex store globally }) |
this.$store
.
Tới giờ nếu bạn mở dự án bằng CodePen trong trình duyệt, bạn sẽ thấy kết quả như sau:
1
2
3 |
state: { score: 0 } |
score()
trong component cha:
1
2
3
4
5 |
computed: { score () { return this .$store.state.score } } |
{{ score }}
:
1 |
< h1 > Score: {{ score }} </ h1 > |
score
bất kể khi nào state thay đổi. Hãy thử thay đổi gía trị điểm và xem kết quả trong 3 component cập nhật ra sao.
this.$store.state
trong những component, như bạn đã thấy bên trên. Nhưng hình dung những kịch bản sau đây:
this.$store.state.score
, bạn quyết định thay đổi tên của score
. Có nghĩa là bạn phải thay đổi tên của biến trong mỗi component sử dụng nó.score()
:
1
2
3
4
5 |
getters: { score (state){ return state.score } } |
state
làm đối số đầu tiên, và sau đó dùng nó để truy xuất các thuộc tính của state.
Chú ý: Getter cũng nhận getters
khác làm đối số thứ hai. Bạn có thể dùng nó để truy xuất những getter khác trong store.
Trong tất cả component, điều chỉnh thuộc tính đã được tính toán score()
để dùng getter score()
thay vì trực tiếp dùng score của state.
1
2
3
4
5 |
computed: { score () { return this .$store.getters.score } } |
score
thành result
, bạn chỉ cần cập nhật ở một nơi duy nhất: trong getter score()
. Hãy thử trong CodePen!
state
làm đối số đầu tiên. Bạn cũng có thể truyền một đối số bổ sung, đây gọi là payload
của sự thay đổi.
Hãy tạo ra một thay đổi increment()
:
1
2
3
4
5 |
mutations: { increment (state, step) { state.score += step } } |
commit()
với tên gọi của thay đổi tương ứng và những đối số bổ sung khả dĩ. Đó có lẽ chỉ cần một, như step
trong trường hợp của chúng ta, hoặc có thể nhiều đối số thuộc về một đối tượng.
Hãy sử dụng mutation increment()
trong hai component con bằng một phương thức được gọi là changeScore()
:
1
2
3
4
5 |
methods: { changeScore (){ this .$store.commit( 'increment' , 3); } } |
this.$score.state.score
, bởi vì chúng ta muốn tường minh theo dấu thay đổi được mutation thực hiện. Với cách này, chúng ta khiến logic ứng dụng rõ ràng hơn, có thể theo dõi và hiểu lý do. Ngoài ra, điều này giúp triển khai các công cụ nhưng Vue DevTools hoặc Vuetron, chúng có thể ghi lại tất cả thay đổi, tạo bản snapshot cho state, và thực hiện gỡ lỗi time-travel.
Giờ hãy sử dụng phương thước changeScore()
. Trong mỗi template của hai component con, hãy tạo một button và một event listener khi click vào nó.
1 |
< button @ click = "changeScore" >Change Score</ button > |
incrementScore()
nào:
1
2
3
4
5
6
7 |
actions: { incrementScore: ({ commit }, delay) => { setTimeout(() => { commit( 'increment' , 3) }, delay) } } |
context
làm đối số đầu tiên. Đối số này có tất cả phương thức và thuộc tính từ store. Thông thường, chúng ta chỉ trích xuất những phần chúng ta cần bằng phương pháp ES2015 argument destructing. Phương thức commit
là điều ta sẽ thường xuyên cần. Action cũng có một đối số payload thứ hai, giống như các mutation.
Trong component ChildB
, thay đổi phương thức changeScore()
:
1
2
3
4
5 |
methods: { changeScore (){ this .$store.dispatch( 'incrementScore' , 3000); } } |
dispatch()
với tên gọi của action tương ứng và những đối số bổ sung, giống như của mutations.
Giờ button Change Score từ component ChildA
sẽ gia tăng điểm số thêm 3. Button y hệt từ component ChildB
sẽ làm việc tương tự, nhưng chậm hơn 3 giây. Trong trường hợp đầu, ta đang xử lý code đồng bộ và dùng một mutation, nhưng trường hợp thứ hai ta xử xý code bất đồng bộ, và chúng ta cần dùng một action thay vào đó. Xem tất cả cùng hoạt động thế nào trong ví dụ CodePen của chúng tôi.
score()
như vầy:
1
2
3
4
5 |
computed: { score () { return this .$store.state.score } } |
mapState()
như thế này:
1
2
3 |
computed: { ...Vuex.mapState([ 'score' ]) } |
score()
được tạo ra tự động cho chúng ta.
Tương tự cho getter, mutation và action.
Để tạo getter score()
, chúng ta dùng hàm hỗ trợ mapGetters()
:
1
2
3 |
computed: { ...Vuex.mapGetters([ 'score' ]) } |
changeScore()
, ta dùng hàm hỗ trợ mapMutations()
như sau:
1
2
3 |
methods: { ...Vuex.mapMutations({changeScore: 'increment' }) } |
1 |
< button @ click = "changeScore(3)" >Change Score</ button > |
changeScore()
dùng một action thay vì mutation, ta dùng mapActions()
như sau:
1
2
3 |
methods: { ...Vuex.mapActions({changeScore: 'incrementScore' }) } |
1 |
< button @ click = "changeScore(3000)" >Change Score</ button > |
...
) mà không cần bất kỳ tiện ích nào.
Trong CodePen của chúng ta, bạn có thể thấy một ví dụ về cách tất cả hàm hỗ trợ mapping được dùng thế nào trong thực hành.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 |
const childB = { state: { result: 3 }, getters: { result (state) { return state.result } }, mutations: { increase (state, step) { state.result += step } }, actions: { increaseResult: ({ commit }, delay) => { setTimeout(() => { commit( 'increase' , 6) }, delay) } } } const childA = { state: { score: 0 }, getters: { score (state) { return state.score } }, mutations: { increment (state, step) { state.score += step } }, actions: { incrementScore: ({ commit }, delay) => { setTimeout(() => { commit( 'increment' , 3) }, delay) } } } const store = new Vuex.Store({ modules: { scoreBoard: childA, resultBoard: childB } }) |
scoreBoard
và resultBoard
trong đối tượng modules
bên trong store. Code cho childA
giống với code trong store trong ví dụ trước đó. Trong code của childB
, chúng ta bổ sung vài thay đổi trong giá trị và tên gọi.
Hãy điều chỉnh component ChildB
để phản ánh các thay đổi trong mô-đun resultBoard
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17 |
Vue.component( 'ChildB' ,{ template:` <div class = "child childB" > <h1> Result: {{ result }} </h1> <button @click= "changeResult()" >Change Result</button> </div>`, computed: { result () { return this .$store.getters.result } }, methods: { changeResult () { this .$store.dispatch( 'increaseResult' , 3000); } } }) |
ChildA
, điều duy nhất ta cần thay đổi là phương thức changeScore()
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17 |
Vue.component( 'ChildA' ,{ template:` <div class = "child childA" > <h1> Score: {{ score }} </h1> <button @click= "changeScore()" >Change Score</button> </div>`, computed: { score () { return this .$store.getters.score } }, methods: { changeScore () { this .$store.dispatch( 'incrementScore' , 3000); } } }) |
namespaced
thành true
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 |
const childB = { namespaced: true , state: { score: 3 }, getters: { score (state) { return state.score } }, mutations: { increment (state, step) { state.score += step } }, actions: { incrementScore: ({ commit }, delay) => { setTimeout(() => { commit( 'increment' , 6) }, delay) } } } const childA = { namespaced: true , state: { score: 0 }, getters: { score (state) { return state.score } }, mutations: { increment (state, step) { state.score += step } }, actions: { incrementScore: ({ commit }, delay) => { setTimeout(() => { commit( 'increment' , 3) }, delay) } } } |
score()
từ mô đun resultBoard
, chúng ta gõ vào như vầy: resultBoard/score
. Nếu ta muốn getter score()
từ mô đun scoreBoard
, thì chúng ta gõ như sau: scoreBoard/score
.
Giờ hãy thay đổi component để phản ánh thay đổi chúng ta đã tạo ra.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 |
Vue.component( 'ChildB' ,{ template:` <div class = "child childB" > <h1> Result: {{ result }} </h1> <button @click= "changeResult()" >Change Result</button> </div>`, computed: { result () { return this .$store.getters[ 'resultBoard/score' ] } }, methods: { changeResult () { this .$store.dispatch( 'resultBoard/incrementScore' , 3000); } } }) Vue.component( 'ChildA' ,{ template:` <div class = "child childA" > <h1> Score: {{ score }} </h1> <button @click= "changeScore()" >Change Score</button> </div>`, computed: { score () { return this .$store.getters[ 'scoreBoard/score' ] } }, methods: { changeScore () { this .$store.dispatch( 'scoreBoard/incrementScore' , 3000); } } }) |
store.js
, state.js
, getters.js
, v.v) Bạn có thể xem ví dụ cấu trúc này khi kết thúc phần kế tiếp.
.vue
. Để hiểu cách này hoạt động ra sao, bạn có thể xem ở Vue Single File Components documentation page.
Vậy trong trường hợp của ta, chúng ta sẽ có 3 file: Parent.vue
, ChildA.vue
và ChildB.vue
.
Cuối cùng, nếu ta kết hợp 3 kỹ thuật này lại, chúng ta sẽ có kết quả là cấu trúc tương tư dưới đây:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17 |
├── index.html └── src ├── main.js ├── App.vue ├── components │ ├── Parent.vue │ ├── ChildA.vue │ ├── ChildB.vue └── store ├── store.js ├── state.js ├── getters.js ├── mutations.js ├── actions.js └── modules ├── childA.js └── childB.js |
Cùng nhau học tập, khám phá các kiến thức nền tảng về Lập trình web, mobile, database nhé.
Nền tảng kiến thức - Hành trang tới tương lai hân hạnh phục vụ Quý khách!
Khám phá, trải nghiệm ngay
Vui lòng đăng nhập để gởi bình luận!
Đăng nhậpChưa có bình luận nào!