React Context 知识回顾
React 组件中,数据传递往往是十分重要的一步,在大多数情况下我们使用prop向下传递,但当我们无需使用常见的一些状态库来处理我们的需求的时候,同时又不想逐级传递我们的状态,此时使用Context Hooks是一个很好的解决方案。
如何使用
创建全局的Context Store
import React, { createContext } from 'react'
const CenterContext = createContext({})
export default CenterContext
主要使用方式
- Provider 方式
const [state, setState] = useState({
name:'1'
})
<CenterContext.Provider value={{state,setState}}>
</CenterContext.Provider>
子组件使用 useContext(CenterContext) 方式使用传递过来的store
- function hooks 中可以使用useContext 来使用我们的状态值
const {state,setState} = useContext(CenterContext)
这样可以调用我们自定义的状态和方法
- 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>
)
}
如何减少或避免重复渲染
- 抽离状态组件 将需要使用状态的组件通过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>
);
};
- 在函数组件中,使用
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>
);
});
- 将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>
);
};
//在使用到稳定的状态的组价中使用稳定的状态 这样的话,
//当不稳定的状态发生变化时,该组件不会被重新渲染
- 结合
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 来实现数据的同步订阅和更新。
- 结合现有的npm库实现减少Context的重复渲染。
use-context-selector
是社区中比较流行的使用Context 来实现状态共享和更新的库。 use-context-selector。 - 使用Jotai 和zustand 这种比较轻量级的状态库。
转载自:https://juejin.cn/post/7356889912127504384