Vue08:vuex

1、为什么要用 vuex ?

组件通信的类型

  • 父子通信
  • 跨级通信
  • 兄弟通信
  • 路由视图级别通信

在这里插入图片描述

2、通信解决方案 - 面试中可能会问到

通信核心:找到数据交互之间的桥梁。如果两者之间能直接沟通,那就直接沟通,如果不能,则找一个两者都能说得上话的人。

  • props/$emit(父子通信)
  • $refs/ref(父子通信)
  • children/parent(父子通信)
  • attrs/listeners(父子通信)
  • provide/inject(父子通信、跨级通信)
  • eventBus(父子通信、跨级通信、兄弟通信)
  • vuex(父子通信、跨级通信、兄弟通信、路由视图级别通信)
  • localStorage/sessionStorage等基于浏览器客户端的存储(父子通信、跨级通信、兄弟通信、路由视图级别通信)

2-1 props/$emit(父子通信)

父级

  1. 通过给子级标签添加动态属性传值
  2. 子集通过props参数接收父级数据
<template>
    <div>
        <h2>MyParent1</h2>
        <MyChild1 :data="name" @sendData="getChildData"></MyChild1>
        <p>我接收到了子级的数据-{{childData}}</p>
    </div>
</template>

<script>
import MyChild1 from "@/components/MyChild1";
export default {
    name: "MyParent1",
    data() {
        return {
            name: "MyParent1-Name",
            childData: ""
        };
    },
    components: {
        MyChild1
    },
    methods: {
        getChildData(data) {
            this.childData = data
        }
    }
};
</script>

子集

  1. 子集通过$emit方法提交给父级对应事件名传值
  2. 父级对应事件名触发自身方法
<template>
    <div>
        <h2>MyChild1</h2>
        <p>我接收来自父级传入的数据:{{ data }}</p>
        <button @click="sendDataToParent">把数据传递给父级</button>
    </div>
</template>

<script>
export default {
    name: "MyChild1",
    props: ["data"],
    data() {
        return {
            name: "MyChid1-Name"
        };
    },
    methods: {
        sendDataToParent() {
            this.$emit("sendData", this.name);
        }
    }
};
</script>

2-2 $refs/ref(父子通信)

  • 与props/emit 不同的是
  • ref不需要子集事先向父级提交
  • 而是父级直接拿到子组件的实例中的数据

父组件

<template>
    <div>
        <h2>MyParent1</h2>
        <MyChild1 :data="name" ref="myChild"></MyChild1>
        <button @click="getChildData">通过ref获取子集数据</button>
        <p>我接收到了子级的数据-{{childData}}</p>
    </div>
</template>

<script>
import MyChild1 from "@/components/MyChild1";
export default {
    name: "MyParent1",
    data() {
        return {
            name: "MyParent1-Name",
            childData: ""
        };
    },
    components: {
        MyChild1
    },
    methods: {
        getChildData() {
            console.log(this.$refs.myChild) // 拿到子级的组件实例对象
            this.childData = this.$refs.myChild.name;
        }
    }
};
</script>

子组件

<template>
    <div>
        <h2>MyChild1</h2>
        <p>我接收来自父级传入的数据:{{ data }}</p>
    </div>
</template>

<script>
export default {
    name: "MyChild1",
    props: ["data"],
    data() {
        return {
            name: "MyChid1-Name"
        };
    },
};
</script>

2-3 children/parent(父子通信)

console.log(this.$children) // 所有子组件实例的数组

在这里插入图片描述
获取子组件数据就更方便了

    methods: {
        getChildData() {
            console.log(this.$children) // 所有子组件的数组
            this.childData = this.$children[0].name;
        }
    }

同理,子组件可通过this.$parent获取父组件数据

	methods: {
		getParentData() {
			console.log(this.$parent);
			this.parentData = this.$parent.name;
		}
	},

2-4 attrs/listeners(父子通信)

attrs 获取父级在子组件标签上写的非props属性(非动态属性)
在这里插入图片描述

	methods: {
		getAttrs() {
			console.log(this.$attrs);	// 获取非props属性
		}
	},

但是不能获取事件,要获取事件可通过$listeners获取
在这里插入图片描述

		getListeners() {
			console.log(this.$listeners);	// 获取事件
		},

2-5 provide/inject(父子通信、跨级通信)

组件与组件之间是隔离的,没有所谓的作用域链
所以如何实现爷爷组件和孙子组件之间的数据传递呢?

方式一:通过$parent

拿爷爷传给父组件的数据

<p>爷爷的名字-{{$parent.data}}</p>

或者直接链式获取

<p>爷爷的名字-{{$parent.$parent.name}}</p>

但是这种方法很麻烦,易出错

方式二:provide/inject 跨级通信

提供数据/注入数据
爷爷组件provide选项

    provide() {
        return {
            pName: this.name
        }
    },

孙子组件inject选项

export default {
    name: "MyChidl1Child2",
    inject: ["pName"]
};
命名冲突的问题解决

如果自己本身有同样的命名,就会出现命名冲突的问题

export default {
    name: "MyChidl1Child2",
    inject: {
        parentPname: {
            from: "pName"
        }
    }
};

2-6- localStorage/sessionStorage等基于浏览器客户端的存储(父子通信、跨级通信、兄弟通信、路由视图级别通信)

  • 如果采用以上的方式进行兄弟组件数据传递的话,会一层层传递,非常繁琐,
  • 所以可以利用第三方来帮助数据传递:例如localstorage

存储

    methods: {
        saveToLS(){
            localStorage.setItem("pName", this.pName)
        }
    },

获取

    methods: {
        getLS() {
            this.pName = localStorage.getItem("pName")
        }
    },

存在问题

  1. 只能直接传字符串,不能是对象
  2. 函数很难传递

2-7 eventBus(父子通信、跨级通信、兄弟通信)

就是一个全局对象,例如挂载到windows

通过Vue实例化一个eventBus对象

import Vue from "vue";

let eventBus = new Vue({})
export default eventBus;

组件引入eventBus并注册一个事件

import eventBus from "@/eventBus";
export default {
    name: "MyChild2",
    data() {
        return {
            pName: ""
        };
    },
    created() {
        eventBus.$on("bus", data => {
            this.pName = data;
        });
    }
};

另一个组件通过eventBus触发这个事件,实现数据存储到Vue全局,数据传递

import eventBus from "@/eventBus"

export default {
    name: "MyChidl1Child2",
    inject: {
        parentPname: {
            from: "pName"
        }
    },
    data() {
        return {
            pName: "我是兄弟组件的pName"
        }
    },
    methods: {
        saveDataToEventBus(){
            eventBus.$emit("bus", this.pName)
        }
    },
};

在这里插入图片描述

3、vuex 是什么?

Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式,类似 redux
vuex(父子通信、跨级通信、兄弟通信、路由视图级别通信)

在这里插入图片描述

这种状态管理模式包含:

  • State : 状态数据源
  • View : 使用状态数据源的视图
  • Actions : 修改更新数据源的操作

这种模式遵循的是 单向数据流 模式

4、vuex 的工作流

在这里插入图片描述

  • State : 存储应用状态数据(React 中的 State)
  • Vue Component : 消费 State
  • Actions : 提交修改 State 的动作(包括异步行为)(React 中的 action)
  • Mutations : 唯一更改 State 的位置(React 中的 Reducer)

5、安装 vuex

npm i vuex
// or
yarn add vuex

6、引入 vuex

通过 <script> 引入

<script src="vue.js"></script>
<script src="vuex.js"></script>

通过 <script> 方式引入,vuex 会自动安装(也就是主动调用 Vue.use(Vuex)

通过 import 引入

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

Vue.use(Vuex)

通过 import 方式引入,需要手动安装(手动调用 Vue.use(Vuex)

7、从 Store 开始

**Store ** 就是仓库,我们前面提到的 state 就存储在 store 中,同时提交动作、修改状态的方法也都由 store 提供和管理

7-1、创建一个 Store

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

Vue.use(Vuex)

let store = new Vuex.Store({
    state: {},
    getters: {},
    mutations: {},
    actions: {}
})

必须在 Vue.use(Vuex) 之后创建 store

8、核心概念

  • state
  • getters
  • mutations
  • actions

8-1、state

8-1-1、state 的创建

存储应用状态数据的对象,state 的值可以是一个对象,也可以是一个返回对象的函数,类似 vue 中组件的 data ,使用函数的方式返回对象可以避免对象引用导致的副作用问题

// let state = {
//   a: 1
// }
let state = _=>({a:1})

const store = new Vuex.Store({
  	state
})
const store2 = new Vuex.Store({
    state
})

console.log(store.state == store2.state)
store.state.a = 100;
console.log(store.state.a, store2.state.a);
  • 通过 store.state 访问状态数据
  • state 数据与组件 data 一样是被追踪的
8-1-2、在组件中使用 store

store文件设置state

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

// 一个用于管理维护以及提供数据修改接口的对象
// 数据最好有一些规范,保证数据操作的安全性
const store = new Vuex.Store({
    // 存储基础数据的位置,类似组件中的data
    state: {
        title: "gaibain",
    }
});

console.log(store)

export default store;

将store引入Vue实例

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: function (h) { return h(App) }
}).$mount('#app')

使用state中的数据

<template>
    <div class="home">
        <h2>Home</h2>
        仓库中数据title: {{$store.state.title}}
    </div>
</template>

<script>
export default {
    name: "Home"
};
</script>

在这里插入图片描述
简化数据

<template>
    <div class="home">
        <h2>Home</h2>
        title
    </div>
</template>

<script>
export default {
    name: "Home",
    data() {
        return {
            title: this.$store.state.title
        }
    },
};
</script>

在这里插入图片描述
简化后出现的 问题: state 的更新并不会更新视图,因为组件中的data和store中的data并无更新关联

解决

使用 computed包裹store数据

<template>
  <div class="home">
    <h2>{{title}}</h2>
    <div>{{content}}</div>
  </div>
</template>

<script>
import store from '@/stores'
export default {
  name: 'home',
  computed: {
    title() {
      return store.state.title
    },
    content() {
      return store.state.content
    }
  }
}
</script>
8-1-3、store 配置

如果每个组件在使用 store 的时候都 import 会比较繁琐,这个时候,我们通过 vuex 提供的 store 选项把 store 对象注入到 vue 的原型上

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from '@/stores'

Vue.config.productionTip = false

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

配置注入后,我们就可以在组件实例中使用 this.$store 来访问 store 对象了

<template>
  <div class="home">
    <h2>{{title}}</h2>
    <div>{{content}}</div>
  </div>
</template>

<script>
// import store from '@/stores' // 可以去掉了
export default {
  name: 'home',
  computed: {
    title() {
      return this.$store.state.title
    },
    content() {
      return this.$store.state.content
    }
  }
}
</script>
8-1-4、使用辅助函数 mapState
  • 当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。
  • 为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键
  • 通常我们把 storestate 通过 mapState 函数映射到组件的 computed
<template>
    <div class="home">
        <h2>Home</h2>
        {{title}}
    </div>
</template>

<script>
import { mapState } from "vuex";
export default {
    name: "Home",
    computed: mapState(["title", "user"])
};

// function mapState(keys) {
//     return keys
//         .map(key => {
//             return {
//                 [key]() {
//                     return this.$store.state[key];
//                 }
//             };
//         })
//         .reduce((prev, curr) => {
//             prev[curr.key] = curr.value;
//             return prev;
//         }, {});
// }
</script>

通过对象方式进行映射
在这里插入图片描述

8-1-5、使用扩展运算符组合mapState

通过对象扩展运算符,可以把 mapState 返回的 state 属性与组件已有计算属性进行融合

<script>
import {mapState} from 'vuex'

export default {
    computed: {
        data() { return "test"},
        ...mapState(["title", "user"])
    }
}
</script>

在这里插入图片描述

当组件中已有与 store 同名的数据名称

使用对象的格式给mapState传参

<template>
    <div class="home">
        <h2>Home</h2>
        {{stateTitle}} - {{title}}
    </div>
</template>

<script>
import { mapState } from "vuex";
export default {
    name: "Home",
    computed: {
        title() {
            return "myTitle";
        },
        ...mapState({
            stateTitle: "title",
            stateUser: "user"
        })
    }
};
</script>

在这里插入图片描述
或者通过函数返回数据展示的格式

<template>
    <div class="home">
        <h2>Home</h2>
        {{user}}
    </div>
</template>

<script>
import { mapState } from "vuex";
export default {
    name: "Home",
    computed: {
        title() {
            return "myTitle";
        },
        ...mapState({
            stateTitle: "title",
            user(state) {
                return state.user.name + "/" + state.user.age
            }
        })
    }
};
</script>

在这里插入图片描述

9、getters

有时候我们需要从 store 中的 state 中派生出一些状态,类似组件的 datacomputedstore 也提供了一个 getters 对象来处理派生数据

9-1、getters 函数

与组件属性一样,我们是通过定义一个函数的形式来返回派生数据的,getters 函数接受两个参数

  • 第一个参数:state 对象
  • 第二个参数:getters 对象

9-2、通过属性访问

同样的,与组件计算属性一样,默认是通过属性的方式去访问 getters 中的数据的,这种方式与组件的计算属性一样,也是会缓存结果的

    // 类似组件中的computed
    getters: {
        test1(state){
            return `我是${state.user.name} 通过属性访问`
        },
    }
import { mapState } from "vuex";
export default {
    name: "Home",
    computed: {
        title() {
            return "myTitle";
        },
        ...mapState({
            stateTitle: "title",
            user(state) {
                return state.user.name + "/" + state.user.age
            },
            test1() {
                return this.$store.getters.test1
            }
        }),
    }
};

9-3、通过方法访问

我们还可以通过闭包函数的形式返回一个函数,来实现给 getters 函数传参,需要注意的是这种方式不支持结果缓存

    getters: {
        test1(state){
            return `我是${state.user.name} 通过属性访问`
        },
        test2:(state) => (id) =>{
            return `我是${state.user.name} 通过方法访问,传入id为:${id}`
        }
    }
    computed: {
		// ...
        test2() {
            return this.$store.getters.test2(1);
        }
    }

在这里插入图片描述

9-4、使用辅助函数 mapGetters

mapState 函数类似,通常映射到组件的 computed

10、mutations

  • 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation(类似 redux 中的 action + reducer)

  • Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)

  • mutation 中的函数不要直接调用

10-1、提交

store.commit(type, payload)
// or
store.commit({
    type: ...,
    payload: ...
})

type

要提交的 mutation 回调函数名称,type 为固定的 key

payload

载荷:提交的额外数据,任意格式

    methods: {
        editTitle() {
            // 直接修改会让数据比较乱
            // this.$store.state.title = "修改了title"
            this.$store.commit("editTitle", "newTitle");
        }
    }

10-2、mutation 函数

mutation 中的函数被 commit 执行的时候,接收两个参数

  • 第一个参数:state 对象
  • 第二个参数: commit 提交的 payload

mutation 函数中,我们就可以通过 state 对象进行状态数据的修改

    // 存储修改数据的方法
    mutations: {
        editTitle(state, newTitle) {
            state.title = newTitle;
        }
    }

10-3、使用辅助函数 mapMutations

mapMutations 函数的使用与 mapStatemapGetters 类似,但是其一般是把组件的 methods 映射为 storemutationscommit 调用

10-4、mutation 函数必须是同步的,不支持异步

  • commit 方法没有返回值,直接就是同步执行

  • 提交了commit以后,组件中可能需要根据提交后的结果去做一些处理,比如显示修改信息(成功了,失败了,这个提示视图有关,那么这个逻辑代码就不方便放在store去做了

    methods: {
        editTitle() {
            // 直接修改会让数据比较乱
            // this.$store.state.title = "修改了title"
            this.$store.commit("editTitle", "newTitle");
            console.log(this.$store.state.title)
        }
    }

在这里插入图片描述

11、actions

action 中的函数与 mutation 中的函数类似,但是它主要用来进行异步任务的处理,然后通过提交 mutation 来修改 state

注意action 中的函数不要直接修改 state,数据的修改还是需要mutation

11-1、提交

store.dispatch(type, payload)
// or
store.dispatch({
    type: ...,
    payload: ...
})

action 任务需要通过 dispatch 方法来提交(派发),与 commit 类似

dispatch 方法有返回值,且一定返回一个 promise 对象

11-2、action 函数

action 中的函数执行的过程中也接受两个参数

  • 第一个参数:store 对象
  • 第二个参数: dispatch 提交的 payload
    // 存储修改数据的方法
    mutations: {
        editTitle(state, newTitle) {
            state.title = newTitle;
        }
    },

    actions: {
        editTitle(store, payload) {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    store.commit("editTitle", payload);
                    resolve();
                }, 1000)
            });
        }
    }

支持异步

    methods: {
        async editTitle() {
            await this.$store.dispatch("editTitle", "newTitle");
            console.log(this.$store.state.title); 
        },

在这里插入图片描述

11-3、使用辅助函数 mapActions

mapMutations 函数类似,把组件的 methods 映射为 storeactionsdispatch 调用

再看vuex 的工作流

在这里插入图片描述

  • State : 存储应用状态数据(React 中的 State)
  • Vue Component : 消费 State
  • Actions : 提交修改 State 的动作(包括异步行为)(React 中的 action)
  • Mutations : 唯一更改 State 的位置(React 中的 Reducer)

12 Module

这个更多的是基于一种代码组织结构上的辅助
数据特别多的时候可以用这个模块化方式管理
modules

structure

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值