Vue3 入坑 Chapter 4
shallowReactive 与 shallowRef
- shallowReactive : 只处理了对象内最外层属性的响应式(也就是浅响应式)
- shallowRef: 只处理了value的响应式, 不进行对象的reactive处理
- 什么时候用浅响应式呢?
- 一般情况下使用ref和reactive即可
- 如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
- 如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef
<template>
<div class="ShallowReactiveAndShallowRef">
<h2>ShallowReactive和ShallowRef</h2>
<h3>m1: {{ m1 }}</h3>
<h3>m2: {{ m2 }}</h3>
<h3>m3: {{ m3 }}</h3>
<h3>m4: {{ m4 }}</h3>
<hr>
<button @click="update">更新数据</button>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, shallowReactive, ref, shallowRef } from 'vue'
export default defineComponent({
name: 'ShallowReactiveAndShallowRef',
setup () {
// 深度劫持(深监视) --- 深度响应式
const m1 = reactive({
name: '名人',
age: 20,
car: {
name: '奔驰',
color: 'red'
}
})
// 浅劫持(浅监视) --- 浅度响应式
const m2 = shallowReactive({
name: '名人',
age: 20,
car: {
name: '奔驰',
color: 'red'
}
})
// 深度劫持(深监视) --- 深度响应式
const m3 = ref({
name: '名人',
age: 20,
car: {
name: '奔驰',
color: 'red'
}
})
// 浅劫持(浅监视) --- 浅度响应式
const m4 = shallowRef({
name: '名人',
age: 20,
car: {
name: '奔驰',
color: 'red'
}
})
const update = () => {
// 更改 m1 的数据 --- reactive 方式
// m1.name += '=='
// m1.car.name += '=='
// 更改 m2 的数据 --- shallowReactive 方式
m2.name += '=='
m2.car.name += '=='
// 更改 m3 的数据 --- ref 方式
// 更改 m4 的数据 --- shallowRef 方式
}
return {
m1,
m2,
m3,
m4,
update
}
}
})
</script>
<style lang="scss" scoped>
</style>
readonly 与 shallowReadonly
- readonly:
- 深度只读数据
- 获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
- 只读代理是深层的:访问的任何嵌套 property 也是只读的。
- shallowReadonly
- 浅只读数据
- 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
- 应用场景:
- 在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除
<template>
<h2>App</h2>
<h3>{{state}}</h3>
<button @click="update">更新</button>
</template>
<script lang="ts">
import { reactive, readonly, shallowReadonly } from 'vue'
/*
readonly: 深度只读数据
获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
只读代理是深层的:访问的任何嵌套 property 也是只读的。
shallowReadonly: 浅只读数据
创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
应用场景:
在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除
*/
export default {
setup () {
const state = reactive({
a: 1,
b: {
c: 2
}
})
// const rState1 = readonly(state)
const rState2 = shallowReadonly(state)
const update = () => {
// rState1.a++ // error
// rState1.b.c++ // error
// rState2.a++ // error
rState2.b.c++
}
return {
state,
update
}
}
}
</script>
toRaw 与 markRaw
- toRaw
- 返回由
reactive
或readonly
方法转换成响应式代理的普通对象。 - 这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
- 返回由
- markRaw
- 标记一个对象,使其永远不会转换为代理。返回对象本身
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类实例或 Vue 组件对象。
- 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。
<template>
<h2>{{state}}</h2>
<button @click="testToRaw">测试toRaw</button>
<button @click="testMarkRaw">测试markRaw</button>
</template>
<script lang="ts">
/*
toRaw: 得到reactive代理对象的目标数据对象
*/
import {
markRaw,
reactive, toRaw,
} from 'vue'
export default {
setup () {
const state = reactive<any>({
name: 'tom',
age: 25,
})
const testToRaw = () => {
const user = toRaw(state)
user.age++ // 界面不会更新
}
const testMarkRaw = () => {
const likes = ['a', 'b']
// state.likes = likes
state.likes = markRaw(likes) // likes数组就不再是响应式的了
setTimeout(() => {
state.likes[0] += '--'
}, 1000)
}
return {
state,
testToRaw,
testMarkRaw,
}
}
}
</script>
toRef
- 为源响应式对象上的某个属性创建一个 ref 对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
- 区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
- 应用: 当要将 某个prop 的 ref 传递给复合函数时,toRef 很有用
<template>
<h2>App</h2>
<p>{{state}}</p>
<p>{{foo}}</p>
<p>{{foo2}}</p>
<button @click="update">更新</button>
<Child :foo="foo"/>
</template>
<script lang="ts">
/*
toRef:
为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
应用: 当要将某个 prop 的 ref 传递给复合函数时,toRef 很有用
*/
import {
reactive,
toRef,
ref,
} from 'vue'
import Child from './Child.vue'
export default {
setup () {
const state = reactive({
foo: 1,
bar: 2
})
const foo = toRef(state, 'foo')
const foo2 = ref(state.foo)
const update = () => {
state.foo++
// foo.value++
// foo2.value++ // foo和state中的数据不会更新
}
return {
state,
foo,
foo2,
update,
}
},
components: {
Child
}
}
</script>
<template>
<h2>Child</h2>
<h3>{{foo}}</h3>
<h3>{{length}}</h3>
</template>
<script lang="ts">
import { computed, defineComponent, Ref, toRef } from 'vue'
const component = defineComponent({
props: {
foo: {
type: Number,
require: true // 必须的
}
},
setup (props, context) {
const length = useFeatureX(toRef(props, 'foo'))
return {
length
}
}
})
function useFeatureX(foo: Ref) {
const lenth = computed(() => foo.value.length)
return lenth
}
export default component
</script>
customRef
- 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
- 需求: 使用 customRef 实现 debounce 的示例
<template>
<div class="customRefTest">
<h2>CustomRef的使用</h2>
<input type="text" v-model="keyword">
<p>{{ keyword }}</p>
</div>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component'
import { customRef } from 'vue'
// 自定义 hook 防抖函数
// value 传入的数据, 将来数据的类型不确定, 所以要使用泛型, delay防抖的间隔时间, 默认是 200 毫秒
function useDebouncedRef<T> (value:T, delay = 200) {
// 准备一个存储定时器的变量
let timeOutId: number
return customRef((track, trigger) => {
return {
// 返回数据的
get () {
// 告诉 Vue 追踪数据
track()
return value
},
// 设置数据的
set (newValue:T) {
// 清理定时器
clearTimeout(timeOutId)
// 开启定时器
timeOutId = setTimeout(() => {
value = newValue
// 告诉 Vue 更新界面
trigger()
}, delay)
}
}
})
}
@Options({
name: 'customRefTest'
})
export default class customRefTest extends Vue {
keyword = useDebouncedRef('abc', 500)
}
</script>
<style lang="scss" scoped>
</style>
provide 与 inject
provide和
inject提供依赖注入,功能类似 2.x 的
provide/inject
需求: 实现跨层级组件(祖孙)间通信
爷爷组件
<template>
<div class="ProvideAndInjectTest">
<h2>provide 与 inject</h2>
<p>当前的颜色: {{ color }}</p>
<button @click="color='red'">红色</button>
<button @click="color='yellow'">黄色</button>
<button @click="color='green'">绿色</button>
<hr>
<ProvideAndInjectChild></ProvideAndInjectChild>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, provide } from 'vue'
import ProvideAndInjectChild from '@/components/ProvideAndInjectChild.vue'
export default defineComponent({
name: 'ProvideAndInjectTest',
components: {
ProvideAndInjectChild
},
setup () {
// 响应式的数据
const color = ref('red')
// 提供数据
provide('color', color)
return {
color
}
}
})
</script>
<style lang="scss" scoped>
</style>
儿子组件
<template>
<div class="ProvideAndInjectChild">
<h3>Son子级组件</h3>
<hr>
<provide-and-inject-grand-child></provide-and-inject-grand-child>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import ProvideAndInjectGrandChild from '@/components/ProvideAndInjectGrandChild.vue'
export default defineComponent({
name: 'ProvideAndInjectChild',
components: {
ProvideAndInjectGrandChild
},
setup () {
return {}
}
})
</script>
<style lang="scss" scoped>
</style>
孙子组件
<template>
<div class="ProvideAndInjectGrandChild">
<h3 :style="{color}">GrandSon孙子组件</h3>
<hr>
</div>
</template>
<script lang="ts">
import { defineComponent, inject } from 'vue'
export default defineComponent({
name: 'ProvideAndInjectGrandChild',
setup () {
const color = inject('color')
return {
color
}
}
})
</script>
<style lang="scss" scoped>
</style>
响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理