Published on

useReactive

Authors
  • avatar
    Name
    李丹秋
    Twitter
提供一种数据响应式的操作体验,定义数据状态不需要写 useState,直接修改属性即可刷新视图。
### 示例
``` TSX
import React from 'react';
import { useReactive } from 'front-base-hooks';

export default () => {
    const state = useReactive({
        count: 0,
        inputVal: '',
        obj: {
            value: '',
        },
    });

    return (
        <div>
            <p> state.count:{state.count}</p>

            <button style={{ marginRight: 8 }} onClick={() => state.count++}>
                state.count++
            </button>
            <button onClick={() => state.count--}>state.count--</button>

            <p style={{ marginTop: 20 }}> state.inputVal: {state.inputVal}</p>
            <input onChange={(e) => (state.inputVal = e.target.value)} />

            <p style={{ marginTop: 20 }}> state.obj.value: {state.obj.value}</p>
            <input onChange={(e) => (state.obj.value = e.target.value)} />
        </div>
    );
};

源码

import isPlainObject from 'lodash/isPlainObject';
import { useMemo, useRef } from 'react';
import useUpdate from '../useUpdate';

const proxyMap = new WeakMap();
const rawMap = new WeakMap();

function observer<T extends Record<string, any>>(initialValue: T, cb: () => void): T {
    const existingProxy = proxyMap.get(initialValue);

    if (existingProxy) {
        return existingProxy;
    }

    // 防止代理已经代理过的对象
    // https://github.com/alibaba/hooks/issues/839
    if (rawMap.has(initialValue)) {
        return initialValue;
    }

    const proxy = new Proxy<T>(initialValue, {
        get(target, key, receiver) {
            const res = Reflect.get(target, key, receiver);

            // Only proxy plain object or array,
            // otherwise it will cause: https://github.com/alibaba/hooks/issues/2080
            // 如果是对象或者数组,就递归代理
            return isPlainObject(res) || Array.isArray(res) ? observer(res, cb) : res;
        },
        set(target, key, val) {
            const ret = Reflect.set(target, key, val);
            // 更新ref的值,并且触发重新渲染
            cb();
            return ret;
        },
        deleteProperty(target, key) {
            const ret = Reflect.deleteProperty(target, key);
            cb();
            return ret;
        },
    });

    proxyMap.set(initialValue, proxy);
    rawMap.set(proxy, initialValue);

    return proxy;
}

function useReactive<S extends Record<string, any>>(initialState: S): S {
    const update = useUpdate();
    const stateRef = useRef<S>(initialState);
    // 使用一个ref去存储值,后续set也是执行ref的修改

    const state = useMemo(() => {
        return observer(stateRef.current, () => {
            update();
        });
    }, []);

    return state;
}

export default useReactive;

整个代码理解起来还是比较容易,就是方式了使用setState这种方式,而是通过代理的方式去监听initState(用ref存储)的变化,然后出发update(useUpdate前面代码有分析过)逻辑,有点类似于vue3的方式,通过代理完成响应式