React 组件模式-有状态组件 x 无状态组件、容器组件 x 展示组件、高阶组件 x 渲染回调(函数作为子组件)

10年服务1亿前端开发工程师

小编推荐:掘金是一个面向程序员的高质量技术社区,从 一线大厂经验分享到前端开发最佳实践,无论是入门还是进阶,来掘金你不会错过前端开发的任何一个技术干货。

有状态组件 x 无状态组件、容器组件 x 展示组件、高阶组件 x 渲染回调(函数作为子组件)

自从我使用 React(Facebook,一个使用 JavaScript 构建用户界面的库)以来已经有一段时间了 – 当我刚刚开始时,我希望我知道一些概念。本文试图总结我迄今为止所学到的一些模式 – 同时希望对于即将进入这个令人敬畏的基于组件的世界的开发人员也有所帮助。

有状态(stateful)组件 和 无状态(stateless)组件

就像有状态和无状态Web服务一样,React 组件也可以在应用程序使用期间保持和操作状态(stateful) , 或者只是一个简单的组件,它接受输入 props 并返回要显示的内容(stateless)。

一个简单的 无状态(stateless) 按钮组件,仅依赖于 props(属性) ,这也称为函数式组件:

const Button = props =>
  <button onClick={props.onClick}>
    {props.text}
  </button>

下面者是一个 有状态(stateful) 的计数器组件 ButtonCounter(使用了 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 + 1 })
  }

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

注意:只有 类(class)组件才会有 state(状态),函数式组件不会有有 state(状态)。关于函数式组件和类组件的说明请参考官方文档

如您所见,第二个组件的 constructor(构造函数) 包含一个组件 state(状态),而第一个组件是一个简单的组件,通过 props(属性) 渲染文本。有状态组件和无状态组件的划分看起来非常简单,但可以使 Button 组件具有高度可重用性。

容器(Container) 组件 和 展示(Presentational) 组件

在处理外部数据时,我们可以将组件划分为这两个新类别。容器(Container) 组件负责访问数据,它常常是超出了React范畴的,例如使用 Redux 或 Relay 进行了绑定。对比而言,虽然 展示(Presentational) 组件与应用程序的其余部分没有依赖关系,它只取决于其自身的 state(状态) 或 接收到的 props(属性) 。让我们来看看将用户列表作为 展示组件 的示例:

const UserList = props =>
  <ul>
    {props.users.map(u => (
      <li>{u.name} — {u.age} years old</li>
    ))}
  </ul>

可以使用我们的容器(Container)组件更新这个列表组件 UserList

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

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

  render() {
    return <UserList users={this.state.users} />
  }
}

这种方法将数据获取与渲染分开,并使 UserList 可重用。如果你想进一步了解这种模式,来自 Dan Abramov 的精彩文章 作了解释更详细的解释。

高阶组件(Higher order components , HOC )

当您想要重用组件逻辑时,高阶组件(简称HOC)非常有用。 它们是 JavaScript 函数,它将组件作为参数并返回一个新组件。

假设你需要构建一个可扩展的菜单组件,当用户点击它时会显示一些子内容。 因此,您可以简单地创建一个通用的HOC来处理它,而不是控制其父组件上的 state(状态) :

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

    toggle() {
      this.setState(prevState => ({ show: !prevState.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>
    )
  }
}

如果你熟悉 Redux 的 connect 函数或者 React Router 的 withRouter 函数,那么你已经使用过高阶组件了。

渲染回调(Render Callbacks)

愚人码头注:这种模式以前也叫 函数作为子组件(Function as Child Components) , 现在最新的叫法为:渲染属性(Render Props),请参阅官方文档

使组件逻辑可重用的另一个好方法是将组件子项转换为函数 – 这就是为什么将渲染回调也称为函数作为子组件。 我们可以举一个例子,使用渲染回调模式重写上面的 Menu 组件:

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

  toggle() {
    this.setState(prevState => ({ show: !prevState.show }))
  }

  render() {
    return this.props.children(this.state.show, this.toggle)
  }
}

现在我们可以传递一个函数作为我们 Toggleable 组件子项:

<Toggleable>
  {(show, onClick) => (
    <div>
      <div onClick={onClick}>
        <h1>First Menu</h1>
      </div>
      {show ?
        <p>Some content</p>
        : null
      }
    </div>
  )}
</Toggleable>

上面的代码已经使用了一个函数作为子代,但是,如果我们想要像我们在 HOC 示例(多个菜单)中那样重用它,我们可以简单地创建一个使用 Toggleable 逻辑的新组件:

const ToggleableMenu = props =>
  
    {(show, onClick) => (

 

 

{props.title}

 

{show && props.children}

)}


我们全新的 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>
    )
  }
}

我们的 Menu 组件看起来与我们的 HOC 示例完全相同!

当我们想要改变渲染内容而不管状态操作时,这种方法非常有用:正如您所看到的,我们将渲染逻辑移到了 ToggleableMenu 子函数中,但 状态(state) 逻辑依然在我们的 Toggleable 组件进行维护。

了解更多

上面的例子只是你可以在你的 React 代码中使用的一些模式的基础知识,如果你真的想深入研究这些主题,我建议你看看这些有用的东西:

以上的一些例子仅仅是 React 设计模式的基础知识。如果你想更加深入地了解关于 React 设计模式的话题,以下是一些非常好的学习资料,值得一看:

原文链接:https://levelup.gitconnected.com/react-component-patterns-ab1f09be2c82

赞(0) 打赏
未经允许不得转载:WEB前端开发 » React 组件模式-有状态组件 x 无状态组件、容器组件 x 展示组件、高阶组件 x 渲染回调(函数作为子组件)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏