Context与Reducer
Context与Reducer
Context
是React
提供的一种跨组件的通信方案,useContext
与useReducer
是在React 16.8
之后提供的Hooks API
,我们可以通过useContext
与useReducer
来完成全局状态管理例如Redux
的轻量级替代方案。
useContext
React Context
适用于父子组件以及隔代组件通信,React Context
提供了一个无需为每层组件手动添加props
就能在组件树间进行数据传递的方法。一般情况下在React
应用中数据是通过props
属性自上而下即由父及子进行传递的,而一旦需要传递的层次过多,那么便会特别麻烦,例如主题配置theme
、地区配置locale
等等。Context
提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递props
。例如React-Router
就是使用这种方式传递数据,这也解释了为什么<Router>
要在所有<Route
>的外面。
当然在这里我们还是要额外讨论下是不是需要使用Context
,使用Context
可能会带来一些性能问题,因为当Context
数据更新时,会导致所有消费Context
的组件以及子组件树中的所有组件都发生re-render
。那么,如果我们需要类似于多层嵌套的结构,应该去如何处理,一种方法是我们直接在当前组件使用已经准备好的props
渲染好组件,再直接将组件传递下去。
export const Page: React.FC<{
user: { name: string; avatar: string };
}> = props => {
const user = props.user;
const Header = (
<>
<span>
<img src={user.avatar}></img>
<span>{user.name}</span>
</span>
<span>...</span>
</>
);
const Body = <></>;
const Footer = <></>;
return (
<>
<Component header={Header} body={Body} footer={Footer}></Component>
</>
);
};
这种对组件的控制反转减少了在应用中要传递的props
数量,这在很多场景下可以使得代码更加干净,使得根组件可以有更多的把控。但是这并不适用于每一个场景,这种将逻辑提升到组件树的更高层次来处理,会使得这些高层组件变得更复杂,并且会强行将低层组件适应这样的形式,这可能不会是你想要的。这样的话,就需要考虑使用Context
了。
说回Context
,Context
提供了类似于服务提供者与消费者模型,在通过React.createContext
创建Context
后,可以通过Context.Provider
来提供数据,最后通过Context.Consumer
来消费数据。在React 16.8
之后,React
提供了useContext
来消费Context
,useContext
接收一个Context
对象并返回该Context
的当前值。
// https://codesandbox.io/s/react-context-reucer-q1ujix?file=/src/store/context.tsx
import React, { createContext } from "react";
export interface ContextProps {
state: {
count: number;
};
}
const defaultContext: ContextProps = {
state: {
count: 1
}
};
export const AppContext = createContext<ContextProps>(defaultContext);
export const AppProvider: React.FC = (props) => {
const { children } = props;
return (
<AppContext.Provider value={defaultContext}>{children}</AppContext.Provider>
);
};
// https://codesandbox.io/s/react-context-reucer-q1ujix?file=/src/App.tsx
import React, { useContext } from "react";
import { AppContext, AppProvider } from "./store/context";
interface Props {}
const Children: React.FC = () => {
const context = useContext(AppContext);
return <div>{context.state.count}</div>;
};
const App: React.FC<Props> = () => {
return (
<AppProvider>
<Children />
</AppProvider>
);
};
export default App;
useReducer
useReducer
是useState
的替代方案,类似于Redux
的使用方法,其接收一个形如(state, action) => newState
的reducer
,并返回当前的state
以及与其配套的dispatch
方法。
const initialState = { count: 0 };
type State = typeof initialState;
const ACTION = {
INCREMENT: "INCREMENT" as const,
SET: "SET" as const,
};
type IncrementAction = {
type: typeof ACTION.INCREMENT;
};
type SetAction = {
type: typeof ACTION.SET;
payload: number;
};
type Action = IncrementAction | SetAction;
function reducer(state: State, action: Action) {
switch (action.type) {
case ACTION.INCREMENT:
return { count: state.count + 1 };
case ACTION.SET:
return { count: action.payload };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<div>
<button onClick={() => dispatch({ type: ACTION.INCREMENT })}>INCREMENT</button>
<button onClick={() => dispatch({ type: ACTION.SET, payload: 10 })}>SET 10</button>
</div>
</>
);
}
或者我们也可以相对简单地去使用useReducer
,例如实现一个useForceUpdate
,当然使用useState
实现也是可以的。
function useForceUpdate() {
const [, forceUpdateByUseReducer] = useReducer<(x: number) => number>(x => x + 1, 0);
const [, forceUpdateByUseState] = useState<Record<string, unknown>>({});
return { forceUpdateByUseReducer, forceUpdateByUseState: () => forceUpdateByUseState({}) };
}
useContext + useReducer
对于状态管理工具而言,我们需要的最基础的就是状态获取与状态更新,并且能够在状态更新的时候更新视图,那么useContext
与useReducer
的组合则完全可以实现这个功能,也就是说,我们可以使用useContext
与useReducer
来实现一个轻量级的redux
。
// https://codesandbox.io/s/react-context-reducer-q1ujix?file=/src/store/reducer.ts
export const initialState = { count: 0 };
type State = typeof initialState;
export const ACTION = {
INCREMENT: "INCREMENT" as const,
SET: "SET" as const
};
type IncrementAction = {
type: typeof ACTION.INCREMENT;
};
type SetAction = {
type: typeof ACTION.SET;
payload: number;
};
export type Action = IncrementAction | SetAction;
export const reducer = (state: State, action: Action) => {
switch (action.type) {
case ACTION.INCREMENT:
return { count: state.count + 1 };
case ACTION.SET:
return { count: action.payload };
default:
throw new Error();
}
};
// https://codesandbox.io/s/react-context-reducer-q1ujix?file=/src/store/context.tsx
import React, { createContext, Dispatch, useReducer } from "react";
import { reducer, initialState, Action } from "./reducer";
export interface ContextProps {
state: {
count: number;
};
dispatch: Dispatch<Action>;
}
const defaultContext: ContextProps = {
state: {
count: 1
},
dispatch: () => void 0
};
export const AppContext = createContext<ContextProps>(defaultContext);
export const AppProvider: React.FC = (props) => {
const { children } = props;
const [state, dispatch] = useReducer(reducer, initialState);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
};
// https://codesandbox.io/s/react-context-reducer-q1ujix?file=/src/App.tsx
import React, { useContext } from "react";
import { AppContext, AppProvider } from "./store/context";
import { ACTION } from "./store/reducer";
interface Props {}
const Children: React.FC = () => {
const { state, dispatch } = useContext(AppContext);
return (
<>
Count: {state.count}
<div>
<button onClick={() => dispatch({ type: ACTION.INCREMENT })}>
INCREMENT
</button>
<button onClick={() => dispatch({ type: ACTION.SET, payload: 10 })}>
SET 10
</button>
</div>
</>
);
};
const App: React.FC<Props> = () => {
return (
<AppProvider>
<Children />
</AppProvider>
);
};
export default App;
我们直接使用Context
与Reducer
来完成状态管理是具有一定优势的,例如这种实现方式比较轻量,且不需要引入第三方库等。当然,也有一定的问题需要去解决,当数据变更时,所有消费Context
的组件都会需要去渲染,当然React
本身就是以多次的re-render
来完成的Virtual DOM
比较由此进行视图更新,在不出现性能问题的情况下这个优化空间并不是很明显,对于这个问题我们也有一定的优化策略:
- 可以完成或者直接使用类似于
useContextSelector
代替useContext
来尽量避免不必要的re-render
,这方面在Redux
中使用还是比较多的。 - 可以使用
React.memo
或者useMemo
的方案来避免不必要的re-render
,通过配合useImmerReducer
也可以在一定程度上缓解re-render
问题。 - 对于不同上下文背景的
Context
进行拆分,用来做到组件按需选用订阅自己的Context
。此外除了层级式按使用场景拆分Context
,一个最佳实践是将多变的和不变的Context
分开,让不变的Context
在外层,多变的Context
在内层。
此外,虽然我们可以直接使用Context
与Reducer
来完成基本的状态管理,我们依然也有着必须使用redux
的理由:
redux
拥有useSelector
这个Hooks
来精确定位组件中的状态变量,来实现按需更新。redux
拥有独立的redux-devtools
工具来进行状态的调试,拥有可视化的状态跟踪、时间回溯等功能。redux
拥有丰富的中间件,例如使用redux-thunk
来进行异步操作,redux-toolkit
官方的工具集等。
每日一题
https://github.com/WindrunnerMax/EveryDay
参考
https://zhuanlan.zhihu.com/p/360242077
https://zhuanlan.zhihu.com/p/313983390
https://www.zhihu.com/question/24972880
https://www.zhihu.com/question/335901795
https://juejin.cn/post/6948333466668777502
https://juejin.cn/post/6973977847547297800
https://segmentfault.com/a/1190000042391689
https://segmentfault.com/a/1190000023747431
https://zh-hans.reactjs.org/docs/context.html#gatsby-focus-wrapper
https://stackoverflow.com/questions/67537701/react-topic-context-vs-redux
Context与Reducer的更多相关文章
- MapReduce的ReduceTask任务的运行源码级分析
MapReduce的MapTask任务的运行源码级分析 这篇文章好不容易恢复了...谢天谢地...这篇文章讲了MapTask的执行流程.咱们这一节讲解ReduceTask的执行流程.ReduceTas ...
- hadoop2 作业执行过程之reduce过程
reduce阶段就是处理map的输出数据,大部分过程和map差不多 //ReduceTask.run方法开始和MapTask类似,包括initialize()初始化,根据情况看是否调用runJobCl ...
- 【Hadoop代码笔记】Hadoop作业提交之Child启动reduce任务
一.概要描述 在上篇博文描述了TaskTracker启动一个独立的java进程来执行Map任务.接上上篇文章,TaskRunner线程执行中,会构造一个java –D** Child address ...
- MapReduce编程系列 — 4:排序
1.项目名称: 2.程序代码: package com.sort; import java.io.IOException; import org.apache.hadoop.conf.Configur ...
- MapReduce编程系列 — 3:数据去重
1.项目名称: 2.程序代码: package com.dedup; import java.io.IOException; import org.apache.hadoop.conf.Configu ...
- MapReduce编程系列 — 2:计算平均分
1.项目名称: 2.程序代码: package com.averagescorecount; import java.io.IOException; import java.util.Iterator ...
- hadoop 各种counter 解读
http://blog.sina.com.cn/s/blog_61ef49250100uxwh.html 经过了两天的休息与放松,精神饱满了吧?上星期我们学习了MapReduce的过程,了解了其基本过 ...
- MapReduce UnitTest
通常情况下,我们需要用小数据集来单元测试我们写好的map函数和reduce函数.而一般我们可以使用Mockito框架来模拟OutputCollector对象(Hadoop版本号小于0.20.0)和Co ...
- 使用Hadoop的MapReduce与HDFS处理数据
hadoop是一个分布式的基础架构,利用分布式实现高效的计算与储存,最核心的设计在于HDFS与MapReduce,HDFS提供了大量数据的存储,mapReduce提供了大量数据计算的实现,通过Java ...
- underscore.js源码解析【集合】
// Collection Functions // -------------------- // The cornerstone, an `each` implementation, aka `f ...
随机推荐
- Git Clone一个GitHub仓库时,发生报错
1.问题 1.使用HTTP方式:Git: fatal: unable to access ' https://github. com/Light-City/CPlusPlusThings. git/' ...
- 痞子衡嵌入式:我拿到了2023年度电子星球(eestar)年度黑马作者
今天收到了「电源网旗下电子星球」 颁发的 2023 年度黑马作者奖牌,这是痞子衡继 2019 年和与非网合作后的第二个媒体平台颁发的奖项.这个奖牌做得很有质感,拿在手里沉甸甸的.此外与奖牌配套的还有一 ...
- ARMv8.0下duckdb的安装与编译过程-解决 Failed to allocate block of 2048 bytes
ARMv8.0下duckdb的安装与编译过程-解决 Failed to allocate block of 2048 bytes 背景 duckdb 是一个很流行的单机版数据库引擎 同事下载了相关的预 ...
- vim工具极简总结
vim工具总结 背景 很多操作记不住. 想着总结当笔记使用. 备忘 基本总结 vim somefile 打开/新建文件 i/a/insert按键 进入插入模式 insert 连续两次 进入替换模式 e ...
- [转帖]命令行非明文密码连接 TiDB
https://tidb.net/blog/6794a34b#%E6%96%B9%E5%BC%8F%E4%B8%80%EF%BC%9A%E5%91%BD%E4%BB%A4%E8%A1%8C%E8%BE ...
- [转帖]Linux——Shell脚本参数传递的2种方法
https://www.cnblogs.com/caoweixiong/p/12334418.html 前言 平时会遇到很多脚本都有参数选项,类似: ./test.sh -f config.conf ...
- [转帖]英伟达H100市面价格飙升!Elon Musk:每个人都在买GPU
https://cj.sina.com.cn/articles/view/5115326071/130e5ae7702001w8oz?sudaref=www.baidu.com&display ...
- 【转帖】FIO磁盘性能测试工具
https://www.jianshu.com/p/70b8c7d5d217 FIO工具介绍 FIO 工具是一款用于测试硬件存储性能的辅助工具,兼具灵活性.可靠性从而从众多性能测试工具中脱颖而出.磁盘 ...
- [转帖]nmon使用及监控数据分析
[使用] [监控数据分析] 参考链接:nmon监控数据分析 性能测试中,各个服务器资源占用统计分析是一个很重要的组成部分,通常我们使用nmon这个工具来进行监控以及监控结果输出. 一.在监控阶段使用类 ...
- Spring 应用合并之路(一):摸石头过河 | 京东云技术团队
公司在推进降本增效,在尝多种手段之后,发现应用太多,每个应用都做跨机房容灾部署,则最少需要 4 台机器(称为容器更合适).那么,将相近应用做一个合并,减少维护项目,提高机器利用率就是一个可选方案. 经 ...