Vue3官网学习之attrs非响应式

422 阅读3分钟

下面对官网这句话进行详细剖析

attrs 和 slots 都是有状态的对象,它们总是会随着组件自身的更新而更新。此外还需注意,和 props 不同,attrs 和 slots 的属性都不是响应式的。

  1. attrsslots 会随着组件自身的更新而更新 这意味着当父组件向子组件传递的 attrs(非 props 的透传属性)或插槽内容发生变化时,子组件中的 attrsslots 的内容会实时更新。

示例代码

父组件

<template>
  <ChildComponent class="updated-class" :title="dynamicTitle">
    <template #default>
      <p>Dynamic Slot Content: {{ dynamicContent }}</p>
    </template>
  </ChildComponent>
</template>

<script>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';

export default {
  components: { ChildComponent },
  setup() {
    const dynamicTitle = ref('Initial Title');
    const dynamicContent = ref('Initial Slot Content');

    setTimeout(() => {
      dynamicTitle.value = 'Updated Title'; // 更新 attrs
      dynamicContent.value = 'Updated Slot Content'; // 更新插槽
    }, 2000);

    return { dynamicTitle, dynamicContent };
  },
};
</script>

子组件

<template>
  <div>
    <p>Title Attr: {{ attrs.title }}</p>
    <p>Class Attr: {{ attrs.class }}</p>
    <slot></slot>
  </div>
</template>

<script>
export default {
  setup(props, { attrs }) {
    return { attrs };
  },
};
</script>

  1. attrsslots 不是响应式的 虽然 attrsslots 的内容会更新,但它们自身不是响应式对象,无法通过 watchcomputed 自动追踪变化。
<script>
export default {
  setup(props, { attrs }) {
    console.log(attrs); // 初始打印的 attrs

    // 不会触发响应式更新
    const untrackedAttr = attrs.title;
    console.log(untrackedAttr); // 只打印初始值,后续变化不会自动更新

    return { attrs, untrackedAttr };
  },
};
</script>

为什么不会更新? 通过 const title = attrs.title; 提取 attrs.title 的值时,title 是一个静态的本地变量,它只在声明时取了 attrs.title 的初始值,后续父组件更新 attrs.title 时,title 不会同步更新。

  1. 避免解构使用: 解构会丢失更新,需要始终通过 attrs.xslots.x 的形式访问。
<script>
export default {
  setup(props, { attrs }) {
    const { title } = attrs; // 解构后 title 不会更新

    return { title, attrs }; // attrs 仍然能更新
  },
};
</script>

  1. onBeforeUpdate 中处理副作用 如果想要在 attrsslots 更新时执行一些逻辑(副作用),需要使用 onBeforeUpdate 钩子捕获更新时机。

示例代码

<script>
import { onBeforeUpdate } from 'vue';

export default {
  setup(props, { attrs, slots }) {
    onBeforeUpdate(() => {
      console.log('Attrs Updated:', attrs);
      console.log('Slots Updated:', slots);
    });

    return { attrs, slots };
  },
};
</script>

  1. 想要解构并动态更新的效果,可以理解为需要对 attrs 的属性(如 title)进行 依赖收集。当 attrs.title 更新时,触发依赖通知,更新解构后的 title 变量,实现响应式。

如何实现依赖收集?

方式 1:使用 Vue 的响应式工具

可以用 refcomputed 包装解构后的值,实现响应式。

setup(props, { attrs }) { 
    const title = computed(() => attrs.title); // 动态计算 attrs.title 
    return { title }; 
},

方式 2:手动在生命周期中同步更新

setup(props, { attrs }) { 
    const title = ref(attrs.title); // 创建响应式变量 
    onBeforeUpdate(() => { 
        title.value = attrs.title; // 在组件更新前同步 attrs.title 
    }); 
    return { title }; 
},

方式 3:通过代理实现响应式监听

使用 reactiveattrs 进行浅代理,对 attrs 的属性访问触发依赖收集。

setup(props, { attrs }) { 
    const reactiveAttrs = reactive(attrs); // 通过 reactive 包装 attrs 
    const { title } = toRefs(reactiveAttrs); // 将 title 解构为响应式变量 
    return { title }; 
},
  • 如果 attrs 频繁更新且属性较多,手动依赖收集可能带来额外性能开销。优先通过 computedonBeforeUpdate 钩子实现更新。
  • attrs 是 Vue 提供的一个特殊的 非响应式对象reactive 包装 attrs,使得其属性具备响应式能力。reactive(attrs) 的作用是为 attrs 添加响应式系统的依赖收集和通知更新能力。
  • toRefs 的设计目标是将一个响应式对象的每个属性解构为单独的响应式 ref。使用 toRefsreactiveAttrs.title 转化为独立的响应式 ref,方便解构使用。
  • toRefs 需要包装在响应式对象上工作。如果输入的对象(如 attrs)不是响应式对象,toRefs 返回的 title 只是一个普通的变量,不具备响应式能力。
const reactiveAttrs = { title: 'Initial Title' }; // 普通对象
const { title } = toRefs(reactiveAttrs); // title 不是响应式的
console.log(title.value); // undefined,因为 reactiveAttrs 不是响应式对象

推荐写法

  • 始终使用 reactive(attrs) 包装 attrs 后,再使用 toRefs 解构其属性。