为React组件编写API,第3部分:道具的顺序很重要

为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属性,例如idtargettitledata-attr等。


由于HTML属性很多,我们可以传递所有道具并添加所需的道具(例如className )。


(注意:您不应传递针对此组件提供的,不在HTML规范中的属性)


在这种情况下,您可以简单地使用className


 const Link = props => { /*    (spread ),     ( ) */ return <a {...props} className="link" /> } 

那就是它变得有趣的地方。


有人通过idtarget时,一切似乎都很好:


 <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 。 因此,决定取决于您要实现的行为。


有三种可能的方案:


  1. 使用我们组件的开发人员应该能够覆盖默认的prop值。
  2. 我们不想让开发商改变一些道具
  3. 开发人员应该能够在保持默认值的同时添加值。

让我们一次将它们分开。


1.使用我们组件的开发人员应该能够覆盖默认的prop值


这通常是您期望其他属性idtitle等的行为。


我们经常在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-idclassName ,他将覆盖默认情况下的那个:


 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 ),但是我们不介意他们更改其他道具,例如iddata-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 => { /*  className   */ 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部分)中对它进行了描述:给行为命名,而不是交互方式




对于每种情况,您可以使用不同的方法。


  1. 大多数时候:开发人员应该能够更改道具的值,该道具的值是默认设置的
  2. 通常用于样式和事件处理程序:开发人员应能够在默认值之上添加一个值
  3. 一种罕见的情况,当您需要限制开发人员的操作时:不允许开发人员更改行为,您需要忽略其值,同时显示警告

Source: https://habr.com/ru/post/zh-CN459380/


All Articles