vue3 实现功能及手写组合api

948 阅读3分钟

在书写vue3的过程中,我们初步了解了vue3的新特性,今天,我们将延续Composition API的话题,来了解Composition API的原理⬇⬇⬇

<-- 首先我们来看第一个 -->

customRef

  • customRef用于自定义一个ref,可以显示的控制依赖追踪触发响应,接受一个工厂函数,两个参数分别用于追踪的track与用于触发响应的triger,并返回一个带有getset属性的对象 看到这个,我们会不会在我们平时应用中想起这个组合api在防抖中会有将极大地帮助,可以很简单的实现防抖效果,下面我们来看:

封装hook函数:

import {customRef} from 'vue';

export default function anti_shake<T>(value: T, delay = 200) {
    let timeOut: number
    //track  追踪数据
    //trigger 更新界面
    return customRef((track, trigger) => {
        return {
            get() {
                track()
                return value
            },
            set(newValue: T) {
                clearTimeout(timeOut)
                timeOut = setTimeout(() => {
                    value = newValue
                    trigger()
                }, delay)
            }
        }
    })
}

引入封装好的hook函数⬇⬇⬇

<script lang="ts">
import {defineComponent, ref} from 'vue';
import anti_shake from "@/hooks/anti_shake";

export default defineComponent({
  name: 'father',

  setup(props) {
    const keyNum = anti_shake('', 1000)
    return {
      keyNum
    }
  }
})
</script>

以上就是我们运用customRef实现防抖的效果。🤭 🤭 🤭

provide 与 inject运用

  • provideinject提供依赖注入,功能类似 2.x 的provide/inject
  • 实现跨层级组件(祖孙)间通信

provideinject可以不经过父级组件直接进行爷爷组件与孙子组件之间的通讯,下面我们来看,

父组件:
<template>
  <p>当前颜色: {{color}}</p>
  <button @click="color='red'"></button>
  <button @click="color='yellow'"></button>
  <button @click="color='blue'"></button>
  <Son />
</template>

<script lang="ts">
import { provide, ref, defineComponent} from 'vue'
import Son from './Son.vue'

export default defineComponent({
  name: 'ProvideInject',
  components: {
    Son
  },
  setup() {
    const color = ref('red')
    provide('color', color)

    return {
      color
    }
  }
})
</script>

子组件:
<template>
    <GrandSon />
</template>

<script lang="ts">
import {defineComponent} from 'vue'
import GrandSon from './GrandSon.vue'

export default defineComponent({
  components: {
    GrandSon
  },
})
</script>

孙子组件:
<template>
  <h3 :style="{color}">孙子组件: {{color}}</h3>
</template>

<script lang="ts">
import { inject, defineComponent } from 'vue'
export default defineComponent({
  setup() {
    const color = inject('color')

    return {
      color
    }
  }
})
</script>

看上面,我们可以直接将父级组件中的color属性传递给孙子组件使用,实现跨层级组件(祖孙)间通信。

判断响应式函数

我们可以通过下面的这些组合api快速的判断属性是否是响应式的,从而提高代码的书写速度。。。

  • isRef: 检查一个值是否为一个 ref 对象
  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

手写组合api

reactive和shallowReactive

首先,我们要理解两这个区别,shallowReactive可以说是浅劫持,而reactive深劫持,因此,下面我们从浏览器角度来理解两个组合api的原理。

<script lang="ts">
import {defineComponent} from 'vue';

// reactiveHandler 处理器
const reactiveHandler = {
  // target 对象     prop 属性
  get(target, prop) {
    const result = Reflect.get(target, prop)
    console.log('拦截读取数据', target, prop)
    return result
  },
  set(target, prop, value) {
    const result = Reflect.set(target, prop, value)
    console.log('拦截修改数据', target, prop, value)
    return result
  },
  deleteProperty(target, prop) {
    const result = Reflect.deleteProperty(target, prop)
    console.log('拦截删除数据', target, prop)
    return result
  }
}

function shallowReactive<T>(target: T): T {
  if (target && typeof target === 'object') {
    return new Proxy(target, reactiveHandler)
  }
  return target
}

function reactive<T>(target: T): T {
  if (target && typeof target === 'object') {
    // 判断当前数据是数组还是对象对象(递归处理)
    if (Array.isArray(target)) {
      target.forEach((item, index) => {
        target[index] = reactive(item)
      })
    } else {
      Object.keys(target).forEach(key => {
        target[key] = reactive(target[key])
      })
    }
    return new Proxy(target, reactiveHandler)
  }
  return target
}

export default defineComponent({
  name: 'father',

  setup(props) {
    const people = reactive({
      name: '小明',
      books: {
        content: '红楼梦'
      }
    })
    // 测试 reactive
    people.name = '小红'
    people.books.content = '水浒传'
    delete people.name
    delete people.books.content

    const people1 = shallowReactive({
      name: '小明',
      books: {
        content: '红楼梦'
      }
    })
    // 测试 shallowReactive
    people1.name = '小红'
    people1.books.content = '水浒传'
    delete people1.name
    delete people1.books.content

    return {
      people,
      people1
    }
  }
})
</script>

测试结果(reactive):

image.png 测试结果(shallowReactive):

image.png

下面我们继续来看⬇⬇⬇

shallowRef 和 ref

和我们上面说的很相似,shallowRef只会将外层数据变成响应式的数据,而ref可以将最底层的数据变成响应式的,下面我们来看两者的触发原理⬇⬇⬇

<script lang="ts">
import {defineComponent, reactive} from 'vue';

function shallowRef(target) {
  const result = {
    get value() {
      return target
    },
    set value(val) {
      target = val
      console.log('set value 数据已更新, 去更新界面')
    }
  }
  return result
}

function ref(target) {
  if (target && typeof target === 'object') {
    target = reactive(target)
  }
  const result = {
    get value() {
      return target
    },
    set value(val) {
      target = val
      console.log('set value 数据已更新, 去更新界面')
    }
  }
  return result
}

export default defineComponent({
  name: 'father',

  setup(props) {
    //测试 shallowRef
    const shallowRef1 = shallowRef({
      a: 'abc',
    })
    shallowRef1.value = '111'
    console.log(shallowRef1.value)

    // 测试 ref
    const ref1 = ref(0)
    const ref2 = ref({
      a: 'abc',
      b: [{x: 1}],
      c: {x: [11]},
    })
    console.log(ref1.value += 1)
    console.log(ref2.value.b[0].x += 1)

    return {}
  }
})
</script>

测试结果:

image.png 由此可见,两者的原理几乎相同,只是深浅程度不同。

⬇ ⬇ ⬇ ⬇ ⬇ ⬇ ⬇ ⬇ ⬇

isRef, isReactive 和 isReadonly

这三个可能以后在写的时候会经常用到,用来判断是不是某个类型的对象, 原理很简单, 我们呢来看一下。

//判断是否是ref对象
function isRef(obj) {
  return obj && obj._is_ref
}

//判断是否是reactive对象
function isReactive(obj) {
  return obj && obj._is_reactive
}


//判断是否是readonly对象
function isReadonly(obj) {
  return obj && obj._is_readonly
}

//是否是reactive或readonly产生的代理对象
function isProxy (obj) {
  return isReactive(obj) || isReadonly(obj)
}

上面这个测试我就不写啦,有用到的可以直接使用。🤭🤭🤭

学到现在啦,是不是该放松一下啦!记得往外面看一看,活动一下哈(●ˇ∀ˇ●)

shallowReadonly 和 readonly

还是和上面一样,深浅程度的问题。

<script lang="ts">
import {defineComponent, reactive} from 'vue';

const readonlyHandler = {
  get(target, key) {
    if (key === '_is_readonly') return true
    return Reflect.get(target, key)
  },

  set() {
    console.warn('只读的, 不能修改')
    return true
  },

  deleteProperty() {
    console.warn('只读的, 不能删除')
    return true
  },
}

//shallowReadonly
function shallowReadonly(obj) {
  return new Proxy(obj, readonlyHandler)
}

//readonly
function readonly(target) {
  if (target && typeof target === 'object') {
    // 判断当前数据是数组还是对象(递归处理)
    if (Array.isArray(target)) {
      target.forEach((item, index) => {
        target[index] = readonly(item)
      })
    } else {
      Object.keys(target).forEach(key => {
        target[key] = readonly(target[key])
      })
    }
    return new Proxy(target, readonlyHandler)
  }

  return target
}

export default defineComponent({
  name: 'father',

  setup(props) {
    //测试 readonly
    const objReadOnly = readonly({
      a: {
        b: 1
      }
    })
    //测试 shallowReadonly
    const objShallowReadOnly = shallowReadonly({
      a: {
        b: 1
      }
    })
    console.log(objReadOnly.a = 1)
    console.log(objReadOnly.a.b = 2)
    console.log(delete objReadOnly.a)
    console.log(objShallowReadOnly.a = 1)
    console.log(objShallowReadOnly.a.b = 2)
    console.log(delete objShallowReadOnly.a)

    return {}
  }
})
</script>

测试结果:

image.png 这些组合api大家都可以看一下,挺重要的,对以后使用vue3做项目会有很大的帮助。。。

新组件

在最后和大家提一点新组件,没事的时候都可以试一下,挺有意思的这几个组件 Fragment(## 片断),Teleport(## 瞬移),Suspense(## 不确定的),这三个是vue3中最新出来的。

Fragment: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中。

Teleport: 提供了一种干净的方法, 让组件的html在父组件界面外的特定标签(很可能是body)下插入显示。

Suspense: 允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验。

拜拜啦!今天的努力,会让你的明天更好哈! 💪💪💪