✋🏻 基础使用
在这里我们先看看如何使用 useSyncExternalStore
这个 新的 API 。之后我们会尝试用这个 API 写一个简单的状态管理器。
当你看完了官方文档之后,让我们来写一个 useScrollY Hook,用于了解 useSyncExternalStore
的使用方法。那我们就先使用 useState、 useEffect
来写这个简单的例子,之后我们使用 useSyncExternalStore
进行替换。
例如👇 示例:
function useScrollY() {
const [state, setState] = useState(0);
const onScroll = () => {
setState(window.scrollY);
};
useEffect(() => {
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, []);
return state;
}
上面的例子十分的简单,我相信你已经看懂了代码,我们使用了 useState 、 useEffect
就实现了这个 Hook。但是如何使用 useSyncExternalStore
这个 API 呢?
请看 👇🏻 例子:
function subscribe(onStateChange) {
window.addEventListener("scroll", onStateChange);
return () => window.removeEventListener("scroll", onStateChange);
}
function useScrollY(selector) {
return useSyncExternalStore(subscribe, () => selector(window.scrollY));
}
首先我们需要一个 subscribe 函数,这函数的主要作用就是用于注册一个回调函数,当存储值发生更改时被调用。接下来我们就需要返回当前的值的函数,也就是第二个参数了。
这样我们就使用 useSyncExternalStore
这个新的 API 实现了这个 Hook。
🔗 在线示例
✋🏻 全局状态管理
经过👆🏻简单的应用,接下来我们开始实现一个简单的状态管理工具。我们可以先使用 useState 、 useEffect
来实现一个简单的全局状态管理,也涉及到了一点点的优化知识,最后我们可以使用 useSyncExternalStore
这个新的 API 进行替换。这样也可以让你循序渐进的理解这个新的 API。
我们先创建一个 store.ts
文件,用于编写创建 Store 的代码。
基本的示例代码如下:
import { useEffect, useState } from "react";
function createStore(initStore: { count1: number; count2: number }) {
let state = initStore;
const listeners = new Set<
(state: { count1: number; count2: number }) => void
>();
return {
getState() {
return state;
},
setState(newState: { count1: number; count2: number }) {
state = newState;
listeners.forEach((listener) => listener(newState));
},
subscribe(listener: (state: { count1: number; count2: number }) => void) {
listeners.add(listener);
return () => listeners.delete(listener);
},
};
}
const store = createStore({
count1: 0,
count2: 0,
});
const useStore = () => {
const [state, setState] = useState(store.getState());
useEffect(() => {
store.subscribe(setState);
}, []);
return state;
};
上面的代码包含了 createStore
、useStore
方法,一个是创建 Store 的方法,后者则是使用 Store 的 Hook 。
接下来就是如何使用这个简单的状态管理。例如下面的代码:
// App.tsx
import { store, useStore } from "./store";
function DisplayCounter({ field }: { field: "count1" | "count2" }) {
const counter = useStore();
return <div>{counter[field]}</div>;
}
function HandleCounter({ field }: { field: "count1" | "count2" }) {
const counter = useStore();
return (
<button
onClick={() => {
const count = counter[field] + 1;
store.setState({ ...counter, [field]: count });
}}
>
+{field}
</button>
);
}
function App() {
return (
<div className="App">
<DisplayCounter field="count1" />
<HandleCounter field="count1" />
<br />
<DisplayCounter field="count2" />
<HandleCounter field="count2" />
</div>
);
}
这样我们就实现了一个简单的状态管理。
点击该示例查看详情 👉🏻 🔗 在线示例
但是这一版代码是存在重复渲染的问题的。例如下面的动图 👇🏻:
我们都知道
如何优化全局状态与组件内部状态之间的连接,对于提高 React 组件性能有着至关重要的作用。 因此这个重复渲染的问题我们必须要修复。
分析代码我们可以知道造成如上图的重复渲染的原因是,我们 store 中的 setState 方法,每次值的引用都在变动,所以会造成页面的重复渲染。
那我们需要怎么优化呢?很简单,我们只需要选择我们自己需要的值进行渲染就可以了,那应该怎么做呢?我们只需要给 useStore
这个 Hook 传递一个 selector 函数,选择我们需要的状态值就可以了。代码如下所示:
const useStore = (
selector: (state: { count1: number; count2: number }) => number
) => {
const [state, setState] = useState(selector(store.getState()));
useEffect(() => {
store.subscribe((state) => setState(selector(state)));
}, []);
return state;
};
改造完毕之后我们再修改使用的代码。例如:
function DisplayCounter({ field }: { field: "count1" | "count2" }) {
const counter = useStore((store) => store[field]);
return <div>{counter}</div>;
}
function HandleCounter({ field }: { field: "count1" | "count2" }) {
const counter = useStore((store) => store[field]);
return (
<button
onClick={() => {
const count = counter + 1;
store.setState({ ...store.getState(), [field]: count });
}}
>
+{field}
</button>
);
}
点击示例查看详情 👉🏻🔗 在线示例
我们已经理解了这个全局状态管理的原理,接下来我们就看看如何使用 useSyncExternalStore
对其进行替换。其实和上面的 useScrollY Hook 一样,我们只需要改造 useStore 这个 Hook 就可以了。
例如 👇 的代码所示:
const useStore = (
selector: (state: { count1: number; count2: number }) => number
) => {
return useSyncExternalStore(store.subscribe, () =>
selector(store.getState())
);
};
点击示例查看详情 👉 🔗 在线示例
希望本文可以让你重视 useSyncExternalStore
这个 API。
如果您还有什么没有理解的地方可以在下方留言告诉我哦!😁!
👉👉👉👉👉 🔗 源码
👏 往期精彩
- 👏React 性能优化实战
- 👏简单设计一个 Form 表单!
- 👏React 组件优化第二弹来袭!!
- 👏这些避免React组件重复渲染的手段你都知道吗?
- 👏项目引入ESBuild,编译直接快上天!!
- 👏在React中使用WebComponents组件的最佳实践
- 👏我是怎样将50+MB的app打包文件优化为4.2MB的?
- 👏styleds-components 的原理你能讲一下吗?
- 👏面试官:你能讲一下extends和寄生式组合继承原型之间的区别?
- 👏前端Leader让我给同事讲讲事件循环
- 👏React与Vue状态更新原理对比
- 👏当React遇到树形穿梭框咋办?
- 👏使用React时要避免的 10 大错误
- 👏webpack打包优化方向指南(理论篇)
- 👏vue遇到拖拽动态生成组件怎么办?
- 👏JS Coding 技巧,大多数人都不会!!
- 👏TypeScript 它不香吗?还不快来!