【React Hooks原理 - useSyncExternalStore】

概述

在React项目中说到状态管理,我们第一时间想到的就是使用useState、useReducer这种Hooks来进行状态管理。但是这种是针对React内部的状态,如果有时候我们需要订阅外部的状态并影响React组件的更新的话,那通过这种内部状态管理API显然不能满足了,这时候就需要使用本文的主角useSyncExternalStore这个Hook了。这个API是React18提供的一个内置API,赋予了React能订阅外部状态的能力,当订阅的外部状态发生改变时,会触发React的重新渲染。本文将会从一下几个方面来逐步介绍该API的使用和原理,有需求的同学自行跳转至感兴趣的部分。

  • 基础使用
  • 源码解析
    • Mount首次渲染时
    • Update 更新渲染时

基础使用

函数定义

先看定义,useSyncExternalStore接受三个参数:subscribegetSnapshotgetServerSnapshot然后返回一个数据快照。

export function useSyncExternalStore<T>(
  subscribe: (() => void) => () => void,
  getSnapshot: () => T,
  getServerSnapshot?: () => T,
): T {
   
  const dispatcher = resolveDispatcher();
  return dispatcher.useSyncExternalStore(
    subscribe,
    getSnapshot,
    getServerSnapshot,
  );
}

详细说明如下:

  • subscribe: 接收一个订阅函数,并返回一个取消订阅函数,该函数主要用于订阅外部store状态,订阅外部store的哪些状态取决于subscribe函数的实现。当订阅的状态值变化时,会触发组件的重现渲染。
  • getSnapshot:一个用于获取当前状态快照的函数,React组件每次渲染都会执行该函数,然后获得当前订阅状态的快照,并将这个快照作为useSyncExternalStore函数返回值
  • getServerSnapshot: 返回订阅状态的初始快照,它只会在服务端渲染(SSR)时,以及在客户端(浏览器端)进行服务端渲染内容的 hydration 时被用到。
  • 返回值是一个数据快照,可以理解为就是getSnapshot的执行结果

PS: 由于getSnapshot会在每次组件渲染时都会执行,所以一般将这个函数定义在组件外部或者使用useCallback包裹,避免不必要的渲染。

想了解为什么使用useCallback包裹能避免重复渲染的可以查看这篇文章:【React Hooks原理 - useCallback、useMemo

由于服务端渲染的是静态页面,不能进行动态交互,所以在React项目中会通过hydration(水合阶段),将其赋予动态交互能力,具体流程本文最后题外话会简单介绍。

语法使用

上面主要介绍了useSyncExternalStore函数定义,这小节主要通过举例Demo的形式让我们初步了解该函数的运用,并方便下面源码解析的理解。(由于第三个参数getServerSnapshot用于SSR,所以本文不做介绍,有兴趣的同学可以查看其他文章了解)

外部第三方Store定义:

  • 这里要注意subscribegetSnapshot这两个函数会在useSyncExternalStoreHook中调用并注入React自己的listener函数,所以这两个函数必须在第三方Store中暴露,否则不能正确使用useSyncExternalStore功能。
const store = {
   
  // 外部状态
  state: {
   
    todos: [],
    user: {
    name: 'Alice', age: 30 }
  },
  // 监听状态变化
  listeners: new Set(),
  // 订阅状态函数
  subscribe(listener) {
   
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  },
  // 获取快照函数
  getSnapshot() {
   
    return this.state;
  },
  // 状态更新后,通知所有注入的监听器并执行
  notify() {
   
    this.listeners.forEach(listener => listener());
  },
  // 更新todos状态
  updateTodos(newTodos) {
   
    this.state.todos = newTodos;
    this.notify();
  },
  // 更新user状态
  updateUser(newUser) {
   
    this.state.user = newUser;
    this.notify();
  }
};

React项目代码:

import React from 'react';
import {
    useSyncExternalStore } from 'react';

const todosStore = {
   
  subscribe(listener) {
   
    return store.subscribe(() => {
   
      const {
    todos } = store.getSnapshot();
      listener(todos);
    });
  },
  getSnapshot() {
   
    const {
    todos } = store.getSnapshot();
    return todos;
  }
};

const TodosComponent = () => {
   
  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值