一个简单的自定义 hook ,让 react 实现响应式编程

820 阅读2分钟

最近看了尤雨溪关于 vue3 的一些分享视频,也去 MDN 看了 Proxy 的内容。我们知道 vue3 是根据 Proxy 实现响应式编程的,那么能不能通过 Proxy 也让 react 实现响应式编程呢

如何实现数据与 Dom 的对应,我还是利用 react 自带的 setState ,很简单地,只需要修改 Proxy 的 set 方法

import {useState} from "react"

const useObserve = <T extends object>(initState: T) => {
    const [observeState, setObserveState] = useState<T>(initState)
    return new Proxy<T>(observeState, {
        set(target: T, p: string | symbol, value: any, receiver: any): boolean {
            const result: boolean = Reflect.set(target, p, value, receiver)
            if (result) {
                setObserveState({...target})
            }
            return result
        }
    })
}

export default useObserve

这是一个基础版本,如果对象是 {a: 3, b: "str"} 这样的简单对象(非嵌套对象),那我们的 hook 还是能生效的,但针对 {a: {b: 3}} 这样的嵌套对象就不行了,可能是嵌套的深层对象并不是 proxy 对象,如果打印对象,可以看到深层的对象并没有 Proxy 的标志,下图中的 {c: 5} 正是 Proxy 对象的 b 参数

Snipaste_2022-02-27_23-30-05.png

那么知道了原因,就好办了,get 的时候判断是不是对象,如果是对象,那么就再用 Proxy 包一层就够了

import { useState } from 'react';

type Target = Record<string | symbol, any>;

const useObserve = <T extends object>(initState: T) => {
  const [observeState, setObserveState] = useState<T>(initState)
  const validator = {
    set(target: Target, p: string | symbol, value: any, receiver: any): boolean {
      const result: boolean = Reflect.set(target, p, value, receiver)
      if (result) {
        setObserveState(prevObserveState => ({ ...prevObserveState }))
      }
      return result
    },
    get(target: Target, p: string | symbol, receiver: any): any {
      const value = Reflect.get(target, p, receiver)
      if (typeof value === 'object' && value !== null) {
        return new Proxy(value, validator)
      }
      return value
    }
  }
  return new Proxy<T>(observeState, validator)
}

export default useObserve;

这里把 validator 独立出来,方便对所有的对象进行重复操作,setObserveState(prevObserveState => ({ ...prevObserveState })) 是关键步骤,使用解构获取最新的对象来实现数据的更新

目前为止,这个 hook 还是很粗糙的,即使测试的高度嵌套的对象都能符合预期,个人即使在项目中还是老老实实的用 react 的机制。如果你想在 react 中使用响应式方式进行编程,有 mobx@vue/reactivity 这种经过大量测试和实践检测的库。希望这篇文章对你有帮助,也欢迎各位在评论区交流