
大家好,这里公众号 Geek技术前线。
今天我们来学习 React 自诞生以来各种类型的 React 组件
自从 React 于 2013 年发布以来,出现了各种类型的组件。有些在现在的 React 应用中仍然至关重要,而另一些则主要出现在旧项目中(或者已被官方废弃)。
React 最初依赖 createClass(已废弃)定义组件,它通过工厂函数创建 React 组件,而不需要 JavaScript Class。由于 JavaScript ES5 缺少类语法,这种方法在 2015 年之前的标准是用于构建 React 组件的方式,而 JavaScript ES6 则引入了类语法:
import createClass from "create-react-class";
const CreateClassComponent = createClass({
getInitialState: function () {
return {
text: "",
};
},
handleChangeText: function (event) {
this.setState({ text: event.target.value });
},
render: function () {
return (
<div>
<p>Text: {this.state.text}</p>
<input
type="text"
value={this.state.text}
onChange={this.handleChangeText}
/>
</div>
);
},
});
export default CreateClassComponent;在这个示例中,React 的 createClass() 工厂方法接收一个对象,该对象定义了 React 组件的方法。getInitialState() 函数用于初始化组件的状态,而必需的 render() 方法使用 JSX 处理输出的显示。可以向对象添加其他方法,例如 incrementCounter(),作为组件的事件处理程序。
生命周期方法同样可以用于处理副作用。例如,如果我们想每次将 text 的状态值写入浏览器的本地存储,可以使用 componentDidUpdate() 生命周期方法。此外,当组件接收到初始状态时,还可以从本地存储读取该值:
import createClass from "create-react-class";
const CreateClassComponent = createClass({
getInitialState: function () {
return {
text: localStorage.getItem("text") || "",
};
},
componentDidUpdate: function () {
localStorage.setItem("text", this.state.text);
},
handleChangeText: function (event) {
this.setState({ text: event.target.value });
},
render: function () {
return (
<div>
<p>Text: {this.state.text}</p>
<input
type="text"
value={this.state.text}
onChange={this.handleChangeText}
/>
</div>
);
},
});
export default CreateClassComponent;React 的 createClass 方法已不再包含在 React 核心包中。如果现在还想尝试使用的话需要安装一个额外的 npm 包 create-react-class。
React Mixins(已废弃)是 React 引入的第一个用于复用组件逻辑的模式。通过使用 Mixin,可以将组件的逻辑提取为一个独立的对象。当在组件中使用 Mixin 时,所有来自 Mixin 的功能都会被引入到该组件中:
import createClass from "create-react-class";
const LocalStorageMixin = {
getInitialState: function () {
return {
text: localStorage.getItem("text") || "",
};
},
componentDidUpdate: function () {
localStorage.setItem("text", this.state.text);
},
};
const CreateClassWithMixinComponent = createClass({
mixins: [LocalStorageMixin],
handleChangeText: function (event) {
this.setState({ text: event.target.value });
},
render: function () {
return (
<div>
<p>Text: {this.state.text}</p>
<input
type="text"
value={this.state.text}
onChange={this.handleChangeText}
/>
</div>
);
},
});
export default CreateClassWithMixinComponent;LocalStorageMixin 封装了处理 text 状态的逻辑,将其存储在本地存储中。在 getInitialState 中初始化 text,并在 componentDidUpdate 中进行更新。通过将 Mixin 添加到 mixins 数组中,组件可以复用这部分共享功能,而不必重复编写代码。
然而,React 中的 Mixins 已经不再使用,因为它们带来了许多缺点,并且仅限于 createClass 组件中使用。
React 类组件(不推荐)在 2015 年 3 月发布的 React 0.13 版本中被引入。在此之前,开发者使用 createClass 函数来定义组件,但最终在 2017 年 4 月发布的 React 15.5 版本中废弃了 createClass,并推荐使用类组件来替代。
类组件的引入是为了利用 JavaScript 的原生类(因为 2015 年发布的 ES6 提供了类的语法),使得 JS 类可以在 React 中使用:
import React from "react";
class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "",
};
this.handleChangeText = this.handleChangeText.bind(this);
}
handleChangeText(event) {
this.setState({ text: event.target.value });
}
render() {
return (
<div>
<p>Text: {this.state.text}</p>
<input
type="text"
value={this.state.text}
onChange={this.handleChangeText}
/>
</div>
);
}
}
export default ClassComponent;用 JavaScript 类编写的 React 组件自带一些方法,比如类的构造函数(主要用于在 React 中设置初始状态或绑定方法),以及必需的 render 方法,用于返回 JSX 作为输出。
所有的内部 React 组件逻辑都来源于面向对象的继承。但需要注意的是,React 不推荐组件使用继承而是推荐使用组合优于继承的原则。
此外,在使用 ES6 箭头函数时,类组件还提供了一种简化的方法,用于自动绑定方法:
import React from "react";
class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "",
};
// 使用箭头函数时不需要手动绑定
// this.handleChangeText = this.handleChangeText.bind(this);
}
handleChangeText = (event) => {
this.setState({ text: event.target.value });
};
render() {
return (
<div>
<p>Text: {this.state.text}</p>
<input
type="text"
value={this.state.text}
onChange={this.handleChangeText}
/>
</div>
);
}
}
export default ClassComponent;React 类组件还提供了多种生命周期方法,用于组件的挂载、更新和卸载。在我们之前的本地存储示例中,也可以通过生命周期方法将其引入为副作用:
import React from "react";
class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
text: localStorage.getItem("text") || "",
};
this.handleChangeText = this.handleChangeText.bind(this);
}
componentDidUpdate() {
localStorage.setItem("text", this.state.text);
}
handleChangeText(event) {
this.setState({ text: event.target.value });
}
render() {
return (
<div>
<p>Text: {this.state.text}</p>
<input
type="text"
value={this.state.text}
onChange={this.handleChangeText}
/>
</div>
);
}
}
export default ClassComponent;随着 React 16.8 版本(2019 年 2 月)引入 React Hooks,使用带有 Hooks 的函数组件成为了行业标准,从而使得类组件逐渐过时。在此之前,类组件与函数组件共存,因为函数组件在没有 Hooks 的情况下,无法管理状态或处理副作用。
React 高阶组件(不再推荐)曾是跨组件复用逻辑的流行高级模式。
高阶组件 的最简单解释是,它是一个以组件为输入并返回一个增强功能组件的函数。让我们回顾一下之前的本地存储功能提取示例:
import React from "react";
const withLocalStorage = (storageKey) => (Component) => {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
value: localStorage.getItem(storageKey) || "",
};
}
componentDidUpdate() {
localStorage.setItem(storageKey, this.state.value);
}
onChangeValue = (event) => {
this.setState({ value: event.target.value });
};
render() {
return (
<Component
value={this.state.value}
onChangeValue={this.onChangeValue}
{...this.props}
/>
);
}
};
};
class ClassComponent extends React.Component {
render() {
return (
<div>
<p>Text: {this.props.value}</p>
<input
type="text"
value={this.props.value}
onChange={this.props.onChangeValue}
/>
</div>
);
}
}
export default withLocalStorage("text")(ClassComponent);通过 withLocalStorage 高阶组件,我们将本地存储逻辑抽象出来,并将其与其他组件结合,赋予它们本地存储功能。
另一种常见的 React 高级模式是 React Render Prop 组件,它通常用作 React 高阶组件(HOCs)的替代方案。值得注意的是,HOCs 和 Render Prop 组件都可以在类组件和函数组件中使用。
然而,在现代 React 应用中,React 高阶组件和 Render Prop 组件的使用已经减少。使用 React Hooks 的函数组件已成为跨组件共享逻辑的主流方法。
React 函数组件(Function Components,FC,过去有时被称为 函数无状态组件)现在常作为类组件的替代方案。它们以函数形式表达,而不是类。在过去,函数组件无法使用状态或处理副作用,因此也被称为无状态组件,但自从 React Hooks 的引入,它们已经能够管理状态和副作用,并重新定义为函数组件。
React Hooks 为函数组件引入了状态管理和副作用处理,使其成为现代 React 应用的 行业标准。React 提供了多种内置的 Hooks,也可以创建自定义 Hooks。以下是将之前的 React 类组件转为函数组件的示例:
import { useState } from "react";
const FunctionComponent = () => {
const [text, setText] = useState("");
const handleChangeText = (event) => {
setText(event.target.value);
};
return (
<div>
<p>Text: {text}</p>
<input type="text" value={text} onChange={handleChangeText} />
</div>
);
};
export default FunctionComponent;上述代码展示了函数组件使用 React 内置的 useState Hook 管理状态。而 React Hooks 的引入也让函数组件能够处理副作用。以下代码展示了使用 React 的 useEffect Hook,该 Hook 每次状态变化时执行:
import { useEffect, useState } from "react";
const FunctionComponent = () => {
const [text, setText] = useState(localStorage.getItem("text") || "");
useEffect(() => {
localStorage.setItem("text", text);
}, [text]);
const handleChangeText = (event) => {
setText(event.target.value);
};
return (
<div>
<p>Text: {text}</p>
<input type="text" value={text} onChange={handleChangeText} />
</div>
);
};
export default FunctionComponent;最后,我们可以将这些 Hooks 提取出来,封装为一个自定义 Hook,以确保组件状态与本地存储同步。最终,它会返回必要的值和设置函数,供函数组件使用:
import { useEffect, useState } from "react";
const useLocalStorage = (storageKey) => {
const [value, setValue] = useState(localStorage.getItem(storageKey) || "");
useEffect(() => {
localStorage.setItem(storageKey, value);
}, [storageKey, value]);
return [value, setValue];
};
const FunctionComponent = () => {
const [text, setText] = useLocalStorage("text");
const handleChangeText = (event) => {
setText(event.target.value);
};
return (
<div>
<p>Text: {text}</p>
<input type="text" value={text} onChange={handleChangeText} />
</div>
);
};
export default FunctionComponent;通过这个自定义 Hook,我们能够复用本地存储逻辑,并将其应用于不同的函数组件。
React 自定义 Hook 的抽象模式可以像 Mixins、高阶组件 (HOC)、以及 Render Prop 组件那样,将可复用的业务逻辑提取出来供不同组件使用。这种方式可以将逻辑封装,并在任意函数组件中复用,是目前 React 推荐的跨组件共享逻辑的最佳方式。
React 在 2023 年推出了 React 服务器组件 (React Server Components, RSC),这使得开发者可以在服务器上执行组件。其主要优势在于:仅将 HTML 发送到客户端,且组件可以访问服务器端资源。
由于服务器组件是在服务器端执行的,不能与之前的示例一一对应,因为它们服务于不同的场景。以下示例展示了一个服务器组件如何在发送渲染后的 JSX 作为 HTML 给客户端之前,从服务器端资源(如数据库)中获取数据:
const ReactServerComponent = async () => {
const posts = await db.query("SELECT * FROM posts");
return (
<div>
<ul>
{posts?.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
};
export default ReactServerComponent;随着服务器组件的出现,React 也引入了 客户端组件 (Client Components) 这一术语,指的是运行在客户端上的传统 React 组件,也就是你在本指南中看到的内容。
与客户端组件不同,服务器组件无法使用 React Hooks 或其他 JavaScript 功能(如事件处理),因为它们是在服务器端运行的。
React 本身仅提供服务器组件的底层规范和构建模块,实际的实现则依赖于 React 框架(如 Next.js)。
目前,异步组件仅支持服务器组件,但未来有望支持客户端组件。如果组件被标记为 async,它可以执行异步操作(例如获取数据)。
在之前的服务器组件示例中,你看到了这种行为,组件从数据库中获取数据,然后在发送已渲染的 JSX 作为 HTML 给客户端之前进行渲染。在客户端组件中无法实现此功能,因为它会阻塞客户端的渲染。
目前,你只能将 JavaScript Promise 传递给客户端组件:
import { Suspense } from "react";
const ReactServerComponent = () => {
const postsPromise = db.query("SELECT * FROM posts");
return (
<div>
<Suspense>
<ReactClientComponent promisedPosts={postsPromise} />
</Suspense>
</div>
);
};并在客户端组件中使用 use API 来解析它:
"use client";
import { use } from "react";
const ReactClientComponent = ({ promisedPosts }) => {
const posts = use(promisedPosts);
return (
<ul>
{posts?.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
};
export { ReactClientComponent };未来,React 可能会支持客户端组件中的异步组件,允许你在渲染之前在客户端组件中获取数据。
所有 React 组件在使用 React Props 时都遵循共同的原则,因 Props 主要用于在组件树中传递信息。然而,对于类组件和函数组件来说,状态管理和副作用处理的使用方式有所不同