大家好,这里公众号 Geek技术前线
。
今天我们来学习 React 自诞生以来各种类型的 React 组件
自从 React 于 2013 年发布以来,出现了各种类型的组件。有些在现在的 React 应用中仍然至关重要,而另一些则主要出现在旧项目中(或者已被官方废弃)。
React createClass
React 最初依赖 createClass(已废弃)定义组件,它通过工厂函数创建 React 组件,而不需要 JavaScript Class。由于 JavaScript ES5 缺少类语法,这种方法在 2015 年之前的标准是用于构建 React 组件的方式,而 JavaScript ES6 则引入了类语法:
代码语言:javascript复制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 Mixins(已废弃)是 React 引入的第一个用于复用组件逻辑的模式。通过使用 Mixin,可以将组件的逻辑提取为一个独立的对象。当在组件中使用 Mixin 时,所有来自 Mixin 的功能都会被引入到该组件中:
代码语言:javascript复制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 类组件
React 类组件(不推荐)在 2015 年 3 月发布的 React 0.13 版本中被引入。在此之前,开发者使用 createClass
函数来定义组件,但最终在 2017 年 4 月发布的 React 15.5 版本中废弃了 createClass
,并推荐使用类组件来替代。
类组件的引入是为了利用 JavaScript 的原生类(因为 2015 年发布的 ES6 提供了类的语法),使得 JS 类可以在 React 中使用:
代码语言:javascript复制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 箭头函数时,类组件还提供了一种简化的方法,用于自动绑定方法:
代码语言:javascript复制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 类组件还提供了多种生命周期方法,用于组件的挂载、更新和卸载。在我们之前的本地存储示例中,也可以通过生命周期方法将其引入为副作用:
代码语言:javascript复制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 高阶组件(模式)
React 高阶组件(不再推荐)曾是跨组件复用逻辑的流行高级模式。
高阶组件 的最简单解释是,它是一个以组件为输入并返回一个增强功能组件的函数。让我们回顾一下之前的本地存储功能提取示例:
代码语言:javascript复制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 函数组件
React 函数组件(Function Components,FC,过去有时被称为 函数无状态组件)现在常作为类组件的替代方案。它们以函数形式表达,而不是类。在过去,函数组件无法使用状态或处理副作用,因此也被称为无状态组件,但自从 React Hooks 的引入,它们已经能够管理状态和副作用,并重新定义为函数组件。
React Hooks 为函数组件引入了状态管理和副作用处理,使其成为现代 React 应用的 行业标准。React 提供了多种内置的 Hooks,也可以创建自定义 Hooks。以下是将之前的 React 类组件转为函数组件的示例:
代码语言:javascript复制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,以确保组件状态与本地存储同步。最终,它会返回必要的值和设置函数,供函数组件使用:
代码语言:javascript复制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 Server Component
React 在 2023 年推出了 React 服务器组件 (React Server Components, RSC),这使得开发者可以在服务器上执行组件。其主要优势在于:仅将 HTML 发送到客户端,且组件可以访问服务器端资源。
由于服务器组件是在服务器端执行的,不能与之前的示例一一对应,因为它们服务于不同的场景。以下示例展示了一个服务器组件如何在发送渲染后的 JSX 作为 HTML 给客户端之前,从服务器端资源(如数据库)中获取数据:
代码语言:javascript复制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 传递给客户端组件:
代码语言:javascript复制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 主要用于在组件树中传递信息。然而,对于类组件和函数组件来说,状态管理和副作用处理的使用方式有所不同
参考
- https://www.robinwieruch.de/react-component-types/