React Context新舊版本 學(xué)習(xí)總結(jié)

前言

Context被翻譯為上下文,在編程領(lǐng)域,這是一個(gè)經(jīng)常會(huì)接觸到的概念,React中也有。

React的官方文檔中,Context被歸類為高級(jí)部分(Advanced),屬于React的高級(jí)API,但官方并不建議在穩(wěn)定版的App中使用Contex。

不過(guò),這并非意味著我們不需要關(guān)注Context。事實(shí)上,很多優(yōu)秀的React組件都通過(guò)Context來(lái)完成自己的功能,比如react-redux的<Provider />,就是通過(guò)Context提供一個(gè)全局態(tài)的store,拖拽組件react-dnd,通過(guò)Context在組件中分發(fā)DOMDragDrop事件,路由組件react-router通過(guò)Context管理路由狀態(tài)等等。在React組件開(kāi)發(fā)中,如果用好Context,可以讓你的組件變得強(qiáng)大,而且靈活。

初識(shí)React Context

當(dāng)你不想在組件樹(shù)中通過(guò)逐層傳遞props或者state的方式來(lái)傳遞數(shù)據(jù)時(shí),可以使用Context來(lái)實(shí)現(xiàn)跨層級(jí)的組件數(shù)據(jù)傳遞。


使用props或者state傳遞數(shù)據(jù),數(shù)據(jù)自頂下流。

使用Context,可以跨越組件進(jìn)行數(shù)據(jù)傳遞。

如何使用Context

如果要Context發(fā)揮作用,需要用到兩種組件,一個(gè)是Context生產(chǎn)者(Provider),通常是一個(gè)父節(jié)點(diǎn),另外是一個(gè)Context的消費(fèi)者(Consumer),通常是一個(gè)或者多個(gè)子節(jié)點(diǎn)。所以Context的使用基于生產(chǎn)者消費(fèi)者模式。

對(duì)于父組件,也就是Context生產(chǎn)者,需要通過(guò)一個(gè)靜態(tài)屬性childContextTypes聲明提供給子組件的Context對(duì)象的屬性,并實(shí)現(xiàn)一個(gè)實(shí)例getChildContext方法,返回一個(gè)代表Context的純對(duì)象 (plain object) 。

import React from 'react'
import PropTypes from 'prop-types'

class MiddleComponent extends React.Component {
  render () {
    return <ChildComponent />
  }
}

class ParentComponent extends React.Component {
  // 聲明Context對(duì)象屬性
  static childContextTypes = {
    propA: PropTypes.string,
    methodA: PropTypes.func
  }
  
  // 返回Context對(duì)象,方法名是約定好的
  getChildContext () {
    return {
      propA: 'propA',
      methodA: () => 'methodA'
    }
  }
  
  render () {
    return <MiddleComponent />
  }
}

而對(duì)于Context的消費(fèi)者,通過(guò)如下方式訪問(wèn)父組件提供的Context。

import React from 'react'
import PropTypes from 'prop-types'

class ChildComponent extends React.Component {
  // 聲明需要使用的Context屬性
  static contextTypes = {
    propA: PropTypes.string
  }
  
  render () {
    const {
      propA,
      methodA
    } = this.context
    
    console.log(`context.propA = ${propA}`)  // context.propA = propA
    console.log(`context.methodA = ${methodA}`)  // context.methodA = undefined
    
    return ...
  }
}

子組件需要通過(guò)一個(gè)靜態(tài)屬性contextTypes聲明后,才能訪問(wèn)父組件Context對(duì)象的屬性,否則,即使屬性名沒(méi)寫(xiě)錯(cuò),拿到的對(duì)象也是undefined。

對(duì)于無(wú)狀態(tài)子組件(Stateless Component),可以通過(guò)如下方式訪問(wèn)父組件的Context

import React from 'react'
import PropTypes from 'prop-types'

const ChildComponent = (props, context) => {
  const {
    propA
  } = context
    
  console.log(`context.propA = ${propA}`)  // context.propA = propA
    
  return ...
}
  
ChildComponent.contextProps = {
  propA: PropTypes.string    
}

而在接下來(lái)的發(fā)行版本中,React對(duì)Context的API做了調(diào)整,更加明確了生產(chǎn)者消費(fèi)者模式的使用方式。

import React from 'react';
import ReactDOM from 'react-dom';

const ThemeContext = React.createContext({
  background: 'red',
  color: 'white'
});

通過(guò)靜態(tài)方法React.createContext()創(chuàng)建一個(gè)Context對(duì)象,這個(gè)Context對(duì)象包含兩個(gè)組件,<Provider />和<Consumer />。

class App extends React.Component {
  render () {
    return (
      <ThemeContext.Provider value={{background: 'green', color: 'white'}}>
        <Header />
      </ThemeContext.Provider>
    );
  }
}

<Provider />的value相當(dāng)于現(xiàn)在的getChildContext()

class Header extends React.Component {
  render () {
    return (
      <Title>Hello React Context API</Title>
    );
  }
}
 
class Title extends React.Component {
  render () {
    return (
      <ThemeContext.Consumer>
        {context => (
          <h1 style={{background: context.background, color: context.color}}>
            {this.props.children}
          </h1>
        )}
      </ThemeContext.Consumer>
    );
  }
}

<Consumer />的children必須是一個(gè)函數(shù),通過(guò)函數(shù)的參數(shù)獲取<Provider />提供的Context
可見(jiàn),Context的新API更加貼近React的風(fēng)格。

總結(jié):

React的context就是一個(gè)全局變量,可以從根組件跨級(jí)別在React的組件中傳遞。React context 的API 有兩個(gè)版本,React16.x 之前 的是老版本的 context,之后的是新版本的context。

1.老版本的context

getChildContext根組件中聲明,一個(gè)函數(shù),返回一個(gè)對(duì)象,就是context childContextType根組件中聲明,指定context的結(jié)構(gòu)類 型,如不指定,會(huì)產(chǎn)生錯(cuò)誤
contextTypes子孫組件中聲明,指定要接收的context的結(jié)構(gòu)類型,可以只是context的一部分結(jié)構(gòu)。contextTypes 沒(méi)有定義,context將是一個(gè)空對(duì)象。
this.context 在子孫組件中通過(guò)此來(lái)獲取上下文

(注:從React v15.5開(kāi)始 ,React.PropTypes 助手函數(shù)已被棄用,可使用 prop-types 庫(kù) 來(lái)定義contextTypes)

舉例如下:

//根組件
class MessageList extends React.Component {
  getChildContext() {
    return {color: "purple",text: "item text"};
  }

  render() {
    const children = this.props.messages.map((message) =>
      <Message text={message.text} />
    );
    return <div>{children}</div>;
  }
}

MessageList.childContextTypes = {
  color: React.PropTypes.string
  text: React.PropTypes.string
};

//中間組件
class Message extends React.Component {
  render() {
    return (
      <div>
        <MessageItem />
        <Button>Delete</Button>
      </div>
    );
  }
}

//孫組件(接收組件)
class MessageItem extends React.Component {
  render() {
    return (
      <div>
        {this.context.text}
      </div>
    );
  }
}

MessageItem.contextTypes = {
  text: React.PropTypes.string
};

class Button extends React.Component {
  render() {
    return (
      <button style={{background: this.context.color}}>
        {this.props.children}
      </button>
    );
  }
}

Button.contextTypes = {
  color: React.PropTypes.string
};

2.新版本的context

新版本的React context使用了Provider和Customer模式,和react-redux的模式非常像。在頂層的Provider中傳入value,
在子孫級(jí)的Consumer中獲取該值,并且能夠傳遞函數(shù),用來(lái)修改context,如下代碼所示:

//創(chuàng)建Context組件
const ThemeContext = React.createContext({
  theme: 'dark',
  toggle: () => {}, //向上下文設(shè)定一個(gè)回調(diào)方法
});

//運(yùn)行APP
class App extends React.Component {
  constructor(props) {
    super(props);

    this.toggle = () => { //設(shè)定toggle方法,會(huì)作為context參數(shù)傳遞
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };

    this.state = {
      theme: themes.light,
      toggle: this.toggle,
    };
  }

  render() {
    return (
      <ThemeContext.Provider value={this.state}> //state包含了toggle方法
        <Content />
      </ThemeContext.Provider>
    );
  }
}

//中間組件
function Content() {
  return (
    <div>
      <Button />
    </div>
  );
}

//接收組件
function Button() {
  return (
    <ThemeContext.Consumer>
      {({theme, toggle}) => (
        <button
          onClick={toggle} //調(diào)用回調(diào)
          style={{backgroundColor: theme}}>
          Toggle Theme
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

案例2

// Context 可以讓我們無(wú)須明確地傳遍每一個(gè)組件,就能將值深入傳遞進(jìn)組件樹(shù)。
// 為當(dāng)前的 theme 創(chuàng)建一個(gè) context(“l(fā)ight”為默認(rèn)值)。
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // 使用一個(gè) Provider 來(lái)將當(dāng)前的 theme 傳遞給以下的組件樹(shù)。
    // 無(wú)論多深,任何組件都能讀取這個(gè)值。
    // 在這個(gè)例子中,我們將 “dark” 作為當(dāng)前的值傳遞下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中間的組件再也不必指明往下傳遞 theme 了。
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // 指定 contextType 讀取當(dāng)前的 theme context。
  // React 會(huì)往上找到最近的 theme Provider,然后使用它的值。
  // 在這個(gè)例子中,當(dāng)前的 theme 值為 “dark”。
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}
// 也可以按照這種形式獲取
function ThemedButton(){
    return (
      <ThemeContext.Counsumer>
        {theme =>(
         <Button theme={theme} />
         )
        }
       </ThemeContext.Counsumer>
      );
}

詳細(xì)用法可以參考官方文檔:https://react.docschina.org/docs/context.html#reactcreatecontext

3. context 在react hooks中的使用

React Hooks是React 16.8.6版本為函數(shù)式組件添加了在各生命周期中獲取state和props的通道??勺屇诓痪帉?xiě)類的情況下使用 state(狀態(tài)) 和其他 React 功能。不再需要寫(xiě)class組件,你的所有組件都將是Function。

hooks為我們提供了useContext方法來(lái)操作context。useContext可以很方便去訂閱context的改變,并在合適的時(shí)候重渲染組件。例如上面的函數(shù)式組件中,通過(guò)Consumer的形式獲取Context的數(shù)據(jù),有了useContext可以改寫(xiě)成下面:

function ThemedButton(){
    const value = useContext(ThemeContxet);
    return (
         <Button theme={value} />
    );
}

通過(guò)useCOntext我們可以直接拿到context的值,而不再需要在函數(shù)式組件外面用context.Consumer包裹了

4. context在如下的生命周期鉤子中可以使用

constructor(props, context)
componentWillReceiveProps(nextProps, nextContext)
shouldComponentUpdate(nextProps, nextState, nextContext)
componentWillUpdate(nextProps, nextState, nextContext)
componentDidUpdate(prevProps, prevState, prevContext)

5. 在無(wú)狀態(tài)組件中可以通過(guò)參數(shù)傳入

function D(props, context) {
  return (
    <div>{this.context.user.name}</div>
  );
}

D.contextTypes = {
  user: React.PropTypes.object.isRequired
}

5. React context的局限性

  1. 在組件樹(shù)中,如果中間某一個(gè)組件 ShouldComponentUpdate returning false 了,會(huì)阻礙 context 的正常傳值,導(dǎo)致子組件無(wú)法獲取更新。
  2. 組件本身 extends React.PureComponent 也會(huì)阻礙 context 的更新。

注意點(diǎn):

  1. Context 應(yīng)該是唯一不可變的.
  2. 組件只在初始化的時(shí)候去獲取 Context

參考:

  1. https://www.tuicool.com/articles/nUryimf
  2. https://segmentfault.com/a/1190000012575622
  3. http://www.itdecent.cn/p/eba2b76b290b
  4. http://www.itdecent.cn/p/6127d4b1e3ce
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容