响应式系统原理 vue3 api
核心
readonly
让一个响应式数据变成只读的。[深只读]深层次的也不可读。
readonly函数只能将一个对象转换为只读对象,但是不能将一个数组或者map等其他类型的数据结构转换为只读对象,如果需要将这些数据结构转换为制度对象,可以使用readonly函数和deepReadonly函数的组合、
javascript
import { readonly, deepReadonly } from 'vue'
const state = readonly({
items: deepReadonly([
items: deepReadonly([
{id: 1, name: 'item1'},
{id: 2, name: 'item2'},
{id: 3, name: 'item3'}
])
])
})
javascript
<template>
<div>
<h1>姓名:{{ name }}</h1>
<h2>年龄:{{ age }}</h2>
<h3>喜欢的水果:{{ likeFood.fruits.apple }}</h3>
<button @click="name += '~'">修改姓名</button>
<button @click="age++">修改年龄</button>
<button @click="likeFood.fruits.apple += '!'">修改水果</button>
</div>
</template>
<script>
import { reactive, toRefs, readonly } from "vue";
export default {
name: "App",
setup() {
// 定义了一段数据
let person = reactive({
name: "张三",
age: 18,
likeFood: {
fruits: {
apple: "苹果",
},
},
});
// 使用了readonly将对象变为只读
let p = readonly(person)
// 将数据返回出去
return {
...toRefs(p),
};
},
};
</script>
属性是只读的,所有数据不可以被修改
watchEffect
watchEffect是要给侦听器,是一个副作用函数,会监听引用数据类型的所有属性。不需要监听某个属性,一旦运行就立即监听,组件卸载的时候会停止监听。使用的时候不需要指定具体监听的谁,回调函数内直接使用就行,只能访问当前最新的值,访问不到之前修改的值
javascript
<template>
<div>
<input type="text" v-model="obj.name">
<button @click="stopWatchEffect">停止监听</button>
</div>
</template>
<script>
import { reactive, watchEffect } from 'vue';
export default {
setup(){
let obj = reactive({
name:'zs'
});
const stop = watchEffect(() => {
console.log('name:',obj.name)
})
const stopWatchEffect = () => {
console.log('停止监听')
stop();
}
return {
obj,
stopWatchEffect,
}
}
}
</script>
侦听副作用传入的函数可以接受一个onInvalidate函数做入参,用来注册清理失效的回调。当下面的情况失效的时候,这个失效回调会被触发。
- 副作用即将被重新执行
- 侦听器被停止,如果在setup或生命周期钩子函数中使用了watchEffect,则在组件卸载的时候
javascript
<template>
<div>
<div id="value">{{count}}</div>
<div @click="countAdd">增加</div>
</div>
</template>
<script>
import {ref, watchEffect} from 'vue'
export default {
setup() {
let count = ref(0)
const countAdd = () => {
count.value++
}
watchEffect(() => {
console.log(count.value)
console.log(document.querySelector("#value")&& document.querySelector("#value").innerText)
})
return {
count,
countAdd
}
}
}
</script>
点击之前的输出为0 null。默认执行监听器,然后更行dom,此时dom还没有生成,所以为null
javascript
<template>
<div>
<div id="value">{{count}}</div>
<div @click="countAdd">增加</div>
</div>
</template>
<script>
import {ref, watchEffect} from 'vue'
export default {
setup() {
let count = ref(0)
const countAdd = () => {
count.value++
}
watchEffect(() => {
console.log(count.value)
console.log(document.querySelector("#value")&& document.querySelector("#value").innerText)
},
{
flush: 'post'
})
return {
count,
countAdd
}
}
}
操作更新之后的dom,就需要配置flush:'post'
- onTrack 将在响应式property或ref作为依赖项被追踪的时候调用
- onTrigger将在依赖项变更导致副作用被触发的时候调用
javascript
watchEffect(
() => {
// 副作用
},
{
onTrigger(e) {
// 依赖变更
}
}
)
watchPostEffect
watchEffect()使用post选项的别名
watchSyncEffect
watchEffect()使用sync选项的别名。在组件更新或者更新之前,立即调用它。同步侦听器不会进行批处理,每当响应式数据发生变化的时候触发。可以用来监听简单的布尔值,避免在可能多次同步修改数据源上使用。
进阶
shallowRef
ref的浅层作用形式,就是浅层响应式。
- ref创建的数据,如果有深层,就会被reactive处理称为深层响应式
- shallowRef创建数据,只有顶层的value属性具有响应式,深层的数据不具有响应式
javascript
<template>
<h1>{{ student.name }}</h1>
<button @click="changeName">更改姓名</button>
</template>
<script setup>
import { shallowRef } from "vue";
const student = shallowRef({
name: "Tsz",
age: 20,
comments: ["好文", "继续努力"]
});
function changeName() {
student.value.name = "Alice";
student.value.comments.push("我喜欢它");
}
</script>
这里的数据不会发生改变。
triggerRef
让shallowRef深层属性发生变化之后强制触发更行
javascript
<template>
<div>
{{ shallowObj.a }}
<button @click="addCount"> +1</button>
</div>
</template>
<script lang='ts' setup>
import { shallowRef, triggerRef } from "vue"
const shallowObj = shallowRef({
a: 1
})
const addCount = () => {
shallowObj.value.a++
//加入triggerRef强制触发更改
triggerRef(shallowObj)
}
</script>
customRef
用途
- 实现防抖节流
- 自定义计算属性
- 优化性能
javascript
const count = customRef((track, trigger) => {
let num = 0
return {
get: () => num,
set: (val) => {
num = val
trigger()
}
}
})
javascript
<template>
<p>Counter: {{counter}}</p>
<button @click="update">Update</button>
</template>
<script setup>
import {customRef} from 'vue'
function useLocalStorage(key string, initialValue: any) {
const value = customRef((track, trigger) => {
return {
get() {
track()
return localStorage.getItem(key) ?? initialValue
},
set(v) {
localStorage.setItem(key, v)
trigger()
}
}
})
return value
}
const counter = useLocalStorage("counter", 0)
const update = () => {
counter.value++
}
</script>
javascript
<script setup>
import { watch, customRef } from "vue"
function useDebouncedRef(delay = 500) {
return customRef((track, trigger)=>{
let timer
let temp
return {
get(){
track()
return temp
},
set(v){
timer && clearInterval(timer)
timer = setInterval(()=>{
temp = v
trigger()
}, delay)
}
}
})
}
const text = useDebouncedRef()
watch(text, (value) => {
console.log(value)
})
<template>
<input v-model="text" />
</template>
shallowReactive
shallowReactive创建浅层响应式数据,深层对象就是原有的不同对象,不具有响应性。
- shallowReactive()函数创建的数据是非深度监听,只会包装包装第一个对象,也就意味着深度的ref不会被自动解包。
javascript
<template>
<div>
<h3>shallowReactive</h3>
<div>{{ user }}</div>
<div>{{ user2 }}</div>
<button @click="change">修改数据源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, shallowReactive } from 'vue'
export default defineComponent({
setup() {
const count = ref(10)
const user = reactive({ name: '张三', age: 18, count })
const count2 = ref(20)
const user2 = shallowReactive({ name: '张三', age: 18, count: count2 })
// 修改数据
const change = () => {
// 1. reactive 操作深层ref 数据时,自动解包
// 此时修改user.count 数据时不用添加.value
// user.count = 20 // 修改 reactive
// 2. shallowReactive 操作深层ref 数据时,不会自动解包
// 此时修改user2.count 数据时必须使用.value
user2.count.value = 40 // 修改 shallowReactive
}
return { user, user2, change }
}
})
</script>
shallowReadonly
让一个响应式数据变成只读的。[浅只读]。只有浅层次的数据是可读的,深层次的是可以修改的
javascript
<template>
<div>
<h1>姓名:{{ name }}</h1>
<h2>年龄:{{ age }}</h2>
<h3>喜欢的水果:{{ likeFood.fruits.apple }}</h3>
<button @click="name += '~'">修改姓名</button>
<button @click="age++">修改年龄</button>
<button @click="likeFood.fruits.apple += '!'">修改水果</button>
</div>
</template>
<script>
import { reactive, toRefs, readonly } from "vue";
export default {
name: "App",
setup() {
// 定义了一段数据
let person = reactive({
name: "张三",
age: 18,
likeFood: {
fruits: {
apple: "苹果",
},
},
});
// 使用了shallowReadonly将对象变为只读
let p = shallowReadonly(person)
// 将数据返回出去
return {
...toRefs(p),
};
},
};
张三,18不可以修改,应为是浅层的,苹果可以更改
shallowReadonly处理深层ref不会自动解包
javascript
<template>
<div>
<h3>shallowReadonly</h3>
<div>{{ user }}</div>
<div>{{ user2 }}</div>
</div>
</template>
<script lang="ts">
import { defineComponent, readonly, ref, shallowReadonly } from 'vue'
export default defineComponent({
setup() {
const count = ref(10)
const user = readonly({ name: '张三', age: 18, count })
const count2 = ref(20)
const user2 = shallowReadonly({ name: '张三', age: 18, count: count2 })
// readonly 处理深层数据为ref 数据时, 会自动解包,不用添加.value
console.log('user.count', user.count)
// shallowReadonly 获取深层ref 数据时必须添加.value, 因为不会自动解包
console.log('user2.count', user2.count.value)
return { user, user2 }
}
})
</script>
toRaw
使用reactive,readonly,shallowReactive,shallowReadonly代理的对象,使用toRawapi方法后,将会返回一个新的原始对象。
markRaw
将一个对象标记为不可代理的对象,无法标记已经被代理的对象,如果标记一个已经被代理的对象是无法生效称为一个不可代理的对象。
effectScope
javascript
<template>
<div>
<h3>shallowReactive</h3>
<div>{{ count }}</div>
<button @click="change">修改数据源</button>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, effectScope, markRaw, reactive, ref, shallowReactive, toRaw, watch, watchEffect } from 'vue'
export default defineComponent({
setup() {
// 创建ref 数据
const count = ref(10)
// 创建副作用作用域
const scope = effectScope()
// 控制台输出 effect 作用域
console.log("scope", scope);
// 收集运行的副作用
scope.run(() => {
// 计算属性副作用
const computedCount = computed(() => count.value * 2
)
// watch 侦听副作用
watch(
count,
() => {
console.log('computedCount', computedCount.value)
console.log('watch count', count.value)
}
)
// watchEffect 副作用
watchEffect(() => {
console.log('watchEffect count', count.value)
})
})
console.log('scope', scope)
// 2秒以后关闭所有的副作用
setTimeout(() => {
scope.stop()
}, 2000)
// 修改数据
const change = () => {
count.value++
}
return { count, change }
}
})
</script>
getCurrentScope
如果有的话,返回当前活跃的 effect 作用域。
onScopeDispose
在当前活跃的 effect 作用域上注册一个处理回调函数。当相关的 effect 作用域停止时会调用这个回调函数
javascript
// 获取当前侦听实例
const allScope = getCurrentScope()
// 执行 allScope.stop()时会触发 onScopeDispose 事件
// 当前页面或组件注销时会触发 onScopeDispose 事件
onScopeDispose(() => {
console.log('已停止所有侦听');
// to do...
})
// 5秒后停止所有侦听,此时会触发 onScopeDispose 事件
setTimeout(() => {
allScope.stop()
}, 5000)
工具
unref
语法糖
javascript
val = isRef(val) ? val.value: val
可以用来对响应式对象解除响应式引用
javascript
<template>
<div>
{{unRefAsCount}}
{{count}}
<button @click="addCount"> +1 </button>
</div>
</template>
<script lang="ts" setup>
import {unref, ref} from 'vue'
const count = ref(1)
let unRefAsCount = unref(count)
const addCount = () => {
count.value++
}
// unRefAsCount
</script>
toRef
toRef 可以根据一个响应式对象中的一个属性,创建一个响应式的 ref。同时这个 ref 和原对象中的属性保持同步,改变原对象属性的值这个 ref 会跟着改变,反之改变这个 ref 的值原对象属性值也会改变
javascript
<template>
<div>
{{ count.a }}
{{ a }}
<button @click="addCount">+1</button>
</div>
</template>
<script lang='ts' setup>
import { ref, toRef } from "vue"
const count = ref({
a: 1,
b: 2
})
const a = toRef(count.value, 'a')
const addCount = () => {
a.value++
}
</script>
toValue
用来规范化一个可以是值、ref 或 getter 的参数
javascript
toValue(1) // --> 1
toValue(ref(1)) // --> 1
toValue(() => 1) // --> 1
toRefs
toRefs 它可以将一个响应式对象转成普通对象,而这个普通对象的每个属性都是响应式的 ref
javascript
<template>
<div>
{{ count.a }}
{{ countAsRefs.a }}
<button @click="addCount">+1</button>
</div>
</template>
<script lang='ts' setup>
import { reactive, toRefs } from "vue"
const count = reactive({
a: 1,
b: 2
})
const countAsRefs = toRefs(count)
const addCount = () => {
countAsRefs.a.value++
}
</script>