Vuex là gì? Cài đặt và làm quen với Vuex

Vuex là gì?

Theo như định nghĩa của trang chủ thì nguyên văn nó như thế này :

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.

Để hiểu hơn là tại sao phải cần đến Vuex ta sẽ qua một ví dụ sau:


Chúng ta có 2 component là Counter chứa 2 chức năng là increment – decrement và component Result có chức năng in ra kết quả

  • Với trường hợp không sử dụng Vuex thì chúng ta sẽ cần truyền sự kiện 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

Đây là trường hợp mới chỉ có một cấp. Vậy nếu cây phân cấp components của ứng dụng là rất nhiều thì điều gì sẽ xảy ra. Trông nó sẽ như thế này :

Sẽ rất là rối và khó quản lý thì ý tưởng của thằng này cũng tương tự như Redux nếu anh em nào đã từng học qua Redux. Thì Vuex cũng vậy, nó sẽ tạo ra một strore chung cho các state để dễ dàng quản lý và thao tác khi có thay đổi:

Cài Đặt

CDN

Vuejs dạng CDN như Jquery :

https://unpkg.com/vuex

nhớ tải cả Vuejs nhá Vuejs

1

2

3

4

    

<script

src

=

“/path/to/vue.js”

>

</script>

    

<script

src

=

“/path/to/vuex.js”

>

</script>

NPM

1

2

3

    

npm

install

vuex

save

Yarn

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

:

{

      

}

    

}

)

;

Các thành phần và cách sử dụng của chúng

1. State

  • Giống như ở mỗi component chúng ta thường có 1 đối tượng 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

  

}

  • Lấy ra giá trị của một biến trong state, thì cũng giống như cách lấy ta giá trị của một attribute trong đối tượng vậy.

1

2

3

4

5

6

7

8

9

    

export

default

{

      

computed

:

{

          

result

(

)

{

            

return

this

.

$

store

.

state

.

result

;

          

}

      

}

    

}

;

helper tên là mapState. Nó sẽ sử dụng toán thử Spread (...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.

2. Getters

Đôi khi chúng ta có một hàm cần tính toán dựa trên biến trong state mà cái hàm này lại xuất hiện ở nhiều component. Bây giờ chả nhẽ ở mỗi component ta lại lôi cái biến đó ra và tạo hàm tính toán lại ví dụ hàm lọc các công việc phải làm và đếm chúng:

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

      

}

    

}

mapSate thì cũng có mapGetters và cách dùng cũng tương tự:

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’

    

}

)

3. Mutations

  • Theo như Docs thì mutations là cách duy nhất mà ta có thể thay đổi thực sự state trong store. Và cách để kích hoạt một mutations đó là ta sẽ commit một chuỗi 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’

)

duy nhất để thay đổi thực sự nhưng mình đã thử một cách và vẫn thấy nó thay đổi được đó là:

1

2

3

4

5

6

7

8

9

10

    

export

default

{

      

name

:

“counter”

,

      

methods

:

{

        

increment

(

)

{

          

this

.

$

store

.

state

.

count

++

;

        

}

      

}

    

}

  • Ngoài commit mỗi tên của hàm thì bạn cũng có thể truyền thêm một tham số bổ sung, nếu như hàm của bạn có định nghĩa nhiều hơn 1 tham số đầu vào là state

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

      

}

    

}

Muntations thì cũng tuân theo Reactivity Rules của Vue. Nên nếu sau khi state đã được khởi tạo ta muốn thêm một biến mới vào trong state, thì ta cần khai báo cho Vue biết rằng ta có một mới muốn thêm vào hoặc là thay thế toàn bộ

1

2

3

4

5

6

7

    

Vue

.

set

(

obj

,

‘newProp’

,

123

)

    

    

OR

    

    

state

.

obj

=

{

.

.

.

state

.

obj

,

newProp

:

123

}

  • Ta có thể dùng các mutations với dạng các hằng số. Điều này sẽ rất giúp ích cho việc đồng bộ tên hàm cũng như phù hợp cho các dự án lớn với nhiều bên tham gia

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

        

}

      

}

    

}

)

mutations này sẽ chạy đồng bộ nên bạn cần cẩn thận khi sử dụng nó, không lại dối tung lên khi kết hợp nó với các hàm bất đồng bộ và không hiểu sao nó lại không chạy.

  • Giống như 2 thằng trên thì mutations cũng có helper đó là mapMutations

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’)`

        

}

)

      

}

    

}

4. Actions

  • Actions cũng giống như mutations nhưng nó khác ở hai điểm:
    • Actions không trực tiếp thay đổi state trong store mà nó sẽ thông qua mutations để thay đổi
    • Nó có thể chứa các hàm bất đồng bộ

    Ví dụ đơn giản

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’

)

      

}

    

}

  • Cách kích hoạt 1 actions khi ở component khác

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

    

}

)

helper nữa đó là mapActions

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’)`

        

}

)

      

}

    

}

5. Modules

  • Bây giờ mới có vài hàm thì nhét hết vào file 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

  • Nếu bạn muốn nhóm các kiểu mutation/action có chung mục đích sử dụng lại với nhau ta có thể sử dụng đinh nghĩa Namespacing với thuộc tính 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’]

              

}

            

}

          

}

        

}

      

}

    

}

)

  • Và khi binding với các helpers mà có sử dụng namespace trông nó sẽ như thế này:

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 đã được tạo với method 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’

]

,

{

      

// …

    

}

)

Cấu trúc ứng dụng

Theo hướng dẫn thì cấu trúc của ứng dụng Vuex nên như này

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

modules cho những đối tượng chứa nhiều hàm trong mutations, actions, getters và vừa tách ra các file actions, mutations dùng cho các đối tượng ít hàm hơn.

 

Học và sử dụng 

  • Vuex store có tính phản ứng. Khi các component thu một trạng thái từ nó, chúng sẽ cập nhật view mỗi khi trạng thái thay đổi.
  • Component không thể trực tiếp biến đổi trạng thái của store. Cách duy nhất để thay đổi trạng thái này thông qua cam kết những thay đổi này một cách rõ ràng. Điều này bảo đảm mỗi thay đổi trạng thái tạo ra một kỷ lục có thể theo dõi làm cho ứng dụng dể sửa lỗi và thử nghiệm hơn.
  • Bạn có thể dễ dàng gỡ lỗi cho ứng dụng qua việc tích hợp Vuex với phần mở rộng DevTools của Vue.
  • Vuex store cho bạn cái nhìn tổng quan cách mọi thứ kết nối và ảnh hướng trong ứng dụng của bạn.
  • Sẽ dễ dàng hơn để bảo trì và đồng bộ trạng thái giữa nhiều component, thậm chí nếu cấu trúc component thay đổi.
  • Vuex giúp các component có thể giao tiếp với nhau trực tiếp.
  • Nếu một component bị xoá bỏ, trạng thái trong Vuex store sẽ được duy trì.

Trước khi bắt đầu, tôi sẽ làm rõ một số điều.

Trước tiên để theo dõi hướng dẫn này, bạn cần hiểu rõ về Vue.js, và hệ thống component của nó, hoặc có trải nghiệm tối thiểu với framework này.

Đồng thời, đính hướng của hướng dẫn này không phải cho bạn xem cách xây dựng một ứng dụng phức tạp thực sự ra sao; chủ yếu muốn tập trung nhiều hơn vào các khái niệm của Vuex và cách bạn có thể dùng chúng để tạo ra các ứng dụng phức tạp. Vì lý do đó, tôi sẽ sử dụng những ví dụ thuần và đơn giản, không chứa code không cần thiết. Khi bạn hoàn toàn nắm bắt khái niệm của Vuex, bạn sẽ có thể áp dụng chúng cho bất kỳ độ phức tạp nào.

Sau cùng, tôi dùng cú pháp ES2015. Nếu chưa quen thuộc với nó, 

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" />

</head>

<body>

<script src="https://unpkg.com/vue"></script><script src="https://unpkg.com/vuex"></script>

<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'

})

Score: ở đây ta dùng để xuất ra trạng thái của ứng dụng.

Điều sau cùng cần làm là đặt thẻ <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.

Getter và actions giống như những dự đoán có tính logic của state và mutation:

  • 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ộ.

Hãy khám khá sơ đồ bên dưới để hiểu rõ hơn:
Vuex State Management Workflow Diagram
Phía bên trái chúng ta có một ví dụ Vuex store, sẽ được chúng ta tạo ra sau trong hướng dẫn này. Ở bên phải, ta có một sơ đồ quy trình của Vuex, cho thấy cách những phần tử Vuex khác biệt cùng hoạt đông và giao tiếp với nhau.

Để đổi trạng thái, một đối tượng Vue cụ thể phải cam kết các thay đổi (ví dụ 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.

Giờ ta đã khám phá cách Vuex hoạt động, hãy tạo một cấu trúc cơ bản của Vuex Store. Đưa code sau đây vào phía trên phần đăng ký component 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

})

this.$store.

Tới giờ nếu App Skeleton

Đối tượng state chứa tất cả dữ liệu được chia sẻ trong ứng dụng. Dĩ nhiên, nếu cần, mỗi component có thể có trạng thái của riêng nó.

Hình dung xem bạn muốn tạo một ứng dụng game, và bạn cần một biến để lưu điểm trong game. Vậy bạn đưa nó vào đối tượng state:

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ử 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:

  1. Trong một ứng dụng lớn, nơi có nhiều component truy xuất trạng thái của store thông qua 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ó.
  2. Bạn muốn dùng một giá trị của state đã được tính toán. Ví dụ, hãy nói rằng bạn thưởng 10 điểm cho người chơi khi họ đạt 100 điểm. Vậy, khi điểm số là 100, 10 điểm sẽ được thêm vào. Có nghĩa là mỗi component cần phải chứa một hàm để dùng lại điểm số và tăng điểm đó thêm 10. Bạn sẽ có những code lặp lại trong mỗi component, điều này hoàn toàn không hay.

Thật may khi Vuex đề xuất một giải pháp để xử lý những tình huống này. Tưởng tượng môt getter trung tâm có thể truy xuất state của store và cung cấp một hàm getter cho mỗi thành phần của state. Nếu cần, getter này có thể áp dụng tính toán cho thành phần của state. Và nếu bạn cần thay đổi tên của vài thuộc tính của state, bạn chỉ cần thay đổi nó ở một nơi, trong getter này.

Hãy tạo ra một getter 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()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 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 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 đó. 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, 

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.jsstate.jsgetters.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.

Chúng ta vừa mô đun hoá Vue store như ta muốn. Tiếp theo là áp dụng cùng chiến lược này cho các component của Vue.js. Chúng ta có thể đưa mỗi component vào từng file với tên mở rộng là .vue. Để hiểu cách này hoạt động ra sao, bạn có thể xem ở Parent.vueChildA.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

Theo như định nghĩa của trang chủ thì nguyên văn nó như thế này :Định nghĩ thì có vẻ khó hiểu như vậy nhưng ta có thể hiểu một cách nôm nalà mộtcủa, nó có chức năng như một cái kho chứa tập trung cáccủa cáctrong ứ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ằngtrêncủa Vuex, mà không cần phải thông qua quan hệ giữa các component.Chúng ta có 2 component làchứa 2 chức năng làvà componentcó chức năng in ra kết quảĐây là trường hợp mới chỉ có một cấp. Vậy nếu cây phân cấp components của ứng dụng là rất nhiều thì điều gì sẽ xảy ra. Trông nó sẽ như thế này :Sẽ rất là rối và khó quản lý thì ý tưởng của thằng này cũng tương tự như Redux nếu anh em nào đã từng học qua Redux. Thì Vuex cũng vậy, nó sẽ tạo ra mộtchung cho cácđể dễ dàng quản lý và thao tác khi có thay đổi:Nếu bạn đang dùngdạng CDN nhưnhớ tải cả Vuejs nháSau khi kiểm tra trongđã cài đặt thành côngta tạo 1 foldervà tạo fileVậy là đã có 1 cái store tập trung của Vuex rồi.Nếu trong state của chúng ta có nhiều biến và ta chỉ muốn lấy ra một số các biến nhưng lại không muốn gọi từng thứ một như thế kia, thì đừng lo đã có cách đó là sử dụng mộttên là. Nó sẽ sử dụng toán thử) 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.Vậy là giờ ta có các giá trịvàđã 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 importkhông lại bảo sao không chạy. Sử dụngthì 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ừ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.Đôi khi chúng ta có một hàm cần tính toán dựa trên biến trong state mà cái hàm này lại xuất hiện ở nhiều component. Bây giờ chả nhẽ ở mỗi component ta lại lôi cái biến đó ra và tạo hàm tính toán lại ví dụ hàm lọc các công việc phải làm và đếm chúng:Thì Vuex nó cho phép ta định nghĩa các hàm như thế này trongVà lấy cũng đơn giản thôi:Ta có thể sử dụng các hàm trong cùngvới nhau:Còn ở trong các component khác thì cũng đơn giản không kémĐã cóthì cũng cóvà cách dùng cũng tương tự:Hoặc là lúc định nghĩ thì một tên nhưng lúc sử dụng ta muốn dùng tên khác cũng chẳng sao đổi được ý mà:Thấy nhà phát triển nói là cáchđể thay đổinhưng mình đã thử một cách và vẫn thấy nó thay đổi được đó là:Mình vẫn thấy nó hoạt động nhưng không thấy Docs nói đến kiểu này hay là nó không chuẩn chỉ và có thể gây lỗi hay như thế nào. Nhưng hoi Docs đã viết cách này dùng để thay đổi, nên mình sẽ dùng cách này cho chắc không lại đến lúc có lỗi thì vỡ mồm.thường thì người ta sẽ gom các đối số thành một Object để có thể chứa được nhiều biến cần truyền vào hơnCòn một cách nữa đó là gom cả tên hàm cần gọi và biến cần truyền vào 1 Object với tên hàm để là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àmthì cũng tuân theocủa. Nên nếu sau khi state đã được khởi tạo ta muốn thêm một biến mới vào trong state, thì ta cần khai báo cho Vue biết rằng ta có một mới muốn thêm vào hoặc là thay thế toàn bộnhưng nó chỉ là một tùy chọn thôi nha bạn không nhất thiết cứ phải sử dụng nó đâu => Một điều cần nhớ đó là thằngnày sẽ chạynên bạn cần cẩn thận khi sử dụng nó, không lại dối tung lên khi kết hợp nó với các hàm bất đồng bộ và không hiểu sao nó lại không chạy.Có thể sử dụng hàm argument destructuring để tạochúng ta cũng có thể truyền thêm một tham sốVà lại thêm một cáinữa đó làHoặc là đưa phần string namespace vào làm đối số đầu tiênCòn nếu không muốn dùng lại những string namespace đó nhiều lần bạn có thể sử dụng. Nó sẽ trả về một đối tượng liên kết với cácmà bạn muốn.Ngoài ra bạn có thể đăng ký các module sau khiđã được tạo với methodTheo hướng dẫn thì cấu trúc của ứng dụng Vuex nên như nàyĐể quản lý chặt chẽ hơn thì chúng ta nên kết hợp vừa tách thành cáccho những đối tượng chứa nhiều hàm trong mutations, actions, getters và vừa tách ra các file actions, mutations dùng cho các đối tượng ít hàm hơn.Học và sử dụng Vue.js rất dễ nên ai cũng có thể xây dựng một ứng dụng đơn giản với framework đó. Ngay cả những người nghiệp dư, với tài liệu hỗ trợ của Vue, cũng có thể làm điều đó. Tuy nhiên mọi thứ trở nên trầm trọng khi sự phức tạp xuất hiện. Thực sư là nhiều component được lồng ghép sâu nhiều cấp với các trạng thái được chia sẻ có thể nhanh chóng khiến ứng dụng của bạn trở thành không thể bảo trì. Vấn đề lớn nhất trong một ứng dụng phức tạp là làm sao để quản lý trạng thái giữa các component mà không viết code hỗn tạp hoặc gây ra các hiệu ứng phụ. Trong hướng dẫn này, bạn sẽ học cách giải quyết vấn đề đó với Vuex : một thư viện quản lý trạng thái để xây dựng ứng dụng Vue.js phức tạp. Vuex là một thư viện quản lý trạng thái đặc biệt dùng để xây dựng những ứng dụng Vue.js quy mô lớn và phức tạp. Nó sử dụng store tập trung hoá, và toàn cục cho tất cả component trong một ứng dụng, tận dụng hệ thống các phản ứng cho những cập nhanh tức thời. Vuex store được xây dựng theo cách để không thể thay đổi trạng thái của nó từ bất kỳ component nào. Bảo đảm rằng trạng thái chỉ có thể biến đổi theo cách có thể dự đoán được. Do đó store của bạn trở thành một nguồn đáng tin: mỗi yếu tố dữ liệu chỉ được lưu một lần và chỉ cho phép đọc để tránh các component của ứng dụng không làm hỏng trạng thái được truy xuất từ các component khác. Bạn sẽ hỏi: Sao tôi lại cần Vuex ngay từ đầu? Tôi không thể chỉ đưa trạng thái đã được chia sẻ vào một file JavaScript và import nó vào ứng dụng Vue.js của tôi phải không? Dĩ nhiên bạn có thể nhưng so với một đối tượng toàn cục đơn giản thì Vuex store có những điểm mạnh và ích lợi đáng kể.Trước khi bắt đầu, tôi sẽ làm rõ một số điều. Trước tiên để theo dõi hướng dẫn này, bạn cần hiểu rõ về Vue.js, và hệ thống component của nó, hoặc có trải nghiệm tối thiểu với framework này. Đồng thời, đính hướng của hướng dẫn này không phải cho bạn xem cách xây dựng một ứng dụng phức tạp thực sự ra sao; chủ yếu muốn tập trung nhiều hơn vào các khái niệm của Vuex và cách bạn có thể dùng chúng để tạo ra các ứng dụng phức tạp. Vì lý do đó, tôi sẽ sử dụng những ví dụ thuần và đơn giản, không chứa code không cần thiết. Khi bạn hoàn toàn nắm bắt khái niệm của Vuex, bạn sẽ có thể áp dụng chúng cho bất kỳ độ phức tạp nào. Sau cùng, tôi dùng cú pháp ES2015. Nếu chưa quen thuộc với nó, bạn có thể học nó ở đây . Và giờ thì bắt đầu thôi! Bước đầu tiên của bắt đầu với Vuex là cần có Vue.js và Vuex được cài đặt trong máy của bạn. Có vài cách để thực hiện điều này, nhưng chúng ta sẽ chọn cách dễ nhất. Chỉ cần tạo một file HTML và bổ sung những liên kết CDN cần thiết:Tôi dùng một vài CSS để làm cho các component đẹp mắt hơn, nhưng bạn không cần quá lo về CSS. Nó chỉ giúp tăng gía trị thị giác cho bạn biết điều gì đang diễn ra. Chỉ cần copy và paste vào thẻbên dưới:Giờ hãy tạo vài component để làm việc. Trong thẻ, bên phải trên thẻ đóng, đư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:Ở đây ta có giá trị Vue, một component cha, và hai component con. Mỗi component có một: ở đây ta dùng để xuất ra trạng thái của ứng dụng. Điều sau cùng cần làm là đặt thẻbao bọc bên ngoài vớingay sau thẻ mở, và đặt component cha vào bên trong:Công tác chuẩn bị hoàn tất, giờ chúng ta sẵn sàng tiếp tục. Trong thực tiễn, chúng ta đương đầu với sự phức tạp bằng cách dùng các chiến lược để tổ chức và cấu trúc nội dung cần sử dụng. Ta tập hợp các thứ liên quan với nhau thành nhiều phần, thể loại khác nhau. Như một thư viện sách, trong đó các cuốn sách được phân loại và xếp vào những phần khác nhau để chúng ta dễ dàng tìm kiếm. Vuex tổ chức dữ liệu và logic có liên quan để định trạng thái trong 4 nhóm: state (trạng thái), getters, mutation (thay đổi), và actions (hành động). Trạng thái và các thay đổi là cơ sở cho Vuex store bất kỳ.Getter và actions giống như những dự đoán có tính logic của state và mutation:Hãy khám khá sơ đồ bên dưới để hiểu rõ hơn:Phía bên trái chúng ta có một ví dụ Vuex store, sẽ được chúng ta tạo ra sau trong hướng dẫn này. Ở bên phải, ta có một sơ đồ quy trình của Vuex, cho thấy cách những phần tử Vuex khác biệt cùng hoạt đông và giao tiếp với nhau. Để đổi trạng thái, một đối tượng Vue cụ thể phải cam kết các thay đổi (ví dụvà sau đó, những thay đổi này làm thay đổi trạng thái (trở thành). Sau đó, getter tự động cập nhật và chúng xuất các thay đổi trong view của component (với). 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ở đâ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. Giờ ta đã khám phá cách Vuex hoạt động, hãy tạo một cấu trúc cơ bản của Vuex Store. Đưa code sau đây vào phía trên phần đăng ký componentĐể cung cấp truy xuất toàn cục đến Vuex store cho mỗi component, ta cần bổ sung thuộc tínhvào đối tượng Vue:Giờ ta có thể truy xuất vào store từ bất kỳ component nào với biến. 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:Đối tượng state chứa tất cả dữ liệu được chia sẻ trong ứng dụng. Dĩ nhiên, nếu cần, mỗi component có thể có trạng thái của riêng nó. Hình dung xem bạn muốn tạo một ứng dụng game, và bạn cần một biến để lưu điểm trong game. Vậy bạn đưa nó vào đối tượng state:Giờ bạn có thể truy xuất điểm của state trực tiếp. Hãy quay lại các component và sử dụng dữ liệu từ store. Để có thể sử dụng lại dữ liệu tương tác từ state của store, bạn nên dùng các thuộc tính đã được tính toán. Vậy hãy tạo ra một thuộc tínhtrong component cha:Trong template của compent cha, đặtGiờ làm tương tự cho 2 component con. Vuex thông minh đến mức sẽ thực hiện tất cả công việc cho chúng ta để cập nhật thuộc tínhbấ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. Dĩ nhiên thật tốt khi bạn có thể tái sử dung từ khoá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:Thật may khi Vuex đề xuất một giải pháp để xử lý những tình huống này. Tưởng tượng môt getter trung tâm có thể truy xuất state của store và cung cấp một hàm getter cho mỗi thành phần của state. Nếu cần, getter này có thể áp dụng tính toán cho thành phần của state. Và nếu bạn cần thay đổi tên của vài thuộc tính của state, bạn chỉ cần thay đổi nó ở một nơi, trong getter này. Hãy tạo ra một getterMột Getter nhầnlà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ậnkhá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để dùng getterthay vì trực tiếp dùng score của state.Giờ nếu bạn quyết định thay đổithành, bạn chỉ cần cập nhật ở một nơi duy nhất: trong getter Hãy thử trong CodePen! Mutation là cách duy nhất cho phép thay đổi state. Kích phát thay đổi đơn giản nghĩa là cam kết các thay đổi trong các phương thức của component. Một mutation gần như một hàm xử lý sự kiện được định nghĩa bằng tên. Các hàm xử lý mutation nhận mộtlàm đối số đầu tiên. Bạn cũng có thể truyền một đối số bổ sung, đây gọi làcủa sự thay đổi. Hãy tạo ra một thay đổiCác thay đổi không thể được gọi trực tiếp. Để thực hiện thay đổi, bạn nên gọi phương thứcvớ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ư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 mutationtrong hai component con bằng một phương thức được gọi làChúng ta đang thực hiện một mutation thay vì thay đổi trực tiếp, 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. 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ó.Khi bạn click vào button, state sẽ tăng thêm 3, và thay đổi này sẽ được phản ánh trong tất cả component. Giờ chúng ta đã đạt thành giao tiếp trực tiếp giữa các component, điều này không xảy ra với Vue.js tích hợp sẵn theo cơ chế “props down, events up”. Hãy xem ví dụ trên CodePen . Action chỉ là một hàm để thực hiện một mutation. Nó sẽ gián tiếp thay đổi state, điều này cho phép xử lý của các hoạt động bất đồng bộ. Cùng tạo một actionnào:Action nhậnlà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ứclà đ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, thay đổi phương thứcĐể gọi một action, ta dùng phương thứcvớ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ờ buttontừ componentsẽ gia tăng điểm số thêm 3. Button y hệt từ componentsẽ 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. Vuex đề xuất một số helper để sắp xếp phù hợp quá trình tạo state, getter, mutation và action. Thay vì tự viết những hàm này, chúng ta có thể nhờ Vuex làm việc này. Hãy xem cách nó hoạt động. Thay vì viết thuộc tínhnhư vầy:Ta chỉ cần dùng hàm hỗ trợnhư thế này:Và thuộc tínhđược tạo ra tự động cho chúng ta. Tương tự cho getter, mutation và action. Để tạo getter, chúng ta dùng hàm hỗ trợĐể tạo các phương thức, ta dùng hàm hỗ trợnhư sau:Khi dùng cho mutation và action với đối số payload, ta phải truyền đối số trong template để định nghĩa phần xử lý sự kiện:Nếu tao muốndùng một action thay vì mutation, ta dùngnhư sau:Lần nữa ta phải định nghĩa độ trì hoãn trong phần xử lý sự kiện.Chú ý: Tất các hàm hỗ trợ mapping trả về một đối tượng. Vì thế nếu chúng ta muốn dùng chúng kết hợp với những thuộc tính được tính toán cục bộ hoặc với các phương thức, thì chúng ta cần hợp nhất chúng vào một đối tượng. Thật may chúng ta có thể làm điều đó với toán tử () 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 . Dường như vấn đề phức tạp liên tục cản trở chúng ta. Chúng tôi đã giải quyết nó trước khi tạo ra Vuex store, ở đây chúng tôi làm cho việc quản lý state và giao tiếp giữa các component trở nên dễ dàng. Trong store đó, chúng ta có tất cả mọi thứ ở cùng một nơi, dễ vận dùng và dễ hiểu. Tuy nhiên khi ứng dụng của ta phát triển, file store dễ quản lý này trở nên càng lúc càng lớn, và kết quả là khó bảo trì hơn. Thêm lần nữa, ta cần những chiến lược và kỹ thuật để cải tiến cấu trúc ứng dụng bằng việc trả nó về hình thái dễ dàng bảo trì. Trong phần này, chúng ta sẽ khám phá vài kỹ thuật giúp chúng ta làm việc này. Vuex cho phép chúng ta chia nhỏ những đối tượng store thành các mô-đun riêng biệt. Mỗi mô-đun có thể có state, mutation, action, getter và những mô-đun cấp dưới cúa riêng nó. Sau khi ta tạo ra những mô-đun cần thiết, chúng ta đăng ký chúng trong store. Hãy xem nó hoạt động thế nào:Trong ví dụ trên, chúng ta tạo ra 2 mô-đun, một cho mỗi component con. Mô-đun chỉ là những object thuần, ta đăng ký chúng nhưvàtrong đối tượngbên trong store. Code chogiống với code trong store trong ví dụ trước đó. Trong code của, chúng ta bổ sung vài thay đổi trong giá trị và tên gọi. Hãy điều chỉnh componentđể phản ánh các thay đổi trong mô-đunTrong component, điều duy nhất ta cần thay đổi là phương thứcNhư bạn cũng thấy, chia nhỏ store thành các mô-đun khiến nó dễ bảo trì và gọn nhẹ hơn, trong khi vẫn giữ chức năng tuyệt vời của nó. Hãy xem qua CodePen mới cập nhật để thấy kết quả. Nếu bạn muốn hoặc cần sử dụng một hoặc cùng tên cho một thuộc tính hoặc phương thức trong các mô đun của bạn, sau đó bạn nên cân nhắc namespace chúng. Nếu không bạn nên quan sát những hiệu ứng phụ kỳ lạ, như việc xử lý những action có cùng tên, hoặc lấy sai giá trị của state. Để namespace một mô đun Vuex, chỉ cần bạn xét thuộc tínhthànhTrong ví dụ trên, chúng ta đã tạo thuộc tính và phương thức có trùng tên với 2 mô đun. Và giờ chúng ta có thể dùng thuộc tính và phương thức với tiếp đầu ngữ là tên của mô đun. Ví dụ nếu ta muốn dùng gettertừ mô đun, chúng ta gõ vào như vầy:. Nếu ta muốn gettertừ mô đun, thì chúng ta gõ như sau:. Giờ hãy thay đổi component để phản ánh thay đổi chúng ta đã tạo ra.Như bạn thấy trong ví dụ CodePen , chúng ta có thể dùng phương thức hoặc thuộc tính ta muốn và nhận kết quả ta mong đợi. Trong phần trước, chúng ta đã cải tiến cấu trúc ứng dụng đến vài mức độ bằng cách phân tách store thành những mô đun. Chúng ta đã làm store rõ ràng hơn và có tổ chức hơn, nhưng tất cả code cho store và mô đun của nó vẫn là một file lớn. Vậy bước đi hợp lý kế tiếp là chia Vuex store thành những file tách biệt. Ý tưởng là cần có một file riêng biệt cho chính store và một file khách cho các đối tượng của nó, bao gồm cả mô đun của nó. Có nghĩa là phân tách các file cho state, getter, mutation, action và cho mỗi mô đun (, 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. Chúng ta vừa mô đun hoá Vue store như ta muốn. Tiếp theo là áp dụng cùng chiến lược này cho các component của Vue.js. Chúng ta có thể đưa mỗi component vào từng file với tên mở rộng là. Để 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:và. 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:Trong repo Github của hướng dẫn , bạn có thể xem dự án hoàn tất với cấu trúc như trên. Hãy tóm tắt một số điểm chính bạn cần nhớ về Vuex: Vuex là thu viện quản lý state giúp ta tạo ra những ứng dụng lớn và phức tạp. Nó dùng một store trung tâm hoá và toàn cục cho tất cả component trong một ứng dụng. Để giả lập state, ta dùng getter. Getter giống các thuộc tính đã tính toán và là giải pháp lý tưởng khi chúng ta cần lọc hoặc tính toán vài điều trong runtime. Vuex store có tính phản ứng, và component không thể trực tiếp biến đổi state của store. Cách duy nhất để biến đổi state là thực hiện mutation, nó là những giao dịch đồng bộ. Mỗi mutation chỉ nên thực hiện một action, phải đơn giản nhất có thể, và chỉ đảm trách cho việc cập nhật một phần của state. Logic bất đồng bộ nên được áp dụng trong action. Mỗi action có thể thực hiện một hoặc nhiều mutation và một mutation có thể được thực hiện bởi nhiều action. Action có thể phức tạp nhưng chúng không bao giờ thay đổi state một cách trực tiếp. Sau cùng, tính mô-đun là mấu chốt của việc bảo trì. Để đương đầu với sự phức tạp và làm code có tính mô-đun, ta cần dùng quy tắc “divide and conquer” (chia để trị) và kỹ thuật chia nhỏ code. Thế đấy! Bạn đã biết các khái niệm chính đằng sau Vuex, và bạn sẵn sàng bắt đầu áp dụng vào thực hành. Nhàm mục đich ngắn gọn và đơn giản, tôi cố ý lược bỏ một số chi tiết và tính năng của Vuex, nên bạn cần đọc tài liệu Vuex đầy đủ để tìm hiểu mọi thứ về Vuex và bộ tính năng của nó.