(譯)React 元件設計模式基礎

創宇前端發表於2018-02-02

原文連結:React Component Patterns

作者:Gustavo Matheus

隨著 React 在前端開發中越來越流行,各種各樣的設計模式及新概念亦層出不窮。本文旨在總結 React 開發中一些常見的設計模式。

有狀態 (Stateful) vs 無狀態 (stateless)

React 元件可以是有狀態的,在其生命週期內可以操縱並改變其內部狀態;React 元件也可以是無狀態的,它僅接受來自父元件傳入的 props,並進行展示。

下面是一個無狀態Button 元件,它的行為完全由傳入的 props 決定:

const Button = props => 
  <button onClick={props.onClick}>
    {props.text}
  </button>
複製程式碼

下面是一個有狀態元件(使用了上述的無狀態元件):

class ButtonCounter extends React.Component {
  constructor() {
    super();
    this.state = { clicks: 0 };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState({ clicks: ++this.state.clicks });
  }

  render() {
    return (
      <Button
        onClick={this.handleClick}
        text={`You've clicked me ${this.state.clicks} times !`}
      />
    )
  }
}
複製程式碼

正如你所看到的,上述 ButtonCounter 元件在 state 中維護了自己的狀態,而之前的 Button 元件僅根據 props 來進行渲染展示。這個區別看似很小,但是無狀態的 Button 元件卻高度可複用。

容器(Container) vs 展示(Presentational) 元件

當與外部資料進行互動時,我們可以把元件分為兩類:

  • 容器元件:主要負責同外部資料進行互動(通訊),譬如與 Redux 等進行資料繫結等。
  • 展示元件:根據自身 state 及接收自父元件的 props 做渲染,並不直接與外部資料來源進行溝通。

我們來看一個展示元件:

const UserList = props =>
  <ul>
    {props.users.map(u => (
      <li>{u.name} - {u.age} years old</li>
    ))}
  </ul>
複製程式碼

而這個展示元件可以被一個容器元件更新:

class UserListContainer extends React.Component {
  constructor() {
    super()
    this.state = { users: [] }
  }

  componentDidMount() {
    fetchUsers(users => this.setState({ users }));
  }

  render() {
    return <UserList users={this.state.users} />
  }
}
複製程式碼

通過將元件區分為容器元件與展示元件,將資料獲取與渲染進行分離。這也使 UserList 可複用。如果你想了解更多,這裡有一些非常好的文章,解釋地非常清楚。

高階(Higher-Order)元件

當你想複用一個元件的邏輯時,高階元件(HOC)就派上用場了。高階元件就是 JavaScript 函式,接收 React 元件作為引數,並返回一個新元件。

舉個例子:編寫一個選單元件,當點選一個選單項時,展開當前選單項,顯示子選單。當然我們可以在父元件裡來控制此選單元件的狀態,但是更優雅的方式,是使用高階元件:

function makeToggleable(Clickable) {
  return class extends React.Component {
    constructor() {
      super();
      this.toggle = this.toggle.bind(this);
      this.state = { show: false };
    }

    toggle() {
      this.setState({ show: !this.state.show });
    }

    render() {
      return (
        <div>
          <Clickable
            {...this.props}
            onClick={this.toggle}
          />
          {this.state.show && this.props.children}
        </div>
      );
    }
  }
}
複製程式碼

通過這種方式,我們可以使用 JavaScript 的裝飾器語法,將我們的邏輯應用於 ToggleableMenu 元件:

@makeToggleable
class ToggleableMenu extends React.Component {
  render() {
    return (
      <div onClick={this.props.onClick}>
        <h1>{this.props.title}</h1>
      </div>
    );
  }
}
複製程式碼

現在,我們可以將任何子選單內容放入 ToggleableMenu 元件中:

class Menu extends React.Component {
  render() {
    return (
      <div>
        <ToggleableMenu title="First Menu">
          <p>Some content</p>
        </ToggleableMenu>
        <ToggleableMenu title="Second Menu">
          <p>Another content</p>
        </ToggleableMenu>
        <ToggleableMenu title="Third Menu">
          <p>More content</p>
        </ToggleableMenu>
      </div>
    );
  }
}
複製程式碼

當你在使用 Reduxconnect,或者 React RouterwithRouter 函式時,你就是在使用高階元件!

渲染回撥(Render Callbacks)

除了上述的高階元件外,渲染回撥是另一種使元件可複用的設計模式。渲染回撥的核心是元件接收的子元件(或子結點,亦即 props.children),不以 React Component 提供,而是以回撥函式的形式提供。以上述 HOC 元件為例,我們通過渲染回撥的方式重寫如下:

class Toggleable extends React.Component {
  constructor() {
    super();
    this.toggle = this.toggle.bind(this);
    this.state = { show: false }
  }

  toggle() {
    this.setState({ show: !this.state.show });
  }

  render() {
    return this.props.children(this.state.show, this.toggle)
  }
}
複製程式碼

現在,我們可以傳入回撥函式給 Toggleable 元件作為子結點。 我們用新方式實現之前的 HOC 元件 ToggleableMenu

const ToggleableMenu = props => (
  <Toggleable>
    {(show, onClick) => (
      <div>
        <div onClick={onClick}>
          <h1>{props.title}</h1>
        </div>
        { show && props.children }
      </div>
    )}
  </Toggleable>
)
複製程式碼

而我們全新的 Menu 元件實現如下:

class Menu extends React.Component {
  render() {
    return (
      <div>
        <ToggleableMenu title="First Menu">
          <p>Some content</p>
        </ToggleableMenu>
        <ToggleableMenu title="Second Menu">
          <p>Another content</p>
        </ToggleableMenu>
        <ToggleableMenu title="Third Menu">
          <p>More content</p>
        </ToggleableMenu>
      </div>
    );
  }
}
複製程式碼

是的,你沒有看錯,新的 Menu 元件同之前以HOC模式實現出來的一模一樣!

在這種實現方式下,我們將元件內部的狀態(state)與元件的渲染邏輯進行剝離。在上面的例子中,我們將渲染邏輯放在了 ToggleableMenu渲染回撥中,而展示元件的狀態(state)依然在 Toggleable 元件內進行維護。

瞭解更多

以上的一些例子僅僅是 React 設計模式的基礎知識。如果你想更加深入地瞭解關於 React 設計模式的話題,以下是一些非常好的學習資料,值得一看:


關注微信公眾號:創宇前端(KnownsecFED),碼上獲取更多優質乾貨!

(譯)React 元件設計模式基礎

相關文章