[鼠年全马] W38 - 使用Vuex管理资料状态(上)


这周要来介绍一个很好用的套件 - Vuex

看到名称应该马上可以理解他就是Vue专案在使用的套件吧xD

有看过我之前的专案开发的文章的人,应该会发现我从来没有用过 Vuex
我也是最近这两周才开始接触它的~

趁着记忆犹新时赶快把它写起来~
如果有使用不当或是有误的地方再麻烦多指教/images/emoticon/emoticon41.gif


#Vuex是什麽?

万用起手式来了~
所谓的Vuex是什麽呢? 他可以做到哪些事呢?

官方是这麽介绍它的:

Vuex是一个专为Vue.js应用程序开发的状态管理模式。
它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex也集成到Vue的官方调试工具devtools extension,提供了诸如零配置的time-travel调试、状态快照导入导出等高级调试功能。

blablabla...
当时看完这段介绍之後我还是不知道Vuex是在做什麽xD

没关系我们继续往下看:

每一个Vuex应用的核心就是store(仓库)。
“store”基本上就是一个容器,它包含着你的应用中大部分的状态(state)。

到这边应该稍微可以理解原来它的用途是「管理资料状态的容器」


#为什麽需要Vuex?

原本的做法就可以在每个元件中存放资料了,那为什麽还要用Vuex呢?

通常我们的资料流向会是父传子,上层传至下层,保持数据单向流通
遇到需要从子层改变父层资料时可以使用 $emit() 呼叫父层方法进而改变数据
遇到兄弟同层之间的元件要互相影响时就会有困难了,我自己的做法是将资料放到父层再用父传子的方式处理
但是不论父传子还是兄弟互传的做法都非常的麻烦且繁琐

这时候Vuex就出现了!!

我们可以将需要用到传递的资料通通丢进Vuex管理
当元件需要使用资料时,可以使用 $store 轻松取得数据
且透过套件提供的 dispatch 方法,可以从任何元件去改变资料数据

那传递呢? 当然用不到罗~
透过Vuex我们可以把传递的方式取代掉,并且还是保持着资料数据单向流通


#Vuex内容

刚才有看到官方文件说「Vuex应用的核心就是store」
Store 就是存放资料状态的容器

那这个容器内包含着哪些东西呢?

  • state: 存放资料状态的物件,类似Vue元件中的 data
  • actions: 存放方法的物件,类似Vue元件中的 methods
    除了改变资料状态以外的工作,都会在这边处理
  • mutations: 负责做改变资料状态的动作
  • getters: 取得资料状态的方法,在取得之前也可以在这先做一些资料处理,类似Vue元件中的 computed

以上四个项目会是基本常使用到的,其他还有像是

  • modules: 模组化的Vuex

#Vuex数据流向


在数据流向的部分
前端元件(component)会透过 ditpatch 呼叫 actions 方法
actions 再透过提交(commit)的方式呼叫 mutations
mutations 再做更新 state 中的资料状态的动作
state 更新後再将数据渲染到元件中

透过这个流程达到单向数据流的操作


#安装Vuex

基本介绍之後,就先来安装它吧~
这里以使用 Vue Cli 专案为主来说明

使用npm安装指令:

npm install vuex --save

官方还有提供其他方式如:

  • Yarn
yarn add vuex
  • script引用
<script src="/path/to/vue.js"></script>
<script src="/path/to/vuex.js"></script>

#建立基本的Store

安装完成之後来建立一个最简单的Store罗~

#Step 1

在专案下新增一个 /store 资料夹,资料夹内再新增一个 index.js 档:

src
└── store
    └── index.js

#Step 2

开启刚刚建立的 index.js 加入基本的程序码:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        msg: "Hello Vuex!!"
    },
});

Step 3

main.js 中引用:

import store from './store'

store 要加进vue喔!!

new Vue({
  ...,
  store,
}).$mount('#app')

#Step 4

上面步骤都完成之後可以来到元件中去抓看看 msg 罗~
在元件的 computed 中使用 $store.state 来取得资料数据,可以在 App.vue 中试试看是否能抓到并渲染在画面上:

//App.vue
<template>
  <div>
    {{ myMsg }}
  </div>
</template>
<script>
export default {
  name: "App",
  computed: {
    myMsg() {
      return this.$store.state.msg;
    },
  },
};

基本的 Store 就建立完成罗!!


#建立TodoList

接着我会用不包含任何功能的TodoList来介绍後面的操作

#Step 1

像刚才 msg 一样,我们在 Store 中建一个TodoList:

export default new Vuex.Store({
    state: {
        todolist: [
            { id: 1, todo: "交女朋友", done: false },
            { id: 2, todo: "财务自由", done: false },
            { id: 3, todo: "当码农", done: true },
        ],
    },
});

#Step 2

App.vue 中抓值并渲染:

<template>
  <div>
    <ul>
      <li v-for="item in myTodo" :key="item.id">
        {{ item.todo }} - {{ item.done ? "已完成" : "未完成" }}
      </li>
    </ul>
  </div>
</template>
<script>
export default {
  name: "App",
  computed: {
    myTodo() {
      return this.$store.state.todolist;
    },
  },
};
</script>

出来的画面长这样:
https://ithelp.ithome.com.tw/upload/images/20201123/20118686uvaNgUU6mi.jpg

#Step 3

前置作业完成了,再来我要加上click事件来切换 [已完成] 及 [未完成] 的状态:

<li
  ...
  @click="todoDone(item.id, !item.done)"
>
  ...
</li>

对应的 todoDone() 方法:

methods: {
  //id: 要修改状态的资料唯一码
  //done: 要修改的值
  todoDone(id, done) {
  
  },
},

#Step 4

再来很重要(应该从头到尾都很重要xD)
我们要使用 dispatch 来呼叫 actions
第一个参数设定要呼叫的 actions 名称
第二个参数设定要传入的值,有多个值的话就用物件的方式带入

假设要呼叫的 actions 名称也叫 todoDone,并且将 id & done 传进去:

todoDone(id, done) {
  this.$store.dispatch("todoDone", { id, done });
},

#Step 5

回到 /store/index.js 加入 todoDone():

actions: {
    todoDone(context, { id, done }) {
    
    },
},

说明一下 actions 的参数结构,每个 actions 会有带两个参数: myActions(context, payload)

  • 第一个参数 context 是个与store实例具有相同方法和属性的对象

Action函数接受一个与store实例具有相同方法和属性的context对象,因此你可以调用context.commit提交一个mutation,或者通过context.state和context.getters来获取state和getters。

我们在 context 中可以使用这些属性:

{ 
    state, // 等同於`store.state`,若在模块中则为局部状态
    rootState, // 等同於`store.state`,只存在於模块中
    commit, // 等同於`store.commit` 
    dispatch , // 等同於`store.dispatch` 
    getters, // 等同於`store.getters` 
    rootGetters // 等同於`store.getters`,只存在於模块中 
}
  • 第二个参数 payload 是带入的参数内容(可不带)

#Step 6

接着我们可以在 actions 中使用 commit 将要修改的数据提交给 mutations 来更新:

todoDone(context, { id, done }) {
    context.commit('TODOLIST', { id, done });
},

第一个参数设定要提交给哪个 mutations 的名称
第二个参数设定要传入的值,有多个值的话就用物件的方式带入

通常actions会有一些资料面的逻辑处理,例如呼叫ajax等等,逻辑处理结束後才会提交到mutations
但范例中因为没有这层,所以看起来像是多做了一个没有用的动作

#Step 7

接着可以加入 mutations:

mutations: {
    TODOLIST(state, { id, done }) {
    
    },
}

这边使用了常量替代Mutation 事件类型:

使用常量替代mutation 事件类型在各种Flux 实现中是很常见的模式。这样可以使linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个app 包含的mutation 一目了然
用不用常量取决於你——在需要多人协作的大型项目中,这会很有帮助。但如果你不喜欢,你完全可以不这样做。

说明一下 mutations 的参数结构,每个 mutations 会有带两个参数: myMutations(state, payload)

  • 第一个参数 state 就是指存放资料状态的 state
  • 第二个参数 payload 是带入的参数内容(可不带)

#Step 8

最後就是更新资料状态
先抓到 state 中的 todolist:

let todolist = state.todolist;

使用阵列的 find() 方法抓到要改的那笔:

let todo = todolist.find((item) => {
    return item.id === id;
});

更新 done 值:

todo.done = done;

整段会是这样:

TODOLIST(state, { id, done }) {
    let todolist = state.todolist;
    let todo = todolist.find((item) => {
        return item.id === id;
    });
    todo.done = done;
},

也可以合并成这样:

TODOLIST(state, { id, done }) {
    state.todolist.find((item) => {
        return item.id === id
    }).done = done;
},

#结果

到这里就完成更新资料状态罗!!
来看看结果吧~
gif已死QQ


先到这罗~
下周继续来探讨模组化以及其他的功能!!
/images/emoticon/emoticon29.gif/images/emoticon/emoticon29.gif/images/emoticon/emoticon29.gif


<<:  [Golang]同步工具-sync包的Wait、Signal、Broadcast方法说明-心智图总结

>>:  MySQL 数字类型资料之基本操作

Day20 X CDN

CDN 这个名词在前面的篇章应该出现过蛮多次的,一直感到困惑的朋友们不用担心,今天终於要来好好介绍...

Day 28 ~ AI从入门到放弃 - 猫狗辨识之三

今天延续之前的主题,我们将使用EfficientNetB0的架构,但不使用预训练权重,参考了Kera...

day30 Kotlin coroutine 结赛统整

有人说作为新手不好理解,我觉得蛮正常的,一来我中文其实不好,二来要理解新概念只用看的其实不够,最好是...

Day 2.来交朋友吧!-Vue.js是谁?

Vue 由前 Google 工程师尤雨溪在 2014 年 2 月所建立的一套开放源码(Open So...

[Tableau Public] day 28:制作台湾姓氏分布-数据仪表板

今天我们来把前面做的五张工作表合成一张仪表板吧! 首先新增一张仪表板,名称为「107年6月_台湾各姓...