Render props、render callback 和高階元件皆可互換[譯]

小紅星閃啊閃發表於2018-10-04

讓 render-xxx 模式都可以互換。

基礎

所有上面提到的三種模式都是為了處理 mixin 要處理的問題的。在我們繼續之前,我們來看一些例子。

如果你已經掌握這兩個概念,可以直接跳過這一節看後面的內容

Render Prop

首先,我們來寫一個元件來記錄 count,並繪製 render prop 裡的繪製都在 render 方法裡呼叫了。

class CounterComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  update = type => {
    if (type === "Inc") {
      this.setState(({ count }) => ({ count: count + 1 }));
    } else if (type === "Dec") {
      this.setState(({ count }) => ({ count: count - 1 }));
    }
  };

  render() {
    return this.props.render({
      ...this.state,
      ...this.props,
      update: this.update
    });
  }
}
複製程式碼

注意:callback 屬性也可以叫個別的名字,不一定就是 render。只不過這個模式叫做 render prop。

接下來我們需要一個元件來把需要的內容繪製到螢幕上:

const Counter = ({ count, update }) => {
  <div>
    <button onClick={() => update("Inc")}>Inc</button>
    {count}
    <button onClick={() => update("Dec")}>Dev</button>
  </div>;
};
複製程式碼

Counter元件接受 count 值和一個 update 方法,然後顯示一個增加、一個減少按鈕以及當前的 count 值。

最後我們可以用CounterComponent元件來給Counter增加一些其他的功能:

render(
  <CounterComponent render={props => <Counter {...props} />>} />,
  document.getElementById('root')
);
複製程式碼

高階元件

上文講解了 render prop 模式。現在來看看怎麼使用高階元件來達到同樣的目的。

const withCounter = Component =>
  class Hoc extends React.Component {
    constructor(props) {
      super(props);
      this.state = { count: 0 };
    }

    update = type => {
      if (type === "Inc") {
        this.setState(({ count }) => ({ count: count + 1 }));
      } else if (type === "Dec") {
        this.setState(({ count }) => ({ count: count - 1 }));
      }
    };

    render() {
      return <Component {...this.state} {} />
    }
  };
複製程式碼

看上面的例子,我們可以要把需要繪製的元件放到另外一個全新的元件裡面去。在這個新的元件裡包含了加強的 state 和 props 等內容。

const CounterExample = withCounter(Counter);
render(<CounterExample />, document.getElementById("root"));
複製程式碼

目前我們已經覆蓋了基本的概念。我們可以使用不同的模式來達到相同的目的。下面我們來關注一下如何讓這幾個模式達到互換的目的。

在 render props 和高階元件之間轉換

有的時候你用的庫提供了一個高階元件,但是你最喜歡的是通過JSX的方式來使用元件。有時會遇到一個提供了 render props 的庫,但是你喜歡的是高階元件。一個很有趣的事實是這些模式可以幻想轉換。

我們來根據上面的例子來加一些方法可以讓高階元件和 render props 模式可以互相轉換。

fromHoc: HOC -> RenderProp
toHoc: RenderProp -> HOC
複製程式碼

toHoc方法可以歸結為:

toHoc: Render => Comp => props => (
  <Render {...Props} render={props => <Comp {...props} />} />
);
複製程式碼

你也可以看使用 Render Props來作為替代實現。

它會把一個 render prop 模式轉化為高階元件。

const withCounter = toHoc(CounterComponent);
const CounterExample = withCounter(Counter);
複製程式碼

從高階元件轉化為 render prop 有一點麻煩。我們需要把一個 render prop 的元件傳入到高階元件裡。多虧了 Brent Jackon 的這篇文章

fromHoc: hoc => {
  class Render extends React.Component {
    render() {
      return this.props.children(this.props);
    }
  }

  return hoc(Render);
};
複製程式碼

或者,使用兩外一種不用 class 的方式。這次要感謝 Rodrigo Pombo 的這個例子

fromHoc: hoc => hoc(props => props.render(props));
複製程式碼

我們可以寫一個輕量的 helper 方法來實現高階元件和 renderprops 的轉化。注意,我們也可以在初始化 toHoc 方法的時候使用另外的 render name,因為 render prop 可以能有一個不一樣的名字,或者是一個子 prop。

const iso = {
  fromHoc: hoc => hoc(props => props.render(props)),
  toHoc: Render => Compo => props => (
    <Render {...props} render={props => <Comp {...props} />} />
  )
};
複製程式碼

總結

Render prop,回撥繪製和高階元件都是可以互換的。大多數的時候 render props 可以滿足要求。但是你也可以通過某些方法讓它在這些模式之間互相轉換。

非常感謝 Brent Jackson,Kent C. Dodds 以及 Rodrigo Pombot 提供了fromHoc方法的實現,幫我們解決了不少的問題。

from:medium.com/javascript-…

相關文章