如何用道具获取器将渲染控制权交给用户

74 阅读4分钟

如果你是egghead.io的订阅者,你也可以通过这两节课来了解这种模式。使用渲染道具的道具集合使用渲染道具的道具获取器

自从几周前我发布了downshift 🏎后。在所有的事情中,我想我收到的最常见的问题是关于 "道具获取器"。据我所知,downshift是第一个实现这种模式的库,所以我想我应该解释一下它为什么有用以及如何实现它。如果你不熟悉downshift,请在继续之前阅读介绍文章。 别担心,我会等你的......

dog wagging the tail while waiting

所以,从你读到的内容来回顾一下,道具获取器是拼图中的一块,让你把渲染交给你的组件的用户(一个伟大的想法)。有一天我在机场从Jared Forsyth那里得到了这个主意。**它基本上是一个函数,当被调用时将返回道具,人们必须将这些道具应用于正确的元素,将所有相关的元素挂在一起,形成总体的组件。**希望这很清楚 😀

为了谈论这个问题,我们实际上将使用我最近写的一个不同的组件,它使用这种模式,叫做react-toggled.

React toggled logo

它相当小,所以我就把它全部粘贴在这里给你看(见这里的语法强调文件):

import {Component} from 'react'
import PropTypes from 'prop-types'

const callAll =
  (...fns) =>
  (...args) =>
    fns.forEach(fn => fn && fn(...args))

class Toggle extends Component {
  static propTypes = {
    defaultOn: PropTypes.bool,
    on: PropTypes.bool,
    onToggle: PropTypes.func,
    children: PropTypes.oneOfType([PropTypes.func, PropTypes.array]).isRequired,
  }
  static defaultProps = {
    defaultOn: false,
    onToggle: () => {},
  }
  state = {
    on: this.getOn({on: this.props.defaultOn}),
  }

  getOn(state = this.state) {
    return this.isOnControlled() ? this.props.on : state.on
  }

  isOnControlled() {
    return this.props.on !== undefined
  }

  getTogglerProps = (props = {}) => ({
    'aria-controls': 'target',
    'aria-expanded': Boolean(this.getOn()),
    ...props,
    onClick: callAll(props.onClick, this.toggle),
  })

  getTogglerStateAndHelpers() {
    return {
      on: this.getOn(),
      getTogglerProps: this.getTogglerProps,
      setOn: this.setOn,
      setOff: this.setOff,
      toggle: this.toggle,
    }
  }

  setOnState = (state = !this.getOn()) => {
    if (this.isOnControlled()) {
      this.props.onToggle(state, this.getTogglerStateAndHelpers())
    } else {
      this.setState({on: state}, () => {
        this.props.onToggle(this.getOn(), this.getTogglerStateAndHelpers())
      })
    }
  }

  setOn = this.setOnState.bind(this, true)
  setOff = this.setOnState.bind(this, false)
  toggle = this.setOnState.bind(this, undefined)

  render() {
    const renderProp = unwrapArray(this.props.children)
    return renderProp(this.getTogglerStateAndHelpers())
  }
}

/**
 * Takes an argument and if it's an array, returns the first item in the array
 * otherwise returns the argument
 * @param {*} arg the maybe-array
 * @return {*} the arg or it's first item
 */

function unwrapArray(arg) {
  return Array.isArray(arg) ? arg[0] : arg
}

export default Toggle

你会注意到this.props.children ,这是为了兼容preact。

而这里是你如何使用react-toggled

<Toggle>
  {({on, getTogglerProps}) => (
    <div>
      <button
        {...getTogglerProps({
          onClick() {
            alert('you clicked!')
          },
        })}
      >
        Toggle me
      </button>
      <div>{on ? 'Toggled On' : 'Toggled Off'}</div>
    </div>
  )}
</Toggle>

这个组件有一些整洁的东西,我可能会在以后的文章中谈及,但现在,让我们把重点放在getTogglerProps 函数上(这是道具获取器)。

这种模式最酷的地方在于,它允许用户渲染他们想要的东西。因此,你的组件负责困难和通用的部分(组件的逻辑),而用户可以负责简单和不太通用的部分:显示什么以及如何根据组件的状态进行样式设计。

因此,如果用户想让<div> 出现在<button> 上面,或者根本不出现,那么用户可以简单地做到这一点,而不必为道具或任何东西查阅任何文档。这是很强大的!

说到这里,我从人们那里得到的关于 "道具获取器 "的最大问题是。

你为什么要用一个函数来获取道具?为什么不把一个普通的对象传给我的渲染回调,让我传播它,而不是调用一个函数?

人们所说的是他们更愿意做:<button {...togglerProps} {...myOwnProps} /> ,而不是<button {...getTogglerProps(myOwnProps)} /> 。我可以理解为什么人们会喜欢这样。感觉那样你有更多的控制权。然而,我们实际上是在用这个函数和你提供的道具做一些有用的事情......

对于这个组件,我们关心的是你应用到你的<button> 的道具onClick 。我们需要调用this.toggle 。但如果你(作为组件的用户)也想为onClick 的处理程序呢?你可以试着这样写:<button onClick={this.handleClick} {...togglerProps} /> 。但你会发现togglerProps覆盖了你自定义的onClick 处理程序,所以你可以把它换成。<button {...togglerProps} onClick={this.handleClick} /> 而现在你有了相反的问题你的自定义onClick 覆盖了来自togglerPropsonClick ,所以react-toggled 根本就不能工作。

有了这个背景,让我们看看如何通过使用一个函数来避免这个问题。 看看getTogglerProps 的实现:

getTogglerProps = (props = {}) => ({
  'aria-controls': 'target',
  'aria-expanded': Boolean(this.getOn()),
  ...props,
  onClick: callAll(props.onClick, this.toggle),
})

你会注意到,onClick 的道具被分配给了callAll(props.onClick, this.toggle)callAll 函数是非常简单的:

const callAll =
  (...fns) =>
  (...args) =>
    fns.forEach(fn => fn && fn(...args))

它做它所说的。调用它所给的所有函数,如果它们存在的话。在我们的例子中,我们的两个onClick 处理程序将根据我们的需要被调用。(如果你不太习惯使用箭头函数,请看转写版本)。


总而言之,道具获取器是使你能够将渲染责任交给组件用户的模式之一(一个非常棒的想法)。 你只能通过渲染道具模式来真正实现它(在我们的例子中,我们使用children 道具,但如果你愿意,你可以使用render 道具)。

这里有几个实现道具获取器模式的项目:

我希望将来能看到更多的人做这样的事情祝大家好运!👍