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

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

它相当小,所以我就把它全部粘贴在这里给你看(见这里的语法强调文件):
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 覆盖了来自togglerProps 的onClick ,所以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 道具)。
这里有几个实现道具获取器模式的项目:
downshift🏎- 构建简单、灵活、符合WAI-ARIA标准的增强型输入React组件的基本要素react-toggled- 构建简单、灵活、可访问的切换组件的组件dub-step🕺- 有风格的步进索引react-stepper-primitive- "步进 "组件的React原语。
我希望将来能看到更多的人做这样的事情祝大家好运!👍