要分享的问题:
- Vue 都提供了哪些传参方式,每种传参方式都有什么优劣?
- 在什么场景下分别适合用什么方式进行传参?
- 怎么去总结这些选用传参方式时背后的根据?
1. 总结一下 vue 中所有的组件间的传参方式
1.1 通过 props 或者 $attrs 传递数据
- 原理:通过 v-bind 指令向子组件传递数据,子组件可通过 props 或者 inheritAttrs 接收数据
- 优点:简单直接,子组件在获取父组件的数据时就好像在获取自己的数据一样
- 缺点:由于单向数据流的设计思路,当子组件希望改变 props 中的数据时,需要经过额外的操作才能改变,在组件树层级较深的时候,跨层级数据传递和管理都不方便,
1.2 通过 emit events 在父子组件间传递数据
- 原理:先通过 v-on 指令对子组件注册 listener,或者通过 $on 注册 listener,再通过 $emit 将数据带上传给 listeners
- 优点:写法简单易读,模式成熟,并与 Vue 组件的生命周期绑定,没有使用原生的 EventListener,所以会自动销毁。而且 events 的作用域受限,不会像 $boardcast 那样导致事件泛滥
- 缺点:由于 events 的作用域受限,在非父子组件间使用 events 做数据通信时会比较尴尬
1.3 通过 vue-router 的 query 或者 params 在不同路由间传递数据
- 原理:其实就是不同页面间通过 url 传参,原来的 search 字符串被解析成 query 对象,REST url 会被解析成 params 对象
- 优点:可以直接在跳转的时候在同一个方法里面把数据带过去,操作简单,支持 REST url,表达性强
- 缺点:当传递的参数是一个对象时,url 会变得很难看,而且 url 有长度限制,传递的数据量有限,不能放需要持久化的数据,因为跳转到其他路由时,参数数据可能会被丢失,比如 openid,platform 之类的参数
1.4 通过 vuex 在不同组件间共享数据
- 原理:引入一个专门用于状态管理的库,用于管理需要全局共享的状态数据
- 优点:有一个专门的地方存放状态,不受 Vue 生命周期的限制,而且状态数据的存取方式比较科学和严格
- 缺点:库比较重,当数据量较少时可以有其他的替代方式(比如可以寄存在 $root 下),对使用者有一定的理解门槛
1.5 通过 EventBus 在不同组件间传递数据
- 原理:仿照事件总线的思路,单独开一个 Vue 实例,专门用于不同组件间的事件通信
- 优点:轻量,不需要额外的第三方依赖,是 Vue 官方文档本身提到的通信方式,理解成本低,非亲属关系的组件之间也能直接通信
- 缺点:当事件量越来越大时,不易管理订阅关系,由于脱离了源组件的生命周期,源组件在销毁时漏掉 $off 操作有可能导致内存泄漏。在使用时要将 EventBus 写成单例模式,否则有多个 bus 时容易使代码混乱
1.6 通过 provide 和 inject 方法传递数据
- 原理:在上游组件暴露出数据对象或方法,下游组件通过遍历 $parent 链找到最近的上游 provide,并注入给自身实例使用
- 优点:可以在祖孙组件间传递数据,由 Vue 本身提供,不需要第三方库依赖,适用于基础组件
- 缺点:本身属于一种 hack 类型的代码,当存在较多个上游 provide 或多个下游 inject 时,代码会变得难以理解
2. 在不同组件关系间对于传参方式的选用
2.1 父子组件间的数据传递
在 Vue 中,处于父子关系的组件,要从父组件获取子组件的数据,有以下几种方式
- 通过绑定 props 将父组件的数据关联到子组件,并修饰 .sync 或者用 v-model 同步来自子组件的数据变化
推荐
- 绑定 listener 事件监听器,当子组件状态或者数据发生变化时,触发事件并将数据传递到父组件
推荐
- 给子组件配置 ref 属性,当父组件需要时直接通过 $refs 去访问子组件内的 data 数据
不推荐
- 父组件通过 $children 获取子组件实例,再访问子组件的数据
不推荐
- 子组件将数据放到 Vuex,父组件从 Vuex 中获取子组件的数据
不推荐
- 父组件通过 EventBus 或者 $root 去注册事件监听,子组件去触发事件上传数据
不推荐
而要从子组件获取父组件的数据,也有以下几种方式
- 获取 props 或者 $attrs 传下来的数据
推荐
- 通过 vuex 去接受父组件共享的数据
不推荐
- 通过 EventBus 去注册监听父组件的事件
不推荐
- 通过 provide 和 inject 获取父组件的数据
不推荐
- 通过在 $parent.$on 监听父组件的事件,接受其传过来的数据
不推荐
- 通过 $parent 直接找到父组件的 data 去获取数据
不推荐
2.2 兄弟组件间的数据传递
在 Vue 中,处于兄弟关系的组件,要相互通信获取数据,有以下几种方式
- 通过 EventBus 或者 $root 去注册事件,由兄弟组件去监听事件传递过来的数据变化
推荐
- 在 Vuex 存放状态,并在两个组件中都监听状态变化
推荐
- 在父组件上绑定状态,并通过 v-model 或者 .sync 绑定到两个兄弟组件中去,以同步数据变化
推荐
- 通过 $parent 再去找 $children 中兄弟组件的数据
不推荐
2.3 祖孙组件间的数据传递
在 Vue 中,处于祖孙关系的组件,而且孙组件被直接写在了祖先组件的 template 内,要从祖先组件获取孙组件的数据,有以下几种方式
- 可以在模板上给孙组件绑定 ref 并通过 $refs 调用孙组件的方法获取数据
推荐
- 可以在模板上给孙组件绑定 listener 获取孙组件传过来的数据
推荐
- 可以在模板上给孙组件绑定 v-model 或者 .sync 同步孙组件的数据
推荐
- 将数据状态存放在 Vuex 内,再由祖先组件和孙组件之间共享
不推荐
- 由孙组件去触发 EventBus 事件,再由祖先组件去监听来自孙组件的数据变化
不推荐
- 祖先组件提供对象类型的 provide 属性,在孙组件 inject 后,修改对象中的属性值,数据会同步回给祖先组件
不推荐
- 通过 $children.$children 去直接访问获取孙组件中的数据
不推荐
如果孙组件不在祖先组件的 template 内,要从祖先组件获取孙组件的数据,有以下几种方式
- 先在子组件上绑定 v-model 或者 .sync,接着再在子组件的模板上通过 v-model 或者 .sync 绑定孙组件,以同步孙组件的数据
推荐
- 现在孙组件上绑定 listener,再给子组件绑定 listener,数据由事件层层上传给祖先组件
推荐
- 由孙组件去注册 EventBus 事件,再由祖先组件去监听来自孙组件的数据变化
推荐
- 将数据状态存放在 Vuex 内,再由祖先组件和孙组件之间共享
不推荐
- 祖先组件提供对象类型的 provide 属性,在孙组件 inject 后,修改对象中的属性值,数据会同步回给祖先组件
不推荐
- 通过 $children.$children 去直接访问获取孙组件中的数据
不推荐
如果孙组件在祖先组件的 template 内,要从孙组件获取祖先组件的数据,有以下几种方式
- 直接在祖先组件的 template 中通过 v-bind 将数据传递到孙组件中,孙组件通过 props 或者 $attrs 进行接收
推荐
- 孙组件在 EventBus 中注册事件,监听来自祖先组件触发的事件数据
不推荐
- 通过 $parent.$parent 向上找到祖先组件,然后访问祖先组件的数据
不推荐
- 祖先组件将数据挂到 Vuex 中,再由孙组件从 Vuex 中去获取数据
不推荐
- 在祖先组件提供 provide 将数据暴露出来,再由孙组件 inject 获取数据
不推荐
如果孙组件不在祖先组件的 template 内,要从孙组件获取祖先组件的数据,有以下几种方式
- 先在子组件上 v-bind 绑定数据,接着再在子组件上通过 v-bind 绑定孙组件,数据层层向下传递
推荐
- 孙组件在 EventBus 中注册事件,监听来自祖先组件触发的事件数据
推荐
- 祖先组件将数据挂到 Vuex 中,再由孙组件从 Vuex 中去获取数据
推荐
- 通过 $parent.$parent 向上找到祖先组件,然后访问祖先组件的数据
不推荐
- 在祖先组件提供 provide 将数据暴露出来,再由孙组件 inject 获取数据
不推荐
2.4 在不同路由组件之间的数据传递
在 Vue 中,若数据需要从前一个路由组件传递到后一个路由组件,有以下的数据传递方式
- 直接通过 vue-router 的 query 和 params 在路由跳转时传递数据
推荐
- 前一个路由组件将数据存放在 Vuex 中,在后一个路由组件中取出来
推荐
- 将数据临时挂在 $root 实例下,待路由跳转完成后取出并移除
不推荐
若要在后一个路由组件点击返回按钮后,传递数据到前一个路由组件,有以下两种方式
- 返回前将数据状态存放在 Vuex 内,然后回到前一个路由组件后从中取出来
推荐
- 将数据临时挂在 $root 实例下,待路由跳转完成后取出并移除
推荐
2.5 无直接关系组件之间的数据传递
在 Vue 中,两个组件之间若没有简单直接的关系,要相互通信获取数据,有以下几种方式
- 通过 EventBus 或者 $root 去注册事件,由兄弟组件去监听事件传递过来的数据变化
推荐
- 在 Vuex 存放状态,并在两个组件中都监听状态变化
推荐
- 将数据挂载 Vue.prototype 下,由 vue 去共享数据,类似于 $msgbox 这种共享数据
一般不推荐,复用性强的时候推荐
3. 实践中选择传参方式的根据
- 在大部分情况下,为了数据流向的清晰,都应该使用 props 将数据一层一层向下传递
- 在 v-bind 绑定具体属性的时候,应尽可能避免直接绑定整个对象,因为绑定对象有可能会破坏 watch 的观察效果
- Vuex 应该尽可能放入可复用的状态,Vuex 模块在使用完后应清理数据
- EventBus 在执行 $on 注册监听事件后,一定要记得使用 $off 注销事件,否则会内存泄漏
- 定义 props 时应尽可能配上默认值,避免一些不必要的渲染错误
- 在使用 vue-router 跳转路由的时候,应该尽可能减少参数的体积,不到万不得已不传整个对象,如果非要传整个对象,应该寄存到 $root, Vuex 或者其他地方去,因为 url 有长度限制,在跳转时参数过长会丢失数据,被寄存的数据记得应该在使用完成后进行销毁,避免内存泄漏
- 在业务组件中应尽可能避免使用 provide / inject 这种代码,因为 provide / inject 的数据流向不清晰,而且当父组件和祖先组件都存在同名的 provide 时,inject 的数据容易有歧义
- 使用 ref 时应该尽可能避免直接访问子孙组件的 data 数据,要修改子孙组件的数据时应该去访问子孙组件的 methods 中的方法,因为直接通过 $refs.xxx.data 去修改数据会导致组件数据混乱,不符合开闭原则