《手写 mini React》Recoil原理

239 阅读1分钟

我们通过定义 atomselector 函数来创建状态。使用 useRecoilStateuseRecoilValue Hooks 来获取和更新状态值。我们还定义了一个简化的 RecoilRoot 组件,但它并没有执行真正的订阅和状态传递,而是演示了它的概念。

// 实现一个简化的 Recoil

// 定义一个存储状态的对象
const stateMap = new Map();

// 定义一个订阅状态变化的对象
const subscriptions = new Map();

// 定义一个生成唯一 key 的函数
function generateKey() {
  return Math.random().toString(36).substring(7);
}

// 定义一个 atom 函数,用于创建原子状态
function atom(initialValue) {
  const key = generateKey();

  // 将初始值存储到 stateMap
  stateMap.set(key, initialValue);

  return {
    key,
  };
}

// 定义一个 selector 函数,用于创建可计算状态
function selector(options) {
  const key = generateKey();

  return {
    key,
    ...options,
  };
}

// 定义一个 useRecoilState Hook,用于获取和设置原子状态的值
function useRecoilState(atom) {
  const key = atom.key;

  const [, forceUpdate] = React.useReducer(x => x + 1, 0);

  const setState = newValue => {
    stateMap.set(key, newValue);
    subscriptions.get(key).forEach(callback => callback());
  };

  React.useEffect(() => {
    if (!subscriptions.has(key)) {
      subscriptions.set(key, new Set());
    }

    subscriptions.get(key).add(forceUpdate);

    return () => {
      subscriptions.get(key).delete(forceUpdate);
    };
  }, [key]);

  return [stateMap.get(key), setState];
}

// 定义一个 useRecoilValue Hook,用于获取原子状态的值
function useRecoilValue(atom) {
  const key = atom.key;
  return stateMap.get(key);
}

// 创建一个简化版的 RecoilRoot,用于提供状态和订阅
function RecoilRoot({ children }) {
  return children;
}

// 使用示例
function App() {
  const countState = atom(0);
  const doubledCountState = selector({
    get: ({ get }) => {
      const count = get(countState);
      return count * 2;
    },
  });

  return (
    <RecoilRoot>
      <Counter countState={countState} />
      <Display doubledCountState={doubledCountState} />
    </RecoilRoot>
  );
}

function Counter({ countState }) {
  const [count, setCount] = useRecoilState(countState);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

function Display({ doubledCountState }) {
  const doubledCount = useRecoilValue(doubledCountState);

  return <p>Doubled Count: {doubledCount}</p>;
}