[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 去进行实现。
本文链接:https://my.lmcjl.com/post/12346.html
4 评论