setup - 组件内使用 Composition API 的入口点
https://vue-composition-api-rfc.netlify.app/zh/api.html#setup
setup 作为组合式 API 的入口点,也就是说,组合式 API 都必须只能在 setup 中使用。
(除了几个辅助函数之外)
setup 调用时机
创建组件实例,然后初始化 props
,紧接着就调用setup
函数。从生命周期钩子的视角来看,它会在 beforeCreate
钩子之前被调用。
setup - 组件内使用 Composition API 的入口点
setup - 组件内使用 Composition API 的入口点
{{username}}
export default { name: "Setup", props: { username: { type: String, default: "haihong" }, }, setup(props, ctx) { console.log("setup"); console.log(props, ctx); }, beforeCreate() { console.log("beforeCreate"); },};
setup 优先于 beforeCreate,但是晚于created。
类型定义
interface Data { [key: string]: unknown}interface SetupContext { attrs: Data slots: Slots emit: (event: string, ...args: unknown[]) => void}function setup(props: Data, context: SetupContext): Data
响应式API:
reactive, ref - 数据响应式
1 reactive - 对象数据响应式
https://vue-composition-api-rfc.netlify.app/zh/api.html#reactive
接收一个普通对象然后返回该普通对象的响应式代理。
{{data.message}}
更新数据
import { reactive } from "vue";export default { name: "ReactiveObject", setup() { const data = reactive({ message: "hello world" }); const updateData = () => { data.message = "hello world " + new Date().getTime(); }; return { data, updateData }; },};
2 ref - 单值数据响应式
https://vue-composition-api-rfc.netlify.app/zh/api.html#ref
接受一个参数值并返回一个响应式且可改变的 ref 对象。
单值数据响应式
单值数据响应式
{{message}}
更新数据
import { ref } from "vue";export default { name: "ReactiveSingleValue", setup() { const message = ref("hello world"); const updateMessage = () => { message.value = "hello world " + new Date().getTime(); }; return { message, updateMessage }; },};
computed - 计算属性
https://vue-composition-api-rfc.netlify.app/zh/api.html#computed
1 只传 getter
返回一个默认不可手动修改的 ref 对象。
computed - 计算属性
computed - 计算属性
{{username}}
import { reactive, computed } from "vue";export default { name: "Computed", setup() { const user = reactive({ firstname: "chen", lastname: "haihong" }); const username = computed(() => user.firstname + " " + user.lastname); username.value = "hello world"; // 报警告,computed value is readonly return { username }; },};
如果是这种使用方式,则会报错 ❌,而不是警告 ⚠️,
setup() { const user = reactive({ firstname: "chen", lastname: "haihong" }); const username = computed({ get: () => user.firstname + " " + user.lastname, }); username.value = "hello world"; // ❌ computed value is readonly return { username }; }
2 同时传 getter、setter
创建一个可手动修改的计算状态。
computed - 计算属性
computed - 计算属性
firstname:
lastname:
username:
import { reactive, computed } from "vue";export default { name: "Computed2", setup() { const user = reactive({ firstname: "Chen", lastname: "Haihong" }); const username = computed({ get: () => user.firstname + " " + user.lastname, set: (value) => { const [firstname, lastname] = value.trim().split(" "); user.firstname = firstname; user.lastname = lastname; }, }); return { user, username }; },};
readonly - “深层”的只读代理
https://vue-composition-api-rfc.netlify.app/zh/api.html#readonly
传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的。
readonly - “深层”的只读代理
readonly - “深层”的只读代理
original.count: {{original.count}}
copy.count: {{copy.count}}
import { reactive, readonly } from "vue";export default { name: "Readonly", setup() { const original = reactive({ count: 0 }); const copy = readonly(original); setInterval(() => { original.count++; copy.count++; // 报警告,Set operation on key "count" failed: target is readonly. Proxy {count: 1} }, 1000); return { original, copy }; },};
watchEffect、watch - 侦听器
watchEffect
立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。
watchEffect - 侦听器
watchEffect - 侦听器
{{data.count}}
手动关闭侦听器
import { reactive, watchEffect } from "vue";export default { name: "WatchEffect", setup() { const data = reactive({ count: 1 }); const stop = watchEffect(() => console.log(`侦听器:${data.count}`)); setInterval(() => { data.count++; }, 1000); return { data, stop }; },};
watch
对比watchEffect
,watch
允许我们:
懒执行副作用,也就是说仅在侦听的源变更时才执行回调;
更明确哪些状态的改变会触发侦听器重新运行副作用;
访问侦听状态变化前后的值。
watch - 侦听器
watch - 侦听器
count1: {{data.count1}}
count2: {{data.count2}}
Stop All
import { reactive, watch } from "vue";export default { name: "Watch", setup() { const data = reactive({ count1: 0, count2: 0 }); // 侦听单个数据源 const stop1 = watch(data, () => console.log("watch1", data.count1, data.count2) ); // 侦听多个数据源 const stop2 = watch([data], () => { console.log("watch2", data.count1, data.count2); }); setInterval(() => { data.count1++; }, 1000); return { data, stopAll: () => { stop1(); stop2(); }, }; },};
辅助函数:
unref - 拆出原始值的语法糖
https://vue-composition-api-rfc.netlify.app/zh/api.html#unref
如果参数是一个 ref 则返回它的 value,否则返回参数本身。它是 val = isRef(val) ? val.value : val 的语法糖。
function useFoo(x: number | Ref) { const unwrapped = unref(x) // unwrapped 一定是 number 类型}
toRef - 为 reactive 对象的属性创建一个 ref
https://vue-composition-api-rfc.netlify.app/zh/api.html#toref
toRef 可以用来为一个 reactive 对象的属性创建一个 ref。这个 ref 可以被传递并且能够保持响应性。
setup() { const user = reactive({ age: 1 }); const age = toRef(user, "age"); age.value++; console.log(user.age); // 2 user.age++; console.log(age.value); // 3}
当您要将一个 prop 中的属性作为 ref 传给组合逻辑函数时,toRef 就派上了用场:
export default { setup(props) { useSomeFeature(toRef(props, 'foo')) },}
toRefs - 解构响应式对象数据
https://vue-composition-api-rfc.netlify.app/zh/api.html#torefs
把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref ,和响应式对象 property 一一对应。
解构响应式对象数据
解构响应式对象数据
Username: {{username}}
Age: {{age}}
import { reactive, toRefs } from "vue";export default { name: "DestructReactiveObject", setup() { const user = reactive({ username: "haihong", age: 10000, }); return { ...toRefs(user) }; },};
当想要从一个组合逻辑函数中返回响应式对象时,用 toRefs 是很有效的,该 API 让消费组件可以 解构 / 扩展(使用 ... 操作符)返回的对象,并不会丢失响应性:
function useFeatureX() { const state = reactive({ foo: 1, bar: 2, }) // 对 state 的逻辑操作 // .... // 返回时将属性都转为 ref return toRefs(state)}export default { setup() { // 可以解构,不会丢失响应性 const { foo, bar } = useFeatureX() return { foo, bar, } },}
isRef、isProxy、isReactive、isReadonly
isRef
检查一个值是否为一个 ref 对象。
isProxy
检查一个对象是否是由 reactive 或者 readonly 方法创建的代理。
isReactive
检查一个对象是否是由 reactive 创建的响应式代理。
如果这个代理是由 readonly 创建的,但是又被 reactive 创建的另一个代理包裹了一层,那么同样也会返回 true。
isReadonly
检查一个对象是否是由 readonly 创建的只读代理。
高级响应式API:
customRef - 自定义一个 ref
https://vue-composition-api-rfc.netlify.app/zh/api.html#customref
customRef 用于自定义一个 ref,可以显式地控制依赖追踪和触发响应 。
接受一个工厂函数,两个参数分别是用于追踪的 track 与用于触发响应的 trigger,并返回一个带有 get 和 set 属性的对象。
类型定义
function customRef(factory: CustomRefFactory): Reftype CustomRefFactory = ( track: () => void, trigger: () => void) => { get: () => T set: (value: T) => void}
示例
customRef - 自定义一个 ref
customRef - 自定义一个 ref
{{text}}
import { customRef } from "vue";export default { name: "CustomRef", setup() { return { text: useDebouncedRef("hello"), }; },};function useDebouncedRef(value, delay = 200) { let timeout; return customRef((track, trigger) => { return { get() { track(); return value; }, set(newValue) { clearTimeout(timeout); timeout = setTimeout(() => { value = newValue; trigger(); }, delay); }, }; });}
markRaw - 添加不可转为响应式数据的标记
显式标记一个对象为“永远不会转为响应式代理”,函数返回这个对象本身。
const foo = markRaw({})console.log(isReactive(reactive(foo))) // false// 如果被 markRaw 标记了,即使在响应式对象中作属性,也依然不是响应式的const bar = reactive({ foo })console.log(isReactive(bar.foo)) // false
使用场景
markRaw 和下面的 shallowXXX 一族的 API 允许你可选择性的覆盖 reactive readonly 默认 "深层的" 特性,或者使用无代理的普通对象。设计这种「浅层读取」有很多原因,比如:
一些值的实际上的用法非常简单,并没有必要转为响应式,比如某个复杂的第三方类库的实例,或者 Vue 组件对象;
当渲染一个元素数量庞大,但是数据是不可变的,跳过 Proxy 的转换可以带来性能提升。
仅影响在根级别
这些 API 被认为是高级的,是因为这种特性仅停留在根级别,所以如果你将一个嵌套的,没有 markRaw 的对象设置为 reactive 对象的属性,在重新访问时,你又会得到一个 Proxy 的版本,在使用中最终会导致标识混淆的严重问题:执行某个操作同时依赖于某个对象的原始版本和代理版本。
const foo = markRaw({ nested: {},})const bar = reactive({ // 尽管 `foo` 己经被标记为 raw 了, 但 foo.nested 并没有 nested: foo.nested,})console.log(foo.nested === bar.nested) // false
标识混淆在一般使用当中应该是非常罕见的,但是要想完全避免这样的问题,必须要对整个响应式系统的工作原理有一个相当清晰的认知。
shallowReactive - 浅层响应式数据对象
https://vue-composition-api-rfc.netlify.app/zh/api.html#shallowreactive
只为某个对象的私有(第一层)属性创建浅层的响应式代理,不会对“属性的属性”做深层次、递归地响应式代理,而只是保留原样。
const state = shallowReactive({ foo: 1, nested: { bar: 2, },})// 变更 state 的自有属性是响应式的state.foo++// ...但不会深层代理isReactive(state.nested) // falsestate.nested.bar++ // 非响应式
shallowReadonly - 创建浅层的只读响应式代理
https://vue-composition-api-rfc.netlify.app/zh/api.html#shallowreadonly
只为某个对象的自有(第一层)属性创建浅层的只读响应式代理,同样也不会做深层次、递归地代理,深层次的属性并不是只读的。
const state = shallowReadonly({
foo: 1,
nested: {
bar: 2,
},
})
// 变更 state 的自有属性会失败
state.foo++
// ...但是嵌套的对象是可以变更的
isReadonly(state.nested) // false
state.nested.bar++ // 嵌套属性依然可修改
shallowRef - 创建浅层的 ref
https://vue-composition-api-rfc.netlify.app/zh/api.html#shallowref
创建一个 ref ,将会追踪它的 .value 更改操作,但是并不会对变更后的 .value 做响应式代理转换(即变更不会调用 reactive)
const foo = shallowRef({})isReactive(foo.value) // false// 更改对操作会触发响应foo.value = {}// 但上面新赋的这个对象并不会变为响应式对象isReactive(foo.value) // false
toRaw - 响应式对象转普通对象
返回由 reactive 或 readonly 方法转换成响应式代理的普通对象。这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发更改。不建议一直持有原始对象的引用。请谨慎使用。
const foo = {}const reactiveFoo = reactive(foo)console.log(toRaw(reactiveFoo) === foo) // true
生命周期:
使用组合方式复用代码
组合式api架构
组合式api架构
usemouse
usemouse
position x: {{x}}
position y: {{y}}
useCount
useCount
{{count}}
Add
Minus
import { onMounted, onUnmounted, ref } from "vue";export default { name: "CompositionApiArchitecture", setup() { const { x, y } = useMouse(); const { count, add, minus } = useCount(); return { x, y, count, add, minus }; },};function useMouse() { const x = ref(0); const y = ref(0); const updateXY = (e) => { x.value = e.x; y.value = e.y; }; onMounted(() => { document.addEventListener("mousemove", updateXY); }); onUnmounted(() => { document.removeEventListener("mousemove", updateXY); }); return { x, y };}function useCount() { const count = ref(0); const add = () => count.value++; const minus = () => count.value--; return { count, add, minus };}
provide 和 inject - 依赖注入
文档:依赖注入
provide - 提供数据
Provide.vue
provide - 提供数据
provide - 提供数据
import { reactive, provide } from "vue";
import Inject from "./08Inject.vue";
export default {
name: "Provide",
components: { Inject },
setup() {
const data = reactive({ name: "haihong" });
provide("data", data);
return {};
},
};
inject - 注入数据
Inject.vue
inject - 注入数据
inject - 注入数据
{{data.name}}
import { inject } from "vue";
export default {
name: "Inject",
setup() {
const data = inject("data", { name: "defaultName" });
return { data };
},
};
Template Refs
文档:模板 Refs
当使用组合式 API 时,reactive refs 和 template refs 的概念已经是统一的。
简易示例
当使用组合式 API 时,reactive refs 和 template refs 的概念已经是统一的。为了获得对模板内元素或组件实例的引用,我们可以像往常一样在 setup() 中声明一个 ref 并返回它:
Template Refs
Template Refs
hello
world
import { onBeforeMount, onMounted, ref } from "vue";
export default {
name: "TemplateRefs",
setup() {
const div = ref(null);
onBeforeMount(() => {
console.log("onBeforeMount", div.value); // undefined
});
onMounted(() => {
// 模板 ref 仅在渲染初始化后才能访问。
console.log("onMounted:", div.value); // 取到dom元素
});
return { div };
},
};
在 v-for 中使用
模板 ref 在 v-for 中使用 vue 没有做特殊处理,需要使用函数型的 ref(3.0 提供的新功能)来自定义处理方式:
Template Refs
Template Refs
{{item}}
import { onBeforeUpdate, onMounted, reactive, ref } from "vue";
export default {
name: "TemplateRefsVFor",
setup() {
const list = reactive([1, 2, 3]);
const divs = ref([]);
// 确保在每次变更之前重置引用
onBeforeUpdate(() => {
divs.value = [];
});
onMounted(() => {
divs.value.forEach((v) => {
console.log(v);
});
});
return { list, divs };
},
};
配合 render 函数 / JSX 的用法
import { ref, onMounted } from "vue";
export default {
name: "TemplateRefsRender",
setup() {
const div = ref(null);
onMounted(() => {
console.log(div.value);
});
// render
return () =>
h("div", {
ref: div,
});
// JSX
return () =>
},
};