组件通信的类型
父子通信跨级通信兄弟通信路由视图级别通信通信核心:找到数据交互之间的桥梁。如果两者之间能直接沟通,那就直接沟通,如果不能,则找一个两者都能说得上话的人。
props/$emit(父子通信)$refs/ref(父子通信)children/parent(父子通信)attrs/listeners(父子通信)provide/inject(父子通信、跨级通信)eventBus(父子通信、跨级通信、兄弟通信)vuex(父子通信、跨级通信、兄弟通信、路由视图级别通信)localStorage/sessionStorage等基于浏览器客户端的存储(父子通信、跨级通信、兄弟通信、路由视图级别通信)父级
通过给子级标签添加动态属性传值子集通过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>子集
子集通过$emit方法提交给父级对应事件名传值父级对应事件名触发自身方法 <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>父组件
<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>获取子组件数据就更方便了
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; } },attrs 获取父级在子组件标签上写的非props属性(非动态属性)
methods: { getAttrs() { console.log(this.$attrs); // 获取非props属性 } },但是不能获取事件,要获取事件可通过$listeners获取
getListeners() { console.log(this.$listeners); // 获取事件 },组件与组件之间是隔离的,没有所谓的作用域链 所以如何实现爷爷组件和孙子组件之间的数据传递呢?
拿爷爷传给父组件的数据
<p>爷爷的名字-{{$parent.data}}</p>或者直接链式获取
<p>爷爷的名字-{{$parent.$parent.name}}</p>但是这种方法很麻烦,易出错
提供数据/注入数据 爷爷组件provide选项
provide() { return { pName: this.name } },孙子组件inject选项
export default { name: "MyChidl1Child2", inject: ["pName"] };如果自己本身有同样的命名,就会出现命名冲突的问题
export default { name: "MyChidl1Child2", inject: { parentPname: { from: "pName" } } };存储
methods: { saveToLS(){ localStorage.setItem("pName", this.pName) } },获取
methods: { getLS() { this.pName = localStorage.getItem("pName") } },存在问题
只能直接传字符串,不能是对象函数很难传递就是一个全局对象,例如挂载到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) } }, };Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式,类似 redux vuex(父子通信、跨级通信、兄弟通信、路由视图级别通信)
这种状态管理模式包含:
State : 状态数据源View : 使用状态数据源的视图Actions : 修改更新数据源的操作这种模式遵循的是 单向数据流 模式
通过 <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))
**Store ** 就是仓库,我们前面提到的 state 就存储在 store 中,同时提交动作、修改状态的方法也都由 store 提供和管理
必须在 Vue.use(Vuex) 之后创建 store
存储应用状态数据的对象,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 一样是被追踪的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>如果每个组件在使用 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>通过对象方式进行映射
通过对象扩展运算符,可以把 mapState 返回的 state 属性与组件已有计算属性进行融合
<script> import {mapState} from 'vuex' export default { computed: { data() { return "test"}, ...mapState(["title", "user"]) } } </script>使用对象的格式给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>有时候我们需要从 store 中的 state 中派生出一些状态,类似组件的 data 与 computed,store 也提供了一个 getters 对象来处理派生数据
与组件属性一样,我们是通过定义一个函数的形式来返回派生数据的,getters 函数接受两个参数
第一个参数:state 对象第二个参数:getters 对象同样的,与组件计算属性一样,默认是通过属性的方式去访问 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 } }), } };我们还可以通过闭包函数的形式返回一个函数,来实现给 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); } }与 mapState 函数类似,通常映射到组件的 computed 上
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation(类似 redux 中的 action + reducer)
Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)
mutation 中的函数不要直接调用
type
要提交的 mutation 回调函数名称,type 为固定的 key
payload
载荷:提交的额外数据,任意格式
methods: { editTitle() { // 直接修改会让数据比较乱 // this.$store.state.title = "修改了title" this.$store.commit("editTitle", "newTitle"); } }mutation 中的函数被 commit 执行的时候,接收两个参数
第一个参数:state 对象第二个参数: commit 提交的 payload在 mutation 函数中,我们就可以通过 state 对象进行状态数据的修改
// 存储修改数据的方法 mutations: { editTitle(state, newTitle) { state.title = newTitle; } }mapMutations 函数的使用与 mapState 和 mapGetters 类似,但是其一般是把组件的 methods 映射为 store 的 mutations 的 commit 调用
commit 方法没有返回值,直接就是同步执行
提交了commit以后,组件中可能需要根据提交后的结果去做一些处理,比如显示修改信息(成功了,失败了,这个提示视图有关,那么这个逻辑代码就不方便放在store去做了
methods: { editTitle() { // 直接修改会让数据比较乱 // this.$store.state.title = "修改了title" this.$store.commit("editTitle", "newTitle"); console.log(this.$store.state.title) } }action 中的函数与 mutation 中的函数类似,但是它主要用来进行异步任务的处理,然后通过提交 mutation 来修改 state
注意:action 中的函数不要直接修改 state,数据的修改还是需要mutation
action 任务需要通过 dispatch 方法来提交(派发),与 commit 类似
dispatch 方法有返回值,且一定返回一个 promise 对象
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); },与 mapMutations 函数类似,把组件的 methods 映射为 store 的 actions 的 dispatch 调用
这个更多的是基于一种代码组织结构上的辅助 数据特别多的时候可以用这个模块化方式管理 modules
structure