[React]实现一个类zustand公共状态库

前言

最新在'深入'学习react reRender机制时发现除了基本的setState触发reRender(props本质上也是setState后的reRender),还有第三方库比如各大百花齐放神魔乱舞的公共状态库也能通知到react去reRender对应的ui。

于是在好奇心的驱使下去查看现代的公共状态库是怎么能不触发setState还可以通知react去更新页面,最终我发现了这个被我们大多数人忽略的hooks: useSyncExternalStore

通过阅读文档可以知道

useSyncExternalStore 是一个让你订阅外部 store 的 React Hook。

具体api使用方法之类的可以自行去看文档,简单来说就是:第三方库变化的时候能通知react告知它该更新页面了。

之前的redux之类的库实现应该是用的context,通过setState来触发reRender从而达到更新ui的目的,所以这类库都需要通过Provider来通知组件更新,而react 18新增的useSyncExternalStore 则可以通过更优雅的方式来达到这个效果,所以这也是react18以来这么多公共状态库出来的原因吧,百花齐放的时代。

能收获什么?

阅读本篇并不能让你手搓一个类似zustand这样的库出来,只是以最简洁的方式展现zustand的基础功能的实现。

开始

声明一个createStore函数,这个函数入参是initFn。同zustand的create形参,它是一个函数,接收一个set,

这个set用于更新createStore的state数据。对照zustand create createStore返回的是一个useStore对象,所以他们的类型应该是:

typescript 复制代码
// 定义状态更新函数类型
type SetState<T> = (upater: Partial<T> | ((prev: T) => Partial<T>)) => void;
// createStreo形参类型:接收set 返回初始状态
type CreateStore<T> = (set: SetState<T>) => T;
// createStore返回的hooks类型
type UseStore<T> = <U>(selector: (state: T) => U) => U;

初始化initFn获取创建store的数据,创建一个Set数据用于触发set时通知react;

typescript 复制代码
export function createStore<T>(initFn: CreateStore<T>): UseStore<T> {
	let state: T;
	const listeners = new Set<() => void>();

	const set: SetState<T> = (updater) => {
		const nextState = typeof updater === 'function' ? updater(state) : updater;
		state = {...state, ...nextState};
		listeners.forEach(l => l());
	}

	state = initFn(set);

	const useStore: UseStore<T> = (selector) => {
		const getSnapshot = () => selector(state);
		const subscribe = (callback: () => void) => {
			listeners.add(callback);
			return () => listeners.delete(callback);
		};
		
		return useSyncExternalStore(subscribe, getSnapshot);
	}
	return useStore;
}

以上就实现了zustand的基本功能,如何使用呢?

按照规范肯定是新建一个store用于保存公共状态了,新建一个useCounterStore来替代vite模板里的count setCount

typescript 复制代码
import { createStore } from '../libs/createStore';

export const useCounterStore = createStore<{
  count: number;
  inc: () => void;
}>((set) => {
  return {
    count: 0,
    inc: () => set((s) => ({ count: s.count + 1 }))
  }
})

ps:这里有没有大佬告诉我怎么来写ts让它自然推到类型而不是我手动写泛型的类型啊。

在App页面引入这个状态

typescript 复制代码
import { useCounterStore } from './store'
...

function App() {
	const count = useCounterStore((s) => s.count);
  	const setCount = useCounterStore(s => s.inc)
}

...

		<button onClick={() => setCount()}>
          count is {count}
        </button>

此时触发这个button click点击事件就能发现count能被正确更新了,还可以自己新建几个路由页面然后使用count状态,在切换路由时自行观察。

相关推荐
jktantan4 小时前
Vue keep-alive 切标签后滚动位置回顶?因为 onDeactivated 里读到的 scrollTop 是 0
前端·javascript·vue.js
2601_962885725 小时前
quotes 字段全解:涨跌幅、振幅、换手率的口径与单位
java·前端·spring
excel5 小时前
encodeURI 和 encodeURIComponent 有什么区别?
前端
前端精髓6 小时前
调用openrouter的免费API模型
java·前端·javascript
赛博守夜人6 小时前
AI安全攻防与治理之十四:AI 安全测试不能脱离传统安全,Web、API、移动端与模型攻击面的组合拳
前端·人工智能·安全
开开心心_Every6 小时前
快速隐藏程序工具自定义快捷键一键切换
java·开发语言·前端·spring·智能手机·word·intellij-idea
networknomad6 小时前
OpenReplay:自己搭的会话回放工具,给前端排错用的
前端·其他
缓慢更新7 小时前
AI 辅助生成单测:从接口定义到用例的流水线
java·服务器·前端·网络·数据库·人工智能
陈随易8 小时前
AI 运维服务器,安全又可靠的方法
前端·后端·程序员
风骏时光牛马8 小时前
AI稳定性治理体系构建与疑难问题深度分析
前端