Vue为什么要谨慎使用$attrs与$listeners

前言

Vue 开发过程中,如遇到祖先组件需要传值到孙子组件时,需要在儿子组件接收 props ,然后再传递给孙子组件,通过使用 v-bind="$attrs" 则会带来极大的便利,但同时也会有一些隐患在其中。

隐患

先来看一个例子:

无情……,于是我打开看了看,尤大说了这么一番话我就好像明白了:

会有一个 id8dep 里面收集了 $attrs 所在的 Watcher ,我们再对比一下有无 v-bind="$attrs" 时的 set

派发更新状态:

 set: function reactiveSetter (newVal) {
 var value = getter ? getter.call(obj) : val;
 /* eslint-disable no-self-compare */
 if (newVal === value || (newVal !== newVal && value !== value)) {
  return
 }
 /* eslint-enable no-self-compare */
 if (process.env.NODE_ENV !== 'production' && customSetter) {
  customSetter();
 }
 if (setter) {
  setter.call(obj, newVal);
 } else {
  val = newVal;
 }
 childOb = !shallow && observe(newVal);
 var a = dep; // 查看当前 dep
 debugger; // debugger 断点
 dep.notify();
 }

这里可以明显看到也是 id8dep 正准备遍历 subs 通知 Watcher 来更新,也能看到 newValvalue

其实值并没有改变而进行了更新这个问题。

问题:$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 会被收集到依赖中,当 inputv-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;

所以会触发 $attrsset ,导致它所在的 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 产生了一些副作用,对于这两者的使用,建议在没有数据频繁变化时可以使用,或者使用上面的奇淫技巧,以及……把产生频繁变化的部分扔到一个单独的组件中让他自己自娱自乐去吧。

到此这篇关于Vue为什么要谨慎使用$attrs与$listeners的文章就介绍到这了,更多相关Vue $attrs与$listeners内容请搜索来客网以前的文章或继续浏览下面的相关文章希望大家以后多多支持来客网!