Vue3 响应式(组合式api)

一、setup()

1.1、选项式api中简单使用

setup() 钩子是在组件中使用组合式 API 的入口

 使用场景:

  • 需要在非单文件组件中使用组合式 API 时。
  • 需要在基于选项式 API 的组件中集成基于组合式 API 的代码时。

当我们使用响应式api的时候,需要在setup()中返回出去暴露给模板和组件实例。其他的选项也可以通过组件实例来获取 setup() 暴露的属性

<script>
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)

    // 返回值会暴露给模板和其他的选项式 API 钩子
    return {
      count
    }
  },

  mounted() {
    console.log(this.count) // 0
  }
}
</script>

<template>
  <button @click="count++">{{ count }}</button>
</template>
  •  在模板中访问从 setup 返回的ref时,它会自动浅层解包,因此你无须再在模板中为它写 .value。当通过 this 访问时也会同样如此解包。
  • setup()自身并不含对组件实例的访问权,即在 setup() 中访问 this 会是 undefined。你可以在选项式 API 中访问组合式 API 暴露的值,但反过来则不行。
  • setup() 应该同步地返回一个对象。唯一可以使用 async setup() 的情况是,该组件是Suspense组件的后裔
  • 对于结合单文件组件使用的组合式 API,推荐通过 <script setup>以获得更加简洁及符合人体工程学的语法。

1.2、<script setup>语法糖

在script 中加入setup 属性,是给script添加语法糖。

Q:什么是语法糖?

A:也称糖衣语法。指的是在计算机语言中添加的某种语法,这种语法对语言的编译结果和功能并没有实际影响, 但是却能更方便程序员使用该语言。

在<script setup>的语法糖中,虽然看上去写法简洁,没有setup()的export default,不需要return,但是实际编译出来还是会有这些代码。

因此,<script setup>也会遵循setup()的某些规则,如模板中ref自动解包,<script setup></script>标签内,this为undefine。

二、ref全家桶 

2.1、ref()

接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value

如果将一个对象赋值给 ref,那么这个对象将通过 reavtive() 转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。 

类型:

function ref<T>(value: T): Ref<UnwrapRef<T>>

interface Ref<T> {
  value: T
}

使用ts为其标注类型

<script setup lang="ts">
import { ref, reactive } from 'vue';
import type { Ref } from 'vue';

const message: Ref<string | number> = ref('你好');//使用接口约束

const value = ref<string | number>('你好吗??');//使用泛型约束

</script>

注意,在模板中使用 ref 时,我们需要附加 .value。为了方便起见,当在模板中使用时,ref 会自动解包。

你也可以直接在事件监听器中改变一个 ref

<button @click="count++">
  {{ count }}
</button>

ref深层响应式,即便是map,或者对象,他的属性的改变依旧能够被监听到。

<template>
  姓名:<input type="text" v-model="person.name" />
  <br />
  年龄:<input type="text" v-model="person.age" />
  <br />
  性别:<input type="text" v-model="person.sex" />
  <br>
  c: <input type="text" name="" id="" v-model="person.a.b.c">
  <br />
  {{ person }}
</template>

<script setup lang="ts">
import { ref } from "vue";
import type { Ref } from "vue";

const person: Ref<any> = ref({
  name: "张三",
  age: 18,
  sex: "男",
  a:{
    b:{
      c:'aaa'
    }
  }
});
</script>

2.2、shallowRef()

ref()的浅层作用形式,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。

类型:

function shallowRef<T>(value: T): ShallowRef<T>

interface ShallowRef<T> {
  value: T
}

实例:

<template>
  {{ person }}
  <br />
  <button @click="chlck1">修改.value</button>
  <button @click="chlck2">替换.value</button>
</template>

<script setup lang="ts">
import { shallowRef } from "vue";
import type { ShallowRef } from "vue";

const person: ShallowRef<Map<string, any>> = shallowRef(new Map());

//不会触发更新
const chlck1 = () => {
  person.value.set("name", "张三");
  console.log(person);
  
};

//会触发更新
const chlck2 = () => {
  const map = new Map();
  map.set("name", "李四");
  person.value = map;
  console.log(person);

};
</script>

2.3、triggerRef()

当你修改shallowRef的属性的时候,是不会触发深层响应的。

这个时候使用triggerRef可以强制触发响应式。

 类型:

function triggerRef(ref: ShallowRef): void

实例:当使用普通修改的时候,因为修改是深层的,所有不会触发响应式。

但是修改之后使用triggerRef()可以将浅层响应式强制触发为深层。

<template>
  <div>
    {{ number }}
    <button @click="but1">普通修改</button
    ><button @click="but2">修改后使用triggerRef</button>
  </div>
</template>

<script setup lang="ts">
import { shallowRef, triggerRef } from "vue";

const number = shallowRef({
  number: 1,
});

const but1 = () => {
  number.value.number++;
  console.log(number.value);
  
};

const but2 = () => {
  number.value.number++;
  triggerRef(number);
  console.log(number.value);
  
};
</script>

 2.4、customRef()

创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。

类型: 

function customRef<T>(factory: CustomRefFactory<T>): Ref<T>

type CustomRefFactory<T> = (
  track: () => void,
  trigger: () => void
) => {
  get: () => T
  set: (value: T) => void
}

自定义一个ref,返回一个customRef()出去,customRef()接收两个参数:

  • track() 依赖收集
  • trigger() 依赖更新 

 customRef()返回一个对象,带有以下两个方法:

  • get(): 拦截对象的访问,通常在这里进行track()依赖收集。
  • set(): 拦截对象的赋值,通常在这里进行trigger()依赖更新。

看到get() 和 set(),你就应该对此有印象。因为vue3的响应式原理就是基于proxy,进行拦截。

 实例:自建一个防抖ref

<template>
    <button style="width: 100px;height: 50px;" @click="number++">{{ number }}</button>
</template>

<script setup lang="ts">
import { customRef } from 'vue';

const myRef = (value: any) => {
   return customRef((track, trigger) => {
        let timmer: any = null;
        return {
            get() {
                track();
                return value;
            },

            set(newValue) {
                value = newValue;
                clearTimeout(timmer);
                timmer = setTimeout(() => {
                    clearTimeout(timmer);
                    trigger();
                }, 500);

            },
        }

    })
}

const number = myRef(0);


</script>

三、reactive

3.1、reavtive()

返回一个对象的响应式代理。

类型:

function reactive<T extends object>(target: T): UnwrapNestedRefs<T>

 详细信息:

  • 响应式转换是“深层”的
  • 当一个 ref 作为一个响应式对象的 property 被访问或更改时,它会自动解包。
  • 当访问到某个响应式数组或 Map 这样的原生集合类型中的 ref 元素时,不会执行 ref 的解包。
  • 返回的对象以及其中嵌套的对象都会通过 Proxy 包裹,

 自动解包示例:

<template>
    <div>
        {{ person }}
        <br>
        <button @click="">点击</button>
    </div>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue';

const name = ref('李四');

const person = reactive({
    name: name,//响应式对象作为属性
    age: 19
})

console.log(name);//这里不会自动解包

console.log(person.name);//这里的name可以不.value  即ref对象作为响应式对象的属性会自动解包。

</script>

 3.2、shallowReactive()

reactive()的浅层作用形式。

 类型:

function shallowReactive<T extends object>(target: T): T

 详细信息:

和 reactive() 不同,这里没有深层级的转换:一个浅层响应式对象里只有根级别的属性是响应式的。属性的值会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了。

示例:

const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 更改状态自身的属性是响应式的
state.foo++

// ...但下层嵌套对象不会被转为响应式
isReactive(state.nested) // false

// 不是响应式的
state.nested.bar++

 四、计算属性computed()

接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象

 类型:

// 只读
function computed<T>(
  getter: () => T,
  // 查看下方的 "计算属性调试" 链接
  debuggerOptions?: DebuggerOptions
): Readonly<Ref<Readonly<T>>>

// 可写的
function computed<T>(
  options: {
    get: () => T
    set: (value: T) => void
  },
  debuggerOptions?: DebuggerOptions
): Ref<T>

 示例:

创建一个只读的计算属性:

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // 错误

 实际场景:简单的购物车,使用计算属性来响应式最后的总价格。

<template>
    <table border="">
        <tr>
            <th>商品</th>
            <th>价格</th>
            <th>数量</th>
            <th>小计</th>
        </tr>
        <tr v-for="item, index in itemArr" :key="index">
            <td>{{ item.name }}</td>
            <td>{{ item.price }}</td>
            <td>{{ item.number }} <button @click="item.number++">+</button><button
                    @click="item.number < 1 ? item : item.number--">-</button></td>
            <td>{{ item.number * item.price }}¥</td>
        </tr>
        <tr>
            <td>合计:</td>
            <td></td>
            <td></td>
            <td>{{ title }}¥</td>
        </tr>
    </table>
</template>

<script setup lang="ts">
import { reactive, computed } from 'vue';

const itemArr = reactive([
    { name: '面包', price: 3, number: 3 },
    { name: '火腿', price: 1.5, number: 2 },
    { name: '泡面', price: 4, number: 1 },
])

//计算属性
const title = computed(() => {
    let sum = 0;
    itemArr.forEach(item => {
        sum += item.number * item.price;
    })

    return sum;
})

</script>


<style>
th,
td {
    width: 100px;
    height: 50px;
    text-align: center;
}

button {
    width: 20px;
}
</style>

 创建一个可修改的计算属性:

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: (val) => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0

五、readonly

5.1、readonly()

接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。

类型:

function readonly<T extends object>(
  target: T
): DeepReadonly<UnwrapNestedRefs<T>>

只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的。

示例:

const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
  // 用来做响应性追踪
  console.log(copy.count)
})

// 更改源属性会触发其依赖的侦听器
original.count++

// 更改该只读副本将会失败,并会得到一个警告
copy.count++ // warning!

5.2、shallowReadonly() 

readonly()的浅层作用形式

类型:

function shallowReadonly<T extends object>(target: T): Readonly<T>

 和 readonly() 不同,这里没有深层级的转换:只有根层级的属性变为了只读。属性的值都会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了。

const state = shallowReadonly({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 更改状态自身的属性会失败
state.foo++

// ...但可以更改下层嵌套对象
isReadonly(state.nested) // false

// 这是可以通过的
state.nested.bar++

六、侦听器

6.1、watch()

 侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。

类型:

// 侦听单个来源
function watch<T>(
  source: WatchSource<T>,
  callback: WatchCallback<T>,
  options?: WatchOptions
): StopHandle

// 侦听多个来源
function watch<T>(
  sources: WatchSource<T>[],
  callback: WatchCallback<T[]>,
  options?: WatchOptions
): StopHandle

type WatchCallback<T> = (
  value: T,
  oldValue: T,
  onCleanup: (cleanupFn: () => void) => void
) => void

type WatchSource<T> =
  | Ref<T> // ref
  | (() => T) // getter
  | T extends object
  ? T
  : never // 响应式对象

interface WatchOptions extends WatchEffectOptions {
  immediate?: boolean // 默认:false
  deep?: boolean // 默认:false
  flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

 参数详解:

第一个参数是侦听器的源,在源发生改变的时候执行回调函数。它可以接受以下类型的源:

  • 一个函数,带有返回值。即getter()
  • 一个ref
  • 一个响应式对象
  • 以上类型组成的数组。

第二个参数是在发生变化时要调用的回调函数。

这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。

当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。

第三个可选的参数是一个对象,用来配置侦听器,支持以下这些选项:

  • immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined
  • deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器。
  • flush:调整回调函数的刷新时机。
  • onTrack / onTrigger:调试侦听器的依赖。

第一个参数是我们需要监听的对象,第二个参数是监听对象发生变化所执行的回调函数,第三个参数是配置。

示例:

监听一个getter():

const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

 监听一个ref

const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})

 当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值:

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})

 深层次监听:

监听一个响应式对象时,会自动开启深层监听。但是监听一个getter,不会自动开启深层监听,需要手动开启配置。

在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。

<template>
  <input type="text" v-model="person.age">
</template>

<script lang="ts" setup>
import { watch, reactive } from 'vue';

const person = reactive({
  name: '李四',
  age: 18
})

watch(
  () => person,
  (newVla, oldVal) => {
    //这里的新值和旧值会是同一个值。
    // 因为深层监听 两者指向的是同一个引用。
    console.log('这是新值:' + newVla.age);
    console.log('这是旧值:' + oldVal.age);
  },
  // 第三个参数为对象,用来配置侦听器
  {
    deep: true,//开启深层监听
  }
)
</script>

停止侦听器:

const stop = watch(source, callback)

// 当已不再需要该侦听器时:
stop()

 

响应式工具

2.5.1、isRef()

检查某个值是否为 ref。

 类型:

function isRef<T>(r: Ref<T> | unknown): r is Ref<T>

实例:在使用响应式之前进行检测,可方式类型错误。

<template>
    <div>
        {{ name + '::' + age }}
    </div>

    <button @click="clickT">改变值</button>
</template>

<script setup lang="ts">
import { ref, isRef } from 'vue'

let name = ref('李四');

let age: any = 18;

const clickT = () => {
    if (isRef(name)) {
        name.value = '张三';
    } else {
        name = ref('张三');
    }

    if (isRef(age)) {
        age.value = 22;
    } else {
        age = ref(22);
    }

}

</script>
2.5.2、unref()

如果参数是 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 计算的一个语法糖。

类型:

function unref<T>(ref: T | Ref<T>): T

示例:

function useFoo(x: number | Ref<number>) {
  const unwrapped = unref(x)
  // unwrapped 现在保证为 number 类型
}
 2.5.3、toRef()

toRef()有两种用法,一种是规划化签名。

另一种是对象属性签名: 基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。

两者返回的都是一个ref对象。

2.5.3.1、规范化签名

根据传入的参数不同,返回不同类型的ref对象。

  • ref对象。
// 按原样返回现有的 ref
toRef(existingRef)
  •  getter函数
// 创建一个只读的 ref,当访问 .value 时会调用此 getter 函数
toRef(() => props.foo)
  • 普通值
// 从非函数的值中创建普通的 ref
// 等同于 ref(1)
toRef(1)
2.5.3.2、对象属性签名

类型:

// 对象属性签名
function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K,
  defaultValue?: T[K]
): ToRef<T[K]>

type ToRef<T> = T extends Ref ? T : Ref<T>

示例:

<template>
    {{ person }}
    <br>
    {{ car }}
    <br>
    <button @click="name = '张三'">改变name</button>
    <button @click="brand = '比亚迪'">改变brand</button>
</template>

<script setup lang="ts">
import { ref, toRef, reactive } from 'vue';
const car = reactive({
    brand: '华为',
})
const person = ref({
    name: '李四',
    age: 22,
    sex: '男'
})

//将person的属性单独拿出来响应
const name = toRef(person.value, 'name');

//如果是reactive的响应式对象
const brand = toRef(car, 'brand');


</script>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值