React Context 知识回顾

React 组件中,数据传递往往是十分重要的一步,在大多数情况下我们使用prop向下传递,但当我们无需使用常见的一些状态库来处理我们的需求的时候,同时又不想逐级传递我们的状态,此时使用Context Hooks是一个很好的解决方案。

如何使用

创建全局的Context Store

ts 复制代码
import React, { createContext } from 'react'
const CenterContext = createContext({})
export default CenterContext

主要使用方式

  1. Provider 方式
ts 复制代码
  const [state, setState] = useState({
    name:'1'
  })
  <CenterContext.Provider value={{state,setState}}>
  </CenterContext.Provider>

子组件使用 useContext(CenterContext) 方式使用传递过来的store

  1. function hooks 中可以使用useContext 来使用我们的状态值
ts 复制代码
 const {state,setState} = useContext(CenterContext)

这样可以调用我们自定义的状态和方法

  1. Context.Consumer 方式消费状态和方法
ts 复制代码
import React from 'react'
import CenterContext from '../CenterContext'
export default function Test2() {
  return (
    <CenterContext.Consumer>
      {
        (value)=>{
          return (
            <div>
              {{value.name}}
            </div>
          )
        }
      }
    </CenterContext.Consumer>
  )
}

如何减少或避免重复渲染

  1. 抽离状态组件 将需要使用状态的组件通过props透传
ts 复制代码
import { FC, useState } from "react";
import CenterContext from "../CenterContext";
export const StateContext: FC<any> = ({ children }) => {
  const [state, setState] = useState({
    name: "1",
  });
  return (
    <CenterContext.Provider value={{ state, setState }}>
      {children}
    </CenterContext.Provider>
  );
};
  1. 在函数组件中,使用useMemo,useCallback,memo等hooks 来缓存状态或者函数的引用,减少不必要的状态更新和函数更新。
ts 复制代码
import { FC, memo, useCallback, useMemo, useState } from "react";
import CenterContext from "../CenterContext";
const StateContext: FC<Record<string, any>> = ({ children }) => {
  const [state, setState] = useState({
    age: 1,
  });

  const stateMe = useMemo(() => {
    return {
      age: "2",
      name: state.age,
    };
  }, [state]);

  const updateState = useCallback(() => {
    setState((state) => ({
      age: state.age + 1,
    }));
  }, []);
  return (
    <CenterContext.Provider value={{ state, stateMe, updateState }}>
      {children}
    </CenterContext.Provider>
  );
};

// export default memo(StateContext);
// 使用memo 包裹 使用状态的组件
const Greeting = memo(function Greeting({ name }) {
  console.log("Greeting was rendered at", new Date().toLocaleTimeString());
  const theme = useContext(CenterContext);
  return (
    <h3>{stateMe.age}</h3>
  );
});
  1. 将state的状态值进行合理的拆分为多个Context ,易变的数据和不变的数据进行拆分。同时使用多个Context 包裹
ts 复制代码
import React, { FC } from "react";
import CenterContext, { StateStableContext } from "../CenterContext";
export const Test2: FC<{ children: React.ReactNode }> = ({ children }) => {
  return (
    <CenterContext.Provider value={{ age: 1 }}>
      <StateStableContext.Provider value={{ name: "zhangsan" }}>
        {children
      </StateStableContext.Provider>
    </CenterContext.Provider>
  );
};

//在使用到稳定的状态的组价中使用稳定的状态 这样的话,
//当不稳定的状态发生变化时,该组件不会被重新渲染
  1. 结合useReducer 或者zustand jotai 等状态库和发布订阅模式实现组件定向更新和渲染。

Provider

ts 复制代码
const MyProvider = ({children}) => {
  const [state, dispatch] = useReducer(reducer, initState);
  
  // ref state
  const stateRef = useRef(null);
  stateRef.current = state;

  // ref 订阅回调数组
  const subscribersRef = useRef([]);

  // state 变化,遍历执行回调
  useEffect(() => {
    subscribersRef.current.forEach(sub => sub());
  }, [JSON.stringfy(state)]);

  // 缓存 value, 利用 ref 拿到最新的 state, subscribe 状态
  const value = useMemo(
    () => ({
      dispatch,
      subscribe: cb => {
        subscribersRef.current.push(cb);
        return () => {
          subscribersRef.current = subscribersRef.current.filter(item => item !== cb);
        };
      },
      getState: () => stateRef.current
    }),
    []
  )

  return <MyContext.Provider children={children} value={value} />;
}

通过自定义的Selector来拿到状态

ts 复制代码
export const useSelector = selector => {
  // 强制更新
  const [, forceRender] = useState(1);
  const store = useContext(MyContext);

  // 获取当前使用的 state
  const selectedStateRef = useRef(null)
  selectedStateRef.current = selector(store.getState());

  // 对比更新回调
  const checkForUpdates = useCallback(() => {
    // 获取变更后的 state
    const newState = selector(store.getState());
    // 对比前后两次 state
    if (!Object.is(newState, selectedStateRef.current)) forceRender(state=>state+1);
  }, [JSON.stringfy(store)]);
  
  // 订阅 state
  useEffect(() => {
    const subscription = store.subscribe(checkForUpdates);
    return () => subscription();
  }, [store, checkForUpdates]);
  
  // 返回需要的 state
  return selectedStateRef.current;
}

useDispatch

ts 复制代码
export const useDispatch = () => { const store = useContext(MyContext); return store.dispatch }

当我们使用自己定义的store来获取状态时,也可以使用useSyncExternalStore API 来订阅我们的数据, 当发生状态变更时会自动刷新我们的视图并同步更新数据。

ts 复制代码
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)

该API需要两个参数

  • subscribe 函数应当订阅该 store 并返回一个取消订阅的函数。
  • getSnapshot 函数应当从该 store 读取数据的快照。
  • 可选 getServerSnapshot:一个函数,返回 store 中数据的初始快照。它只会在服务端渲染时,以及在客户端进行服务端渲染内容的 hydration 时被用到。快照在服务端与客户端之间必须相同,它通常是从服务端序列化并传到客户端的。如果你忽略此参数,在服务端渲染这个组件会抛出一个错误。 (通常用于服务端渲染SSR)。

zustand内部也是使用了useSyncExternalStore 来实现数据的同步订阅和更新。

  1. 结合现有的npm库实现减少Context的重复渲染。use-context-selector 是社区中比较流行的使用Context 来实现状态共享和更新的库。 use-context-selector
  2. 使用Jotai 和zustand 这种比较轻量级的状态库。
相关推荐
lemon_sjdk2 分钟前
Java飞机大战小游戏(升级版)
java·前端·python
G等你下课4 分钟前
如何用 useReducer + useContext 构建全局状态管理
前端·react.js
欧阳天羲5 分钟前
AI 增强大前端数据加密与隐私保护:技术实现与合规遵
前端·人工智能·状态模式
慧一居士6 分钟前
Axios 和Express 区别对比
前端
I'mxx14 分钟前
【html常见页面布局】
前端·css·html
万少20 分钟前
云测试提前定位和解决问题 萤火故事屋 上架流程
前端·harmonyos·客户端
快起来别睡了29 分钟前
让你的React 路由不再迷路
react.js
brzhang1 小时前
OpenAI 7周发布Codex,我们的数据库迁移为何要花一年?
前端·后端·架构
军军君011 小时前
基于Springboot+UniApp+Ai实现模拟面试小工具三:后端项目基础框架搭建上
前端·vue.js·spring boot·面试·elementui·微信小程序·uni-app
布丁05231 小时前
DOM编程实例(不重要,可忽略)
前端·javascript·html