组件是 React 应用程序的构建块。几乎不可能构建一个 React 应用程序而不使用组件。它非常普遍,以至于一些第三方软件包为您提供了可用于将功能集成到应用程序中的组件。
这些第三方组件往往是可复用的。它们与您可能在应用程序中使用的组件之间的区别在于特定性。
我的意思是,假设您经营一家销售 polo 衫的公司。您可以做两件事
- 您可以生产已经印有设计的 polo 衫,或者
- 您可以让买家选择他们想要的图案。
一些基本的东西将保持一致,例如所有 polo 衫都应该是短袖的。但用户可以选择衬衫的款式,例如颜色和尺寸。在这种情况下,短袖 polo 衫将成为一个很好的 React 组件:它是同一件物品,但有不同的款式。
现在假设您正在开发一个登录表单。与 polo 衫一样,表单具有一致的特征,但我们关注的不是尺寸和颜色的变化,而是输入字段、提交按钮,甚至可能还有忘记密码链接。这可以被组件化并实现输入、按钮、链接等的变体。
输入元素示例
让我们从创建表单输入字段的角度来看。以下是如何将典型的文本输入作为 React 组件显示:
class Form extends React.Component {
this.state = {
username: ''
}
handleChange = (event) => {
this.setSate({ username: event.target.value })
}
render() {
return (
<input
name="username"
type={type}
placeholder="Enter username"
onChange={this.handleChange}
value={this.state.username}
/>
)
}
}
为了使此输入元素可在其他地方和项目中复用,我们必须将其提取到其自己的组件中。让我们称之为FormInput
。
const FormInput = ({
name,
type,
placeholder,
onChange,
className,
value,
error,
children,
label,
...props
}) => {
return (
<React.Fragment>
<label htmlFor={name}>{label}</label>
<input
id={name}
name={name}
type={type}
placeholder={placeholder}
onChange={onChange}
value={value}
className={className}
style={error && {border: 'solid 1px red'}}
/>
{ error && <p>{ error }</p>}
</React.Fragment>
)
}
FormInput.defaultProps = {
type: "text",
className: ""
}
FormInput.propTypes = {
name: PropTypes.string.isRequired,
type: PropTypes.string,
placeholder: PropTypes.string.isRequired,
type: PropTypes.oneOf(['text', 'number', 'password']),
className: PropTypes.string,
value: PropTypes.any,
onChange: PropTypes.func.isRequired
}
该组件接受某些 props,例如我们需要使用有效标记创建输入所需的属性,包括占位符、值和名称。我们在 render 函数中设置输入元素,将属性值设置为传递给组件的 props。我们甚至将输入绑定到标签以确保它们始终在一起。您可以看到我们没有通过预定义任何内容来做出假设。目的是确保该组件可以在尽可能多的场景中使用。
这使得它成为一个好的组件,因为它强制执行良好的标记(Brad Frost 称之为愚蠢的 React),这表明并非每个组件都必须是某种高度复杂的功能。再说一次,如果我们谈论的是非常基本的东西,比如静态标题,那么使用 React 组件可能就有点过头了。创建可复用组件的可能标准可能是当您需要在应用程序的其他部分使用相同的功能时。如果该组件只使用一次,则通常不需要“可复用”组件。
我们可以在另一个组件LoginPage
中使用我们的输入组件。
class LoginPage extends React.Component {
state = {
user: {
username: "",
password: ""
},
errors: {},
submitted: false
};
handleChange = event => {
const { user } = this.state;
user[event.target.name] = event.target.value;
this.setState({ user });
};
onSubmit = () => {
const {
user: { username, password }
} = this.state;
let err = {};
if (!username) {
err.username = "Enter your username!";
}
if (password.length < 8) {
err.password = "Password must be at least 8 characters!";
}
this.setState({ errors: err }, () => {
if (Object.getOwnPropertyNames(this.state.errors).length === 0) {
this.setState({ submitted: true });
}
});
};
render() {
const {
submitted,
errors,
user: { username, password }
} = this.state;
return (
<React.Fragment>
{submitted ? (
<p>Welcome onboard, {username}!</p>
) : (
<React.Fragment>
<h3>Login!</h3>
<FormInput
label="Username"
name="username"
type="text"
value={username}
onChange={this.handleChange}
placeholder="Enter username..."
error={errors.username}
required
className="input"
/>
<FormInput
label="Password"
name="password"
type="password"
value={password}
onChange={this.handleChange}
placeholder="Enter password..."
error={errors.password}
className="input"
required
/>
<Button
type="submit"
label="Submit"
className="button"
handleClick={this.onSubmit}
/>
</React.Fragment>
)}
</React.Fragment>
);
}
}
看到LoginPage
如何两次使用FormInput
了吗?我们将其同时用作用户名和密码的文本输入。如果我们想更改输入的功能,我们可以在创建的FormInput
组件文件中进行更改,这些更改将应用于使用输入组件的每个实例。这就是拥有可复用组件的基本优势:您无需重复自己。
甚至错误也是从FormInput
组件中显示的。
onSubmit
函数首先验证我们从表单中获取的user
对象,确保它符合存在username
值的结构。请注意,我们甚至可以扩展输入的功能,就像我们所做的那样,检查密码是否包含至少八个字符。
如果您查看代码,您会看到其中有一个Button
组件。它与 HTML <button>
元素不同,而是一个组件,它接收定义我们想要的按钮类型(提交、重置、按钮)、其类名、点击时要执行的操作以及标签的 props。有许多其他按钮属性我们可以集成以强制执行所需的任何标准。
const Button = (props) => (
<button
type={props.type}
className={props.className}
onClick={props.handleClick}
>
{props.label}
</button>
)
这是将所有组件组合在一起后的最终登录表单。
查看 CodePen 上 Kingsley Silas Chijioke (@kinsomicrote) 的笔
可复用按钮组件
在 CodePen 上。
想自己尝试一下吗?尝试开发一个可复用的<select>
元素。如果这太难,您可以从<textarea>
元素开始,然后是复选框,然后再跳到<select>
。关键是要使其通用。我想看看您想出了什么,所以在评论区链接您的作品!
不错!
假设您想为密码输入添加一个“眼睛”图标,并具有显示/隐藏功能。
您是否会在 FormInput 组件中添加该逻辑,然后通过“addPasswordReveal” prop 启用它,或者创建新的单独组件“FormInputPassword”呢?
如果对于不同类型,您有很多不同的情况怎么办?
谢谢,Roy。
对于您提到的第一个场景,我会按照您所说的去做——添加图标,然后使用布尔值来显示或隐藏它。如果我想拥有不同的类型,我可以像您提到的那样创建一个名为 FormInputPassword 的新组件。但我担心这样做可能没有必要。因此,我可能会选择使用一个 name prop 来确定要显示的图标类型。默认情况下,我可以选择将值设置为 false,因此当基于名称时,将使用适合该名称的图标。
希望这能回答您的问题。
嗨,快速的可访问性提示。您使用错误的属性连接了标签和输入。标签上的 for 属性指向一个 id。不幸的是,您没有在输入字段上设置 id。因此,您无法单击标签来聚焦输入。
如果您不喜欢使用 for 和 id,您可以将输入元素包装在标签中。
你好,Jonas,
感谢您提醒我注意这一点,我已经更新了文章和演示。
我感谢。
好文章,谢谢。我会开始练习。
嗨,Silas,
感谢您的文章,写得非常好。但是我认为这不是一个好的例子。当您封装
<input>
时,您实际上是在为开发人员创建新的 API,因此开发人员必须在创建新的<input>
之前查找 API。要有效地封装它,您需要向组件中添加更多内容,否则它不是 DRY 的。例如,样式类、隐藏输入状态、可访问性、输入验证。我在一个 bootstrap REACT 库中看到了这个问题——通常使用额外类启用的所有选项都变成了属性,有时名称略有不同John