面向初學者的高階組件介紹

做者:Brandon Newtonjavascript

原文:Higher-Order Components (HOCs) for Beginnershtml

談點:一篇面向初學者的 HOC 介紹。高階組件聽起來挺唬人的,只看名字恐怕不是那麼容易明白到底是何物,並且一般來說高階組件並非組件,而是接受組件做爲參數,而且返回組件的函數。早期利用 ES5 的 mixin 語法來作的事,基本均可以使用高階組件代替,並且能作的還有更多。java

前言

寫這篇文章的原由是其餘關於高階組件(Higher-Order Components)的文章,包含官方文檔,都令初學者感到至關困惑。我知道有高階組件這樣一個東西,但不知道它到底有什麼用。因此,想經過一篇文章來對高階組件有一個更好的理解。react

在此以前,咱們須要先來說一下 JavaScript 中的函數。git

ES6 箭頭函數簡介

接下來將提供一些箭頭函數的簡單示例,若是以前沒有使用過,能夠認爲它們與普通函數基本一致。下面的代碼會展現箭頭函數與普通函數的區別。github

function () {
  return 42
}

// same as:
() => 42

// same as:
() => {
  return 42
}

function person(name) {
  return { name: name }
}

// same as:
(name) => {
  return { name: name }
}

閱讀 MDN 的箭頭函數文檔 瞭解更多信息。json

做爲值的函數與部分調用

就像是數字、字符串、布爾值 同樣,函數也是值,意味着能夠像傳遞其餘數據同樣傳遞函數,能夠將函數做爲參數傳遞給另一個函數。redux

const execute = (someFunction) => someFunction()

execute(() => alert('Executed'))

也能夠在在函數中返回一個函數:api

const getOne = () => () => 1

getOne()()

之因此在 getOne 後面有兩個 () ,是由於第一個返回的返回值是一個函數。以下:數組

const getOne = () => () => 1

getOne
//=> () => () => 1

getOne()
//=> () => 1

getOne()()
//=> 1

從函數返回函數能夠幫助咱們追蹤初始輸入函數。例如,下面的函數接受一個數字做爲參數,並返回一個將該參數乘以新參數的函數:

const multiply = (x) => (y) => x * y

multiply(5)(20)

這個示例跟上述 getOne 同樣,在下面這個例子,讓 x = 5,y = 20。

const multiply = (x) => (y) => x * y

multiply
//=> (x) => (y) => x * y

multiply(5)
//=> (y) => 5 * y

multiply(5)(20)
//=> 5 * 20

在只傳入一個參數調用 multiply 函數時,即部分調用該函數。好比,multiply(5) 講獲得一個將其輸入值乘以 5 的函數,multiply(7) 將獲得一個將其輸入值乘以 7 的函數。依此類推。經過部分調用能夠建立一個預約義功能的新函數:

const multiply = (x) => (y) => x * y

const multiplyByFive = multiply(5)
const multiplyBy100 = multiply(100)

multiplyByFive(20)
//=> 100
multiply(5)(20)
//=> 100

multiplyBy100(5)
//=> 500
multiply(100)(5)
//=> 500

一開始看起來彷佛沒什麼用,可是,經過部分調用這種方式能夠編寫可讀性更高,更易於理解的代碼。舉個例子,能夠用一種更清晰的方式來代替 style-components 的函數插入語法。

// before
const Button = styled.button`
  background-color: ${({ theme }) => theme.bgColor}
  color: ${({ theme }) => theme.textColor}
`

<Button theme={themes.primary}>Submit</Button>
// after
const fromTheme = (prop) => ({ theme }) => theme[prop]

const Button = styled.button`
  background-color: ${fromTheme("bgColor")}
  color: ${fromTheme("textColor")}
`

<Button theme={themes.primary}>Submit</Button>

咱們建立一個接受一個字符串做爲參數的函數 fromTheme("textColor"):它返回一個接受具備 theme 屬性的對象的函數:({ theme }) => theme[prop],而後再經過初始傳入的字符串 "textColor" 進行查找。咱們能夠作得更多,寫相似的 backgroundColortextColor 這種部分調用 fromTheme 的函數:

const fromTheme = (prop) => ({ theme }) => theme[prop]
const backgroundColor = fromTheme("bgColor")
const textColor = fromTheme("textColor")

const Button = styled.button`
  background-color: ${backgroundColor}
  color: ${textColor}
`

<Button theme={themes.primary}>Submit</Button>

高階函數

高階函數的定義是,接受函數做爲參數的函數。若是曾經使用過相似 map 這樣的函數,可能已經很熟悉高階函數。若是不熟悉 map,它是一個數組遍歷的方法,接受一個函數做爲參數應用到數組中的每一個元素。例如,能夠像這樣對一個數組做平方:

const square = (x) => x * x

[1, 2, 3].map(square)
//=> [ 1, 4, 9 ]

能夠實現一個咱們本身的 map 版原本說明這個概念:

const map = (fn, array) => {
  const mappedArray = []

  for (let i = 0; i < array.length; i++) {
    mappedArray.push(
      // apply fn with the current element of the array
      fn(array[i])
    )
  }

  return mappedArray
}

而後再使用咱們的 map 版原本對一個數組做平方:

const square = (x) => x * x

console.log(map(square, [1, 2, 3, 4, 5]))
//=> [ 1, 4, 9, 16, 25 ]

譯者注:咱們也能夠將 map 方法從對象中解耦出來:

const map = (fn, array) => Array.prototype.map.call(array, fn)

這樣也能夠像上述例子同樣調用。
或者更函數式的作法,再來點柯里化:

const map = array => fn => Array.prototype.map.call(array, fn)

或者是返回一個 <li>的 React 元素數組:

const HeroList = ({ heroes }) => (
  <ul>
    {map((hero) => (
      <li key={hero}>{hero}</li>
    ), heroes)}
  </ul>
)

<HeroList heroes=[
  "Wonder Woman",
  "Black Widow",
  "Spider Man",
  "Storm",
  "Deadpool"
]/>
/*=> (
  <ul>
    <li>Wonder Woman</li>
    <li>Black Widow</li>
    <li>Spider Man</li>
    <li>Storm</li>
    <li>Deadpool</li>
  </ul>
)*/

高階組件

咱們知道,高階函數是接受函數做爲參數的函數。在 React 中,任何返回 JSX 的函數都被稱爲無狀態函數組件,簡稱爲函數組件。基本的函數組件以下所示:

const Title = (props) => <h1>{props.children}</h1>

<Title>Higher-Order Components(HOCs) for React Newbies</Title>
//=> <h1>Higher-Order Components(HOCs) for React Newbies</h1>

高階組件則是接受組件做爲參數並返回組件的函數。如何使用傳入組件徹底取決於你,甚至能夠徹底忽視它:

// Technically an HOC
const ignore = (anything) => (props) => <h1>:)</h1>

const IgnoreHeroList = ignore(HeroList)
<IgnoreHeroList />
//=> <h1>:)</h1>

能夠編寫一個將輸入轉換成大寫的 HOC:

const yell = (PassedComponent) =>
  ({ children, ...props }) =>
    <PassedComponent {...props}>
      {children.toUpperCase()}!
    </PassedComponent>

const Title = (props) => <h1>{props.children}</h1>
const AngryTitle = yell(Title)

<AngryTitle>Whatever</AngryTitle>
//=> <h1>WHATEVER!</h1>

你也能夠返回一個有狀態組件,由於 JavaScript 中的類不過是函數的語法糖。這樣就可使用到 React 生命週期的方法,好比 componentDidMount。這是 HOCs 真正有用的地方。咱們如今能夠作一些稍微有趣點的事,好比將 HTTP 請求的結果傳遞給函數組件。

const withGists = (PassedComponent) =>
  class WithGists extends React.Component {
    state = {
      gists: []
    }

    componentDidMount() {
      fetch("https://api.github.com/gists/public")
      .then((r) => r.json())
      .then((gists) => this.setState({
        gists: gists
      }))
    }

    render() {
      return (
        <PassedComponent
          {...this.props}
          gists={this.state.gists}
        />
      )
    }
  }


const Gists = ({ gists }) => (
  <pre>{JSON.stringify(gists, null, 2)}</pre>
)

const GistsList = withGists(Gists)

<GistsList />
//=> Before api request finishes:
// <Gists gists={[]} />
// 
//=> After api request finishes:
// <Gists gists={[
//  { /* … */ },
//  { /* … */ },
//  { /* … */ }
// ]} />

withGists 會傳遞 gist api 調用的結果,而且你能夠在任何組件上使用。點擊這裏 能夠看到一個更加完整的例子。

結論:高階組件是 ???

react-redux 也是使用 HOC, connect 將應用 store 的值傳遞到「已鏈接」 的組件。它還會執行一些錯誤檢查和組件生命週期優化,若是手動完成將致使編寫大量重複代碼。

若是你發現本身在不一樣地方編寫了大量的代碼,那麼也能夠將代碼重構成可重用的 HOC。

HOCs 很是具備表現力,可使用它們創造不少很酷的東西。

儘量地保持你的 HOC 簡單,不要編寫須要閱讀長篇大論才能理解的代碼

附加練習

下面有一些練習,來鞏固對 HOC 的理解:

相關文章
相關標籤/搜索