High Order Component

首先要聲明的一點是:HOC 的主要目的不是幫助提高性能,而是幫你更好得組織代碼。它可以讓你的代碼更加清晰,讓重複的邏輯可以更容易被發現和抽象。它甚至有可能導致性能有更多的開銷。

在介紹 HOC 之前,我們需要先了解一下這個概念的來源:High Order Function。

High Order Function

什麼是 High Order Function?

下面是來自維基百科的定義:

A function that does at least one of the following:

  • Takes one or more functions as arguments,

  • Returns a function as its result.

就是說如果一個方法得參數是方法,或者返回了一個方法,那它就是 High Order Function。下面我們來看兩個例子:

const map = (mapper) => (array) => {n const result = [];n for (item in array) {n result.push(mapper(item));n }n return result;n}n

這個例子是一個 map 方法,我們可能已經在很多地方都用過了。它接受一個方法來處理數據,同時返回一個方法,所以它就是一個 High Order Function。

下面再看一個例子:

const add = (m) => (n) => m + n;nconst add4 = add(4);nadd4(2); // 6n

第二個例子是一個 add 方法,它接受一個數字,返回一個方法,所以它也是 High Order Function。如果我們有許多地方的邏輯都是與 4 相加,那麼我們可能就需要 add4 這個方法了。

為什麼需要 High Order Function?

從我個人來看,function 就是邏輯,有時候我們希望我們的邏輯可以更加靈活,所以我們需要傳遞方法來靈活的定製邏輯,就像 map;有時候我們希望曾強一個現有的方法,下面就是一個例子:

const mapArgument = (argumentMapper) => (targetFunction) => {n return (arg) => targetFunction(argumentMapper(arg));n}nnconst consoleNumber = number => console.log(`The number is: ${number}`);nconst enhance = mapArgument(number => number * 2);nconst consoleDoubledNumber = enhance(consoleNumber);nnconsoleNumber(2) // The number is: 2nconsoleDoubledNumber(2) // The number is: 4n

mapArguments 是一個 High Order Function,他可以改變 targetFunction 的參數,從而改變它的行為,也可以理解為增強它。比如說,我們有一個方法 consoleNumber 來列印出輸入得數字,然後我們現在希望列印這個數字的 2 倍,我們可以通過 mapArgument 生成一個 enhance 方法,通過把 enhance 方法套用到 consoleNumber 上實現這個需求。

同時 enhance 方法不僅可以作用到 consoleNumber 上,它可以作用到所有輸入得參數是數字的方法上,讓輸入乘以 2,類似的抽象可以幫助我們在寫代碼的時候減少很多重複邏輯。

這種增強不僅可以針對方法,還可以針對 component,於是我們就有了 High Order Component。

High Order Component

什麼是 High Order Component?

下面是來自 Facebook 對於 High Order Component 的定義:

A higher-order component is a function that takes a component and returns a new component.

跟 High Order Function 很類似。只是輸入和輸出都必須是 component。下面我們來看一個例子:

import React from react;nimport { connect } from react-redux;nnconst User = ({ username }) => <div>The user is: {username}</div>;nconst UserPage = connect(n ({ state }) => ({ username: state.user.username })n)(User);n

如果大家對於 redux 熟悉的話,那麼你其實已經使用了很多得 High Order Component,因為 react-redux 的 connect 就是一個 High Order Component。

為什麼要使用 High Order Component?

首先我們來看看 connect 的作用是什麼?它的存在是因為每一個 container 都需要在 componentDidMount 的時候監聽 store 的改變,然後改變 state,最後在 componentWillUnmount 裡面銷毀這個監聽。所以 HOC 的作用就是抽取 component 之間的重複邏輯。以前我們是使用 mixin 來抽取重複邏輯的,但是現在已經被廢棄掉了, HOC 就是它的替代品。

問題

我們先來一起看一個例子:

connect(state => ({ n firstName: state.user.firstName, n lastName: state.user.lastName n}))(n withHandler({ onClick: () => {} })(n withProps(({ firstName, lastName }) => n ({ name: `${firstName} ${lastName}` }))(n pure(({ name, onClick }) => (n <div onClick={onClick}>The user is: {name}</div>n ))n )n )n)n

如果我們有非常多的 HOC 需要套用到一個 component 上面,就會變成一個方法嵌套另一個方法,這種嵌套的方法是非常難看的。同時我們有很多常用的 HOC,我們不想每個項目都去實現一次。所以開源社區從函數式編程借來了 compose 的概念,做了一個叫 recompose 的庫,來解決這兩個問題。

Recompose

什麼是 recompose

Recompose 提供了 compose 方法和很多常用的 HOC。我們先來看看如何用 recompose 來重寫之前的例子:

import { compose, withHandler, withProps, pure } from recompose;nncompose(n connect(state => ({ n firstName: state.user.firstName,n lastName: state.user.lastName,n })),n withHandler({ onClick: () => {} }),n withProps(({ firstName, lastName }) => ({ n name: `${firstName} ${lastName}`,n })),n pure,n)(({ name, onClick }) => (n <div onClick={onClick}>The user is: {name}</div>n));n

我們可以看到,compose 解決了函數嵌套的問題,同時常用的 HOC 都可以直接從 recompose 拿出來用。

Recompose 的好處

  • 它可以幫助我們把所有與 render 不相關的邏輯都抽取出來,讓 view 更容易變成 functional stateless component。
  • 與 render 不相關的邏輯的邏輯都會放在同樣的地方,我們更容易發現一些重複的邏輯,例如:相同的 handler,相同的 state 和 setState
  • 這些重複的邏輯被抽離以後,我們通過 compose 可以很容易的被套用到 component 上

總結

High Order Function 給出了如何在 function 之間做抽象的範式;High Order Component 給出了如何在 component 之間做抽象的範式。

其實 HOC 只是一個很簡單的概念,但是通過 recompose 和 HOC,我們可以更加函數式的寫 component。當整個項目都使用越來越多的函數式編程,我們會感受到由量變引起的質變,感受到函數式的力量。

參考資料

  • High Order Function 的定義:Higher-order function

  • High Order Function 的介紹:Eloquent JavaScript

  • Facebook 寫的 HOC 介紹:Higher-Order Components - React

  • 為什麼 Facebook 使用 HOC 來替代 Mxin Mixins Considered Harmful - React Blog

  • Recompose 關於性能的說明:acdlite/recompose

推薦閱讀:

React + Redux 性能優化
React 父組件引發子組件重渲的時候,如何保持子組件的狀態更新不受影響?
React V16 錯誤處理(componentDidCatch 示例)

TAG:前端开发 | 函数式编程 | React |