React@16.3 全新的Context API進階教程

前言

最近看了下React16.3的新文檔,發現官方悄悄地改了不少東西了。其中我最感興趣的天然就是這個全新的Context API了。因此寫了這篇文章來總結分享一下。其餘的變更在這篇文章裏或許會說起。vue

本文你能夠在個人github上面找到,轉載請標註這個地址就好了。node

什麼是Context API

Context API是React提供的一種跨節點數據訪問的方式。衆所周知,React是單向數據流的,Vue裏面的props也借鑑了這一思想。react

可是不少時候,這種單向數據流的設定卻變得不是那麼友好。咱們每每須要從更高層的節點獲取一些數據,若是使用傳統的prop傳遞數據,就須要每一層都手動地向下傳遞。對於層次很高的組件,這種方法十分地煩人,極大地下降了工做效率。git

因而,React使用了Context APIContext API存在已久,可是舊的Context API存在不少問題,而且使用起來也並非特別方便,官方並不建議使用老版本的Context API。因而不少開發者選擇了Redux之類的狀態管理工具。程序員

受到Redux的影響,React在16.3.0版本中推出了全新的Context APIgithub

一些你須要提早知道的東西

  1. 衆所周知,長期起來JavaScript一直沒有模塊系統。nodejs使用require做爲彌補方法。ECMAScript6以後,引入了全新的import語法標準。import語法標準有個尤其重要的不一樣(相比較require),那就是:import導入的數據是引用的。這意味着多個文件導入同一個數據,並非導入的拷貝,而是導入的引用。數組

  2. react@16.3的聲明文件(d.ts)貌似沒有更新,意味着若是你如今使用Typescript,那麼可能會報錯。markdown

  3. React如今推薦使用render propsrender props爲組件渲染的代碼複用以及代碼傳遞提供了新的思路,其實本質上就是經過props傳遞HOC函數來控制組件的渲染。app

  4. 或許你曾經聽過「Context API是用來替代Redux」之類的傳聞,然而事實並不是如此。ReduxContext API解決的問題並不同,會形成那樣的錯覺多是由於他們的使用方法有點兒同樣。dom

  5. React16.3有幾個新特性,最主要的變化是Context,還有就是廢除了幾個生命週期,好比ComponentWillReceiveProps(說實話,實際項目中,這個生命週期徹底能夠用ComponentWillUpdate來替換)

  6. React16.3中的refs再也不推薦直接傳遞一個函數了,而是使用了全新的React.createRef來替代。固然之前的方法依舊適用,畢竟是爲了兼容。

開始使用

React.createContext

createContext用來建立一個Context,它接受一個參數,這個參數會做爲Context傳遞的默認值。須要注意的是,若是你傳入的參數是個對象,那麼當你更改Context的時候,內部會調用Object.is來比較對象是否相等。這會致使一些性能上的問題。固然,這並不重要,由於大部分狀況下,這點兒性能損失能夠忽略。

咱們看下這個例子,這是一個提供主題(Light/Dark)類型的Context

// context.js
import * as React from 'react';
// 默認主題是Light
export const { Provider, Consumer } = React.createContext("Light");

複製代碼

接下來咱們只須要在須要的文件裏import就好了

Provider

Provider是須要使用Context的全部組件的根組件。它接受一個value做爲props,它表示Context傳遞的值,它會修改你在建立Context時候設定的默認值。

import { Provider } from './context';
import * as React from 'react';
import { render } from 'react-dom';
import App from './app';


const root = (
    <Provider value='Dark'> <App /> </Provider>
);

render(root, document.getElementById('root'));


複製代碼

Consumer

Consumer表示消費者,它接受一個render props做爲惟一的children。其實就是一個函數,這個函數會接收到Context傳遞的數據做爲參數,而且須要返回一個組件。

// app.jsx

import { Consumer } from './context';
import * as React from 'react';

export default class App extends React.Component {
    render() {
        return (
            <Consumer> { theme => <div>Now, the theme is { theme }</div> } </Consumer>
        )
    }
}
複製代碼

一些須要注意的地方

多層嵌套

Context爲了確保從新渲染的快速性,React須要保證每一個Consumer都是獨立的節點。

const ThemeContext = React.createContext('light');
const UserContext = React.createContext();

function Toolbar(props) {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Toolbar />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}
複製代碼

當層次更加複雜的時候,會變得很煩人。所以推薦當層次超過兩層以後,建立一個本身的render prop或許是個不錯的主意。在實際工程中,其實並不建議多層嵌套。更爲適合的時,提供一對ProvierConsumer對,傳遞狀態管理工具對應的實例就好了。

在生命週期中使用

在以前的Context API中,在一些聲明週期中會暴露一個context的參數,以供開發者更爲方便的訪問。新版API並無這個參數傳遞了,更爲推薦的方式是直接把Context的值經過props傳遞給組件。具體來講,就像下面這個官方的例子這樣。

class Button extends React.Component {
  componentDidMount() {
    // ThemeContext value is this.props.theme
  }

  componentDidUpdate(prevProps, prevState) {
    // Previous ThemeContext value is prevProps.theme
    // New ThemeContext value is this.props.theme
  }

  render() {
    const {theme, children} = this.props;
    return (
      <button className={theme ? 'dark' : 'light'}> {children} </button>
    );
  }
}

export default props => (
  <ThemeContext.Consumer>
    {theme => <Button {...props} theme={theme} />}
  </ThemeContext.Consumer>
);
複製代碼

不像之前那樣,能夠直接經過this.context訪問,新版本的Context只能在render方法裏面訪問。由於Context只暴露在Consumerrender prop裏面。我的以爲這是這個版本API的一個缺點。因此只有採用上面這種折中的方式,再包裝一個函數組件來封裝到props裏面去。相比較而言,仍是麻煩了一點兒。在組件樹裏面多了一個函數組件,也是一個缺點。

Consumer封裝

當一個Context的值多個組件都在使用的時候,你須要手動地每次都寫一次Consumerredner prop。這是很煩的,程序員都是很懶的(至少我是這樣),所以這個時候利用一下React的HOC來封裝一下來簡化這個過程。

const ThemeContext = React.createContext('light');

function ThemedButton(props) {
  return (
    <ThemeContext.Consumer>
      {theme => <button className={theme} {...props} />}
    </ThemeContext.Consumer>
  );
}
複製代碼

接下來,當你須要使用Context的時候,就不須要在寫什麼Consumer

export default props => (
    ThemeButton(props)
);
複製代碼

轉發refs

當你封裝完一個Consumer以後,或許你想要用ref來獲取Consumer裏面根組件的實例或者對應的DOM。若是直接在Consumer上使用ref,是得不到想要的結果的。因而在React16.3裏面,使用了一種全新的技術(不肯定是否是16.3才引入的),叫作轉發refs 。不只僅用在Context裏面,實際上,在任何你想要把ref傳遞給組件內部的子組件的時候,你均可以使用轉發refs

具體來講,你須要使用一個新的API:React.forwardRef((props, ref) => React.ReactElement),如下面這個爲例:

class FancyButton extends React.Component {}

// Use context to pass the current "theme" to FancyButton.
// Use forwardRef to pass refs to FancyButton as well.
export default React.forwardRef((props, ref) => (
  <ThemeContext.Consumer>
      {
        theme => (
            <FancyButton {...props} theme={theme} ref={ref} />
        )
      }
  </ThemeContext.Consumer>
));
複製代碼

React.forwardRef()接受一個函數做爲參數。實際上,你能夠將這個函數當作一個函數組件,它的第一個參數和函數組件同樣。不一樣的地方在於,它多了一個ref。這意味着若是你在React.forwardRef建立的組件上使用ref的話,它並不會直接被組件消化掉,而是向內部進行了轉發,讓須要消化它的組件去消化。

若是你以爲難以理解,其實這種方法徹底能夠用另外一種方法替代。咱們知道,在React中,ref並不會出如今props中,它被特殊對待。可是換個名字不就好了嗎。

須要提一下的是,之前咱們獲取ref是傳遞一個函數(不推薦使用字符串,這是一個歷史遺留的問題,ref會在某些狀況下沒法獲取到正確的值。vuejs可使用,不要搞混了)。可是這個過程很煩的,咱們只須要把實例或者DOM賦值給對應的變量就好了,每次都寫一下這個同樣模板的代碼,很煩人的好嗎。「千呼萬喚」中,React終於聽到了。如今只須要React.createRef就能夠簡化這個過程了。

class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        this.myRef = React.createRef();
    }
    render() {
        return <div ref={this.myRef} />; } } 複製代碼

使用方法就這麼簡單,沒什麼特別的地方。

回到上面的話題,如今咱們用props來實現轉發refs的功能。

class Input extends React.Component {

    reder() {
		return (
			<label>Autofocus Input:</label>
			<input ref={this.props.forwardRef} type="text" />
		)
    }

}

function forwardRef(Component, ref) {
	return (<Component forwardRef={ref} />); } // 使用forwardRef let input = React.createRef(); forwardRef(Input, input); // 當組件綁定成功以後 input.current.focus(); 複製代碼

React.createRef返回的值中,current屬性表示的就是對應的DOM或者組件實例。forwardRef並無什麼特殊的含義,就是一個簡單的props。這個用法就像是狀態提高同樣。

相關文章
相關標籤/搜索