[React 进阶系列] Functional Component 与 Class Component 中使用 Context
Posted GoldenaArcher
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[React 进阶系列] Functional Component 与 Class Component 中使用 Context相关的知识,希望对你有一定的参考价值。
[React 进阶系列] Functional Component 与 Class Component 中使用 Context
今天碰到了一个需求,大抵由这几个部分组成:
- 说需要异步获取一些数据,并且这个数据需要在 Layout 组件中同步渲染和更新
- 每一次访问一个新的页面的时候,都需要重新拉这个数据重新进行渲染
- 调用 API 的时候需要使用到这个值
有点像阉割版的验证
结构大概是这样的:
|- App
| |- Layout
| | |- Component
刚开始老板没说需要在 Layout 中渲染数据,所以所有的调用都是在 component level 进行实现。但是一旦说 Layout 也需要更新,那么这个就无法实现了——毕竟父组件的状态没有更新。
后面合计了一下究竟是做 props drilling 还是用 context 后,决定用 context 实现——路由都封装了,再改真的好麻烦。
Context 的封装
这个在 [React 进阶系列] React Context 案例学习:子组件内更新父组件的状态 中也写了一下,封装的东西基本就没有改,拿来直接用就好。不过我导出的时候没选择用对象,而是用数组。
核心逻辑就是到处一个值和一个 changeHandler,当子组件调用 changeHandler 的时候,只要值产生了变化,那就会触发一个全局的重新渲染。
import createContext, useContext, useState from 'react';
// 只是 FP 可以不用导出
export const SomeContext = createContext();
export const useSomeContext = () => useContext(SomeContext);
export const SomeContextProvider = ( children ) =>
const [val, setVal] = useState(null);
const updateVal = async () =>
setval(null);
try
const res = await someWrappedApiCall();
setVal(res);
catch (e)
console.error(e);
setVal(null);
;
return (
<SomeContext.Provider value=[val, updateVal]>
children
</SomeContext.Provider>
);
;
App 层包一个 Provider 即可:
export default function App()
return (
<SomeContextProvider>
<AppMainEntry />
</SomeContextProvider>
);
FP 中使用
这里跟其他大部分的需求差不多,不过我这里需要依靠 Context 中传来的值去进行 API 调用,所以需要使用 useEffect 去确认有值,并且是正确的值才能进行更新。
确保值的正确这一步在 context 中的 try/catch 中简略的实现了,因此在子组件中只要拿到值,那么就可以直接用了。
import useSomeContext from '../../context/someContext';
export default function ExampleComp()
const [val, updateVal] = useval();
useEffect(() =>
updateVal();
, []);
useEffect(() =>
if (val) fetchData(val);
, [val]);
没什么好说的,真的方便。
不过这串代码也重复了好几次,有点想说要不要再封一个 HOC 去通过那个 HOC 进行 context 管理……不过需要解决的问题就在于,数据还是在子组件里完成拉取的,管理这个调用稍微有点麻烦。出于对 ddl 的尊重暂时搁置,之后可以再合计一下看看能不能想到什么办法去解决这个问题。
🐎一个参考问题之后回顾一下: HOC - Functional Component
class component 中使用
找了一些资料,不过大部分都说时使用 SomeContext.Consumer
,然而因为我需要在生命周期中使用这个值,所以 Consumer 没法用(Consumer 在 return 中被调用)。
再找了一下,React 在 18 年年底推出了一个 contextType 可以解决这个问题,具体消息可以看官方 release:React v16.6.0: lazy, memo and contextType,官方文档的 demo 说,这么调用 context 就可以在生命周期函数中取值:
class MyClass extends React.Component
componentDidMount()
let value = this.context;
/* perform a side-effect at mount using the value of MyContext */
componentDidUpdate()
let value = this.context;
/* ... */
componentWillUnmount()
let value = this.context;
/* ... */
render()
let value = this.context;
/* render something based on the value of MyContext */
MyClass.contextType = MyContext;
正好符合需求,所以我稍微改了一下:
class ExampleClassComponent extends React.Component
componentDidMount()
const [_, updateVal] = this.context;
updateVal();
componentDidUpdate(prevProps, prevState)
const [val] = this.context;
if (!prevState.valFetched && val)
this.setState( valFetched: true );
this.fetchData(val);
ExampleClassComponent.contextType = SomeContext;
export default ExampleClassComponent;
因为用的是 this
进行的绑定,componentDidUpdate 中不管是 prevProps 也好,prevState 也罢,我都没办法拿到上一个 snapshot 中的值,因此也只能另外设置一个变量去防止无限更新。
总体来说只要升级到了 16.8(甚至是 16.6)之后,context 的使用就变得还是挺方便的,对于不常变动的全局变量——这里一个页面只刷新一次,相当于是 auth 验证一类的存在,我觉得也不算是频繁刷新——倒也不是非得使用 redux 去进行实现。
以上是关于[React 进阶系列] Functional Component 与 Class Component 中使用 Context的主要内容,如果未能解决你的问题,请参考以下文章
[React 进阶系列] Functional Component 与 Class Component 中使用 Context
[React 进阶系列] React Context 案例学习:子组件内更新父组件的状态