React系列之一起認識Render Prop

張文軒發表於2019-02-28

1.mixins

寫過react專案的應該都碰到過,不同元件複用相同程式碼的問題,在react早期使用React.createClass建立元件的時代,我們經常使用的是mixins來實現程式碼複用。比如有個元件A,它用來實時的獲取滑鼠的位置。

//A元件
import React from 'react'
import ReactDOM from 'react-dom'

const App = React.createClass({
  getInitialState() {
    return { x: 0, y: 0 }
  },

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  },

  render() {
    const { x, y } = this.state

    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
        <h1>The mouse position is ({x}, {y})</h1>
      </div>
    )
  }
})

ReactDOM.render(<App/>, document.getElementById('app'))
複製程式碼

如果此時有個元件B也想整合這個功能,我們可以通過mixins,程式碼是這樣的

//B元件
import React from 'react'
import ReactDOM from 'react-dom'

const MouseMixin = {
  getInitialState() {
    return { x: 0, y: 0 }
  },

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  }
}

const App = React.createClass({
  // Use the mixin!
  mixins: [ MouseMixin ],
  
  render() {
    const { x, y } = this.state

    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
        <h1>The mouse position is ({x}, {y})</h1>
      </div>
    )
  }
})

ReactDOM.render(<App/>, document.getElementById('app'))
複製程式碼

很容易是吧~但委屈的是react16之後就不再支援mixins了,因為es6普及了呀!。

React系列之一起認識Render Prop
那怎麼辦呢?辦法總是有的,HOC(高階元件)的概念被提出來,什麼是高階元件?說白了其實就是把函式當做引數傳入到另一個函式中,在我們展開高階元件前,我們先來想想除了因為es6的普及react不再支援mixins之外,mixins還有啥其它缺點不。當然是有的,有以下幾點:

  1. 難以溯源,mixins修改state,在元件內部你無法知道state從哪來,尤其是有多個mixins的情況。
  2. 名稱空間,多個mixins修改同一個state導致的命名衝突。

2.HOC(高階元件)

所以為了代替mixins,很多人就提出了HOC(高階元件),程式碼是下面這樣的。

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

const withMouse = (Component) => {
  return class extends React.Component {
    state = { x: 0, y: 0 }

    handleMouseMove = (event) => {
      this.setState({
        x: event.clientX,
        y: event.clientY
      })
    }

    render() {
      return (
        <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
          <Component {...this.props} mouse={this.state}/>
        </div>
      )
    }
  }
}

class App extends React.Component{
    render() {
      // 代替直接處理state,我們從props裡獲得x,y座標
      const { x, y } = this.props.mouse

      return (
        <div style={{ height: '100%' }}>
            <h1>The mouse position is ({x}, {y})</h1>
        </div>
      )
    }
}

//把App元件當做引數傳到withMouse方法裡面,在withMouse內部通過props獲得x、y座標值
const AppWithMouse = withMouse(App)

ReactDOM.render(<AppWithMouse/>, document.getElementById('app'))

複製程式碼

看起來很不錯的樣子!

React系列之一起認識Render Prop
但是,回到之前mixins存在的問題,我們想一想,HOC有上述的問題麼?我們來看下:

  1. 難以溯源,跟mixins不同的是,我們不再糾結state的源頭,我們現在要糾結的是HOC的props裡提供了些啥...
  2. 名稱空間的衝突,這個問題依然存在,props裡屬性名可能會被多個HOC重複使用。

我的天.....

React系列之一起認識Render Prop

3.Render Prop

幸運女神降臨! mmp,前面都是炮灰,到了劃重點的時候啦!

Render Prop是個值為函式的屬性,通過Render Prop,元件知道什麼應該被渲染

很糊塗是不是,看程式碼

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

class Mouse extends React.Component {
  static propTypes = {
    render: PropTypes.func.isRequired
  }

  state = { x: 0, y: 0 }

  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  }

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    )
  }
}

const App = React.createClass({
  render() {
    return (
      <div style={{ height: '100%' }}>
        <Mouse render={({ x, y }) => (
          <h1>The mouse position is ({x}, {y})</h1>
        )}/>
      </div>
    )
  }
})

ReactDOM.render(<App/>, document.getElementById('app'))
複製程式碼

看明白了麼,這裡我們通過定義一個render屬性,值是個函式,描述了我們想要渲染的元素,然後在子元件裡面呼叫該render方法,再回頭看下之前的兩個問題,難以溯源,現在主動權在父元件上,我要什麼資料你們給我拿來就行了,你們子元件各自去實現,我只要結果不要過程,因而就不存在資料來源問題,名稱空間的問題也沒了。好厲害~~~。 最後偷偷的告訴你們一個更厲害的,上面的render方法裡面我們是直接寫出了渲染x,y值,只適用於當前App元件,我們可以通過高階元件來達到為任何元件新增該功能,程式碼是這樣的。

const withMouse = (Component) => {
    return class extends React.Component{
        render() {
            return <Mouse render={mouse=>(
                <Component {...this.props} mouse={mouse}/>
            )}/>
        }
    }
}
複製程式碼

據說react-router原始碼裡面為每個元件增加路由屬性就是通過該方法!

好了!大功完成了,歡迎一起討論學習~

個人部落格地址:意卿

相關文章