[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 Functional?

[React 进阶系列] 组成与继承

[React 进阶系列] React Context 案例学习:子组件内更新父组件的状态

ReactNative进阶(十九):React Native按钮Touchable系列组件使用详解

[React] Creating a Stateless Functional Component