API 设计方法
我们已经深入了解了 Material UI 的使用方式,并且 v1 的重写使我们能够完全重新思考组件 API。
API 设计很难,因为你可以让它看起来简单,但实际上它可能具有迷惑性的复杂性,或者让它实际上很简单,但看起来很复杂。 @sebmarkbage
正如 Sebastian Markbage 指出的那样,没有抽象比错误的抽象更优越。我们正在提供低级组件,以最大限度地提高组合能力。
组合
您可能已经注意到 API 在组件组合方面存在一些不一致性。为了提供一些透明度,我们在设计 API 时一直遵循以下规则
- 使用
children
prop 是使用 React 进行组合的惯用方式。 - 有时我们只需要有限的子组件组合,例如,当我们不需要允许子组件顺序排列时。在这种情况下,提供显式 prop 可以使实现更简单、性能更高;例如,
Tab
组件接受icon
和label
prop。 - API 一致性很重要。
规则
除了上述组合权衡之外,我们还强制执行以下规则
展开
传递给组件但未明确记录的 props 将会被展开到根元素;例如,className
prop 将应用于根元素。
现在,假设您想要禁用 MenuItem
上的涟漪效果。您可以利用展开行为
<MenuItem disableRipple />
disableRipple
prop 将按以下方式传递:MenuItem
> ListItem
> ButtonBase
。
原生属性
我们避免记录 DOM 支持的原生属性,例如 className
。
CSS 类名
所有组件都接受 classes
prop 来定制样式。类名设计旨在满足两个约束:使类名结构尽可能简单,同时足以实现 Material Design 指南。
- 应用于根元素的类名始终称为
root
。 - 所有默认样式都分组在一个类名中。
- 应用于非根元素的类名以元素名称为前缀,例如 Dialog 组件中的
paperWidthXs
。 - 由布尔值 prop 应用的变体**不**带前缀,例如由
rounded
prop 应用的rounded
类名。 - 由枚举 prop 应用的变体**带**前缀,例如由
color="primary"
prop 应用的colorPrimary
类名。 - 一个变体具有**一级特异性**。
color
和variant
prop 被视为一个变体。样式特异性越低,就越容易覆盖。 - 我们增加了变体修饰符的特异性。对于伪类(
:hover
、:focus
等),我们**已经必须这样做**。它允许更多的控制,但代价是更多的样板代码。希望它也更直观。
const styles = {
root: {
color: green[600],
'&$checked': {
color: green[500],
},
},
checked: {},
};
嵌套组件
组件内部的嵌套组件具有
- 它们自己的扁平化 props,当这些 props 对于顶层组件抽象至关重要时,例如
Input
组件的id
prop。 - 它们自己的
xxxProps
prop,当用户可能需要调整内部 render 方法的子组件时,例如,在使用Input
组件的组件上公开inputProps
和InputProps
prop。 - 它们自己的
xxxComponent
prop,用于执行组件注入。 - 它们自己的
xxxRef
prop,当您可能需要执行命令式操作时,例如,公开inputRef
prop 以访问Input
组件上的原生input
。这有助于回答“如何访问 DOM 元素?”的问题。
Prop 命名
布尔值
布尔值 prop 的默认值应为
false
。这允许更好的简写表示法。考虑一个默认启用的输入框示例。应该如何命名控制此状态的 prop?它应该被称为disabled
❌ <Input enabled={false} /> ✅ <Input disabled />
如果布尔值的名称是单个词,它应该是形容词或名词,而不是动词。这是因为 props 描述的是**状态**而不是**动作**。例如,输入框 prop 可以由状态控制,而状态不会用动词来描述
const [disabled, setDisabled] = React.useState(false); ❌ <Input disable={disabled} /> ✅ <Input disabled={disabled} />
受控组件
大多数受控组件由 value
和 onChange
prop 控制。open
/ onClose
/ onOpen
组合也用于显示相关状态。在事件较多的情况下,名词在前,动词在后——例如:onPageChange
、onRowsChange
。
布尔值 vs. 枚举
有两种选项可以为组件的变体设计 API:使用布尔值或使用枚举。例如,让我们以具有不同类型的按钮为例。每个选项都有其优点和缺点
选项 1 布尔值
type Props = { contained: boolean; fab: boolean; };
此 API 启用简写表示法:
<Button>
、<Button contained />
、<Button fab />
。选项 2 枚举
type Props = { variant: 'text' | 'contained' | 'fab'; };
此 API 更加冗长:
<Button>
、<Button variant="contained">
、<Button variant="fab">
。然而,它可以防止使用无效的组合,限制暴露的 props 数量,并且可以轻松地在未来支持新值。
Material UI 组件根据以下规则结合使用了这两种方法
- 当需要 2 个可能的值时,使用**布尔值**。
- 当需要 **> 2** 个可能的值时,或者如果将来可能需要额外的可能值时,使用**枚举**。
回到之前的按钮示例;由于它需要 3 个可能的值,我们使用枚举。
Ref
ref
被转发到根元素。这意味着,在不通过 component
prop 更改渲染的根元素的情况下,它会被转发到组件渲染的最外层 DOM 元素。如果您通过 component
prop 传递不同的组件,则 ref 将附加到该组件。
术语表
- host component:
react-dom
上下文中的 DOM 节点类型,例如'div'
。另请参阅 React Implementation Notes。 - host element:
react-dom
上下文中的 DOM 节点,例如window.HTMLDivElement
的实例。 - outermost:从上到下读取组件树时的第一个组件,即广度优先搜索。
- root component:渲染 host component 的最外层组件。
- root element:渲染 host component 的最外层元素。