React 数据共享:context上下文使用方式

583 阅读3分钟

React的数据传递方式有:

  1. props:只能父传子
  2. redux:数据传递不多的时候使用会有点重
  3. 数据共享:context 上下文

今天来介绍第三种数据共享写法:

背景:

当需要跨层级传递数据时, 如 孙需要父组件的值,那么就需要父给子,子给孙层层传递。数据量又比较小,引入redux略显沉重,props层层传递又笨重。所以这个时候就可以使用context上下文共享数据。

在v16.3之前的context只是官方的实验性API,其实官方是不推荐开发者使用的,但是架不住很多框架依旧在使用它;所以官方在v16.3发布的新的context API,新的API会更加的易用,本文也是以v16.3为准。

注意⚠️:接受的都是最近一层 provider中的value属性

React@16.x 版本之前(旧):

1. 封装 Provide 代码:

  1. 静态属性 childContextType:声明所需要的数据结构
  2. PropTypes (prop-types库): 来检查声明类型
  3. getChildContext():返回需要提供的context值
import PropTypes from 'prop-types'
class Provide extends React.Component{
    // 1.声明:检查context共享值的类型
    static childContextType = {
      // 2.用PropTypes来声明context类型
       store: PropTypes.object.isRequired
    }
    constructor(props){
     // 获取传进来的参数 并赋值给store变量
        super(props);
        this.store = {...props}
    }
    // 3.返回共享值
    getChildContext(){
      return {
         store: this.store
      }
    }
    render(){
        return (
            <div>
                {this.props.children}
            </div>
        )
    }

}

2. 父组件(提供者)代码 :通过包裹使所有子组件共享数据

import Provide from './provide';
import CustomChild from './CustomChild';

class CustomParent extends React.component{
    render(){
        return (
         // 使用provide共享信息,所有北provide组件包括的均可以获取到共享的data信息
          <Provide data={{name:"xx",sex:""}}>
              // 子组件中会有name,sex的信息
              <CustomChild/>
              <CustomChild1/>
          </Provide>
        )
    }
}

3. 子组件(消费者)代码:通过this.context.xx 获取

class CustomChild extends React.component{
    render(){
        return (
          <div>
              共享信息:{this.context.store.name}
          </div>
        )
    
    }

}


备注:
propTypes校验: 防止缺陷并规划组件使用的数据种类
prop-types库: 只在开发模式中进行类型评估,运行在生产环境不会耗费额外的精力 react@16之前是核心react库的一部分, 现在作为prop-types包单独存在

React@16 以后的版本(新):

React提供了一个createContext的方法,该方法返回一个包含了:

  • 参数:defaultValue默认值:在没有<TopContext.Provider/>包裹的时候才会生效
  • 返回值:
    • Provider对象:提供者 传参使用value
    • Consumer对象:消费者 必须以函数方式获取

最顶层组件(提供者):Provider

  import {createContext} from "react";
   const defaultValue={
     author:"李三"
    }
   export const TopContext = createContext(defaultValue);
 
   //...省略其他代码
   this.state={
       author:"赵四"
   }
   render() {
     return (
         <div className="App">
            <TopContext.Provider value={this.state}>
                <Parent/>
             </TopContext.Provider>
         </div>
    );
    }
    // ...省略其他代码

子组件(消费者):取值必须是函数

import { Component } from "react";
import { TopContext } from "../../const/context";
class ShowAuthor extends Component {
    render() {
        return (
            <>
                <TopContext.Consumer>
                    {value => <p>作者: {value.author}</p>}
                </TopContext.Consumer>
            </>
        )
    }
}

export default ShowAuthor;
 

从上面代码可以看出,每个子组件使用的时候都要引入<TopContext.Consumer>并包裹,看起来有些重复。所以我们有2种优化方式:

方式一:可以封装一个connect方法:

  • 参数:mapstate函数-> 返回共享的值
  • 调用函数的参数:组件本身
  • 返回值:一个函数。所以调用方式是connect(参数)(组件);

connect 方法:

import { TopContext } from "../constant/context"

const connectContext = (mapState) => {
    // 该函数实际是一个高阶函数工厂
    return (WrappedComponent) => {
        const Component = (props) => {
            return (
                <TopContext.Consumer>
                    {
                        value => {
                            let mappedProps = mapState(value);
                            return <WrappedComponent {...props} {...mappedProps} />
                        }
                    }
                </TopContext.Consumer>
            )

        }
        Component.displayName = `connect(${WrappedComponent.displayName || WrappedComponent.name || "Component"})`
        return Component;
    }
}

export default connectContext;

子组件(消费组件):

import connectContext from "../../js/connect";

const Detail = ({author})=> {
    return (
        <div>
            <p>共享的数据作者:{author}</p>
        </div>
    )

}
const mapState = (state)=>{
    return {...state}
}
export default connectContext(mapState)(Detail);

方式二:使用contextType + this.context取值

子组件(消费组件):

export default class ConnectTypeEg extends PureComponent {
  render() {
    return (
      <div>
      通过赋值给[contextType]方式 取值:{this.context.author} 
      </div>
    )
  }
}
ConnectTypeEg.contextType = TopContext;



HOOK: useContext

Hook 是 React 16.8.0 版本增加的新特性,可以在函数组件中使用 state以及其他的 React 特性。只能在函数组件中使用

父组件:创建+包裹

export const ContactContext = createContext({});

function parent(){
    <ContactContext.Provider value={{ itemData: res }} >                     
        <ShowName />
        <ShowSex/>
    </ContactContext.Provider>
}

子组件接收数据:useContext(父定义的context)

const ShowName = () => {
    const {itemData:{name}} = useContext(ContactContext);
    return (
          <span className='name'>{name}</span>
    )
}

总结:

context 可以共享数据

  • 16.3之前的老版本
    • 提供者:getChildContext + static childContextType
    • 消费者:this.context.xx 取值
  • 16.3之后的版本:
    • 类组件:React.createContext
      • 提供者:TopContext.Provider
      • 消费者:TopContext.Consumer
        • 优化方式一:组件.contextType= TopContext; this.context.xx取值
        • 优化方式二:
          • 封装connect函数,直接从组件参数获取
    • 函数组件(hook):
      • 提供者:createContext.Provider

      • 消费者:useContext(TopContext)返回值