【Vue3】响应式

2022/12/05 12:15:132022/12/01 16:15:15

Vue3 中使用了 Proxy 来创建响应式对象,仅将 getter / setter 用于 ref(文档位置open in new window)。

响应性语法糖open in new window是实验性功能,默认是禁用的。

使用方式

setup(props, ctx)

使用选项式 API 时可以在 setup() 中声明响应式状态,其返回值如果是对象则会暴露给模板和组件实例,如果返回一个渲染函数 h() 则会替代 <template> 模板(需调用 expose() 方法声明要导出的属性)。

  • props 解构需要借助工具函数,否则会失去响应性。
export default {
  setup(props, { attrs, slots, emit, expose }) {
    // 将 `props` 转为一个其中全是 ref 的对象,然后解构
    const { title } = toRefs(props);

    // `title` 是一个追踪着 `props.title` 的 ref
    console.log(title.value);

    // 或者,将 `props` 的单个属性转为一个 ref
    const title = toRef(props, "title");
  },
};
  • ctx 是 setup 上下文对象,这是个非响应式对象,可以正常解构。

    • ctx.attrs 透传 Attributes(非响应式的对象,等价于 $attrs)。
    • ctx.slots 插槽(非响应式的对象,等价于 $slots)。
    • ctx.emit 触发事件(函数,等价于 $emit)。
    • ctx.expose 调用该函数后,当父组件通过模板引用访问该组件实例时,只能访问 expose 函数暴露出的内容(函数)。
export default {
  setup(props, { attrs, slots, emit, expose }) {
    // 让组件实例处于 “关闭状态”,即不向父组件暴露任何东西
    expose();

    const publicCount = ref(0);
    // 有选择地暴露局部状态
    expose({ count: publicCount });

    // 返回一个渲染函数将会阻止我们返回其他东西。
    // 对于组件内部来说,这样没有问题,但如果我们想通过模板引用将这个组件的方法暴露给父组件,就需要调用 expose() 方法了
    return () => h("div", count.value);
  },
};

<script setup>

在组件中使用见 组件配置(<script setup>

<script setup> 标签中的导入和声明可以在当前组件模板中直接使用,可以理解为模板中的表达式和 <script setup> 中的代码处在同一个作用域中。

API

reactive()

文档位置open in new window

reactive() 是深层响应,可用 shallowReactive()open in new window 方法实现浅层响应。

为保证访问代理的一致性,对同一个原始对象调用 reactive() 会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive() 会返回其本身:

// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy); // true

// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy); // true

reactive() 的局限性

  • reactive() 方法只对对象类型有效。

  • 只有用 reactive() 返回的 proxy 对象访问的属性才是响应式的。

const state = reactive({ count: 0 });

// 只有这种调用得到的值是响应式的。
state.count;

// n 是一个局部变量,同 state.count
// 失去响应性连接
let n = state.count;
// 不影响原始的 state
n++;

// count 也和 state.count 失去了响应性连接
let { count } = state;
// 不会影响原始的 state
count++;

// 该函数接收一个普通数字,并且
// 将无法跟踪 state.count 的变化
callSomeFunction(state.count);

ref()

文档位置open in new window

ref() 方法将传入的值包装为一个带 .value 属性的 ref 对象,这样就能将任何值都包装为响应式对象,在未被解包时需要用 .value 去访问属性。

ref 的解包

  • ref 对象在模板中作为顶层属性被访问时,它们会被自动“解包”,不需要使用 .value 就能读取数据。
  • ref 对象被嵌套在一个深层响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样。

工具函数open in new window