OverRainbow

如何更好的定义React组件API

☕️ 2 min read

背景

经过一段时间的 React 项目开发,写了不少的组件,也踩过不少的坑。本文学习整理了一些觉得比较实用的组件 API 编写的建议。

组件 API 建议

尽可能最小化 API 暴露数量

这意味着更少的学习成本,让其他人更容易理解和使用。

易于检索的目录结构

不要过早的组织层次复杂的目录结构,在组件数量并不多的时候这么做是吃力不讨好。扁平的目录结构天生拥有字母顺序检索优势,便于其他人检索。

避免写 renderXXX 类似的方法

大部分的 render 开头的函数大概率可能应该自身是一个组件。

// Instead of this
class Items extends React.Component {
  renderItems ({ items }) {
    return items.map(item => (
      <li key={item.id}>
        {renderItem(item)}
      </li>
    ))
  }

  renderItem (item) {
    return (
      <div>
        {item.name}
      </div>
    )
  }

  render () {
    return (
      <ul>
        {renderItems(this.props)
      </ul>
    )
  }
}
// Do this
const ItemList = ({ items }) =>
  <ul>
    {items.map(item => (
      <li key={item.id}>
        <Item {...item} />
      </li>
    )}
  </ul>

const Item = ({ name }) =>
  <div>{item.name}</div>

class Items extends React.Component {
  render () {
    const { items } = this.props
    return <ItemList items={items} />
  }
}

在数据边界拆分组件

一些普通展示类组件的 API,通常应当由其数据模型来定义,而不是分开来传 props。

// Instead of this
<Card
  image={product.thumbnail}
  title={product.name}
  text={product.description}
  link={product.permalink}
/>

// Do this
<ProductCard {...product} />

很可能 ProductCard 并不能复用,那么只留这么一份定义就可以了。你可以遵循“Rule of three”,也就是假如同样的逻辑在代码中出现了 3 次,就重构这个东西吧。

避免为了重用组件而写笨重的组件 reusable !== flexible

别为了避免写一个新的组件,而给一个组件增加抽象属性和额外逻辑。例如,一个 Button 可能可以接受不同的 color,size,shape,但没必要每次都传一堆 props 组合进去,所谓Apropcalypse

// Instead of this
<Button
  variant='secondary'
  size='large'
  outline
  label='Buy Now'
  icon='shoppingBag'
  onClick={handleClick}
/>

// Do this
<SecondaryButton
  size='large'
  onClick={handleClick}>
  <Icon name='shoppingBag' />
  Buy Now
</SecondaryButton>

用 composition

要尽量用 composition 的方式而不是 inherit 的方式,保证组件的灵活性。不要重复发明 props.children。如果定义的 props 接受的文字而不是基于数据结构,可能用 composition 是更好的方式。

// Instead of this
<Header
  title='Hello'
  subhead='This is a header'
  text='And it has arbitrary props'
/>

// Do this
<Header>
  <Heading>Hello</Heading>
  <Subhead>This is a header</Subhead>
  <Text>And it uses composition</Text>
</Header>

采用 composition 的方式,就不需要那么多的 documentation 了。你也可以将 composition 版本的组件绑定到某种数据模型,就像这样。

const PageHeader = ({
  title,
  description
}) =>
  <Header>
    <Heading>{title}</Heading>
    <Text>{description}</Text>
  </Header>

// And ideally can be used like this
<PageHeader {...page} />

避免用枚举值做布尔值 props

布尔值作为 props 有时候会令人困惑,例如

<Button primary />
<Button secondary />

那么这样会怎样?

<Button primary secondary />

这样一来不看文档就真看不懂了。因此可以尝试下面的方式

<Button variant="primary" />

虽然多打了几个字,但更具可读性。

保持 props API 平行

尽量保持不同组件间取同样名字的 props,这样更容易猜测用法和记住名称。

// Instead of this
<DatePicker
  date={date}
  onSelect={handleDateChange}
/>

// Do this
<DatePicker
  value={date}
  onChange={handleDateChange}
/>

Styled System 库鼓励多个组件采用平行的 props API。例如 color 在 rebass 库的所有组件中都有同样的效果。

// example from Rebass
<Box color='tomato' />
<Heading color='tomato' />

寻求团队建议

以上的建议并不适用于所有需求,需要针对业务做灵活的变化。最好的建议是跟团队讨论,创建 RFCs 和 PRs,并尝试Readme Driven Development。写 React 组件很容易,但为团队创建一个好用的组件库值得花时间和努力去把事情做对。

Refs

Defining Component APIs in React

thinking-in-react

Rule of three

Apropcalypse(强烈推荐)