如何覆盖现有的 React 组件
介绍
许多开发人员在构建应用程序时都求助于开源可重用组件库。这些库节省了时间和精力,并最大限度地减少了频繁重建常用组件(如菜单、表单控件和模式)的繁琐工作。
但是,即使您使用此类库,您可能仍希望更改组件的行为或外观。确实存在设计可能与设计者提供的模型不匹配的情况,并且组件不提供更改其样式的方法。
幸运的是,有一个解决方案。阅读本指南以了解如何覆盖现有的 React.js 类。
在深入研究之前,重要的是纵观全局并了解经常阻碍直接使用现有组件的因素:
- 样式:您可能需要重新设置组件及其内部样式。对于全局 CSS 来说,这相当简单。但是,对于 CSS-in-JS 来说,这可能有点棘手,因为组件实际上封装了样式。
- Props:您可能需要更改传递给内部组件的 props。例如,假设您想要向元素添加aria-label ,或者需要将className作为测试目标传递。
- 渲染:您可能只是希望覆盖特定内部组件的渲染或行为。例如,您可能希望向日期选择器添加巧妙的选择选项,如“最近 30 天”。
如果您希望更好地控制组件的渲染方式,那么您可能会发现一种有用的解决方案是渲染道具模式。但是,如果您只想覆盖样式或修改内部元素上的道具,渲染道具可能会有点麻烦。此外,一些开发人员提供诸如getFooStyle或getFooProps之类的道具来自定义内部元素,但这种手势很少一致且无法保证。
因此,完美的解决方案应该是跨所有组件的统一 API,该 API 具有适应性、灵活性和易用性。此解决方案就是覆盖模式。
覆盖公共 API
看一下下面的代码片段,它描述了如何使用覆盖模式自定义可重用的自动完成组件:
// App.js
render() {
<Autocomplete
options={this.props.products}
overrides={{
Root: {
props: {'aria-label': 'Select an option'},
style: ({$isOpen}) => ({borderColor: $isOpen ? 'blue' : 'grey'}),
},
Option: {
component: CustomOption
}
}}
/>
}
// CustomOption.js
export default function CustomOption({onClick, $option}) {
return (
<div onClick={onClick}>
<h4>{$option.name}</h4>
<small>{$option.price}</small>
</div>
);
}
现在,每个元素都有一个可供开发人员挑选的标识符。上面的代码使用了Root和Option。将这些标识符视为类名更容易,省去了 CSS 级联和全局命名空间的麻烦。
您可以为每个内部元素覆盖 props、样式和组件。覆盖过程非常简单。一旦您指定一个对象,它就会以更高的优先级沿着默认 props 传播。如您在上面的代码中看到的那样,它用于将aria-label附加到根元素。
要覆盖样式,您有两个选择。您可以传递样式对象,也可以传递一个函数,该函数将获取有关组件当前内部状态的 props,从而让您能够根据组件状态(例如isError或isSelected )动态修改样式。请记住,从函数返回的样式对象现在已与默认元素样式集成。
重写组件时,您可以选择传入无状态函数组件或 React 组件类,然后提供自己的渲染行为,或者更好的是,添加不同的处理程序。可以将其视为一种依赖注入形式,可以释放无限的可能性。
行动中的覆盖
要了解开发人员如何使用覆盖,请查看此示例。目标是生成一个具有与单选按钮组相同的 API、键盘控件和事件但外观不同的表单元素。解决方案是在已经正常运行的 RadioGroup 组件之上添加一系列样式覆盖。这可以节省开发和维护的时间、精力和成本。
以下代码演示了如何在自动完成组件内部实现覆盖:
// Autocomplete.js
import React from 'react';
import * as defaultComponents from './styled-elements';
class Autocomplete extends React.Component {
// Setup & handlers omitted to keep this example short
getSharedStyleProps() {
const {isOpen, isError} = this.state;
return {
$isOpen: isOpen
$isError: isError
};
}
render() {
const {isOpen, query, value} = this.state;
const {options, overrides} = this.props;
const {
Root: {component: Root, props: rootProps},
Input: {component: Input, props: inputProps},
List: {component: List, props: listProps},
Option: {component: Option, props: optionProps},
} = getComponents(defaultComponents, overrides);
const sharedProps = this.getSharedStyleProps();
return (
<Root {...sharedProps} {...rootProps}>
<Input
type="text"
value={query}
onChange={this.onInputChange}
{...sharedProps}
{...inputProps}
/>
{isOpen && (
<List {...sharedProps} {...listProps}>
{options.map(option => {
<Option
onClick={this.onOptionClick.bind(this, option)}
$option={option}
{...sharedProps}
{...optionProps}
>
{option.label}
</Option>
})}
</List>
)}
</Root>
);
}
}
如您所见,render 方法不包含 DOM 基元,例如<div> 。或者,您必须从相邻文件导入默认子组件集。上面的示例使用 CSS-in-JS 库来生成包含所有默认样式的组件。因此,每当使用overrides传递组件的实现时,它都会优先于默认值。
请注意,getComponents只是一个辅助函数,用于卸载覆盖并将它们组合到默认样式组件集中。实现此目的的最简单方法如下所示:
function getComponents(defaultComponents, overrides) {
return Object.keys(defaultComponents).reduce((acc, name) => {
const override = overrides[name] || {};
acc[name] = {
component: override.component || defaultComponents[name],
props: {$style: override.style, ...override.props},
};
return acc;
}, {});
}
此样式覆盖为$style属性,并将其与所有覆盖属性相结合。这是因为原始 CSS-in-JS 实现会检测$style属性并将其与默认样式进行深度合并。
子组件还会接收sharedProps。后者是一组与组件状态有关的 props,这些 props 用于动态更改组件的样式或渲染。例如,如果发生错误,边框颜色可能会变为红色。此类 props 以$为前缀,表示这些是独特的 props,不应作为属性传递给底层 DOM 元素。
权衡
与软件设计模式一样,在使用覆盖之前需要考虑一些折衷和权衡。
刚性
由于每个内部元素现在都有一个唯一标识符,允许将其作为覆盖目标,因此修改 DOM 结构可能经常导致重大更改。此问题也适用于 CSS 更改。例如,如果消费者试图覆盖子元素,无意中认为它位于弹性框内,那么从display: flex更改为display: block可能会成为重大更改。
类似的考虑因素本来很容易被忽视,但封装在您的组件中,现在却成为一个关键问题。
因此,修改组件 DOM 结构或样式时必须非常小心。如有疑问,请选择主要版本升级。
文档
使用覆盖后,您的内部元素现在属于公共 API。如果您想要详尽说明,请提供一份文档来解释每个元素及其接收的 props。您还可以考虑添加一个友好的 DOM 结构图,并使用其标识符标记元素。
为了让其他开发人员的生活更轻松,您可以选择使用 Typescript 或 Flow 为每个组件键入覆盖对象。
作品
假设您希望构建一个可重复使用的分页组件,该组件在内部使用按钮。您必须为这个简单的概念考虑几个因素。您计划如何通过分页公开按钮覆盖?消费者是否希望设置多个按钮的样式?您可能需要进行实验才能找到适合您的应用的方法。
复杂
覆盖会增加工作的复杂性,这是不争的事实。您必须考虑用户覆盖内部组件的所有方式。对于一个在您自己的应用程序中重复使用几次的简单组件,最好不要增加额外的复杂性。但是,如果您的应用程序将被许多开发人员使用,那么复杂性就是您必须付出的代价。
结论
本指南介绍了覆盖,并简单演示了如何使用它们。覆盖是一个仍在不断发展的新概念。然而,使用它的结果相当令人印象深刻。它为开发人员提供了一致的方法,可以使用统一的 API 自定义他们需要的任何内容。
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~