上节课我们介绍了Fragment这个东西可以代替我们的div标签,但是在渲染时会把标签丢弃掉,只保留标签体内容,这节课我们来介绍一下关于context的相关内容。

回顾

在这节课内容开始之前,我们对上节课内容做一个简单的回顾

  • Fragment可以使得渲染时不产生不必要的div,空标签也可以达到相同效果
  • Fragment可以且仅可以接收key值,但是空标签什么都不能接

以上便是上节课的内容,接下来我们来介绍一下Context

什么是context

我们还记不记得我们在看类组件的时候看见组件实例对象上除了三大属性之外,还有一个context属性?其实这个是组件间的一种通信方式,通常用于祖组件后代组件之间

什么意思呢?比如说这样:A组件有一个子组件B,而B组件还有一个子组件C,我们知道A组件要想给B组件传递数据可以通过props,但是如果A想给C传递数据,是不是要先通过props传给B,然后B再通过props传给C?这样做的话也太麻烦了。而context就可以使得A直接把数据传给C

Context

那么我们知道context的作用了,那么我们应该这么去使用呢?我们来看一下:

export default class A extends Component {
  state = {name:"jingxun"}
  render() {
    return (
      <div>
        <h3>Component A</h3>
        <h4>name: {this.state.name}</h4>
        <B />
      </div>
    );
  }
}

class B extends Component {
  render() {
    return (
      <div>
        <h3>Component B</h3>
        <h4>Recive name from A: ?????</h4>
        <C />
      </div>
    );
  }
}

class C extends Component {
  render() {
    return (
      <div>
        <h3>Component C</h3>
        <h4>Recive name from A: ?????</h4>
      </div>
    );
  }
}

首先我们定义了三个组件,但是我们只渲染A组件,A组件中渲染B组件,B组件中渲染C组件,我们在三个组件中都想展示Astate中的name,但是BC组件中我们先用问号代替,因为我们还没有传任何东西给这两个组件。那么我们来看一下效果:

image-20220121082707939

我们看到页面上成功展示了我们想要的信息,而且我们加了样式,三个组件的父子关系都能很清晰的展示出来,那么,我们怎么给B组件还有C组件传递数据呢?首先给B组件传数据是不是非常简单啊?直接一个props进去就行了?问题是不是在C组件?给C组件传递数据的话我们目前来说,第一是不是也可以让B组件给C传一个props?另外PubSub包括redux是不是都可以啊?但是我们来看现在我们用context怎么传:

const NameContext = React.createContext();
export default class A extends Component {
  state = { name: "jingxun" }
  render() {
    return (
      <div className="parent">
        <h3>Component A</h3>
        <h4>name: {this.state.name}</h4>
        <NameContext.Provider value={this.state.name}>
          <B />
        </NameContext.Provider>
      </div>
    );
  }
}

class B extends Component {...}

class C extends Component {
  render() {
    console.log(this);
    return (...);
  }
}

我们来看一下上面这段代码,我们先把不需要关注的代码隐藏掉,我们做了什么?是不是定义了一个NameContext?可能大家有人已经注意到了,我们这里首字母大写了。那么是什么意思呢?这就说明这是一个组件,通React.createContext方法创建出来的一个组件,这个组件中有一个Provider,我们利用NameContext.Provider标签来为该标签内包裹的所有层级的组件都提供一个value,大家再学redux的时候我们是不是就已经说过这个东西了?用Provider来提供store,这里也是一样,我们用Provider来提供context,那么我们现在来看看C组件中能不能接收到吧。打印一下C组件的this:

image-20220121084532292

我们来看一下,诶好像没有啊,这是怎么回事呢?因为我们并不是说用Provider一包就一劳永逸了,我得在被包裹的组件以及子组件中声明,只有声明了才能使用,否则的话context里面是接不到东西的。那么我们在C组件中声明一下试试看:

class C extends Component {
  static contextType = NameContext;
  render() {
    console.log(this);
    return (
      <div className="grand">
        <h3>Component C</h3>
        <h4>Recive name from A: ?????</h4>
      </div>
    );
  }
}

我们来看这段代码,我们之前说声明是不是不知道怎么声明?那么我们来看,我们在C组件中只要加一行代码就是我们声明一个静态属性叫contextType,这个属性就是我们之前定义的Context组件,这个和PubSub有些类似,我们有消息发布者,是不是要有订阅者啊?所以说这一步操作就相当于是订阅,那么我们这次再来看一下:

image-20220121085312819

这次我们成功拿到了context的数据,但是这是个字符串,我们组件的context属性是不是默认是个对象类型啊?所以说我们Provider标签在提供value的时候直接传一个对象就符合规范了。

const NameContext = React.createContext();
export default class A extends Component {
  state = { name: "jingxun" }
  render() {
    const { name } = this.state;
    return (
      <div className="parent">
        <h3>Component A</h3>
        <h4>name: {name}</h4>
        <NameContext.Provider value={{name}}>
          <B name={name}/>
        </NameContext.Provider>
      </div>
    );
  }
}

class B extends Component {
  render() {
    return (
      <div className="child">
        <h3>Component B</h3>
        <h4>Recive name from A: {this.props.name}</h4>
        <C />
      </div>
    );
  }
}

class C extends Component {
  static contextType = NameContext;
  render() {
    return (
      <div className="grand">
        <h3>Component C</h3>
        <h4>Recive name from A: {this.context.name}</h4>
      </div>
    );
  }
}

我们来看这段完整代码,A通过props传数据给B,但是ProviderB以及B的后代组件都提供了context,但是B没有用,B的子组件C在组件中声明使用了context。那么我们来看一下效果:

image-20220121085937150

这样我们便实现了我们之前所定的需求,但是还有一个问题,如果我们的C组件的是一个函数式组件怎么办?函数式组件有this吗?函数式组件有静态属性吗?这俩东西函数式组件是不是都没有啊?那么函数式组件是不是就不能用context了呢?其实并不是,我们来看:

function C(){
  return (
    <div className="grand">
      <h3>Component C</h3>
      <h4>Recive name from A: 
        <NameContext.Consumer>
          {value => value.name}
        </NameContext.Consumer>
      </h4>
    </div>
  );
}

我们来看一下,我们是不是把C组件改成了一个函数式组件?但是我们的函数式组件并没有静态属性之类的东西,没有关系,Context组件中还有另一个组件标签,就是Consumer,这是什么呢?我们说了Provider是提供者,那么有提供者自然就要有使用者嘛,我们在这个标签中直接写一个函数,这个函数接收一个参数value,这个value就是我们在Provider标签中传入的value属性,然后这个函数返回我们想要展示的值就可以了,我们来看一下效果:

image-20220121091515046

C这次也依然能成功展示A传过来的值。而且这个方法在类组件中也能用。

总结

  • context可以让祖组件后代组件之间通信
  • 通过React.createContext方法创建Context组件
  • 通过<Xxxxx.Provider value={{key:value}}>包裹组件标签来给组件以及其子组件传递context
  • 通过在组件中声明static contextType来接收context,但是只适用于类组件
  • 通过<Xxxxx.Consumer>标签包裹函数来使用context的数据,还方法使用与所有组件

Copyright statement:The articles of this site are all original if there is no special explanation, indicate the source please when you reprint.

Link of this article:https://work.lynchow.com/article/extend_context/