为React组件编写API,第1部分:不要创建冲突的道具
编写用于React组件的API,第2部分:为行为命名,而非交互
为React组件编写API,第3部分:道具的顺序很重要
编写用于React组件的API,第4部分:当心提示!
编写用于React组件的API,第5部分:使用组合
我们为React组件编写API,第6部分:在组件之间创建通信
让我们从一个显示锚标记的简单React组件开始:

<Link href="sid.studio">Click me</Link> // : <a href="sid.studio" class="link">Click me</a>
组件代码如下所示:
const Link = props => { return ( <a href={props.href} className="link"> {props.children} </a> ) }
我们还希望能够向元素添加html属性,例如id
, target
, title
, data-attr
等。
由于HTML属性很多,我们可以传递所有道具并添加所需的道具(例如className
)。
(注意:您不应传递针对此组件提供的,不在HTML规范中的属性)
在这种情况下,您可以简单地使用className
const Link = props => { return <a {...props} className="link" /> }
那就是它变得有趣的地方。
有人通过id
或target
时,一切似乎都很好:
<Link href="sid.studio" id="my-link">Click me</Link> // : <a href="sid.studio" id="my-link" class="link">Click me</a>
但是当有人通过className
时会发生什么?

<Link href="sid.studio" className="red-link">Click me</Link> // : <a href="sid.studio" class="link">Click me</a>
好吧,什么都没发生。 React完全忽略了自定义类。 让我们回到函数:
const Link = props => { return <a {...props} className="link" /> }
好的,让我们想象一下...props
如何编译的,上面的代码与此等效:
const Link = props => { return ( <a href="sid.studio" className="red-link" className="link" > Click me </a> ) }
看到冲突了吗? 有两个className
。 React如何处理这个问题?
好吧,React什么都不做。 通天塔!
请记住,JSX“产生”了React.createElement
。 道具将转换为对象并作为参数传递。 对象不支持重复键,因此第二个className
将覆盖第一个。
const Link = props => { return React.createElement( 'a', { className: 'link', href: 'sid.studio' }, 'Click me' ) }
好的,现在我们知道了这个问题,我们该如何解决呢?
理解此错误是由于名称冲突而发生的,这对您很有用,这可能发生在任何prop上,而不仅仅是className
。 因此,决定取决于您要实现的行为。
有三种可能的方案:
- 使用我们组件的开发人员应该能够覆盖默认的prop值。
- 我们不想让开发商改变一些道具
- 开发人员应该能够在保持默认值的同时添加值。
让我们一次将它们分开。
1.使用我们组件的开发人员应该能够覆盖默认的prop值
这通常是您期望其他属性id
, title
等的行为。
我们经常在cosmos(我正在开发的设计系统)中看到test id
设置。 每个组件都会收到一个默认的data-test-id
,有时开发人员想附加自己的测试标识符以指示特定用法。
这是一个这样的用例:

const Breadcrumb = () => ( <div className="breadcrumb" data-test-id="breadcrumb"> <Link data-test-id="breadcrumb.link">Home</Link> <Link data-test-id="breadcrumb.link">Parent</Link> <Link data-test-id="breadcrumb.link">Page</Link> </div> )
Breadcrumb
使用该链接,但是您希望能够在具有更具体data-test-id
使用它。 有一个缺陷。
在大多数情况下,自定义道具应优先于默认道具。
在实践中,这意味着默认的props应该先走,然后{...props}
覆盖它们。
const Link = props => { return ( <a className="link" data-test-id="link" {...props} /> ) }
请记住, data-test-id
的第二次出现(来自props)将覆盖第一个(默认情况下)。 因此,当开发人员附加自己的data-test-id
或className
,他将覆盖默认情况下的那个:
1. <Link href="sid.studio">Click me</Link> 2. <Link href="sid.studio" data-test-id="breadcrumb.link">Click me</Link> // : 1. <a class="link" href="sid.studio" data-test-id="link">Click me</a> 2. <a class="link" href="sid.studio" data-test-id="breadcrumb.link">Click me</a>
我们可以使用className
进行相同操作:

<Link href="sid.studio" className="red-link">Click me</Link> // : <a href="sid.studio" class="red-link" data-test-id="link">Click me</a>
看起来很奇怪,我不确定我们是否应该允许这样做! 让我们进一步讨论一下。
2.我们不想让开发商改变一些道具
假设我们不希望开发人员更改外观(通过className
),但是我们不介意他们更改其他道具,例如id
, data-test-id
等。
我们可以通过排序属性的顺序来做到这一点:
const Link = props => { return ( <a data-test-id="link" {...props} className="link" /> ) }
请记住,右侧的属性将覆盖左侧的属性。 因此,可以重新定义{...props}
之前的所有内容,但是不能重新定义其后的所有内容。
为了简化开发人员的工作,可以显示警告,提示您无法指定className
。
我喜欢为此创建自己的类型检查道具:
Link.PropTypes = { className: function(props) { if (props.className) { return new Error( ` className Link, ` ) } } }
如果您对如何编写道具感兴趣,我有一段视频介绍如何检查道具的自定义类型 。
现在,当开发人员尝试覆盖className
,它将无法正常工作,并且开发人员将收到警告。

<Link href="sid.studio" className="red-link">Click me</Link> // : <a href="sid.studio" class="link">Click me</a>
: : className Link,
老实说,我只需要使用此模板一次或两次。 通常,您使用组件信任开发人员。
这带给我们分享。
3.开发人员应该能够在保持默认值的同时添加值
这也许是类的最常见用例。

<Link href="sid.studio" className="underline">Click me</Link> // : <a href="sid.studio" class="link underline">Click me</a>
实现看起来像这样:
const Link = props => { const { className, otherProps } = props const classes = 'link ' + className return ( <a data-test-id="link" className={classes} {...otherProps} /* */ /> ) }
该模板对于接受已经具有事件处理程序的组件的事件处理程序(例如onClick
)也很有用。

<Switch onClick={value => console.log(value)} />
这是该组件的实现形式:
class Switch extends React.Component { state = { enabled: false } onToggle = event => { this.setState({ enabled: !this.state.enabled }) if (typeof this.props.onClick === 'function') { this.props.onClick(event, this.state.enabled) } } render() { return <div class="toggler" onClick={this.onToggle} /> } }
还有一种避免事件处理程序中名称冲突的方法;我在《 React组件的写作API》(第2部分)中对它进行了描述:给行为命名,而不是交互方式 。
对于每种情况,您可以使用不同的方法。
- 大多数时候:开发人员应该能够更改道具的值,该道具的值是默认设置的
- 通常用于样式和事件处理程序:开发人员应能够在默认值之上添加一个值
- 一种罕见的情况,当您需要限制开发人员的操作时:不允许开发人员更改行为,您需要忽略其值,同时显示警告