子组件和组合
HTML 从来就是通过嵌套来工作的:<select> 包裹它的 <option>,<ul> 包裹它的 <li>,<button> 包裹它的标签。中间的元素是外层元素的子元素,两者配合工作才能呈现你看到的效果。React 组件也可以用同样的方式工作。与其通过具名 props 传递所有东西,一个组件可以像原生元素那样包裹内容,而这个单一的想法就是区分一个你**配置的组件和一个你组合**的组件的关键。
本章像课程的其他部分一样,通过为组件库构建 Button 来展开论述:先是 children,然后是 prop 展开,最后是让组件在实际应用中可复用的收尾工作。
Children 作为接口
Props 章节介绍过 children:组件的开始标签和结束标签之间的内容会作为一个名叫 children 的 prop 传入。它为复用性带来的收益比看起来要大得多。
function Button({ children }) {
return <button>{children}</button>
}
// 使用:<Button>立即购买</Button>组件决定了 children 在哪里渲染,调用方决定了它们是什么。比较一下用 text prop 的情况,它在调用方只想要文本的时候还可以,但如果调用方想要超过文本的东西,它就不行了。有了 children,调用方就可以传递任何东西:
// 使用:
// <Button>
// <CartIcon />
// 立即购买
// </Button>在标签左边加一个图标,不需要 icon prop,不需要 iconPosition="left" 选项,组件作者甚至不需要预见这些。想把图标放在右边?把它移到文本的另一侧就行了。组件作者原本要发明的每一个配置 prop 都消解了,调用方只需要写出他们想要的标记。
转发其他 props
渲染原生 <button> 的 Button 还有第二个问题:事件处理。当调用方写 <Button onClick={...}> 时,这个 onClick 是一个自定义 prop 落在你的组件上;除非组件把它传给底层的真实 <button>,否则什么都不会发生。你可以手动转发 onClick,然后转发 onDoubleClick,再转发 style,再转发 className,但仍然覆盖不了原生 button 接受的所有东西。展开语法可以一次性转发它们全部:
function Button({ children, ...rest }) {
return <button {...rest}>{children}</button>
}解构你的组件实际处理的 props,用 rest 语法收集其他的,然后把 rest 展开到底层元素上。调用方传递的每一个有效 button 属性,包括事件处理器,都落在了它该去的地方。你的组件为自己发明的 props(比如 variant)被解构出去,永远不会到达 DOM 元素。
为你自己的 API 留出空间
可复用的组件通常在原生元素的基础上增加选项。本课程的 button 使用 size("sm" 或 "lg")和 variant("success"、"warning"、"danger"),每个都映射到一个 CSS 类:
function Button({ children, size, variant, className, ...rest }) {
const sizeClass = size ? `button-${size}` : ''
const variantClass = variant ? `button-${variant}` : ''
const allClasses = [sizeClass, variantClass, className].filter(Boolean).join(' ')
return (
<button className={allClasses} {...rest}>
{children}
</button>
)
}这个合并避免了一个微妙的 bug。如果组件设置了 className 而调用方也传递了一个,无论哪个最后落到元素上就赢了:在你自己的 className 之后展开 rest,调用方的类会无声地覆盖你的;在之前展开,你的会覆盖他们的。把 className 解构出来并自己合并字符串是解决办法。clsx 或 classnames 这样的库就是专门做这个合并的,当条件增多时尤其有用。
children 的 Button 不需要 text prop、icon prop 和 icon-position prop。无论谁使用它,都在标签里面按他们想要的顺序写上图标和文本,Button 就会把这些内容放在该去的地方。 嵌套,这是 HTML 一直在做的事,也是 React 最好的复用技巧。
接下来:复合组件,一个组件变成多个协同工作的组件。

