在JS代码的三个或四个不同位置进行相同的更改是一项需要注意的技术。如果还有更多的元素,则代码支持会变成面粉。因此,对于长期或大型项目,应编写代码以便可以在单独的组件中执行。我从事前端开发已有10年了,将谈论使用组件创建前端元素-这极大地简化了前端开发人员的工作。在Mail.ru Cloud Solutions的支持下撰写。什么是前端组件,为什么需要它们
HTML标记 -有条件的组件“零”级。它们每个都有自己的功能和目的。CSS类是创建甚至一个小型站点时通常会达到的下一个抽象级别。在将样式应用于CSS类的规则中,我们描述了作为元素的条件子集的一部分的所有元素的行为。应用于CSS类的规则以及任何其他元素(例如HTML标记),都可以集中设置和更改显示任意数量的相同类型元素的规则。有多种用于处理元素样式的工具-实际上是CSS,Sass,LESS,PostCSS,以及用于应用样式的方法-BEM,SMACSS,Atomic CSS,CSS模块,样式化组件。实际上,组件是:- 具有相同样式和布局(HTML)和行为(JS)的相同类型的元素;
- 风格和行为元素相似,彼此之间略有不同。
Web Components技术正在开发中,它使您可以创建自定义HTML标记,并在布局中包含模板代码段。但是,由于现代的前端开发框架(例如Angular,Vue,React),组件已被广泛使用。JavaScript功能使连接组件变得容易:import {Header, Footer} from "./components/common";
render() {
return (
...
)
}
所有主要项目都进入其现成组件库,或使用其中一个现成组件。什么时候需要从复制代码过渡到创建组件的问题是单独决定的,没有明确的配方。不仅要记住代码的编写,而且要记住它的支持。CSS类中具有相同布局和隔离样式的简单复制/粘贴可以在一段时间内创建显示,而没有任何特殊的风险。但是,如果将用JS编写的行为逻辑添加到每个元素,则从2-3个元素中就可以真正感受到重用代码的好处,尤其是在支持和修改先前编写的代码时。可重复使用的React组件
假设我们的应用程序已经足够大,并且我们决定编写自己的组件库。我建议为此使用流行的React前端开发工具。它的优点之一是能够简单有效地使用嵌入式组件。在下面的代码中,应用程序的较早组件使用三个嵌套组件:AppHeader,Article,AppFooter:import React from "react";
import AppHeader from "./components/AppHeader";
import Article from "./components/Article";
import AppFooter from "./components/AppFooter";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
title : "My App",
contacts : "8 800 100 20 30"
firtsArticleTitle : "Welcome",
secondArticleTitle : "Let's speak about..."
}
};
render() {
return (
<>
<AppHeader
title={this.state.title}
/>
<Article
title={this.state.firstArticleTitle}
/>
<Article
title={this.state.secondArticleTitle}
/>
<AppFooter
contacts={this.state.contacts}
/>
</>
)
}
}
请注意:现在不需要在布局中使用高级包装标签-通常是这样div
。 Modern React提供了Fragment工具,该工具的缩写记录<></>
。在上面的示例中,您可以在这些标签中使用平面标签层次结构。我们使用了三个库组件,其中之一在一个块中是两次。来自父应用程序的数据被传输到组件的props,并将通过属性在内部可用this.props
。这种方法是React的典型方法,可让您从典型元素中快速组装视图。特别是如果您的应用程序具有许多相似的页面,这些页面仅在文章(模型)和功能的内容上有所不同。但是,我们可能需要修改库组件。例如,具有相同功能的组件可能不仅在文本内容上有所不同,而且在设计上也可能有所不同:颜色,凹痕,边框。也可以提供相同组件的不同功能。下面考虑以下情况:根据回调的存在,我们的组件可能是“响应式”的,或者只是保留一个View来在页面上呈现元素:
...
render() {
return (
<Article
text={this.state.articleText}
onClick={(e) => this.bindTap(e)}
customClass={this.state.mainCustomClass}
/>
)
}
import React from "react";
export default class Article extends React.Component {
constructor(props) {
super(props);
};
render() {
let cName="default";
if (this.props.customClass) cName = cName + " " this.props.customClass;
let bgColor="#fff";
if (this.props.bgColor) bgColor = this.props.bgColor;
return (
{this.props.onClick &&
<div
className={cName}
onClick={(e) => this.props.onClick(e)}
style={{background : bgColor}}
>
<p>{this.props.text}<p/>
</div>
}
{!this.props.onClick &&
<div className={cName}>
<p>{this.props.text}<p/>
</div>
}
)
}
}
在React中,还有另一种扩展组件功能的技术。在调用参数中,您不仅可以传输数据或回调,还可以传输整个布局:
...
render() {
return (
<Article
title={this.state.articleTitle}
text={
<>
<p>Please read the article</p>
<p>Thirst of all, I should say programming React is a very good practice.</p>
</>
}
/>
)
}
import React from "react";
export default class Article extends React.Component {
constructor(props) {
super(props);
};
render() {
return (
<div className="article">
<h2>{this.props.title}</h2>
{this.props.text}
</div>
)
}
}
组件的内部布局将在传输到时完整复制props
。使用insert and use模式将其他布局转移到库组件通常更方便this.props.children
。这种方法更好地用于修改负责假定各种内部内容的应用程序或站点的典型块的通用组件:上限,边栏,带有广告的块等。
...
render() {
return (
<Article title={this.state.articleTitle}>
<p>Please read the article</p>
<p>First of all, I should say programming React is a very good practice.</p>
</Article>
)
}
import React from "react";
export default class Article extends React.Component {
constructor(props) {
super(props);
};
render() {
return (
<div className="article">
<h2>{this.props.title}</h2>
{this.props.children}
</div>
)
}
}
完整的React组件
上面考虑了仅负责View的组件。但是,我们很可能不仅需要将映射提交给库,还需要将标准数据处理逻辑提交给库。让我们看一下“电话”组件,该组件旨在输入电话号码。它可以使用插件验证程序库屏蔽输入的号码,并通知高级组件电话输入正确或不正确:
import React from "react";
import Validator from "../helpers/Validator";
export default class Phone extends React.Component {
constructor(props) {
super(props);
this.state = {
value : this.props.value || "",
name : this.props.name,
onceValidated : false,
isValid : false,
isWrong : true
}
this.ref = React.createRef();
};
componentDidMount = () => {
this.setValidation();
};
setValidation = () => {
const validationSuccess = (formattedValue) => {
this.setState({
value : formattedValue,
isValid : true,
isWrong : false,
onceValidated : true
});
this.props.setPhoneValue({
value : formattedValue,
item : this.state.name,
isValid : true
})
}
const validationFail = (formattedValue) => {
this.setState({
value : formattedValue,
isValid : false,
isWrong : true,
});
this.props.setPhoneValue({
value : formattedValue,
item : this.state.name,
isValid : false
})
}
new Validator({
element : this.ref.current,
callbacks : {
success : validationSuccess,
fail : validationFail
}
});
}
render() {
return (
<div className="form-group">
<labeL htmlFor={this.props.name}>
<input
name={this.props.name}
id={this.props.name}
type="tel"
placeholder={this.props.placeholder}
defaultValue={this.state.value}
ref={this.ref}
/>
</label>
</div>
)
}
}
该组件已经具有内部状态,可以与调用它的外部代码共享该状态的一部分。另一部分保留在组件内部,在其上方的示例中onceValidated
。因此,组件的部分逻辑被完全封装在其中。可以说典型行为与应用程序的其他部分无关。例如,根据号码是否经过验证,我们可以显示不同的文本提示。我们不仅考虑了显示器,还考虑了数据处理逻辑,使用了一个单独的可重用组件。MV组件
如果我们的标准组件支持高级功能并具有充分发展的行为逻辑,则值得将其分为两个部分:- “智能”用于处理数据(
Model
); - 显示“哑”(
View
)。
通过调用一个组件将继续连接。现在将会Model
。第二部分- View
将通过调用,render()
其中props
一部分来自应用程序,另一部分已经是组件本身的状态:
...
render() {
return (
<Phone
name={this.state.mobilePhoneName}
placeholder={"You mobile phone"}
/>
)
}
import React from "react";
import Validator from "../helpers/Validator";
import PhoneView from "./PhoneView";
export default class Phone extends React.Component {
constructor(props) {
super(props);
this.state = {
value : this.props.value || "",
name : this.props.name,
onceValidated : false,
isValid : false,
isWrong : true
}
this.ref = React.createRef();
};
componentDidMount = () => {
this.setValidation();
};
setValidation = () => {
const validationSuccess = (formattedValue) => {
...
}
const validationFail = (formattedValue) => {
...
}
new Validator({
element : this.ref.current,
...
});
}
render() {
return (
<PhoneView
name={this.props.name}
placeholder={this.props.placeholder}
value={this.state.value}
ref={this.ref}
/>
)
}
}
import React from "react";
const PhoneView = React.forwardRef((props, ref) => (
<div className="form-group">
<labeL htmlFor={props.name}>
<input
name={props.name}
id={props.name}
type="tel"
ref={ref}
placeholder={props.placeholder}
value={props.value}
/>
</label>
</div>
));
export default PhoneView;
值得关注的工具React.forwardRef()
。它允许您ref
在组件中创建Phone
,但是将其直接绑定到中的布局元素PhoneView
。ref
然后,所有照常操作将在中提供Phone
。例如,如果我们需要连接电话号码验证器。这种方法的另一个特点是最大程度地简化了View
组件。实际上,这部分没有内置方法就定义为const。仅从模型进行布局和数据替换。现在,我们的可重用组件分为Model
和View
,我们可以分别开发业务逻辑和布局代码。我们还可以从更小的组件元素组装布局。组件上运行的整个应用程序的状态
如上所示,应用程序可以通过传递参数或排版以及使用回调来管理组件。为了使应用程序成功运行,上层需要接收有关嵌套重用组件状态的有意义的数据。但是,这可能不是整个应用程序的最高级别。如果我们有一个客户端授权块和用于在其中输入登录名和密码的可重用组件,则整个应用程序无需知道任何给定时间这些简单组件处于什么状态。相反,授权块本身可以根据简单的重用组件的状态来计算新状态并将其传递:授权块是否正确填充。对于大量的组件嵌套,有必要监视数据的工作组织,以便始终知道“真理的来源”在哪里。我已经写了一些与React中异步状态转换相关的困难的文章。
可重用组件应始终通过回调传递管理可能的组件块所需的数据。但是,您无需传输额外的数据即可避免不必要地重绘DOM树的大部分,也不会使处理组件更改的代码复杂化。
组织数据的另一种方法是使用组件调用上下文。这是本机的React方法。,从版本16.3开始提供,请勿与早期版本混淆!createContext
React getChildContext
这样,您就不必通过组件的“厚度”传递组件到组件嵌套树的下方。或使用专门的库来进行数据管理和更改交付,例如Redux和Mobx(请参阅Mobx + React包上的文章)。如果我们在Mobx上构建可重用组件的库,则此类组件的每种类型都将拥有自己的商店。也就是说,有关组件每个实例状态的“真相源”,可以从整个应用程序中的任何位置进行端到端访问。对于Redux及其唯一的数据仓库,所有组件的所有状态都将集中在一个位置。一些现成的React组件库
有流行的现成组件库,通常,这些库最初是公司的内部项目:- Material-UI — , Material Design Google.
- React-Bootstrap — , . : API , , .
- VKUI — «». VK mini apps, (. VK mini apps). VKUI «». «» . vkconnect — iOS Android.
- ARUI Feather — React- -. , . open source, .
所有这些库均旨在构建元素的布局及其样式。使用回调配置与环境的交互。因此,如果您要创建本文第三段和第四段中描述的成熟的可重用组件,则必须自己做。也许,以上述组件之一的View作为组件的视图。本文是在Mail.ru云解决方案的支持下编写的。