• 聊聊 React 中被低估的 useSyncExternalStore Hooks


    在 React 18 中新增加了很多 Hooks,其中包括 useSyncExternalStore(),它的作用是获取外部数据源。

    在一些状态管理库中,这个 Hooks 已经被广泛才用了。比如 Redux 内部就在使用它来实现选择器系统。

    那么我们如何在自己的代码中使用 useSyncExternalStore 呢?

    本文会演示一个例子,在这个例子中,Hooks 会触发无用的渲染。然后我会再通过 useSyncExternalStore 来避免这种无用渲染。

    Hooks 导致无用渲染

    假设我使用了 React-Router 来开发应用,其中会用到 useLocation() 这个 Hook。

    useLocation 会返回一个包含很多属性的对象,比如 pathname, hash, search 等。我们可能不会使用它的所有属性。但是当这些属性中的任意一个被更新时,只要使用该 Hooks 的组件就会重新渲染。

    示例代码如下:

    function CurrentPathname() {const { pathname } = useLocation();return 
    {pathname}
    ; } function CurrentHash() {const { hash } = useLocation();return
    {hash}
    ; } function Links() {return (
    #link1#link2#link3
    ); } function App() {return (
    ); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    当我们点击任何一个 link 标签时,hash 都会发生变化,同时 CurrentPathname 组件都会重新渲染,即使它甚至没有使用 hash 属性。

    这个现象背后的道理是:当一个 Hooks 返回的数据我们并没有用到时,React 组件仍然会重新渲染。

    如果你不注意,将 useLocation 放在 React 组件树的顶层使用,那么组件树中任意一个组件修改了 location 上面的属性,都可能会重新渲染整个组件数,对应用的性能损害极大。

    拿 useLocation 举例的目的不是说 React-Router 做得不好,而是想说明这个问题。

    尽管你现在知道了 Hooks 过度返回属性的危害,但是仍然很难保证自己写 Hooks 的时候为了便捷性而不会这样做,或者其他第三方 Hooks 库也可能过度返回属性。

    useSyncExternalStore 能否破解?

    React 官方文档中介绍了 useSyncExternalStore 的作用及用法:

    useSyncExternalStore 是一个推荐用于从外部数据源读取和订阅的 Hooks,它与选择性水合和时间切片等并发渲染功能兼容。这个 Hooks 返回 store 的值并接受三个参数:

    • subscribe: 注册回调的函数,每当 store 更改时调用该回调函数。
    • getSnapshot:返回 store 当前值的函数。
    • getServerSnapshot:返回服务器渲染期间使用的快照的函数。
    function useSyncExternalStore(subscribe: (onStoreChange: () => void) => () => void,getSnapshot: () => Snapshot,getServerSnapshot?: () => Snapshot
    ): Snapshot; 
    
    • 1
    • 2

    从描述来看,这似乎有点抽象。我相信你也没有一下子能够明白它的作用。

    React 提供了一个 beta 文档页面,其中给出了一个很好的例子:

    function subscribe(callback) {window.addEventListener("online", callback);window.addEventListener("offline", callback);return () => {window.removeEventListener("online", callback);window.removeEventListener("offline", callback);};
    }
    
    function useOnlineStatus() {return useSyncExternalStore(subscribe,() => navigator.onLine,() => true);
    }
    
    function ChatIndicator() {const isOnline = useOnlineStatus();// ...
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    有了示例代码,我们应该很容易明白了这个 Hooks 的作用了。

    开发 useHistorySelector

    现在我们利用 useSyncExternal 优化一下 useLocation。

    浏览器的 history 也可以被视为外部数据源。

    React-Router 暴露了 useSyncExternalStore 需要连接的所有属性:

    案例使用 React-Router v5:React-Router v6 的解决方案将有所不同。

    实现 useHistorySelector() 其实非常简单:

    function useHistorySelector(selector) {const history = useHistory();return useSyncExternalStore(history.listen, () =>selector(history));
    } 
    
    • 1
    • 2

    然后使用这个 Hooks 重构我们的应用。

    function CurrentPathname() {const pathname = useHistorySelector((history) => history.location.pathname);return 
    {pathname}
    ; } function CurrentHash() {const hash = useHistorySelector((history) => history.location.hash);return
    {hash}
    ; }
    • 1
    • 2
    • 3
    • 4
    • 5

    现在我们点击上面的 link 时,CurrentPathname 组件将不会重新渲染!

    另一个例子:scrollY

    我们可以订阅很多外部数据源,在上面实现自己的选择器系统。这样可以最大程度上优化 React 的重新渲染。

    假设我们要使用 scrollY 来获取页面的位置。我们可以实现这个自定义的 Hooks:

    function subscribe(onStoreChange) {global.window?.addEventListener("scroll", onStoreChange);return () =>global.window?.removeEventListener("scroll",onStoreChange);
    }
    
    function useScrollY(selector = (id) => id) {return useSyncExternalStore(subscribe,() => selector(global.window?.scrollY),() => undefined);
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    现在可以把这个 Hooks 和选择器一起使用:

    function ScrollY() {const scrollY = useScrollY();return 
    {scrollY}
    ; } function ScrollYFloored() {const to = 100;const scrollYFloored = useScrollY((y) =>y ? Math.floor(y / to) * to : undefined);return
    {scrollYFloored}
    ; }
    • 1
    • 2
    • 3
    • 4
    • 5

    当我们滚动页面时,ScrollYFloored 组件会比 ScrollY 组件重新渲染的次数更少!

    总结

    我个人感觉 useSyncExternalStore 这个 Hooks 目前在 React 生态系统中没有被充分使用,但它值得更多关注。我们完全可以订阅许多外部的数据源来改善应用性能。

    如果你还没有升级到 React 18,npm 上有一个 shim:use-sync-external-store。你可以在旧版本的 React 中使用它。

    \

    最后

    为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



    有需要的小伙伴,可以点击下方卡片领取,无偿分享

  • 相关阅读:
    设计模式之原型模式--超越实例化的魔法,从复制到创造的无限可能
    【阿旭机器学习实战】【18】KMeans聚类中的常见问题
    Linux C/C++ 学习笔记(八):实现http客户端请求
    性能提升 25 倍:Rust 有望取代 C 和 C++,成为机器学习首选 Python 后端
    你了解Polysciences PEI 25K转染试剂吗?
    集合划分,将集合S划分成k个子集合,每个子集合互不相交且不为空集,所有子集合加在一起是S
    使用mshta和csv注入配合获得主机权限
    如何用 GPTs 帮你写科研项目申请书?
    k8s--storageClass自动创建PV
    降低模拟量信号干扰的10个有效方法
  • 原文地址:https://blog.csdn.net/weixin_53312997/article/details/127768211