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
}

  

posted @ 2023-07-07 09:38  TheYouth  阅读(160)  评论(0编辑  收藏  举报