count返回0_vue3.0学习笔记

本文详细介绍了Vue3.0的Composition API,包括setup的使用时机、reactive、ref、computed、watchEffect和watch等响应式API的用法,以及如何通过customRef自定义响应式行为。同时,还讨论了markRaw、shallowReactive和shallowRef等用于优化性能的API。
摘要由CSDN通过智能技术生成

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 的入口点

{{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。

c82e8f7a7d312e4b0c20edd463ed5cdd.png

类型定义

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 }; },};

602f186a8103b96a6e3ae802f2d9d698.png

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 }; },};

fb32f0cc493fb2d35903e837745485db.png

computed - 计算属性

https://vue-composition-api-rfc.netlify.app/zh/api.html#computed

1 只传 getter

返回一个默认不可手动修改的 ref 对象。

  

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 }; },};

97e3c0d8ffef0a6dba833de820d74124.png

如果是这种使用方式,则会报错 ❌,而不是警告 ⚠️,

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 };  }

91c48596d47cdf12aa199c3a2cb733af.png

2 同时传 getter、setter

创建一个可手动修改的计算状态。

  

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 }; },};

180800cbdc733de70446ac5d1caa87c1.png

readonly - “深层”的只读代理

https://vue-composition-api-rfc.netlify.app/zh/api.html#readonly

传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的。

  

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 }; },};

1da3f90f12e3f5aed6bfddcc3c709dd6.png

watchEffect、watch - 侦听器

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 }; },};

81d9012c4dfa262efc8f35e6c8829dbe.png

watch

对比watchEffectwatch允许我们:

  • 懒执行副作用,也就是说仅在侦听的源变更时才执行回调;

  • 更明确哪些状态的改变会触发侦听器重新运行副作用;

  • 访问侦听状态变化前后的值。

  

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) }; },};

1b2413afadf11b4862b2c97ee250fbd1.png

当想要从一个组合逻辑函数中返回响应式对象时,用 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

{{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) // falsestate.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架构

usemouse

position x: {{x}}

position y: {{y}}

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 - 提供数据

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 - 注入数据

{{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

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 }; },};

ab6892d09dddcd160e136873681572fa.png

在 v-for 中使用

模板 ref 在 v-for 中使用 vue 没有做特殊处理,需要使用函数型的 ref(3.0 提供的新功能)来自定义处理方式:

  

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 () =>   },};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值