深刻Vuex最佳實踐

深刻Vuex最佳實踐

前言

在開始正文以前先廢話一段,我上週寫的一篇面試文章竟然火了🤓, 開心了一上午,其實我也纔剛開始寫文章確定是比不上那些大佬的質量,因此能火我也感到佷意外,很感謝支持個人朋友, 我也會努力花時間在這方面爲讀者產出更好的文章,不少朋友問是我怎麼自學的,你們能夠看下個人博客,我有寫過本身自學的方法雖說不上多好但也是本身自學這麼久的一些好的學習方式,仍是能夠給一些學前端不久的小夥伴一些參考,但願你們加油!javascript

起步

核心概念

好了,如今開始回到正文,在講Vuex以前咱們先來了解下Vuex是什麼?css

Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則證狀態以一種可預測的方式發生變化html

上面是官方給的解釋,其實講的已經很清楚了,它就是專門爲Vue.js開發的一套集中式狀態管理模式的庫,那它解決了什呢?前端

使用Vuex管理數據的好處vue

  • 可以在Vuex 中集中管理共享的數據,便於開發和後期進行維護
  • 可以高效的實現組件之間的數據共享,提升開發效率
  • 存儲在Vuex中的數據是響應式的,當數據發生改變時,視圖中的數據也會同步更新

因此基於上面三個優勢咱們就能明白,基於Vuex集中管理共享的數據,解決了多個組件之間的數據共享問題,而且由於數據是響應式的,因此數據變化視圖也會更新,因此咱們使用Vuex以後就不須要關注不一樣視圖(組件)依賴同一狀態(數據)的問題, 咱們能夠將全部精力放在狀態(數據)更新上就能夠了,剩下的Vuex會幫咱們解決。java

講明白Vuex的慨念後,咱們來看下面兩張官方給的圖node

  • state,驅動應用的數據源;
  • view,以聲明方式將 state 映射到視圖;
  • actions,響應在 view 上的用戶輸入致使的狀態變化

這是一個單項數據流的簡單示意圖,它的問題在於:ios

  • 多個視圖依賴於同一狀態。
  • 來自不一樣視圖的行爲須要變動同一狀態。

因此有了Vuex集中式狀態管理模式`git

Vuex的核心特性

上圖很好的解釋了Vuex的特性,在解釋圖片想要表達的意思以前咱們先來解釋下圖中出現單詞都表明什麼角色。github

  • State

    State提供惟一的公共數據源,全部共享的數據都要統一放到Store中的State中存儲

  • Mutation

    Mutation用於修改變動$store中的數據

  • Action

    在mutations中不能編寫異步的代碼,會致使vue調試器的顯示出錯。 在vuex中咱們可使用Action來執行異步操做。

  • Getter

    Getter用於對Store中的數據進行加工處理造成新的數據 它只會包裝Store中保存的數據,並不會修改Store中保存的數據,當Store中的數據發生變化時,Getter生成的內容也隨之變化

手動實踐

接下來咱們經過實踐的方式來體驗下上面這張圖的完整流程。

tip: 本文章由於是寫實踐方面,因此代碼量會有點多,建議你們邊看文章邊動手操做。

// 建立一個項目
 vue create vuex(你的項目名稱) 
 建立好的以後項目中有個store文件夾下面的index就是你的狀態管理庫, 接下來咱們體驗下完整的vuex狀態管理流程。
複製代碼

修改index.js

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import getters from './getters'
import actions from './actions'
import mutations from './mutations'

Vue.use(Vuex)
// 導出Store的實例
export default new Vuex.Store({ ********
state, // 數據源
getters, // 能夠對數據源進行二次處理
actions, // 用於觸發mutations中函數來修改state中的數據, 主要用於彌補mutations不能編寫異步代碼的問題
mutations // 用於修改數據源中的數據
})
複製代碼

建立state.js文件

const defaultLevel = '初級前端開發'
const salary = '5000'
const ages = [3, 2, 1, 4, 52, 20, 22, 10];


export default { // 提供了3個數據源
defaultLevel,
salary,
ages
}
複製代碼

建立mutations.js

const upgrade = (state, newLevel) => {
state.defaultLevel = newLevel
}

const upSalary = (state, newSalary) => {
state.salary = newSalary
}

export default { // 提供了2個修改數據源的方法
upgrade,
upSalary
}
複製代碼

建立actions.js

export default {
changeLevel({commit}, newLevel) { // actions中能夠編寫異步代碼
return new Promise((resolve) => {
setTimeout(() => {
commit('upgrade', newLevel)
console.log('打怪升級...');
console.log('打怪升級...');
console.log('打怪升級...');
resolve('升級完成了')
}, 2000);
})
},

}
複製代碼

建立getters.js

/* eslint-disable */
const filterAge = (state) => (term) => state.ages.filter((age) => age > term) // ES6語法

export default { // 提供了一個對數據源進行過濾的方法
filterAge
}

複製代碼

最後的總體目錄

app.js

<template> <div class="app"> <div class="example1"> <h1>例子1</h1> <div>目前等級: {{defaultLevel}} 薪資{{salary}}</div> <button @click="upgradeHandler">升級</button> </div> <div class="example2"> <h1>例子2</h1> <ul> <li v-for="item in ages" :key="item.toString()">{{item}}</li> </ul> <button @click="filterHandler">篩選</button> <div> <span>符合條件的數</span> <ul> <li v-for="item in newAge" :key='item.toString()'>{{item}}</li> </ul> </div> </div> </div> </template>

<script> /* eslint-disable */ import { mapState, mapGetters, mapActions, mapMutation, mapMutations, } from 'vuex' export default { name: 'app', data() { return { newAge: [] } }, computed: { // 在計算屬性中經過輔助函數, 將state中的兩個數據導出來 ...mapState(['defaultLevel', 'salary', 'ages']), }, methods: { ...mapActions(['changeLevel', 'changeLevel2']), ...mapMutations(['upSalary']), async upgradeHandler() { let ret = await this.changeLevel('中級前端開發') // 等待異步執行的結果 console.log(ret);

   setTimeout(() =&gt; {
     this.upSalary(10000) // 同步代碼能夠不經過actions的方式
   }, 1500);
   
   
   // this.newAge = this.getAge('10')
 },

 filterHandler() {
  this.newAge = this.$store.getters.filterAge(10)
 }
複製代碼

} } </script>

複製代碼setTimeout(() =&gt; { this.upSalary(10000) // 同步代碼能夠不經過actions的方式 }, 1500); // this.newAge = this.getAge('10') }, filterHandler() { this.newAge = this.$store.getters.filterAge(10) } 複製代碼<style> .example1 { margin-bottom: 50px; padding-bottom: 30px; border-bottom: 2px solid #000; } </style> 複製代碼

最後咱們來看看上面兩個例子的效果

例子1

例子2

建議先動手寫下代碼再來看效果

寫完上面那些代碼相信你們已經體會到了Vuex帶來的好處,接下來我用大白話解釋下Vuex

Vuex解決上面說的問題,組件(視圖)引用state(數據源)展現視圖,我經過手動dispatch來觸發actions中的方法commit(提交)觸發mutations來修改state(數據源)從新渲染數據改變組件(視圖)

好了, 如今咱們來寫一個todoList案列鞏固一下。

方便你們對照代碼看效果,能夠點這裏看實現的效果,源碼倉庫:

案列

A.初始化案例

能夠選擇從新初始化一個vuex的項目,也能夠用如今這個,咱們就用這個來吧。

而後打開public文件夾建立api文件夾,建立一個list.json文件模擬一下數據,文件代碼以下:

[
{
"id": 0,
"info": "Racing car sprays burning fuel into crowd.",
"done": false
},
{
"id": 1,
"info": "Japanese princess to wed commoner.",
"done": false
},
{
"id": 2,
"info": "Australian walks 100km after outback crash.",
"done": false
},
{
"id": 3,
"info": "Man charged over missing wedding girl.",
"done": false
},
{
"id": 4,
"info": "Los Angeles battles huge wildfires.",
"done": false
}
]
複製代碼

接着安裝下項目所須要的庫和插件

$ npm install vue-router axios ant-design-vue babel-plugin-import less-loader node-less --save-dev
複製代碼

注意: 若是less版本在3.x以上使用ant-design-vue是會報錯的,個人版本是3.10.3報錯了對於這個問題issue上有不少人解答,對於不一樣的版本環境可能解決的方案不同。

issue地址

個人解決方案: 建立vue.config.js添加以下代碼

module.exports = {
css: {
loaderOptions: {
less: {
lessOptions:{
javascriptEnabled: true,
}
}
}
},
}
複製代碼

再接着,打開main.js,添加store下的index.js``的引入,以下:

import Vue from 'vue'
import App from './Doto.vue'
import store from './store下的`index.js`'


/* 完整引入方式 **/
// 1. 導入 ant-design-vue 組件庫
import Antd from 'ant-design-vue'
// 2. 導入組件庫的樣式表
import 'ant-design-vue/dist/antd.css'
// 3. 安裝組件庫
Vue.use(Antd)

/** 按需加載方式: 引入模塊便可,無需單獨引入樣式**/
import { List, Button, Input, Checkbox} from 'ant-design-vue'
// 使用組件
Vue.use(List)
Vue.use(Button)
Vue.use(Input)
Vue.use(Checkbox)

new Vue({
store,
render: h => h(App)
}).$mount('#app')
複製代碼

再接着打開store文件夾下的index.js,添加axios請求json文件獲取數據的代碼,以下:

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

Vue.use(Vuex)

export default new Vuex.Store({
state: {
//全部任務列表
list: [],
//文本輸入框中的值
inputValue: 'Beige'
},
mutations: {
initList(state, list) {
state.list = list
},
setInputValue(state,value){
state.inputValue = value
}
},
actions: {
getList(context) {
axios.get('api/list.json').then(({ data }) => {
console.log(data);
context.commit('initList', data)
})
}
}
})
複製代碼

最後,建立Doto.vue並配置路由, 將store中的數據獲取並展現:

<template>
<div class="doto">
<a-input placeholder="請輸入任務" class="my_ipt" :value="inputValue" @change="handleInputChange" />
<a-button type="primary">添加事項</a-button>

<a-list bordered :dataSource="list" class="dt_list">
<a-list-item slot="renderItem" slot-scope="item">
<!-- 複選框 -->
<a-checkbox :checked="item.done">{{item.info}}</a-checkbox>
<!-- 刪除連接 -->
<a slot="actions">刪除</a>
</a-list-item>

<!-- footer區域 -->
<div slot="footer" class="footer">
<!-- 未完成的任務個數 -->
<span>0條剩餘</span>
<!-- 操做按鈕 -->
<a-button-group>
<a-button type="primary">所有</a-button>
<a-button>未完成</a-button>
<a-button>已完成</a-button>
</a-button-group>
<!-- 把已經完成的任務清空 -->
<a>清除已完成</a>
</div>
</a-list>
</div>
</template>

<script>
import { mapState } from 'vuex'
export default {
name: 'app',
data() {
return {
// list:[]
}
},
created(){
// console.log(this.$store);
this.$store.dispatch('getList')
},
methods:{
handleInputChange(e){
// console.log(e.target.value)
this.$store.commit('setInputValue',e.target.value)
}
},
computed:{
...mapState(['list','inputValue'])
}
}
</script>

<style scoped>
.doto {
margin: 20px 50px;
}

.my_ipt {
width: 500px;
margin-right: 10px;
}

.dt_list {
width: 500px;
margin-top: 10px;
}

.footer {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
複製代碼

B.完成添加事項

首先,打開Doto.vue文件,給「添加事項」按鈕綁定點擊事件也能夠給表單添加鍵盤事件,編寫處理函數

//綁定事件
<a-button type="primary" @click="addItemToList">添加事項</a-button>
<a-input placeholder="請輸入任務" class="my_ipt" :value="inputValue" @change="handleInputChange" @keydown.enter="addItemToList"/>

//編寫事件處理函數
methods:{
......
addItemToList(){
//向列表中新增事項
if(this.inputValue.trim().length <= 0){
return this.$message.warning('文本框內容不能爲空')
}

this.$store.commit('addItem')
}
}
複製代碼

而後打開store下的index.js編寫addItem

export default new Vuex.Store({
state: {
//全部任務列表
list: [],
//文本輸入框中的值
inputValue: 'AAA',
//下一個id
nextId:5
},
mutations: {
........
//添加列表項
addItem(state){
const obj = {
id :state.nextId,
info: state.inputValue.trim(),
done:false
}
//將建立好的事項添加到數組list中
state.list.push(obj)
//將nextId值自增
state.nextId++
state.inputValue = ''
}
}
......
})

複製代碼

C.完成刪除事項

首先,打開Doto.vue文件,給「刪除」按鈕綁定點擊事件,編寫處理函數

//綁定事件
<a slot="actions" @click="removeItemById(item.id)">刪除</a>

//編寫事件處理函數
methods:{
......
removeItemById(id){
//根據id刪除事項
this.$store.commit('removeItem',id)
}
}
複製代碼

而後打開store下index編寫removeItem

export default new Vuex.Store({
......
mutations: {
........
removeItem(state,id){
//根據id刪除事項數據
const index = state.list.findIndex( x => x.id === id )
// console.log(index);
if(index != -1) state.list.splice(index,1);
}
}
......
})
複製代碼

D.完成選中狀態的改變

首先,打開Doto.vue文件,給「複選」按鈕綁定點擊事件,編寫處理函數

//綁定事件
<a-checkbox :checked="item.done" @change="cbStateChanged(item.id,$event)">{{item.info}}</a-checkbox>

//編寫事件處理函數
methods:{
......
cbStateChanged(id,e){
//複選框狀態改變時觸發
const param = {
id:id,
status:e.target.checked
}

//根據id更改事項狀態
this.$store.commit('changeStatus',param)
}
}
複製代碼

而後打開store下的index.js編寫changeStatus

export default new Vuex.Store({
......
mutations: {
........
changeStatus(state,param){
//根據id改變對應事項的狀態
const index = state.list.findIndex( x => x.id === param.id )
if(index != -1) state.list[index].done = param.status
}
}
......
})
複製代碼

E.剩餘項統計

打開store下的index.js,添加getters完成剩餘項統計

getters:{
unDoneLength(state){
const temp = state.list.filter( x => x.done === false )
console.log(temp)
return temp.length
}
}
複製代碼

打開Doto.vue,使用getters展現剩餘項

//使用映射好的計算屬性展現剩餘項
<!-- 未完成的任務個數 -->
<span>{{unDoneLength}}條剩餘</span>

//導入getters
import { mapState,mapGetters } from 'vuex'
//映射
computed:{
...mapState(['list','inputValue']),
...mapGetters(['unDoneLength'])
}
複製代碼

F.清除完成事項

首先,打開Doto.vue文件,給「清除已完成」按鈕綁定點擊事件,編寫處理函數

<!-- 把已經完成的任務清空 -->
<a @click="clean">清除已完成</a>

//編寫事件處理函數
methods:{
......
cleanDone(){
//清除已經完成的事項
this.$store.commit('cleanDone')
}
}
複製代碼

而後打開store下的index.js編寫cleanDone

export default new Vuex.Store({
......
mutations: {
........
cleanDone(state){
state.list = state.list.filter( x => x.done === false )
}
}
......
})
複製代碼

G.點擊選項卡切換事項

打開Doto.vue,給「所有」,「未完成」,「已完成」三個選項卡綁定點擊事件,編寫處理函數 並將列表數據來源更改成一個getters。

<a-list bordered :dataSource="infoList" class="dt_list">
......
<!-- 操做按鈕 -->
<a-button-group>
<a-button :type="viewKey ==='all'?'primary':'default'" @click="changeList('all')">所有</a-button>
<a-button :type="viewKey ==='undone'?'primary':'default'" @click="changeList('undone')">未完成</a-button>
<a-button :type="viewKey ==='done'?'primary':'default'" @click="changeList('done')">已完成</a-button>
</a-button-group>
......
</a-list>

//編寫事件處理函數以及映射計算屬性
methods:{
......
changeList( key ){
//點擊「所有」,「已完成」,「未完成」時觸發
this.$store.commit('changeKey',key)
}
},
computed:{
...mapState(['list','inputValue','viewKey']),
...mapGetters(['unDoneLength','infoList'])
}
複製代碼

打開store下的index.js,添加gettersmutationsstate

export default new Vuex.Store({
state: {
......
//保存默認的選項卡值
viewKey:'all'
},
mutations: {
......
changeKey(state,key){
// 當用戶點擊「所有」,「已完成」,「未完成」選項卡時觸發
state.viewKey = key
}
},
......
getters:{
.......
infoList(state){
if(state.viewKey === 'all'){
return state.list
}
if(state.viewKey === 'undone'){
return state.list.filter( x => x.done === false )
}
if(state.viewKey === 'done'){
return state.list.filter( x => x.done === true )
}
}
}
})
複製代碼

Vuex原理解析

好了, 通過上面的案列相信你們已經對Vuex的使用瞭解的差很少了,接下來咱們來說下Vuex它的原理是這麼樣的?

Vuex的原理關鍵: 使用Vue實例來管理狀態

咱們先來看下效果:

接下來仍是經過代碼的形式來解析下Vuex內部的原理

<html>
<head>
<title>vuex 原理解析</title>
<script src='./vue.js'></script>
</head>
<body>
<!-- 首先在dom的層面上定義了三個vue的實例 -->
<div id="root">{{data}}</div>
<div id="root2">{{data2}}</div>
<div id="root3">
<button @click="change">change</button>
</div>

<script>
// 定義一個實現Vuex的插件
function registerPlugin(Vue) {...}
// 使用這個插件
Vue.use(registerPlugin)
new Vue({
el: '#root',
computed: { // 經過計算屬性根據數據變化來實時改變引用的視圖
data() {
return this.$store.state.message
}
}
})
new Vue({
el: '#root2',
computed: {
data2() {
return this.$store.state.message
}
}
})
new Vue({
el: '#root3',
methods: {
change() { // 提供一個change方法來改變store(倉庫)中的state(數據源)
const newValue = this.$store.state.message + '.'
this.$store.mutations.setMessage(newValue)
}
}
})
</script>
</body>
複製代碼

模仿Vuex源碼實現

<script>
// 定義一個實現Vuex的插件
function registerPlugin(Vue) {
// 自定義一個對象來模仿Vuex, 本質上的Vuex也就是一個對象
const vuex = {}
// 狀態管理的核心, 經過一個純粹的Vue實例來提供數據,
vuex._vm = new Vue({
data: {
message: 'hello vue.js'
}
})
// 定義state來指向vue構造出來的vue實例
vuex.state = vuex._vm

/* 定義一個mutations方法來更新state中的數據 */
vuex.mutations = {
setMessage(value) {
vuex.state.message = value
}
}
// 將全部實例上都掛載一個$store屬性指向vuex對象, 因此每一個實例均可以直接經過this.$store來引用vuex
function init() {
this.$store = vuex
}
// 經過一個全局的mixin方法在每一個實例beforeCreate階段調用init方法
Vue.mixin({
beforeCreate: init
})
}
</script>
複製代碼

寫在最後

由於是是實踐文,因此這整篇文章都是經過代碼的方式來說的,對於一些概念性和基礎性語法的東西講的比較少。若是 這篇文章對你有幫助請點個贊🤓

看完兩件小事

若是你以爲個人文章對你挺有幫助,我想請你幫我兩個小忙:

  1. 關注個人 GitHub 博文,讓咱們成爲長期關係
  2. 關注公衆號「前端自學驛站」,全部文章、資料第一時間首發公衆號,公衆號後臺回覆「 教程」 免費領取我精心整理的前端視頻教程
img
相關文章
相關標籤/搜索