前言
在 Vue
开发过程中,如遇到祖先组件需要传值到孙子组件时,需要在儿子组件接收 props
,然后再传递给孙子组件,通过使用 v-bind="$attrs"
则会带来极大的便利,但同时也会有一些隐患在其中。
隐患
先来看一个例子:
父组件:
{ template: ` <div> <input type="text" v-model="input" placeholder="please input"> <test :test="test" /> </div> `, data() { return { input: '', test: '1111', }; }, }
子组件:
{ template: '<div v-bind="$attrs"></div>', updated() { console.log('Why should I update"text-align: center">无情……,于是我打开看了看,尤大说了这么一番话我就好像明白了:
那既然不是“bug”,那来看看是为什么吧。
前因
首先介绍一个前提,就是
Vue
在更新组件的时候是更新对应的data
和props
触发Watcher
通知来更新渲染的。每一个组件都有一个唯一对应的
Watcher
,所以在子组件上的props
没有更新的时候,是不会触发子组件的更新的。当我们去掉子组件上的v-bind="$attrs"
时可以发现,updated
钩子不会再执行,所以可以发现问题就出现在这里。原因分析
Vue
源码中搜索$attrs
,找到src/core/instance/render.js
文件:export function initRender (vm: Component) { // ... defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true) defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true) }噢,amazing!就是它。可以看到在
initRender
方法中,将$attrs
属性绑定到了this
上,并且设置成响应式对象,离发现奥秘又近了一步。依赖收集
我们知道
Vue
会通过Object.defineProperty
方法来进行依赖收集,由于这部分内容也比较多,这里只进行一个简单了解。Object.defineProperty(obj, key, { get: function reactiveGetter () { const value = getter "htmlcode">Object.defineProperty(obj, key, { set: function reactiveSetter (newVal) { const value = getter "htmlcode">notify () { // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }了解到这些基础后,我们再回头看看
$attrs
是如何触发子组件的updated
方法的。要知道子组件会被更新,肯定是在某个地方访问到了
$attrs
,依赖被收集到subs
里了,才会在派发时被通知需要更新。我们对比添加v-bind="$attrs"
和不添加v-bind="$attrs"
调试一下源码可以看到:get: function reactiveGetter () { var value = getter "$attrs" 时,会多收集到一个依赖。会有一个
id
为8
的dep
里面收集了$attrs
所在的Watcher
,我们再对比一下有无v-bind="$attrs"
时的set
派发更新状态:
set: function reactiveSetter (newVal) { var value = getter "text-align: center">这里可以明显看到也是
id
为8
的dep
正准备遍历subs
通知Watcher
来更新,也能看到newVal
与value
其实值并没有改变而进行了更新这个问题。
问题:$attrs 的依赖是如何被收集的呢?
我们知道依赖收集是在
get
中完成的,但是我们初始化的时候并没有访问数据,那这是怎么实现的呢?答案就在
vm._render()
这个方法会生成Vnode
并在这个过程中会访问到数据,从而收集到了依赖。那还是没有解答出这个问题呀,别急,这还是一个铺垫,因为你在
vm._render()
里也找不到在哪访问到了$attrs
...柳暗花明
我们的代码里和
vm._render()
都没有对$attrs
访问,原因只可能出现在v-bind
上了,我们使用vue-template-compiler
对模板进行编译看看:const compiler = require('vue-template-compiler'); const result = compiler.compile( // ` // <div :test="test"> // <p>测试内容</p> // </div> // ` ` <div v-bind="$attrs"> <p>测试内容</p> </div> ` ); console.log(result.render); // with (this) { // return _c( // 'div', // { attrs: { test: test } }, // [ // _c('p', [_v('测试内容')]) // ] // ); // } // with (this) { // return _c( // 'div', // _b({}, 'div', $attrs, false), // [ // _c('p', [_v('测试内容')]) // ] // ); // }这就是最终访问
$attrs
的地方了,所以$attrs
会被收集到依赖中,当input
中v-model
的值更新时,触发set
通知更新,而在更新组件时调用的updateChildComponent
方法中会对$attrs
进行赋值:// update $attrs and $listeners hash // these are also reactive so they may trigger child update if the child // used them during render vm.$attrs = parentVnode.data.attrs || emptyObject; vm.$listeners = listeners || emptyObject;所以会触发
$attrs
的set
,导致它所在的Watcher
进行更新,也就会导致子组件更新了。而如果没有绑定v-bind="$attrs"
,则虽然也会到这一步,但是没有依赖收集的过程,就无法去更新子组件了。奇淫技巧
如果又想图人家身子,啊呸,图人家方便,又想要好点的性能怎么办呢?这里有一个曲线救国的方法:
<template> <Child v-bind="attrsCopy" /> </template> <script> import _ from 'lodash'; import Child from './Child'; export default { name: 'Child', components: { Child, }, data() { return { attrsCopy: {}, }; }, watch: { $attrs: { handler(newVal, value) { if (!_.isEqual(newVal, value)) { this.attrsCopy = _.cloneDeep(newVal); } }, immediate: true, }, }, }; </script>总结
到此为止,我们就已经分析完了
$attrs
数据没有变化,却让子组件更新的原因,源码中有这样一段话:// $attrs & $listeners are exposed for easier HOC creation. // they need to be reactive so that HOCs using them are always updated
一开始这样设计目的是为了
HOC
高阶组件更好的创建使用,便于HOC
组件总能对数据变化做出反应,但是在实际过程中与v-model
产生了一些副作用,对于这两者的使用,建议在没有数据频繁变化时可以使用,或者使用上面的奇淫技巧,以及……把产生频繁变化的部分扔到一个单独的组件中让他自己自娱自乐去吧。广告合作:本站广告合作请联系QQ:858582 申请时备注:广告合作(否则不回)
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!暂无评论...
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]