gracekrcx / weekly-notes

4 stars 0 forks source link

HOC in react #70

Open gracekrcx opened 4 years ago

gracekrcx commented 4 years ago

higher-order component (HOC) in react

希望透過底下的問題去初步了解 HOC


官方文章 官方文件是一切的開頭。

A higher-order component (HOC) is an advanced technique in React for reusing component logic. HOCs are not part of the React API, per se. They are a pattern that emerges from React’s compositional nature. Concretely, a higher-order component is a function that takes a component and returns a new component.

翻譯: HOC 的目的是為了 reuse component 的 logic。 HOC 是一個 pattern,它們是從 React 的組成性質中產生的一種 pattern。

白話來說: HOC 是 react 用來 reuse component logic 的 pattern,讓元件透過 HOC 包裝後可以得到共同的功能,HOC 是一個 function,他輸入一個 component 然後回傳一個新的,或是說被附加功能的 component。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

HOC 本質

  1. 是 Functional Programming 裡的 higher-order function higher-order function is a function that does at least one of the following:

    takes one or more functions as arguments returns a function as its result.

  2. HOC 不關心你傳遞的數據 (props) 是什麼,並且被包裝組件( WrappedComponent )不關心數據來源

HOC 實際應用

reuse component logic

  1. react-redux 的 connect 上,或是官方的範例上可以清楚的了解,當有許多 component 需要在 componentDidMount 時做相同的事,這時就很適合用 HOC 這個 pattern

  2. 許多 component 都需要 loading 的控制或是 errorHandle, auth

    
    const List = ({ data, isLoading }) => (
    isLoading ?
    <div>我正在 loading...</div> :
    <div>context</div>
    )

// 1. 上面範例需要修改原來組件的代碼 // 2. 如果有其它組件,也需要 loading 功能,這時就需要重複寫相同的判斷邏輯


3. 當遇到多個元件有相同的 state 而且在生命週期函數有相同的處理邏輯,就可以考慮建立 HOC 來處理。由於這些 state 被抽離到父元件,便會以 props 的方式傳進去,被 HOC 包起來的子元件會接收到 HOC 額外提供的 props

### HOC 使用上注意 3 點
1. displayName
2. ref : ref 引用的是最外層的容器組件而不是被包裝組件(WrappedComponent)
3. static method

參考文章:
[進階 React Component Patterns 筆記(下)](https://blog.techbridge.cc/2018/07/21/advanced-react-component-patterns-note-II/)
[React 高階組件淺析](https://segmentfault.com/a/1190000010845410)

### HOC 優點
1. HOC 不會修改傳入的 original Component
2. HOC is a pure function with zero side-effects. 無副作用的純函式
3. 邏輯與 UI 切開,UI 變成 functional stateless component,各司其職
4. HOC 適合打造 data pipeline

### HOC 缺點
[探索Vue高阶组件](http://hcysun.me/2018/01/05/%E6%8E%A2%E7%B4%A2Vue%E9%AB%98%E9%98%B6%E7%BB%84%E4%BB%B6/)
文章中提到,HOC 不是銀彈,可參考 Michael Jackson - Never Write Another HoC,其觀點是:使用普通 component 配合 render prop 可以做任何 HOC 能做的事情。

### compose
```js
const EnhancedList = compose(
  withRouter,
  flatten('list'),
  withLoading,
)(List)
  1. 也可以用 pipe 函數,它和 compose 函數類似,pipe 的組合順序是從左到右而 compose 是『右到左』。

  2. The compose utility function is provided by many third-party libraries including lodash (as lodash.flowRight), Redux, and Ramda.

註:component 是一個以資料為進、UI 為出的函數。link

結論

  1. HOC 好拆,好搬移,好共用
  2. 把共用的邏輯抽出來,然後就可以串接不同的邏輯在一個 pure function 上 例如:一個 pure function 要有 loading 要有 auth,就可以使用 HOC 去處理

學習心得

接觸新東西,又是一個蠻大的主題時,很難由淺到深一次吸收,在有限的時間裡,先把該懂的基礎了解,艱深的範例之後有機會再回頭看

參考文章

High Order Component 如何理解React 高階組件(HOC)? 探索Vue高阶组件 進階 React Component Patterns 筆記(下) HOC 與Render Props,談我從她們身上學到什麼 React Developer 不可不知的 Higher Order Component(HOC / 高階元件) Understanding Higher-Order Components in React 從Redux來學習Functional Programming(compose篇) form example Higher Order Component(HOC) design pattern in React. Creating forms in React with hooks and HOC

gracekrcx commented 4 years ago

recompose

withState

用 withState 把 state 獨立出來,以下面的例子來說,counter 初始值是 0,setCounter 用來設定 counter 的值,counter 跟 setCounter 會被當成 props 傳進 Component 內

import { withState } from 'recompose'
const enhance = withState('counter', 'setCounter', 0)
const Counter = ({ counter, setCounter }) => (
  <div>
    Count: {counter}
    <button onClick={() => setCounter(n => n + 1)}> + </button>
  </div>
)
export default enhance(Counter)

compose

import { compose } from 'recompose'
const MyComponent = () => (
  // ...
)
const enhance = compose(hoc1, hoc2, hoc3)
export default enhance(MyComponent)
// export default hoc1(hoc2(hoc3(MyComponent)))  // compose 由右到左

參考文章 對 recompose 的介紹

gracekrcx commented 4 years ago

閱讀筆記 React Higher Order Components in depth

hocFactory:: W: React.Component => E: React.Component

W (Wrapped Component) is the React.Component being wrapped E (Enhanced Component) is the new, HOC, React.Component being returned.

被 wrap 的 component 可以 mean one of two things:

  1. Props Proxy (屬性代理)
  2. Inheritance Inversion (反向繼承)

props proxy : HOC 對傳給 WrappedComponent 的 porps 進行操作 Inheritance Inversion : HOC 繼承 WrappedComponent

function iiHOC(WrappedComponent) {
  return class Enhancer extends WrappedComponent {  //  繼承 WrappedComponent
    render() {
      return super.render()
    }
  }
}

返回的 HOC 繼承(Enhancer)了 WrappedComponent。WrappedComponent 被動地被Enhancer 繼承,而不是WrappedComponent 去繼承 Enhancer。Inheritance Inversion 允許 HOC 通過 this 訪問到 WrappedComponent。(不是很懂就先記下來)

What can I do with HOCs?

state Abstraction

看了些文章之後,覺得 state Abstraction 像是你如何對你取得的資料做一個資料結構上的配置,是 array 還是 object,要自己一個 key 還是和別人合在一起

參考文章: React 高階組件淺析 深入理解 React 高阶组件

gracekrcx commented 4 years ago

接續上篇

Appendix A: HOC and parameters

使用參數在 HOCs 是很有用的 Example: HOC parameters with a trivial Props Proxy.

function HOCFactoryFactory(...params){
  // do something with params
  return function HOCFactory(WrappedComponent) {
    return class HOC extends React.Component {
      render() {
        return <WrappedComponent {...this.props}/>
      }
    }
  }
}

HOCFactoryFactory(params)(WrappedComponent)

Appendix B: Difference with Parent Components

Example: Parent Components accessing its children.

class Parent extends React.Component {
    render() {
      return (
        <div>
          {this.props.children}
        </div>
      )
    }
  }
}

render((
  <Parent>
    {children}
  </Parent>
  ), mountNode)

Generally, if you can do it with Parent Components you should because it’s much less hacky than HOCs, but as the list above states they are less flexible than HOCs.

gracekrcx commented 4 years ago

第一個 function call 的 output 是第二個 function call 的 input, As a result, we get a compound function.

const listGroup = items =>
  listGroupTag(listGroupItems(items))

composed 之後

const listGroup = items =>
  compose(listGroupTag, listGroupItems)(items)

好處: Breaking our code out into multiple library files allows us to reuse these functions in other projects.

文章: Functional JavaScript: Function Composition For Every Day Use

gracekrcx commented 4 years ago

If you stick to this pattern, you’ll end up with reusable components that are both readable and easy to test as each component is only responsible for a single task.

Making components reusable means to decouple them from the data.

So, it’s all about functions. To be more precise, it’s all about simple functions. This means that each function should only be responsible for a single task. The simpler the function, the more reusable it is.

you also decorate the presentational component using HoC with the functionality you want. As a result, you can reuse a particular presentational component in different places of your application and decorate it with the HoC you want for a particular case.

By using the HoC pattern in the previous example, we moved all the logic to the HoC, and just let the base component render the UI. As a result, our presentational component became reusable since it just receives data as props and renders it to the screen.

The HoC and the base component are both reusable and independent of each other. The HoC doesn’t know where its data goes and the presentational component has no idea where its data is coming from.

This HoC doesn’t have any side effects. It doesn’t mutate anything. It’s a pure function.

If you need a complex logic for your presentational component, you don’t need to store it all inside a single component or in a single HoC. Instead, you just compose several simple HoCs together and enhance your presentational component with them.

The HoC pattern helps us to achieve this since its idea is to move the logic to the HoC and let the presentational functional component take care of the UI rendering.

Higher-Order Components: The Ultimate Guide

gracekrcx commented 4 years ago

最基本的 pattern

import React from 'react';

const higherOrderComponent = (WrappedComponent) => {
  return class HOC extends React.Component {
    render() {
      return <WrappedComponent {...this.props} />;
    }
  }
};

The goal of this pattern is to decompose the logic into simpler and smaller functions that can be reused. A rule of thumb is a function does just one task and does it well. This also avoids side effects ( changing anything that is not owned by the function ) , and makes debugging and maintenance a whole lot easier.

Introduction to higher-order components (HOC) in React github

gracekrcx commented 4 years ago
import _ from 'lodash';
import handleKeyboard from './handleKeyboard';
import restrictWebviewAccess from './restrictWebviewAccess';
import fillWidth from './fillWidth';
import sessionEvents from './sessionEvents';
import browserFunctionality from './browserFunctionality';
import reloadOnReactivate from './reloadOnReactivate';
import addSplash from './addSplash';
import WebView from './WebView'; // Base Component

// Compose the new webview
export default _.flow([
  restrictWebviewAccess, // block access to external sites
  browserFunctionality, // handle android back button
  reloadOnReactivate, // reload after the app has been inactive
  handleKeyboard, // make android not hide webview input
  fillWidth,// make webview full width
  sessionEvents, // add login, logout session events
  addSplash // add splash to webview
])(WebView);
  1. 可以看到切割多個 function 的作法
  2. 以及 HOC 多層的 ref 解決方法
  3. 在 input onChange, onBlur 都是一個一個 HOC 去疊加(妙)

Stacked Higher Order Components (HOC) done the right way

gracekrcx commented 4 years ago

How then do we handle form specifics, props and state for multiple different forms?

Identify the state or props that every form should have

perhaps the following:

formAction
formName
handleChange
handleSubmit
inputNames
notes
errors

link

gracekrcx commented 4 years ago

You can imagine that in a large app, this same pattern of subscribing to DataSource and calling setState will occur over and over again. We want an abstraction that allows us to define this logic in a single place and share it across many components. This is where higher-order components excel.

這段文裡出現了 abstraction, an abstraction 一個抽象,意思感覺是經過思考過後組合一個東西出來,所以 functional component 很常在翻譯的時候突然冒出一個直翻的抽象

由「為什麼 “abstraction”不應該譯為“抽象化”」談正名

「abstraction …中文應該像是萃取、或是提煉吧…。」 abstraction 是摘要/提取出你所需要或感興趣的系統物件或類別的特質。 「乘法是加法的抽象」

gracekrcx commented 2 years ago

Function Parameters VS Arguments Parameters-vs-Arguments