邂逅Vue3源码-2.reactivity(上)

1.准备工作

首先你需要先了解一下vue3响应式的核心api,和他们的用法。你可以去官方查看。这里我就简单的介绍一下用法和特性

vue3响应式核心api有4个

  • reactive:深度代理
  • shallowReactive:浅度代理 对象中的引用类型不会被代理
  • readonly:深度只读代理 被代理的对象只读
  • shallowReadonly:浅度只读代理 对象中的引用类型不会被代理且可以修改

注意:接下来开发都在reactivity这个包,所以这个包现在是根目录

2.reactive.js

我们需要在src下面创建一个reactive.js文件,这个文件中存放我们响应式的api,也就是上面所的那四个核心的api

首先我们将四个方法都先写出来,内容后续填充

// 深度代理
export function reactive(target) {}

// 浅度代理 对象中的引用类型不会被代理
export function shallowReactive(target) {}

// 深度只读代理 被代理的对象只读
export function readonly(target) {}

// 浅度只读代理 对象中的引用类型不会被代理且可以修改
export function shallowReadonly(target) {}

那么这四个函数的逻辑其实总结来就两个,是否是只读的 是否是深度遍历,现在我们希望有这么一个公共的函数,可以通过不同参数去做不同的处理,所以我们可以去创建一个公共的函数createReactiveObject

// 该方法是将对象进行代理并且返回
// target被代理的对象
// isReadonly是否是只读的
// baseHandlers 拦截方法
export function createReactiveObject(target, isReadonly, baseHandlers) {}

现在我们需要写一些工具方法,我们将这些方法都放到shared包中

shared/src/index.ts

// 判断是否是一个对象
export const isObject = (value) => typeof value === 'object' && value !== null
// 将两个对象合并
export const extend = Object.assign

现在需要创建一个baseHandleers.ts文件,这个文件用来存放不同的api的拦截方法,如果将这些方法都写在reactive.ts中,那么会导致内容太多,难以维护

baseHandleers.ts

// 实现new Proxy的handler

export const mutableHandlers = {}

export const shallowReactiveHandlers = {}

export const readonlyHandlers = {}

export const shallowReadonlyHandlers = {}

现在我们回过头来编写createReactiveObject函数

我们知道其实这些api的底层就使用new Proxy,所以最核心的就是对数据的修改和数据的获取进行拦截,所以这个函数要做的就是将代理的后对象返回

// 存放被代理的对象  weakmap会被垃圾回收 不会造成内存泄漏
const reactiveMap = new WeakMap() // 存放非只读的代理
const readonlyMap = new WeakMap() // 存放只读的代理


export function createReactiveObject(target, isReadonly, baseHandlers) {
  // 判断是否是对象 reactive这个api只能拦截对象类型
  if (!isObject(target)) return target

  // 如果这个对象被代理过,就不用二次代理了,直接将结果返回即可
  // 所以我们需要一个存储空间来存储,那么存储空间分为两种 一种是只读的代理,一种是非只读的代理,所以我们应该创建两个空间,reactiveMap和readonlyMap
 
  const proxyMap = isReadonly ? readonlyMap : reactiveMap
  const existProxy = proxyMap.get(target)
  if (existProxy) return existProxy

  // 创建代理
  const proxy = new Proxy(target, baseHandlers)
  return proxy
}

现在我们需要将4个api中调用createReactiveObject方法,并传入不同参数

// 深度代理
export function reactive(target) {
  return createReactiveObject(target, false, mutableHandlers)
}

// 浅度代理 对象中的引用类型不会被代理
export function shallowReactive(target) {
  return createReactiveObject(target, false, shallowReactiveHandlers)
}

// 深度只读代理 被代理的对象只读
export function readonly(target) {
  return createReactiveObject(target, true, readonlyHandlers)
}

// 浅度只读代理 对象中的引用类型不会被代理且可以修改
export function shallowReadonly(target) {
  return createReactiveObject(target, true, shallowReadonlyHandlers)
}

现在我们只需要关心每个api的handler即可,将视线拉到baseHandlers.ts

其实这四个handlers核心都是一样的 都要 get 和set ,我们先来编写get,还是和上面一样,我们创建公共的createGetter方法,用来根据参数不同生成不同的get

const get = createGetter()
const shallowGet = createGetter(false, true)
const readonlyGet = createGetter(true)
const shallowReadonlyGet = createGetter(true, true)

// isReadonly 是否是只读
// 是否是浅代理
function createGetter(isReadonly = false, shallow = false) {
  
}

export const mutableHandlers = {
  get,
}
export const shallowReactiveHandlers = {
  get: shallowGet,
}

export const readonlyHandlers = {
    get: readonlyGet,
}

export const shallowReadonlyHandlers = {
    get: shallowReadonlyGet,
}

现在我们来看一下createGetter这个方法是怎么编写的

function createGetter(isReadonly = false, shallow = false) {
  // vue3的代理模式是懒代理 只有当获取值的时候才会进行代理 所以性能很高
  // vue2的代理是一上来就进行递归
 
  // target, key, receiver这些参数是new Proxy中handlers的参数,具体可以去mdn查
  return function (target, key, receiver) {
    // 以下写法等同 target[key] 不过使用Reflect会有很多好处 自行百度
    const res = Reflect.get(target, key, receiver)

    // 如果是只读代理 那么就没必要收集依赖
    if (!isReadonly) {
      // 收集依赖,等到数据变化后更新对应的视图

      track(target, TrackOptypes.GET, key)
    }
    
    // 如果是浅代理 直接返回当前获取的值 
    if (shallow) return res

    // 如果获取的值是对象(隐藏含义:获取的值不是浅代理且是对象,所以要继续代理当前值),判断当前代理的对象是否是readonly 如果是那么用readonly继续代理当前获取的值 反之用reactive代理当前获取的值
    if (isObject(res)) return isReadonly ? readonly(res) : reactive(res)

    return res
  }
}

现在我们已经完成了reactive基本的框架了,并且已经实现了每个api的get方法,现在需要在index.ts将所有的api导出即可

// 导出方法 不实现功能
export {
  reactive,
  shallowReactive,
  readonly,
  shallowReadonly,
} from './reactive'

3.测试

写了这么多,总要测试一下,我们可以在根目录创建一个examples文件夹,然后创建html文件进行测试

DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Documenttitle>
head>

<body>
  
  <script src="./../node_modules/@vue/reactivity/dist/reactivity.global.js">script>

  <script>
    const { reactive, shallowReactive, readonly, shallowReadonly, } = VueReactivity

    let obj = { name: 'zs', info: { age: 18 } }
    const reactiveObj = reactive(obj)
    const shallowReactiveObj = shallowReactive(obj)
    const readonlyObj = readonly(obj)
    const shallowReadonlyObj = shallowReadonly(obj)

    console.log(reactiveObj.info)

  script>
body>

html>

4.仓库地址

地址 :https://github.com/wscymdb/vu3-code/tree/reactivity1

github

你可能感兴趣的:(邂逅Vue3源码,vue.js,javascript)