React Context那些事

Skyshine發表於2019-11-14

簡介

從Android的角度來理解:Context是一個場景,描述的是一個應用程式環境的資訊,即上下文,代表與作業系統的互動的一種過程。 React的context就是一個全域性變數,可以從根元件跨級別在React的元件中傳遞。

React context使用了Provider和Customer模式,和react-redux的模式非常像。在頂層的Provider中傳入value, 在子孫級的Consumer中獲取該值,並且能夠傳遞函式,用來修改context,多用於子孫級別的元件通訊

元件的通訊

props

最常見的父子傳值,如果要實現跨多代傳值,有點麻煩,需一層一層傳遞下去

context

Context 提供了一種方式,能夠讓資料在元件樹中傳遞時不必一級一級的手動傳遞

狀態管理工具(redux等)

例項

類元件

//建立Context元件
const ThemeContext = React.createContext({
  theme: 'dark',
  change: () => {}, //向上下文設定一個回撥方法
});

//執行APP
class App extends React.Component {
  constructor(props) {
    super(props);

    this.change = () => { //change,會作為context引數傳遞
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };

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

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

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

//接收元件
function Button() {
  return (
    <ThemeContext.Consumer>
      {({theme, change}) => (
        <button
          onClick={change} //呼叫回撥
          style={{backgroundColor: theme}}>
          Toggle Theme
        </button>
      )}
    </ThemeContext.Consumer>
);
}
複製程式碼

使用多個context

import React, { createContext } from 'react';

    // 建立Context的唯一方法
    const ThemeContext = createContext()
    const SizeContext = createContext()
    
    
    class App extends React.Component {
      state = {
        theme: 'red',
        size: 'small'
      }
      render () {
        const { theme, size } = this.state
        return (
          // 使用 Context.Provider 包裹後續元件,value 指定值 
          <ThemeContext.Provider value={theme}>
            {/* 當出現多個Context的時候,只需要將Context.Provider 巢狀即可 */}
            <SizeContext.Provider value={size}>
              {/* 當Context的Provider值更改時,Consumer 的值必須重新渲染 */}
              <button onClick={() => {this.setState({ theme: 'yellow', size: 'big'})}}>按鈕</button>
              <Middle></Middle>
            </SizeContext.Provider>
          </ThemeContext.Provider>
        )
      }
    }

    class Middle extends React.Component {
      render () {
        return <Bottom></Bottom>
      }
    }
    
    class Bottom extends React.Component {
      render () {
        return (
          // Context.Consumer Consumer消費者使用Context得值
          // 但子元件不能是其他元件,必須渲染一個函式,函式的引數就是Context得值
          // 當出現 多個Consumer的時候,進行巢狀,每個Consumer 的子元件必須是一個函式,即可
          <ThemeContext.Consumer>
            {
              theme => (
                <SizeContext.Consumer>
                  {
                    size => (<h1>ThemeContext 的 值為 {theme}; SizeContext 的值為 {size}</h1>)
                  }
                </SizeContext.Consumer>
              )
            }
          </ThemeContext.Consumer>
        )
      }
    }
    
    export default App;
複製程式碼

contextType

官方定義:掛載在 class 上的 contextType 屬性會被重賦值為一個由 React.createContext() 建立的 Context 物件。這能讓你使用 this.context 來消費最近 Context 上的那個值。你可以在任何生命週期中訪問到它,包括 render 函式中。

實際作用:contextType 可以簡化 context 的使用,不使用 consumer 也可以共享變數

import React, { createContext } from 'react';
    const ThemeContext = createContext()
    const SizeContext = createContext()
    class App extends React.Component {
      state = {
        theme: 'red',
        size: 'small'
      }
      render () {
        const { theme, size } = this.state
        return (
          // 使用 Context.Provider 包裹後續元件,value 指定值 
          <ThemeContext.Provider value={theme}>
            {/* 當出現多個Context的時候,只需要將Context.Provider 巢狀即可 */}
            <SizeContext.Provider value={size}>
              {/* 當Context的Provider值更改時,Consumer 的值必須重新渲染 */}
              <button onClick={() => {this.setState({ theme: 'yellow', size: 'big'})}}>按鈕</button>
              <Middle></Middle>
            </SizeContext.Provider>
          </ThemeContext.Provider>
        )
      }
    }
    class Middle extends React.Component {
      render () {
        return <Bottom></Bottom>
      }
    }
    
    class Bottom extends React.Component {
      // 申明靜態變數、contextType 將 context 直接賦值於 contextType
      static contextType = ThemeContext
      
      render () {
        // 在 render 函式中 可以直接 訪問 this.context 獲取共享變數、這樣就可以不使用 consumer
        const theme = this.context
        return (
          // Context.Consumer Consumer消費者使用Context得值
          // 但子元件不能是其他元件,必須渲染一個函式,函式的引數就是Context得值
          // 當出現 多個Consumer的時候,進行巢狀,每個Consumer 的子元件必須是一個函式,即可
          <div>
            <h1>ThemeContext 的 值為 {theme} </h1>
          </div>
        )
      }
    }
    
    export default App;
複製程式碼

注意: contextType 只能在類元件中使用 ? 注意下面在函式元件使用的案例

一個元件如果有多個 consumer , contextType 只對其中一個有效,所以說,contextType 只能有一個

Context 缺點

網路說法:

  1. 在元件樹中,如果中間某一個元件 ShouldComponentUpdate returning false 了,會阻礙 context 的正常傳值,導致子元件無法獲取更新。 官方文件:

  2. 當 Provider 的 value 值發生變化時,它內部的所有消費元件都會重新渲染。Provider 及其內部 consumer 元件都不受制於 shouldComponentUpdate 函式,因此當 consumer 元件在其祖先元件退出更新的情況下也能更新。

  3. 元件本身 extends React.PureComponent 也會阻礙 context 的更新。

  4. 沒有redux好用

警告

注意:context類似於全域性變數做法,會讓元件失去獨立性、複用起來更困難,不能濫用、但本身它一定有適合使用的場景,具體看情況使用

引申

Component和PureComponent

React.PureComponent 與 React.Component 幾乎完全相同,但 React.PureComponent 通過prop和state的淺對比來實現 shouldComponentUpate()。 PureComponent 裡面也有一個內建的shouldComponentUpate.還要有immutable 對資料的管理不然會有BUG

如果React元件的 render() 函式在給定相同的props和state下渲染為相同的結果,在某些場景下你可以使用 React.PureComponent 來提升效能。

React.PureComponent 的 shouldComponentUpdate() 只會對物件進行淺對比。如果物件包含複雜的資料結構,它可能會因深層的資料不一致而產生錯誤的否定判斷(表現為物件深層的資料已改變檢視卻沒有更新, 原文:false-negatives)。當你期望只擁有簡單的props和state時,才去繼承 PureComponent ,或者在你知道深層的資料結構已經發生改變時使用 forceUpate() 。或者,考慮使用 不可變物件 來促進巢狀資料的快速比較。

無狀態函式式元件

常規函式寫法

function Item(props) {
  return (
    <div className='item'>
      {props.title}
      <span
        className='deleteItem'
        onClick={props.remove(props.id)}
      > x </span>
    </div>
  )
}
複製程式碼

無生命週期方法

函式式元件,有時也被稱為無狀態元件,沒有任何生命週期方法,意味著每次上層元件樹狀態發生變更時它們都會重新渲染,這就是因為缺少 shouldComponentUpdate 方法導致的。這也同樣意味著您不能定義某些基於元件掛載和解除安裝的行為。

沒有 this 和 ref

在函式式元件中既不能使用 this 關鍵字或訪問到 ref。

如果您將 context 定義為函式的一個 props

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

D.contextTypes = {
  user: React.PropTypes.object.isRequired
}
複製程式碼

優勢?

通過將邏輯和資料處理與 UI 展示剝離開來,我們就可以避免在展示型元件中處理任何的狀態。無狀態函式式元件強制實施了這樣的風格,因為您無論如何都沒有辦法處理本地狀態了。它強制您將任何的狀態處理移交至上層的元件樹,而讓下層的元件只做它們所做的——關注 UI 的展示。

容器型元件 (container component):

1、主要關注元件資料如何互動

2、擁有自身的state,從伺服器獲取資料,或與redux等其他資料處理模組協作

3、需要通過類定義元件宣告,幷包含生命週期函式和其他附加方法

展示型元件 (presentational component):

1、主要負責元件內容如何展示

2、從props接收父元件傳遞來的資料

3、大多數情況可以通過函式定義元件宣告

沒有邏輯意味著相同的表示具有相同的資料。 1、解耦了介面和資料的邏輯

2、更好的可複用性,比如同一個回覆列表展示元件可以套用不同資料來源的容器元件

3、利於團隊協作,一個人負責介面結構,一個人負責資料互動

缺陷

......

相關文章