大型组件中选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。 如果能够将同一个逻辑关注点相关代码收集在一起会更好。而这正是组合式 API 使我们能够做到的。
通过创建 Vue 组件,我们可以将界面中重复的部分连同其功能一起提取为可重用的代码段。然而,光靠这一点可能并不够,尤其是当你的应用变得非常大的时候,共享和重用代码变得尤为重要。
为了开始使用 组合式 API,我们首先需要一个可以实际使用它的地方。在 Vue 组件中,我们将此位置称为 setup。
简单举例:
角色权限页面功能:新增/删除角色、获取角色列表、获取角色对应的权限信息、编辑角色权限信息。
- 通常以前会将这些功能写在一个sfc组件(.vue),当需要修改其中1处功能逻辑时,我们就需要跳转对应的各个options api选项进行操作。当有些业务组件复杂时,代码量上去了,浏览起来会更吃力。
- 而有了setup、composition api后,我们可以将这例子中的4个功能逻辑拆分到对应的.js文件文件,使用composition api编写业务代码,然后引入到sfc组件的setup里使用。此时我们修改其中1处功能逻辑,只需要去对应的.js文件进行修改即可,而不必被其他的功能逻辑代码干扰。
错误用法:
有些人写setup,以为是将vue2的写法,挪到setup内声明、return出去,导致setup内代码特别长,并且比options api写法更难以阅读!(当然代码量少的情况下,不影响阅读 也可不拆分)
setup()
钩子是在组件中使用组合式 API 的入口,通常只在以下情况下使用:
- 需要在非单文件组件中使用组合式 API 时。
- 需要在基于选项式 API 的组件中集成基于组合式 API 的代码时。
其他情况下,都应优先使用
script setup
语法。
一个人的信息
姓名:{{name}}
年龄:{{age}}
性别:{{sex}}
a的值是:{{a}}
注意事项:
setup 内的 this 是不指向组件实例的! this > undefined
props 对象是响应式的——即在传入新的 props 时会对其进行更新,通过使用 watchEffect 或 watch 进行观测和响应;
但是,请不要解构 props 对象,因为它会失去响应式;
语法糖
是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。相比于普通的
语法,它具有更多优势:
: 使用
的组件是默认关闭的,也即通过模板 ref 或者 $parent 链获取到的组件的公开实例,不会暴露任何在
中声明的绑定。
ref
函数)const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
let person = reactive({
name: 'dselegent',
age: 21,
});
console.log(person);
通过reactive创建的其实就是ref创建的value属性内容
注意:比如reactive中的数据是个对象,我们正常情况只能把整个对象导出去,如果只是想用某个属性,直接导出去的话不是响应式的,后面可以用toRef或者toRefs解决这种需求。
reactive()
的种种限制归根结底是因为 JavaScript 没有可以作用于所有值类型的 “引用” 机制。为此,Vue 提供了一个 ref()
方法来允许我们创建可以使用任何值类型的响应式 ref
作用: 定义一个响应式的数据
语法: const xxx = ref(initValue)
xxx.value
.value
,直接:{{xxx}}
备注:
Object.defineProperty()
的get
与set
完成的。reactive
函数。import { ref } from 'vue'
const count = ref(0)
ref()
将传入参数的值包装为一个带 .value
属性的 ref 对象:
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
TypeScript 用户请参阅:为 ref 标注类型
和响应式对象的属性类似,ref 的 .value
属性也是响应式的。
如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。
若要避免这种深层次的转换,请使用
shallowRef()
来替代。
初体验
name:{{ name }}
{{ person.name }}
ref输出
let name = ref('ds');
console.log(name);
let person = ref({
name: 'dselegent',
age: 21,
});
console.log(person);
一个包含对象类型值的 ref 可以响应式地替换整个对象:
const objectRef = ref({ count: 0 })
// 这是响应式的替换
objectRef.value = { count: 1 }
ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性:
const obj = {
foo: ref(1),
bar: ref(2)
}
// 该函数接收一个 ref
// 需要通过 .value 取值
// 但它会保持响应性
callSomeFunction(obj.foo)
// 仍然是响应式的
const { foo, bar } = obj
简言之,ref()
让我们能创造一种对任意值的 “引用”,并能够在不丢失响应性的前提下传递这些引用。这个功能很重要,因为它经常用于将逻辑提取到 组合函数 中。
当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value
。下面是之前的计数器例子,用 ref()
代替:
请注意,仅当 ref 是模板渲染上下文的顶层属性时才适用自动“解包”。 例如, foo 是顶层属性,但 object.foo 不是。
所以我们给出以下 object:
const object = { foo: ref(1) }
下面的表达式将不会像预期的那样工作:
{{ object.foo + 1 }}
渲染的结果会是一个 [object Object]
,因为 object.foo
是一个 ref 对象。我们可以通过将 foo
改成顶层属性来解决这个问题:
const { foo } = object
{{ foo + 1 }}
现在渲染结果将是 2
。
需要注意的是,如果一个 ref 是文本插值(即一个 {{ }}
符号)计算的最终值,它也将被解包。因此下面的渲染结果将为 1
:
{{ object.foo }}
这只是文本插值的一个方便功能,相当于 {{ object.foo.value }}
。
当一个 ref
被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样:
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
如果将一个新的 ref 赋值给一个关联了已有 ref 的属性,那么它会替换掉旧的 ref:
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// 原始 ref 现在已经和 state.count 失去联系
console.log(count.value) // 1
只有当嵌套在一个深层响应式对象内时,才会发生 ref 解包。当其作为浅层响应式对象的属性被访问时不会解包。
跟响应式对象不同,当 ref 作为响应式数组或像 Map
这种原生集合类型的元素被访问时,不会进行解包。
const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)
响应性语法糖目前是一个实验性功能,默认是禁用的,需要显式选择使用。
相对于普通的 JavaScript 变量,我们不得不用相对繁琐的 .value
来获取 ref 的值。这是一个受限于 JavaScript 语言限制的缺点。然而,通过编译时转换,我们可以让编译器帮我们省去使用 .value
的麻烦。Vue 提供了一种编译时转换,使得我们可以像这样书写之前的“计数器”示例:
你可以在 响应性语法糖 章节中了解更多细节。请注意它仍处于实验性阶段,在最终提案落地前仍可能发生改动。