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