vue-day09----vuex、vuex传递数据流程、vuex的底层原理、vuex实现todolist、vuex辅助函数、vuex的modules配置项、vuex中数据丢失了如何解决、mock数据和json-server的使用
### vuex
1、什么是vuex?
官方:vuex是一个公共状态管理,通俗来说就是一种最好的非父子组件传值方案。
所谓的vuex就是一个公共的内存对象,它把所有组件需要公用的状态放到了一个公共的内存空间里,并且给每一个状态做了一个数据劫持(给每个状态添加了一个getter和setter方法)。
2、vuex的基本使用
①安装:npm install vuex -S
②引入组件:import Vuex from "vuex";
③使用vuex:Vue.use(Vuex);
④创建公共的内存对象:const store=new Vuex.Store({});
⑤将vuex导出挂载到vue身上:new Vue({Store});
⑥组件中引用state值:$store.state.n(this. 可以省略)
3、vuex中常用的配置项
state----存储公共的状态
mutations----修改公共状态
mutations中的函数都有2个参数:
参数一:state
参数二:传递的参数
actions----处理异步
actions中的函数都有2个参数:
参数一:是一个对象----{commit,dispatch,state,getters}
参数二:传递的参数
modules----公共状态私有化
场景:多人合作时定义一个自己的小型的store,里面有state、actions、mutations、getters,放到modules中。
getters----公共状态计算属性,相当于computed,依赖于state中的状态,当state中的属性发生改变的时候会触发getters中的方法
getters中的函数有个参数是state
### vuex传递数据的流程:
注意:*千万不要在组件的内部去修改state里面的数据。-----不要在组件中写 this.$store.state.num++ 这样的语句,正确的流程是 this.$store.commit('addNumber') ,然后在store.js中通过mutations中的addNumber方法去修改state数据。
为什么要去遵循:
①因为我们想要更明确地追踪到状态的变化
②通过vue-devtools来进行时间旅行的状态检测
修改数据的流程:
①组件中用dispatch()去触发actions中的方法
②actions中的方法通过解构commit后用commit()去触发mutations中的方法
③mutations中的方法才是去修改state中的数据
④因为数据是响应式的,所以数据变视图变
传参:dispatch()的第一个参数是actions中的函数名称,第二个参数是传递给actions中方法的参数。actions中的方法第一个参数是一个对象,里面有commit,可以通过解构拿到commit方法:{commit},第二个参数是接收到dispatch()传来的参数params。commit()的第一个参数是mutations中函数名称,第二个参数是传递给mutations中方法的参数params。mutations中的方法第一个参数是state对象,第二个参数是commit()传来的参数params。
步骤:(两个组件公用state中的n,其中一个组件通过dispatch()修改state中的n,state修改后触发视图更新,两个组件的n都更改了)
①新建store/index.js:
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const store=new Vuex.Store({ state:{ n:10 }, actions:{ handleActionsAdd({commit},params){ commit("handleMutationsAdd",params); } }, mutations:{ handleMutationsAdd(state,params){ state.n++; console.log(params) } } }); export default store;
②main.js中引入并挂载到vue实例身上:
import store from './store'; new Vue({ store, render: h => h(App) }).$mount('#app');
③组件中用dispatch()方法触发actions中的handleActionsAdd():(注意组件这里一定不要去修改state中的数据)
methods: { handleAdd(){ this.$store.dispatch("handleActionsAdd","wql"); } }
### vuex的底层原理
步骤:
①src下新建wql-vuex/index.js:
let Vue; function install(_Vue){ Vue=_Vue; Vue.mixin({// Vue.mixin()----给vue的当前组件扩展一些属性和方法 // 将store的实例挂在vue的实例身上 beforeCreate(){ if(this.$options.store){ Vue.prototype.$store = this.$options.store; } } }); } class Store{ constructor(options){ // 将state中的状态转换为响应式状态 this.state=new Vue({ data:options.state }); this.mutations=options.mutations||{};// 初始化mutations this.actions=options.actions||{};// 初始化actions options.getters&&this.handleGetters(options.getters);// 初始化getters } commit(eventName,params){ let fn=this.mutations[eventName]; fn(this.state,params); } dispatch(eventName,params){ let fn=this.actions[eventName]; // dispatch()是在组件中调用的,this执行那个组件,这里用bind()将this重新指向Store,bind()和call()、apply()的区别就是bind()应用于回调函数的绑定对象,在回调函数触发的时候执行,而call()、apply()会立即执行 fn({commit:this.commit.bind(this),dispatch:this.dispatch.bind(this),state:this.state},params); } handleGetters(getters){ let that=this; this.getters={}; Object.keys(getters).forEach(key=>{ // 这里的this指向的是Object,而我们需要让this指向当前的类Store,用that替换this。有的时候改变this指向需要用到bind(),bind()是用于回调函数的绑定this对象,当函数触发时执行,回调函数的绑定this不能用call()或apply(),因为它们俩会立即执行。 Object.defineProperty(that.getters,key,{ get:function(){ return getters[key](that.state); } }); }); } } export default {Store,install};
注意:
1、这里只实现了部分vuex的功能,可以进行数据公用、修改数据、getters计算属性。
2、handleGetters()中的Object.defineProperty()中的this指向的是对象,我们需要让this重新指向Store。
②store/index.js中引入vuex时:
import Vuex from "vuex"; 可以改为 import Vuex from "../wql-vuex";
### vuex实现todolist
步骤:
①conponents中新建InputVal.vue和List.vue:
InputVal.vue:(value值取自vuex中state。定义input事件,当input框中值发生变化时触发actions中方法以更改state中inputVal。点击add按钮触发actions中方法以更改state中list。)
<template> <div> <!-- 这里的事件函数如果不写(),就是不传参数,此时在methods中方法可以接收到e对象;如果写了(),而没有手动传递$event,此时methods中方法无法接收到e对象 --> <input type="text" :value="$store.state.inputVal" @input="handleInput($event)"> <button @click="add">添加</button> </div> </template> <script> export default { name:"Input", methods: { handleInput(e){ this.$store.dispatch("handleActionsChange",e.target.value); }, add(){ this.$store.dispatch("handleActionsListAdd"); } }, } </script>
List.vue:(遍历state中的list)
<template> <div> <ul> <li v-for="(item,index) in $store.state.list" :key="index">{{item}}</li> </ul> </div> </template>
②store/index.js中:(actions中的方法通过InputVal.vue中dispatch()触发,再通过commit()触发mutations中的方法以修改state中的值。)
import Vue from "vue"; import Vuex from "../wql-vuex"; Vue.use(Vuex); const store=new Vuex.Store({ state:{ inputVal:"", list:[] }, actions:{ handleActionsChange({commit},value){ commit("handleMutationsChange",value); }, handleActionsListAdd({commit}){ commit("handleMutationsListAdd"); } }, mutations:{ handleMutationsChange(state,params){ state.inputVal=params; }, handleMutationsListAdd(state){ state.list.push(state.inputVal); state.inputVal=""; } } }); export default store;
③App.vue中引入和渲染InputVal.vue、List.vue:
import InputVal from "components/InputVal.vue"; import List from "components/List.vue"; export default { name: "app", components: { One, Two, InputVal, List } };
<h1>todolist</h1> <InputVal></InputVal> <List></List>
④这里的todolist是同步的,所以可以直接在InputVal.vue中触发commit(),而省去了store/index.js中actions中的handleActionsChange()和handleActionsListAdd():
InputVal.vue:(执行同步方法可以直接在组件中用commit())
export default { name:"Input", methods: { handleInput(e){ console.log(e) this.$store.commit("handleMutationsChange",e.target.value); // this.$store.dispatch("handleActionsChange",e.target.value); }, add(){ this.$store.commit("handleMutationsListAdd"); // this.$store.dispatch("handleActionsListAdd"); } } }
store/index.js:
import Vue from "vue"; import Vuex from "../wql-vuex"; Vue.use(Vuex); const store=new Vuex.Store({ state:{ inputVal:"", list:[] }, mutations:{ handleMutationsChange(state,params){ state.inputVal=params; }, handleMutationsListAdd(state){ state.list.push(state.inputVal); state.inputVal=""; } } }); export default store;
注意:
1、只要用了vuex,input标签中就不能使用v-model。v-model原理是直接修改数据,如果使用了v-model那么数据直接在组件中就修改了,而vuex要求数据在mutations中修改。
2、如果不涉及到异步,可以在组件中直接触发commit(),而涉及到异步,就要用到actions,在actions中做axios请求,然后将拿到的值通过触发mutations时传递过去。mutations中只管做数据修改,actions中会做业务逻辑处理(异步、数据请求)。M(数据)V(视图)VM(映射)
3、input事件是在输入框每次变化时都会执行一次,change事件在输入框操作完后,按下回车键执行。
4、事件函数如果不写(),就是不传参数,此时在methods中方法可以接收到e对象;如果写了(),而没有手动传递$event,此时methods中方法无法接收到e对象。
5、在组件中使用state的值时,如果是在标签中如<input type="text" :value="$store.state.inputVal">可以直接获取到$store,this可以省略;如果是在js中想要使用$store,this不能省略如 computed:{inputVal(){return this.$store.state.inputVal;}}
### vuex辅助函数
概念:通过映射的关系将vuex中的方法或者状态映射给组件的某一个属性。辅助函数的返回值都是一个对象。
1、mapState()----映射到computed
2、mapMutations()----映射到methods
3、mapActions()----映射到methods
4、mapGetters()----映射到computed
步骤:
(1)mapState()----映射到组件的computed身上才能使用
①store/index.js中:
state:{ name:"吴小明", age:24 }
②如果在组件中想要使用name和age可以通过:
<h2>{{$store.state.name}}</h2> <h2>{{$store.state.age}}</h2>
这是最原始的方法,this可以省略;还可以通过computed计算属性:
computed: { name(){ return this.$store.state.name; }, age(){ return this.$store.state.age; } }
这样可以直接使用name和age:
<h2>{{name}}</h2> <h2>{{age}}</h2>
接下来通过mapState()获取state中的name和age:
引入mapState():import {mapState,mapMutations,mapActions,mapGetters} from "vuex";
computed中使用:
computed: { ...mapState(["name","age"]) // 利用辅助函数可以省略name()和age()方法 }
如果computed中只有一个mapState(),computed也可以写成:computed:mapState(["name","age"])
这是以数组的形式接收name和age,还可以用对象形式(推荐):
computed: { ...mapState({ name:state=>state.name, age:state=>state.age }) }
推荐以对象的形式去接收state中的状态,这样可以随意更改接收的名字,还以改映射的值,而这里做的修改没有动vuex中的数据,只在本组件起作用。其中,name:state=>state.name 是 name:(state)=>{return state.name} 的简写。
(2)mapMutations()----映射到组件的methods身上才能使用
①store/index.js中定义mutations中mutationsAgeAdd():
mutations:{ mutationsAgeAdd(state){ state.age++; } }
②组件中要想使用mutationsAgeAdd()可以弄个按钮写上ageAdd点击事件,触发commit():
methods:{ ageAdd(){ this.$store.commit("mutationsAgeAdd"); } }
现在通过辅助函数mapMutations():
methods:{ ...mapMutations(["mutationsAgeAdd"]) }
然后将按钮<button @click="ageAdd">ageAdd</button>替换为<button @click="mutationsAgeAdd">ageAdd</button>,点击事件的名称和通过mapMutations()接收到的还是名称一致。
这是以数组的形式接收,还可以用对象的形式接收:
methods:{ ...mapMutations({ ageAdd:"mutationsAgeAdd" }) }
推荐用对象接收,这样点击事件的名称可以自己定义,这里我还使用ageAdd:<button @click="ageAdd">ageAdd</button>。不管是数组还是对象形式接收,数组的项和对象的属性值都是mutations中定义的函数名称,不同的是对象可以定义属性的名称,这样用起来更方便一点。
(3)mapActions()----映射到组件的methods身上才能使用
①store/index.js中定义actions中actionsAgeAdd():
actions:{ actionsAgeAdd({commit}){ commit("mutationsAgeAdd"); } }
②组件中先在methods中定义一个方法去触发dispatch(),在弄个按钮<button @click="actionsAgeAdd">actionsAgeAdd</button>点击触发:
methods: { actionsAgeAdd(){ this.$store.dispatch("actionsAgeAdd"); } }
现在使用辅助函数:
methods: { ...mapActions({ actionsAgeAdd:"actionsAgeAdd" }) }
这样,在按钮中直接使用actionsAgeAdd方法(属性名)可以触发到actions中的actionsAgeAdd方法(属性值)。
(4)mapGetters()----映射到组件的computed身上才能使用
①store/index.js中定义getters中double():
getters:{ double(state){ return state.age*2; } }
②在组件中可以通过$store.getters.double拿到:
<p>{{$store.getters.double}}</p>
现在使用辅助函数:
computed: { ...mapGetters({ double:"double" }) }
这样可以在template中直接使用double拿到getters中的double():<p>{{double}}</p>
### vuex的modules配置项
概念:modules中存放的是小型的vuex,可以理解它是一个小型的状态管理仓库集合。modules中的每一个小模块的配置项与store里面的配置项一样,具有state、mutations、actions、getters。
注意:
1、子模块在导出时一定要加 namespaced:true 实现子模块私有化。
2、当设置了子模块私有化后访问子模块的方法必须要通过 子模块名/方法名,如:city/mutationsSonHandle。
作用:当多人合作时,定义一个自己的私有模块,以区分项目中的状态。
步骤:
①store下新建city/index.js:
export default{ state:{ cityName:"北京" }, actions:{ actionsSonHandle({commit}){ commit("mutationsSonHandle") } }, mutations:{ handleMutationsAdd(){ console.log("子模块") }, mutationsSonHandle(){ console.log("调用子模块了") } }, getters:{ }, // 实现子模块私有化,当子模块和主模块有同名方法时,子模块的方法前会加 city/ 如 handleMutationsAdd 为 city/handleMutationsAdd namespaced:true }
②store/index.js中引入并注册city模块:
import city from "./city";
modules:{
city
}
③如何使用city模块:
在state属性前加上子模块名,即(如果此时主模块state中有city属性,则子模块会替代主模块的该属性):
<p>{{$store.state.city.cityName}}</p>
当使用city模块中方法时,加上模块名,即(如果不设置子模块私有化,会同时执行主模块和子模块的同名方法):
methods: { sonHandle(){ // this.$store.dispatch("city/actionsSonHandle"); this.$store.commit("city/mutationsSonHandle"); } }
Question:
1、vuex数据传递的流程
①组件中用dispatch()去触发actions中的方法
②actions中的方法通过解构commit后用commit()去触发mutations中的方法
③mutations中的方法才是去修改state中的数据
④因为数据是响应式的,所以数据变视图变
2、vuex中数据刷新浏览器丢失了如何解决
数据优化的时候需要用到本地存储:localStorage sessionStorage
3、什么时候需要用到vuex?
①不是所有的项目都需要用到vuex
②只有复杂的中大型项目需要用到vuex
③如果小型项目用到了vuex会导致运行速度特别慢
4、vuex中常用配置项是哪些?每一个的作用是什么?
state
mutations
actions
getters
modules
5、vuex的底层原理
### mock数据和json-server的使用
json-server使用步骤:
安装:npm install json-server -g(cmd终端中安装)
使用:json-server 文件名
①day09下新建data/data.json:
{ "data":[ { "username":"wql", "age":24, "id":1 }, { "username":"吴小明", "age":23, "id":2 } ] }
②用cmd打开data文件夹,json-server data.json启动后会显示:
\{^_^}/ hi! Loading data.json Done Resources http://localhost:3000/data Home http://localhost:3000 Type s + enter at any time to create a snapshot of the database
③浏览器中http://localhost:3000/data可以访问到data.json中数据
④可以新建一个文件jsonserver/index.html对data.json中数据做增删改查操作:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>jsonserver</title> </head> <body> <button onClick="insert()">增</button> <button onClick="remove()">删</button> <button onClick="update()">改</button> <button onClick="find()">查</button> </body> </html> <script src="https://cdn.bootcss.com/jquery/1.11.3/jquery.js"></script> <script> /* json-server mock数据: 接口地址:/user/login 请求类型:GET 测试接口:http://www.baidu.com 请求字段: username password 响应字段: { data:{ list:[], code:"" } } 安装:npm install json-server -g 启动mock数据:json-server 文件名称 在json-server中请求的类型有很多种: get---获取 post---提交 put---修改 patch---修改某一个属性 delete---删除数据 */ function insert(){ $.ajax({ type:"post", url:"http://localhost:3000/data",// mock数据没有跨域问题 data:{ username:"吴彦祖", age:20 }, success:function(data){ console.log(data) } }); } function remove(){ $.ajax({ type:"delete", url:"http://localhost:3000/data/1",// 删除id为1的数据 success:function(data){ console.log(data) } }); } function update(){ $.ajax({ type:"patch",// 不建议用put方式,put会将data数据替换掉data.json中的对应数据 url:"http://localhost:3000/data/1",// 修改id为1的数据 data:{ name:"孙艺珍", age:200 }, success:function(data){ console.log(data) } }); } function find(){ $.ajax({ type:"get", url:"http://localhost:3000/data?q=彦",// /1---拿到第一条数据 ?id=1---拿到第一条数据,数组包裹 ?q=彦---模糊查询 success:function(data){ console.log(data) } }); } </script>
### 购物车
先启动数据:json-server goods.json
再启动项目:npm run dev