likes
comments
collection
share

React Context 知识回顾

作者站长头像
站长
· 阅读数 1

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

如何使用

创建全局的Context Store

import React, { createContext } from 'react'
const CenterContext = createContext({})
export default CenterContext

主要使用方式

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

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

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

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

  1. Context.Consumer 方式消费状态和方法
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透传
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 来缓存状态或者函数的引用,减少不必要的状态更新和函数更新。
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 包裹
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

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来拿到状态

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

export const useDispatch = () => { const store = useContext(MyContext); return store.dispatch }

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

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 这种比较轻量级的状态库。