React中的Context和Portals用法

wuzhengyan2015發表於2018-07-29

React16.3更新了很多新的內容:生命週期、Context、React.createRef()、Portals等等。對於更新飛快的前端來說,我們應該已經習慣了要不斷學習╮(╯▽╰)╭。本文將介紹官方文件兩個結合新內容Context和Portals。

Context

Context 提供了一種不用手動一層層傳遞props就能在元件樹中傳遞資料的方式。

在傳統的React應用中,資料通常從父元件通過props一層層傳遞給子元件,但是這種方式在屬性需要被很多元件用到的時候會顯得很麻煩。

所以Context就被設計來在元件樹中共享一些“全域性”資料。

直接看例子吧

import React, {Component} from 'react'
const ThemeContext = React.createContext('light')

class App extends Component {
  render () {
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    )
  }
}

function Toolbar () {
  return (
    <div>
      <ThemeButton />
    </div>
  )
}

function ThemeButton (props) {
  return (
    <ThemeContext.Consumer>
      { theme => <button {...props} theme={theme}>{theme}</button> }
    </ThemeContext.Consumer>
  )
}
複製程式碼

可以看到ThemeButton中直接獲取到了theme屬性,並沒有通過Toolbar來傳遞props,這正是Context做的事。

上面的例子中出現新的API:React.createContext、Provider、Consumer。接下來一一介紹下。

React.createContext

const {Provider, Consumer} = React.createContext(defaultValue);
複製程式碼

React.createContext 建立了 { Provider, Consumer } 這一對物件。當你使用Consumer來獲取資料時,它會匹配在最近的一個對應的Provider。

defaultValue僅用在Consumer沒有匹配到Provider時,Consumer就會使用defaultValue。

Provider

<Provider value={/* some value */}>
複製程式碼

Provider是一個允許Consumer訂閱context變化的元件。

Provider接受一個value屬性,當它變化時,它後代Consumer元件也會相應的接受到變化的值。

Consumer

<Consumer>
  {value => /* render something based on the context value */}
</Consumer>
複製程式碼

Consumer是一個訂閱context變化的元件。它需要在節點中傳入一個函式,函式接受一個當前的context值,返回一個React節點。

一旦父節點中的Provider改變context值,Consumer會重新渲染。這邊要注意的是及時Consumer父元件中shouldComponentUpdate返回false,Consumer包含的元件還是會進行更新。

API相當簡潔明瞭,最後看個例子鞏固下。

import React, {Component} from 'react'
const ThemeContext = React.createContext({
  theme: 'light',
  changeTheme: () => {}
})

class App extends Component {
  constructor () {
    super()
    this.changeTheme = this.changeTheme.bind(this)
    this.state = {
      theme: 'light',
      changeTheme: this.changeTheme
    }
  }
  changeTheme () {
      this.setState({
          theme: 'dark'
      })
  }
  render () {
    return (
      <ThemeContext.Provider value={this.state}>
        <Toolbar />
      </ThemeContext.Provider>
    )
  }
}

function Toolbar () {
  return (
    <div>
      <ThemeButton />
    </div>
  )
}

function ThemeButton (props) {
  return (
    <ThemeContext.Consumer>
      { ({theme, changeTheme}) => <button {...props} onClick={changeTheme}>{theme}</button> }
    </ThemeContext.Consumer>
  )
}
複製程式碼

Portals

Portals 提供一種把元件子元素渲染到其他Dom節點下的方法

ReactDOM.createPortal(child, container)
複製程式碼

第一個引數就是可渲染的react元素, 第二個引數就是要渲染在其下的節點。

import React, {Component} from 'react'
import ReactDOM from 'react-dom';

function App () {
    return (
        <div>
            <Modal>
                {[1, 2, 3, 4]}
            </Modal>
        </div>
    )
}

class Modal extends Component {
  constructor(props) {
    super(props);
    this.el = document.createElement('div');
  }
  componentDidMount() {
    document.body.appendChild(this.el)
  }
  componentWillUnmount() {
    document.body.removeChild(this.el)
  }
  render() {
    return ReactDOM.createPortal(
      this.props.children,
      this.el,
    )
  }
}
複製程式碼

上面的例子中陣列內容會被渲染到document.body下的一個div中,而不是渲染到App渲染的div中。這就實現了把元素渲染到其他DOM節點下的功能。

這邊要注意的是Modal的事件冒泡還是會經過App中元素,而不是直接到body去。

總結

本文介紹了兩個新API:Context和Portals。這兩個使用起來還是比較簡單的,但是還是要專案應用才能找到最佳實踐。

相關文章