一、Vuex是什麼?
介紹:Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。
理解:核心就是 store(倉庫),倉庫是用來幹什麼的?你就當它用來儲存東西的。
二、上方介紹提到的狀態管理模式是什麼?
首先我們先看一張圖:
這個狀態自管理應用包含以下幾個部分:
- state,驅動應用的數據源;
- view,以聲明方式將 state 映射到視圖;
- actions,響應在 view 上的用戶輸入導致的狀態變化。
new Vue({
// state
data () {
return {
count: 0
}
},
// view
template: `
<div>{{ count }}</div>
`,
// actions
methods: {
increment () {
this.count++
}
}
})
三、我們什麼時候應該用到Vuex呢?
1.小應用不建議使用Vuex,因爲小項目使用 Vuex 可能會比較繁瑣冗餘;
2.中大型單頁應用,因爲要考慮如何更好地在組件外部管理狀態,Vuex 將會成爲自然而然的選擇;
四、對於使用Vuex的理解是什麼?
由於Vue是單向數據流,子組件內部不能直接修改從父級傳遞過來的數據,子組件與子組件之間無法相互傳遞數據。如果我們想讓兩個子組件之間進行通信的話,可以藉助子組件 A 向父組件傳值,父組件接收子組件 A 的數據後再傳給 B 組件這樣的方式進行通信。
但是這樣會有一個問題,就是如果子組件 A 的父組件上面還有一層爺爺組件,或者還有更多祖父類型的層級,那麼是不是會很麻煩。
因此,我們會想到能不能我們將一個共有的數據存在一個特定的地方,用的時候自己去拿,這樣就不需要一層層傳值,於是乎 Vuex 就應運而生了。
五、核心概念
有五種屬性,分別是 State、 Getter、Mutation 、Action、 Module。
State # 單一狀態樹
Vuex 使用單一狀態樹——是的,用一個對象就包含了全部的應用層級狀態。至此它便作爲一個“唯一數據源 (SSOT)”而存在。這也意味着,每個應用將僅僅包含一個 store 實例。單一狀態樹讓我們能夠直接地定位任一特定的狀態片段,在調試的過程中也能輕易地取得整個當前應用狀態的快照。
Getter
Vuex 允許我們在 store 中定義“getter”(可以認爲是 store 的計算屬性)。就像計算屬性一樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會被重新計算。
Mutation
更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。Vuex 中的 mutation 非常類似於事件:每個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。這個回調函數就是我們實際進行狀態更改的地方,並且它會接受 state 作爲第一個參數:
Action
Action 類似於 mutation,不同在於:
- Action 提交的是 mutation,而不是直接變更狀態。
- Action 可以包含任意異步操作。
Module
由於使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象。當應用變得非常複雜時,store 對象就有可能變得相當臃腫。爲了解決以上問題,Vuex 允許我們將 store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行同樣方式的分割:
六、創建項目,開始實戰
安裝vuex:
npm install vuex --save
項目目錄(具體處已用箭頭標註):
目錄我們可以看到有一個父組件HelloWorld和兩個子組件JobList和JobTop。我們接下來就在這幾個父子組件裏面來介紹傳值方法;
HelloWorld.vue
我們在父組件裏面引入兩個子組件,並呈現出數據,方便接下來我們繼續傳值操作:
<template>
<div>
<job-top :job="job"></job-top>
<job-list :jobs="jobs"></job-list>
</div>
</template>
<script>
import JobTop from './component/JobTop';
import JobList from './component/JobList';
export default {
name: 'HelloWorld',
props: {},
data () {
return {
job:'',
jobs:[]
}
},
components:{
JobTop,
JobList,
},
methods: {
getListInfo() {
//這裏模擬的接口數據
this.getListInfoSucc({
"code":true,
"job":"web",
"data":{
"jobs":[
{
"id":1,
"name":"web"
},
{
"id":2,
"name":"C++"
},
{
"id":3,
"name":"python"
},
{
"id":4,
"name":"java"
}
]
}
})
},
getListInfoSucc(res){
if(res.code){
this.job=res.job;
this.jobs=res.data.jobs;
}
},
},
mounted() {
this.getListInfo();
},
}
</script>
<style scoped>
</style>
JobTop.vue
這裏我們繼續單個數據呈現,到時候方便顯示切換後的數據:
<template>
<p>{{job}}</p>
</template>
<script>
export default {
name:'JobTop',
props:{
job:String,
},
data(){
return{
}
},
}
</script>
<style>
</style>
JobList.vue
這裏是父組件裏面通過接口獲取的數組,進行遍歷所呈現出列表形式,我們之後再進行點擊傳值
<template>
<div>
<button v-for="item of jobs" :key="item.id">{{item.name}}</button>
</div>
</template>
<script>
export default {
name:'JobList',
props:{
jobs:Array,
},
data(){
return{
}
},
}
</script>
<style>
</style>
頁面呈現效果如下:
好,現在頁面呈現出來了,我接着要寫點擊事件來進行傳值,我們會想到三種傳值方式:
1.父子組件傳值
在 jobList.vue 中定義 jobClick 方法,將 changeJob 方法注入,並將所選中的 job 值傳入
<template>
<div>
<button v-for="item of jobs" :key="item.id" @click="jobClick(item.name)">{{item.name}}</button>
</div>
</template>
<script>
export default {
name:'JobList',
props:{
jobs:Array,
},
data(){
return{
}
},
methods:{
//父組件傳值
jobClick(name){
this.$emit("changeJob",name)
}
}
}
</script>
<style>
</style>
在父組件 HelloWorld.vue 中接收
<template>
<div>
<job-top :job="job"></job-top>
<job-list :jobs="jobs" @changeJob="ChangeJobClick"></job-list>
</div>
</template>
<script>
import JobTop from './component/JobTop';
import JobList from './component/JobList';
export default {
name: 'HelloWorld',
props: {},
data () {
return {
job:'',
jobs:[]
}
},
components:{
JobTop,
JobList,
},
methods: {
getListInfo() {
this.getListInfoSucc({
"code":true,
"job":"web",
"data":{
"jobs":[
{
"id":1,
"name":"web"
},
{
"id":2,
"name":"C++"
},
{
"id":3,
"name":"python"
},
{
"id":4,
"name":"java"
}
]
}
})
},
getListInfoSucc(res){
if(res.code){
this.job=res.job;
this.jobs=res.data.jobs;
}
},
ChangeJobClick(name){
console.log("選中的崗位",name);
this.job=name;
}
},
mounted() {
this.getListInfo();
},
}
</script>
<style scoped>
</style>
在父組件的的 job-list 標籤中寫上子組件 JobLict.vue注入的 changeJob方法,並指向ChangeJobClick 方法,ChangeJobClick 方法中接收傳過來的選中的 job 值,並將該值傳遞給子組件 JobTop.vue,JobTop頁面無需改動,頁面效果如下:
2、原型鏈繼承
正好之前一篇博客講過 javascript 的原型鏈繼承,vue 其實就是 js 的一種框架,它也有prototype 屬性,我們叫做 Bus總線方法。如下:
JobList.vue
<template>
<div>
<button v-for="item of jobs" :key="item.id" @click="jobClick(item.name)">{{item.name}}</button>
</div>
</template>
<script>
import Vue from 'vue'
Vue.prototype.bus=new Vue();
export default {
name:'JobList',
props:{
jobs:Array,
},
data(){
return{
}
},
methods:{
//原型鏈繼承
jobClick(name){
console.log("點擊某個崗位:",name)
this.bus.$emit("changeJob",name)
}
}
}
</script>
<style>
</style>
我們現在 Vue 的 prototype 屬性中引入 bus ,然後在 button 按鈕上綁定 jobClick 點擊事件,並傳入點擊的 job ,然後在 bus總線上注入 changeJob 方法,並將點擊的 job 值傳入,我們再來看一下
JobTop.vue
<template>
<p>{{jobself}}</p>
</template>
<script>
export default {
name:'JobTop',
props:{
job:String,
},
data(){
return{
jobself:this.job
}
},
mounted () {
//原型鏈繼承
var that=this;
this.bus.$on("changeJob",function(name){
console.log("點擊後傳過來的job",name)
that.jobself=name;
})
}
}
</script>
<style>
</style>
HelloWorld頁面無需更改,最終效果圖如下:
3、Vuex
接下來介紹今天的主角 Vuex ,Vuex 說白了就像是開闢了一個組件共有的內存空間,組件都可以去取去用;
我們在src創建 store 文件夾,裏面添加一個 index.js 來存儲共享數據,在 main.js 中引入 store文件,並在 Vue 實例中注入 store 。
main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import Vuex from 'vuex'
import store from './store'
Vue.use(VueRouter)
Vue.use(Vuex)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
JobList.vue
<template>
<div>
<button v-for="item of jobs" :key="item.id" @click="jobClick(item.name)">{{item.name}}</button>
</div>
</template>
<script>
export default {
name:'JobList',
props:{
jobs:Array,
},
data(){
return{
}
},
methods:{
//vuex
jobClick(name){
this.$store.dispatch("changeJob",name)
},
}
}
</script>
<style>
</style>
在 JobList.vue 中添加 jobClick 點擊方法,根據上面說的流程將一個 changJob 事件通過 dispatch 方法注入,並將所選的 job 值傳入。
倉庫 store 包含了應用的數據(狀態)和操作過程。 Vuex 裏的數據都是響應式的,任何組件使用同一 store 的數據時,只要 store 的數據變化,對應的組件也會立即更新。
mutation 裏不應該異步操作數據,所以有了 actions 選項 action與mutation很像,不同的是 action 裏面提交的是 mutation ,並且可以異步操作業務邏輯。action 在組件 通過$store.dispatch 觸發。
store文件夾下的index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
job:"web"
},
actions: {
changeJob(ctx,name){
console.log("action",ctx,name);
ctx.commit("changeJob",name);
}
},
mutations: {
changeJob(state,name){
console.log("mutation",state,name);
state.job=name;
}
}
})
JobTop.vue
獲取到job的值,渲染出來
<template>
<p>{{this.$store.state.job}}</p>
</template>
<script>
export default {
name:'JobTop',
props:{
job:String,
},
data(){
return{
}
},
}
</script>
<style>
</style>
HelloWorld頁面不需要改動,頁面效果如下:
或者將store/index.js改爲:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
job:"web",
list: [10,18,30,45]
},
getters: {
filteredList: state =>{
return state.list.filter(item => item<30);
}
},
actions: {
// changeJob(ctx,name){
// console.log("action",ctx,name);
// ctx.commit("changeJob",name);
// },
// 獲取
changeJob({state, commit}, params) {
console.log("....."+state.job) //沒有commit時,是上一次的值
console.log("=====>"+params);
commit("changeJob", params);
console.log("-----"+state.job)
}
},
mutations: {
changeJob(state,name){
console.log("mutation",state,name);
state.job=name;
}
}
})
結果:
注意commit 前後對 state.job 的影響:點python,點java,再點java
4、重構
以上就是vue的幾種組件傳參方式,大家想了解的更加官方,請閱覽官方地址:https://vuex.vuejs.org/zh/
根據官方 Vuex 目錄,我們再深一步講解一下核心概念,對上面的代碼進行一下重構。
JobList.vue
<template>
<div>
<button v-for="item of jobs" :key="item.id" @click="jobClick(item.name)">{{item.name}}</button>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
name:'JobList',
props:{
jobs:Array,
},
data(){
return{
}
},
methods:{
//vuex
jobClick(name){
this.changeJob(name)
},
...mapMutations(['changeJob'])
}
}
</script>
<style>
</style>
尤大大爲我們提供了封裝好的一些方法,如上面的 ...mapMutations() ,意思是 mutations 裏有一個 changeJob ,我們將其映射到該組件的一個 changeJob的方法裏,這樣就可以直接調用 changeJob 方法並傳值了
JobTop.vue
<template>
<p>{{this.job}}</p>
</template>
<script>
import { mapState } from 'vuex'
export default {
name:'JobTop',
props:{
},
data(){
return{
}
},
computed:{
...mapState(['job'])
},
}
</script>
<style>
</style>
在 mapState 裏有一個 city 屬性,將該屬性映射到該組件的 city 屬性裏,
效果圖如下:
5、getters用法
Vuex 定義了某個數據 list,它是一個數組,如果只想得到小於30 的數據,最容易想到的方法可能是在組件的計算屬性裏進行過濾。
JobTop.vue
<template>
<div>{{list}}</div>
</template>
<script>
export default {
name:'JobTop',
props:{
job:String,
},
data(){
return{
}
},
computed:{
list(){
return this.$store.state.list.filter(item => item<30)
}
}
}
</script>
<style>
</style>
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
job:"web",
list: [10,18,30,45]
}
})
結果:
[ 10, 18 ]
這樣寫完全沒有問題。但如果還有其他的組件也需要過濾後的數據時,就得 computed 的代碼完全複製一份,而且需要修改過濾方法時,每個用到的組件都得修改,這明顯不是我們期望的結果。如果能將 computed的方法也提取出來就方便多了, getters就是來做這件事的。
使用 getters 改寫上面的示例:
JobTop.vue
<template>
<div>{{list}}</div>
</template>
<script>
export default {
name:'JobTop',
props:{
job:String,
},
data(){
return{
}
},
computed:{
list(){
return this.$store.getters.filteredList;
}
}
}
</script>
<style>
</style>
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
job:"web",
list: [10,18,30,45]
},
getters: {
filteredList: state =>{
return state.list.filter(item => item<30);
}
}
})
結果:
[ 10, 18 ]
6、modules用法
它用來將 store 分割到不同模塊。當你的項目足夠大時, store 裏的 state、getters、utations、actions 會非常多,都放在 main.js 裏顯得不是很友好,使用 modules 可以把它們寫到不同的文件中。每個 module 擁有自己的 state、getters、mutations、actions ,而且可以 多層嵌套。
比如下面的示例:
const moduleA = {
state: { . . . } ,
mutations: { . . . } ,
actions: { . . . ) ,
getters: { . . . )
}
const moduleB= {
state: { . . . } ,
mutations: { . . . } ,
actions: { . . . ) ,
getters: { . . . )
}
const store= new Vuex.Store ( {
modules : {
a: moduleA,
b: moduleB
}
})
store.state.a // moduleA 的狀態
store.state.b // moduleB 的狀態
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const moduleA = {
state: {
job:"web",
list: [10,18,30,45]
},
getters: {
filteredList: state =>{
return state.list.filter(item => item<30);
}
},
}
export default new Vuex.Store({
modules : {
a: moduleA,
}
})
JobTop.vue
這裏不需要修改:
computed:{
list(){
return this.$store.getters.filteredList;
}
}
參考:https://blog.csdn.net/weixin_44811417/article/details/90766421