vue3 新功能

vue 官方文档真的不错

Provide / Inject

通常,当我们需要从父组件向子组件传递数据时,我们使用 props。但一些深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。

对于这种情况,我们可以使用一对 provideinject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。

实际上,你可以将依赖注入看作是“长距离的 prop”,除了:

  • 父组件不需要知道哪些子组件使用了它 provide 的 property
  • 子组件不需要知道 inject 的 property 来自哪里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const app = Vue.createApp({});

app.component("todo-list", {
data() {
return {
todos: ["Feed a cat", "Buy tickets"],
};
},
provide() {
// provider使用 组件实例时,需要写成函数
return {
todoLength: this.todos.length,
};
},
provide: {
// provider不使用 组件实例,可以直接写为对象
user: "John Doe",
},

template: `
<div>
{{ todos.length }}
<!-- 模板的其余部分 -->
</div>
`,
});

app.component("todo-list-statistics", {
inject: ["user"],
created() {
console.log(`Injected property: ${this.user}`); // > 注入的 property: John Doe
},
});

但默认情况下,provide/inject 绑定并不是响应式的,可以为 provide 分配一个组合式 API computed property:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app.component("todo-list", {
// ...
provide() {
return {
todoLength: Vue.computed(() => this.todos.length),
};
},
});

app.component("todo-list-statistics", {
inject: ["todoLength"],
created() {
console.log(`Injected property: ${this.todoLength.value}`); // > 注入的 property: 5
},
});

生命周期钩子

下表包含如何在 setup () 内部调用生命周期钩子:

选项式 APIHook inside setup
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered
activatedonActivated
deactivatedonDeactivated

因为 setup 是围绕 beforeCreatecreated 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

组合式 API

使用(data、computed、methods、watch)组件选项时,当我们的组件开始变得很大时,逻辑关注点也会增长,并且组件会难以阅读和理解。组合式 API 意在把同一个逻辑关注点相关代码收集在一起抽成单个可复用的模块。

为了开始使用组合式 API,我们首先需要一个可以实际使用它的地方。在 Vue 组件中,我们将此位置称为<font style="color:rgb(44, 62, 80);"></font><font style="color:rgb(71, 101, 130);">setup</font>

新的 setup 选项在组件被创建之前执行,一旦 props 被解析完成,它就将被作为组合式 API 的入口。

setup** 选项是一个接收 **props****context** 的函数,我们将在之后进行讨论。此外,我们将 **setup** 返回的所有内容都暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。**

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/components/UserRepositories.vue

export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true,
},
},
setup(props) {
console.log(props); // { user: '' }

return {}; // 这里返回的任何内容都可以用于组件的其余部分
},
// 组件的“其余部分”
};

Context

传递给 setup 函数的第二个参数是 contextcontext 是一个普通 JavaScript 对象,暴露了其它可能在 setup 中有用的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// MyBook.vue

export default {
setup(props, context) {
// Attribute (非响应式对象,等同于 $attrs)
console.log(context.attrs);

// 插槽 (非响应式对象,等同于 $slots)
console.log(context.slots);

// 触发事件 (方法,等同于 $emit)
console.log(context.emit);

// 暴露公共 property (函数)
console.log(context.expose);
},
};

context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context 使用 ES6 解构。

1
2
3
4
5
6
// MyBook.vue
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}

attrsslots 是有状态的对象,它们总是会随组件本身的更新而更新。这意味着你应该避免对它们进行解构,并始终以 attrs.xslots.x 的方式引用 property。请注意,与 props 不同,attrsslots 的 property 是响应式的。如果你打算根据 attrsslots 的更改应用副作用,那么应该在 onBeforeUpdate 生命周期钩子中执行此操作。

注意事项

  • setup 参数里的 props 是响应式的,但不能使用 ES6 解构,它会消除 prop 的响应性。如果需要解构 prop,可以在 setup 函数中使用 toRefs 函数来完成此操作
1
2
3
4
5
6
7
8
// MyBook.vue
import { toRefs } from 'vue'

setup(props) {
const { title } = toRefs(props)

console.log(title.value)
}
  • 执行 setup 时,你只能访问以下 property:props,attrs,slots,emit;换句话说,你将无法访问以下组件选项:data (<font style="color:rgb(44, 62, 80);">可用 ref | reactive 代替</font>) 、computed (<font style="color:rgb(44, 62, 80);">组合api代替</font>)、methods (<font style="color:rgb(44, 62, 80);">setup中的方法就是 methods</font>)、模板的 refs (<font style="color:rgb(44, 62, 80);">同react-useRef,ref绑定在模板上即可获取组件实例</font>)

  • refs 在 vue 的 template 模板中,会自动解包,就是不需要使用 <font style="color:rgb(44, 62, 80);">.value</font> 就可获取其值,但是使用 jsx 语法时 必须 使用 <font style="color:rgb(44, 62, 80);">.value</font>才能拿到值

  • 都用 setup 了,就不需要使用 this 了,使用 this 容易与其它选项式 API 产生混淆

粗略认识

  • toRef, 可以用于定义可选 props, toRefs 多用于解构 props
1
2
3
4
// 定义可选 props
const title = toRef(props, "title");
// 解构 props
const { title } = toRefs(props);

ref 与 reactive 的区别

reactive() 用于创建引用类型响应数据

ref() 用于创建基本类型响应数据,但他也可以创建引用类型的响应数据,但是 ref 本质上 最后还是会调用 <font style="color:rgb(64, 64, 64);">reactive</font>

ref 返回的是 RefImpl,而 ref 的 RefImpl._value(可以通过控制台打印查看到 RefImpl 内的_value)是一个 reactive 代理的原始对象;reactive 返回就是一个 Proxy 了。