源码学习——手写 zustand

项目搭建

cra 创建 react 项目

npx create-react-app my-zustand

运行

npm start

安装 zustand

npm i zustand

使用 zustand

// App.js
import { create } from 'zustand';

const useUserStore = create((set) => ({
  firstName: '',
  lastName: '',
  updateFirstName: (firstName) => set(() => ({ firstName })),
  updateLastName: (lastName) => set(() => ({ lastName })),
}));

function App() {
  const { firstName, updateFirstName } = useUserStore((state) => state);
  return (
    <div
      style={{
        height: '100vh',
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
      }}
    >
      <div style={{ marginRight: '10px' }}>
        <div>firstName: {firstName}</div>
        <input onChange={(e) => updateFirstName(e.currentTarget.value)} />
      </div>

      <LastName />
    </div>
  );
}

function LastName() {
  const { lastName, updateLastName } = useUserStore((state) => state);
  return (
    <div>
      <div>lastName: {lastName}</div>
      <input onChange={(e) => updateLastName(e.currentTarget.value)} />
    </div>
  );
}

export default App;

[lightbox title=""][/lightbox]

实现 zustand

核心原理:

  1. 基于闭包保存全局状态
  2. 基于发布订阅模式实现响应式
// my-zustand.js
const createStore = (createState) => {
  let state;
  const listeners = new Set();

  const setState = (partial) => {
    const nextState = typeof partial === 'function' ? partial(state) : partial;

    if (!Object.is(nextState, state)) {
      const previousState = state;
      state = Object.assign({}, state, nextState);
      listeners.forEach((listener) => listener(state, previousState));
    }
  };

  const getState = () => state;

  const subscribe = (listener) => {
    listeners.add(listener);
    return () => listeners.delete(listener);
  };

  const destory = () => {
    listeners.clear();
  };

  const api = { setState, getState, subscribe, destory };

  state = createState(setState, getState, api);

  return api;
};

状态变了如何触发页面渲染?使用useState

// 模拟实现,有漏洞,仅用于表达思想
const useStore = (api, selector) => {
  const [, render] = useState(0);
  useEffect(() => {
    api.subscribe((state, previousState) => {
      const newState = selector(state);
      const oldState = selector(previousState);
      if (newState !== oldState) {
        render(Math.random());
      }
    });
  }, []);

  return selector(api.getState());
};
export const create = (createState) => {
  const api = createStore(createState);
  return (selector) => useStore(api, selector);
};

替换 create 函数后运行,功能正常

// import { create } from 'zustand'; ---
import { create } from './my-zustand'; // +++

使用 useSyncExternalStore 优化

事实上,react 提供了一个 hook,用来订阅外部 store,store 变化以后会触发 rerender

[lightbox title=""][/lightbox]

重构 useStore

const useStore = (api, selector) =>
  useSyncExternalStore(api.subscribe, () => selector(api.getState()));

运行后功能正常

最终源码

// my-zustand.js
import { useSyncExternalStore } from 'react';
const createStore = (createState) => {
  let state;
  const listeners = new Set();

  const setState = (partial) => {
    const nextState = typeof partial === 'function' ? partial(state) : partial;

    if (!Object.is(nextState, state)) {
      const previousState = state;
      state = Object.assign({}, state, nextState);
      listeners.forEach((listener) => listener(state, previousState));
    }
  };

  const getState = () => state;

  const subscribe = (listener) => {
    listeners.add(listener);
    return () => listeners.delete(listener);
  };

  const destory = () => {
    listeners.clear();
  };

  const api = { setState, getState, subscribe, destory };

  state = createState(setState, getState, api);

  return api;
};

const useStore = (api, selector) =>
  useSyncExternalStore(api.subscribe, () => selector(api.getState()));

export const create = (createState) => {
  const api = createStore(createState);
  return (selector) => useStore(api, selector);
};

 

上一篇 藏在罐子里的爱
下一篇 Redis实现几十万数据缓存的神奇之处(redis缓存几十万数据)
太行听风

太行听风管理员

“我”在河南,心在“你”附近

本月创作热力图

文章目录
随机文章
1 一行命令搭建临时文件服务器:5 种语言实现的本地文件共享方案(Python/Node.js/Go/Rust/PHP)
一行命令搭建临时文件服务器:5 种语言实现的本地文件共享方案(Python/Node.js/Go/Rust/PHP)
2
腾讯云1024限时活动:免费领取一个月轻量
腾讯云1024限时活动:免费领取一个月轻量
3
为什么启用了 WP Super Cache 动态缓存,小工具却全局不显示?
为什么启用了 WP Super Cache 动态缓存,小工具却全局不显示?
4
推荐一个好用的WordPress SMTP插件
推荐一个好用的WordPress SMTP插件
5
阿里云ECS服务器2G内存实际只有1.7G问题解决指南
阿里云ECS服务器2G内存实际只有1.7G问题解决指南
站长声明

本站部分内容转载自网络,作品版权归原作者及来源网站所有,任何内容转载、商业用途等均须联系原作者并注明来源。