pcwu's TIL Notes


[React] Recompose 學習筆記

之前已經筆記過 Higher Order Component, pure function 和 compose 了,終於輪到應用在 React 的 Recompose 了!

React Component

React 的元件分成 Class Component 和 Stateless Component (Functional Component)兩種,其中 Stateless 因為所需建立的東西較少,效能差距明顯,而且是可以寫成 pure function 會更利於開發和測試。

Recompose

React 中 Functional Component 雖然效能佳,但比起 Class Component 能做的事太少,於是就有神人開發了一個工具叫 Recompose,利用 Higher Order Component 的方式來加強 Functional Component 的功能。

以下是 Recompose 的介紹

Recompose is a React utility belt for function components and higher-order components. Think of it like lodash for React.

簡單來說希望能透過 Recompose 這個工具,讓大家在開發 React 元件時,能夠保持以 stateless, pure function 的方式開發,減少 setState 的濫用和增進整體效能。

作者有一個演講影片,先記錄一下,有空再去看。

Recompse 例子

以下簡單筆記2個常用的: withStatewithHandlers

withState()

官方文件如此定義:

withState(
  stateName: string,
  stateUpdaterName: string,
  initialState: any | (props: Object) => any
): HigherOrderComponent

簡單來說就是 Functional(Stateless) Component 既然沒有 state,但有時卻非用 state 不可,只好透過一層元件給予 Functional Component 2個 props 使用: 一個是 state,一個是更新 state 的 function (stateUpdater)。除此之外還需要給這個 state 一個初始值。

特別要注意的是,如果初始值是一個函式的話,會將父元件給予的 props 當作引數餵入此初始函式,回傳值即為初始值。

以下舉一個例子:

const enhance = withState('counter', 'setCounter', props => props.counter);

// 除了原本父元件給的 props 外,還會增加 counter, setCounter 2個 props
const Counter = enhance(({ counter, setCounter }) =>
  <div>
    Count: {counter}
    <button onClick={() => setCounter(n => n + 1)}>++</button>
    <button onClick={() => setCounter(n => n - 1)}>--</button>
  </div>
)

所以可以簡單看得出來, withState 夾在原本 Counter 的父元件跟 Counter 之間,新增了一個 state 叫 counter,並設定了一個更新的函式叫 setCounter (引數為 counter,回傳值會成為 counter 新的值 ),且初始值為從父元件傳過來的 props.counter

withHandlers()

官方文件如此定義:

withHandlers(
  handlerCreators: {
    [handlerName: string]: (props: Object) => Function
  } |
  handlerCreatorsFactory: (initialProps) => {
    [handlerName: string]: (props: Object) => Function
  }
): HigherOrderComponent

如果以剛剛 counter 的例子來擴充的話,會長這樣:

const enhance = compose(
  withState('counter', 'setCounter', 0),
  withHandlers({
    increment: ({ setCounter }) => () => setCounter(n => n + 1),
    decrement: ({ setCounter }) => () =>  setCounter(n => n - 1),
    reset: ({ setCounter }) => () => setCounter(0)
  }))
)

const Counter = enhance(({ counter, increment, decrement, reset }) =>
  <div>
    Count: {counter}
    <button onClick={() => increment() }>++</button>
    <button onClick={() => decrement() }>--</button>
    <button onClick={() => reset() }>RESET</button>
  </div>
)

withStatewithHandlers 搭配處理表單

表單處理常常需要有一個 state 紀錄目前使用者表單上的輸入值,可以進行即時正確性的確認,也需要 onChangeonSubmit 事件的處理。這種情況下,利用withStatewithHandlers 搭配剛剛好!

const enhance = compose(
  withState('value', 'updateValue', ''),
  withHandlers({
    onChange: props => event => {
      props.updateValue(event.target.value)
    },
    onSubmit: props => event => {
      event.preventDefault()
      submitForm(props.value)
    }
  })
)

const Form = enhance(({ value, onChange, onSubmit }) =>
  <form onSubmit={onSubmit}>
    <label>Value
      <input type="text" value={value} onChange={onChange} />
    </label>
  </form>
)

這邊特別注意的是, withState 中的 value, updateValue,對於 withHandlers 來說是父元件傳下來的 props,所以調用時是 props.updateValueprops.Value,當然也可以使用前一個 counter 例子中的解構賦值。以下2種不同方式比較:

onChange: props => event => {
  props.updateValue(event.target.value)
}

onChange: ({ updateValue }) => event => {
  updateValue(event.target.value)
}

剩下的幾乎還沒什麼用到,等有用熟了再來筆記一下。

Reference