vue3 ref和reactive全家桶(小满zs vue3 笔记六)
tip1: 无响应式数据(控制台数据已经变化,但是页面无刷新),为什么要用ref和reactive
<template> <div>认识Ref全家桶</div> <div>{{ message }}</div> <button @click="change">改变</button> </template> <script setup lang="ts"> let message: string = '无响应式数据' // console.log 打印改变了,但message没有刷新,原因不是响应式 const change = () => { message = '改变数据' console.log(message) } </script>
tip2: ref和reactive的区别
1. ref用于原始值基本数据类型不支持对象(不是深层次的可以用ref),reactive支持对象
2. ref定义的需要.value来访问,reactive则不需要
3. ...
tip3: 技巧2 格式化vue3打印结构
原格式(包几层还需要在dep里的_value里取):
优化后的格式:
tip4: 用户代码片段,自动生成模板面板,
1. 设置模板 vscode 最下面齿轮设置图标->用户代码片段->输入vue.json来设置模板,如下:
{ "vue3": { "prefix": "vue3", "body": [ "<template>", "<div>", "</div>", "</template>", "", "<script setup lang='ts'>", "", "</script>", "", "<style>", "", "</style>", "$2" ], "description": "Log output to console" } }
tip5: 如何获取dom上元素内容
dom: <div ref="div"></div> ref的名称和获取后的一致,这里的名称是div js: const div = ref<HTMLDivElement>() const change = () => { message.value = '改变customRef' console.log(div.value?.innerText) // 这样可以获取ref元素为div上的属性数据 }
一. ref, Ref (响应式数据)
<template> <div>认识Ref全家桶</div> <div>{{ messageRe }}</div> <div>{{ messageR }}</div> <button @click="change">改变</button> </template> <script setup lang="ts"> import { ref, Ref } from 'vue' let messageR = ref('响应式数据') // 自动推类型 // 或 let messageRe: Ref<string> = ref('响应式数据') // 通过Ref设置类型,因为只设置类型会报错,两边不一致<string> !== ref<string>,
需要通过Ref来设置,这样两边类型才能相等,才能改变它的值 const change = () => { messageR.value = '响应式数据-改变' messageRe.value = '响应式数据-改变Ref' console.log(messageR, messageRe) } </script>
二. isRef
<template> <div>isRef,是判断是否是ref类型</div> <div>{{ messageRe }}</div> <button @click="change">改变</button> </template> <script setup lang="ts"> import { ref, Ref, isRef } from 'vue' let messageR = ref('响应式数据') // 或 let messageRe: Ref<string> = ref('响应式数据') const change = () => { messageR.value = '响应式数据-改变' console.log(messageR, messageRe, isRef(messageR), isRef(messageRe)) } </script>
三. shallowRef(浅层次,不能改变vlaue值)
<template> <div> <div>{{ message }}</div> <button @click="change">改变</button> </div> </template> <script setup lang='ts'> import { Ref, shallowRef } from 'vue' type Obj = { name: string } let message: Ref<Obj> = shallowRef({ name: '认识shallowRef' }) const change = () => { message.value.name = '改变shallowRef' // message.value = { name: '监听改变shallowRef'} // console.log里的数据已经改变,但页面数据一直不刷新 // 强制改变triggerRef, shallowRef和ref不能放在一块写,放在一块会多次调用triggerRef,ref底层就是调用triggerRef // triggerRef(message) console.log(message) } </script>
四. customRef,
track收集依赖,
trigger 触发依赖
<template> <div ref="div"> customRef: {{ message }} </div> <button @click="change">customRef更新</button> </template> <script setup lang='ts'> import { customRef, ref } from 'vue' let timer: any // 防抖 // T泛型 function myRef <T = any>(value: T) { // track return customRef((track, trigger) => { return { get () { track() // 收集依赖 return value }, set (newVal) { // 更新值 // 调用接口时 clearTimeout(timer) timer = setTimeout(() => { value = newVal console.log('=====触发了') timer = null }, 500); trigger() // 触发依赖 } } }) } let message = myRef<string>('customRef') const div = ref<HTMLDivElement>() const change = () => { message.value = '改变customRef' console.log(div.value?.innerText) // 这样可以获取ref元素为div上的属性数据 } </script>
五. ref源码解析
packages -> reactivity -> ref.ts
// 构造多种场景类型 export function ref<T extends Ref>(value: T): T export function ref<T>(value: T): Ref<UnwrapRef<T>> export function ref<T = any>(): Ref<T | undefined> export function ref(value?: unknown) { // 创建ref ,传入当前value及false return createRef(value, false) } function createRef(rawValue: unknown, shallow: boolean) { // 判断如果是Ref时直接返回 if (isRef(rawValue)) { return rawValue } // 否则创建,rawValue当前value值, shallow false return new RefImpl(rawValue, shallow) } // 创建 class RefImpl<T> { private _value: T private _rawValue: T public dep?: Dep = undefined public readonly __v_isRef = true // 接收,如果是true时直接返回value,否则执行toReactive constructor(value: T, public readonly __v_isShallow: boolean) { this._rawValue = __v_isShallow ? value : toRaw(value) this._value = __v_isShallow ? value : toReactive(value) } // 收集依赖 get value() { trackRefValue(this) return this._value } // 触发依赖 set value(newVal) { const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal) newVal = useDirectValue ? newVal : toRaw(newVal) if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = useDirectValue ? newVal : toReactive(newVal) triggerRefValue(this, newVal) } } }
shallowRef, 直接传的是true,所以它的值不会变
六. reactive, readonly只读,shallowReactive(只到第一个属性那里.a = {},会被reactive影响)
1. 表单提交,只能触发对象,字符串和number不支持
<template> <div> reactive: <form> <div>{{ form.name }}</div> <div>{{ form.age }}</div> <input v-model="form.name"/> <input v-model="form.age"/> <!-- 不写.prevent 会触发本身的组件属性来刷新 --> <button @click.prevent="submit">提交表单</button> </form> </div> </template> <script setup lang='ts'> import { reactive } from 'vue'; type Ages = { name: string, age: number } let form = reactive<Ages>({ name: 'li li', age: 32 }) const submit = () => { console.log(form) } </script>
2. 数组添加,reactive proxy 不能直接赋值,否则破坏响应式对象,可以作为一个属性去赋值解决
<template> <div> reactive: <form> <ul> <li v-for="(item, index) in items" :key="index">{{ item }}</li> </ul> <!-- 不写.prevent 会触发本身的组件属性来刷新 --> <button @click.prevent="submit">提交表单</button> </form> </div> </template> <script setup lang='ts'> import { reactive } from 'vue'; type Obj = { array?: Array<string> } const items = reactive<Obj>({ array: [] }) const readOnlyItems = readOnly(items) const submit = () => { // 如果是接口返回的话 const data = ['aa', 'bb', 'cc'] // 直接赋值会报错, // items = data // 1. push // items.push(...data) // 2. 可以为它增加一个属性 items.array = data
// 直接修改会报警告,可以修改items.array,不能修改readOnlyItems的array,因为是只读的
readOnlyItems.array = data
console.log(items) } </script>
七. reactive源码
packages -> reactivity -> reactive.ts
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T> export function reactive(target: object) { // if trying to observe a readonly proxy, return the readonly version. // 判断是否是只读的代理,如果是直接返回 if (isReadonly(target)) { return target } // 创建reactive对象 return createReactiveObject( target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap ) } function createReactiveObject( target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any>, proxyMap: WeakMap<Target, any> ) { // 判断是否是object对象类型, 否则报警告,reactive是不能处理string,number常规类型的 if (!isObject(target)) { if (__DEV__) { console.warn(`value cannot be made reactive: ${String(target)}`) } return target } // target is already a Proxy, return it. // exception: calling readonly() on a reactive object if ( target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE]) ) { return target } // target already has corresponding Proxy // 判断是否存在,如果存在直接返回 const existingProxy = proxyMap.get(target) if (existingProxy) { return existingProxy } // only specific value types can be observed. // 判断是否在白名单中,如果在直接返回 const targetType = getTargetType(target) if (targetType === TargetType.INVALID) { return target } // 创建代理对象 const proxy = new Proxy( target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers ) proxyMap.set(target, proxy) return proxy }
将来的自己,会感谢现在不放弃的自己!