Vue3:简单使用 reactive、shallowReactive、readonly

126 阅读4分钟

[vue3源码](GitHub - vuejs/core: 🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.)

reactive

  1. 只支持引用类型
import { reactive } from 'vue'

const data = reactive('童渊') // 报错

const data2 = reactive({}) // 正确
  1. 访问属性不需要 .value
import { reactive, onMounted } from 'vue'
const data = reactive({
  name: "童渊"
})
onMounted(() => {
  console.log(data.name); // 童渊
})
  1. 使用数组例子
import { reactive } from 'vue'
const data = reactive(["诸葛亮", "黄月英", "诸葛果"])
  <div>
    <select name="select" id="select">
      <option :value="index" v-for="(item,index) in data" :key="index">{{ item }}</option>
    </select>
  </div>
  1. 使用对象例子
const data = reactive({
  nickName: "",
  pwd: ""
})
const submit = function () {
  console.log(data); // reactive 对象 Proxy {nickName: '', pwd: ''}
}
  <div>
    <form>
      <input type="text" v-model="data.nickName"><br />
      <input type="text" v-model="data.pwd"><br />
      <button @click.prevent="submit">登录</button>
    </form>
  </div>
  1. 不可以给reactive对象直接赋值,会破坏响应式数据。模拟异步操作:
import { reactive, onMounted } from 'vue'

let data = reactive({}) // <--- 传的是一个对象 {}

const getData = function (): any {

  setTimeout(() => {
    let res = {
      code: 200,
      data: [
        { name: '吕布' },
        { name: "关羽" },
        { name: "张飞" },
        { name: "刘备" }
      ]
    }
    data = res.data
    console.log(data);
  }, 2000)
}

onMounted(getData)
    <ul>
      <li v-for="(item, index) in data" :key="index">{{ item }}</li>
    </ul>

像上面这样写,页面中是不会有任何渲染的,尽管控制台已经打印了数据

解决方案:

如果你传递的是一个对象的话,那么就再新增一个属性,把数据赋值给属性

import { reactive, onMounted } from 'vue'

interface IList {
  name: string
}

let data = reactive({
  // list: [] as any,
  // list: <any>[],
  list: [] as IList[] // <------------- 新增的 list 属性
})

const getData = function (): any {

  setTimeout(() => {
    let res = {
      code: 200,
      data: [
        { name: '吕布' },
        { name: "关羽" },
        { name: "张飞" },
        { name: "刘备" }
      ]
    }
    data.list = res.data // <---------- 把数据赋值给属性
    console.log(data);
  }, 2000)
}

onMounted(getData)

  <div>
    <ul>
      <li v-for="(item, index) in data.list" :key="index">{{ item.name }}</li>
    </ul>
  </div>

你如果传的是一个数组,可以利用数组push方法+解构赋值来实现:

import { reactive, onMounted } from 'vue'

interface IData {
  name: string
}

let data = reactive(
  // [] as any
  // <any>[]
  [] as IData[] // < ---------- 传的是数组
)

const getData = function (): any {

  setTimeout(() => {
    let res = {
      code: 200,
      data: [
        { name: '吕布' },
        { name: "关羽" },
        { name: "张飞" },
        { name: "刘备" }
      ]
    }
    data.push(...res.data)
    console.log(data);
  }, 2000)
}

onMounted(getData)
  <div>
    <ul>
      <li v-for="(item, index) in data" :key="index">{{ item.name }}</li>
    </ul>
  </div>

上面这种方法,同样可以实现保持响应式

shallowReactive

shallowRef差不多

  1. 浅层响应式
import { reactive, shallowReactive } from 'vue'

const data = reactive({
  hero: {
    name: '小乔',
  }
})

const shallowData = shallowReactive({
  food: {
    name: "苹果",
    price: {
      p1: '4'
    }
  }
})

const change = function () {
  console.log(data, shallowData);
  shallowData.food.price.p1 = "888"
  console.log(shallowData.food.price.p1);
}
  <div>
    <div>{{ shallowData }}</div>
    <button @click="change">修改</button>
  </div>

像上面这种写法,虽然值确实是发生了改变,但是页面却不会更新

shallowReactive响应式只到第一个属性,需要改成下面这种写法

const change = function () {
  shallowData.food = { name: '香蕉', price: { p1: "888" } }
}

如果只传一个基础数据类型,那么就会报错:

let shallowData = shallowReactive({
  name: "郭嘉"
})

const change = function () {
  shallowData = '张角'
}

报错信息:

let shallowData: ShallowReactive<{  
name: string;  
}>

不能将类型“string”分配给类型“ShallowReactive<{ name: string; }>”。  
不能将类型“string”分配给类型“{ name: string; }”。
  1. reactive放在一起修改值时,会受 reactive 的影响
import { reactive, shallowReactive } from 'vue'

const data = reactive({ name: "司马徽" })

let shallowData = shallowReactive({
  sObj: {
    name: "于吉"
  }
})

const change = function () {
  console.log('修改前: ', data.name); // 修改前:  司马徽
  console.log('修改前: ', shallowData.sObj.name); // 修改前:  于吉

  data.name = "张宝"
  shallowData.sObj.name = "孙策"

  console.log('修改后: ', data.name); // 修改后:  张宝
  console.log('修改后: ', shallowData.sObj.name); // 修改后:  孙策
}
  <div>
    <button @click="change">修改</button>
  </div>

当修改reactive的值发生改变时,shallowReactive的值也同样会被修改

readonly

  1. reactive对象的值,变成只读的
import { reactive, readonly, onMounted } from 'vue'

const data = reactive({ name: "周瑜" })

const readonlyData = readonly(data)

onMounted(() => {
  readonlyData.name = "左慈" // 无法为“name”赋值,因为它是只读属性。
})
  1. 也是会被reactive所影响
import { reactive, readonly, onMounted } from 'vue'

const data = reactive({ name: "周瑜" })

const readonlyData = readonly(data)

onMounted(() => {
  console.log('修改前', data.name); // 修改前 周瑜
  console.log('修改前', readonlyData.name); // 修改前 周瑜

  data.name = "贾诩"

  console.log('修改后', data.name); // 修改后 贾诩
  console.log('修改后', readonlyData.name); // 修改后 贾诩
})

源码解析

文件位置: \core-main\packages\reactivity\src\reactive.ts

// <T extends object> // 泛型约束
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
  )
}

至于reactive为什么只接收引用数据类型,可以看到reactive它是有泛型约束的,它继承于object

createReactiveObject函数:

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any> 
) {
  // 如果我们传的不是引用类型 -> 报错
  // 是引用类型,直接返回
  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
  // 不了解,有待学习 WeakMap 是个啥?
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only specific value types can be observed.
  // 英文翻译: 只能观察到特定的值类型
  // 意思是类似于白名单、通行证之类?
  // 如果我们传的对象,在这个白名单中,就直接返回,没有就直接创建 Proxy 代理
  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
}

关于reactive的源码暂时就看到这里吧

总结

reactive

  1. 创建响应式对象(深层)
  2. 只支持引用类型
  3. 访问值不需要.value
  4. 不可以直接给 reactive 对象赋值,会破坏响应式

shallowReactive

  1. 创建响应式对象(浅层)
  2. 会被 reactive 对象影响

readonly

  1. reactive 对象的值变成只读的
  2. 会被 reactive 对象影响