组件是 React 的核心基石,也是 React 生态中最具代表性的设计思想。它将 UI 拆分为独立、可复用的单元,就像乐高积木一样,通过组合不同的组件可以构建出复杂的页面。从早期的类组件到如今的函数组件 + Hooks,React 组件的开发模式不断进化,变得更加简洁、灵活。本文将从组件本质、分类、创建、核心特性、通信、复用、性能优化等维度,全面解析 React 组件的使用方法与最佳实践,帮助你彻底掌握这一核心技能。
一、React 组件的本质:什么是组件?
1. 组件的定义
React 组件是独立的、可复用的 UI 单元,它接收输入(Props),处理内部逻辑(State/Hooks),最终返回用于描述 UI 的 JSX(或 React 元素)。简单来说,组件就是一个 "函数":输入数据,输出 UI。
2. 组件的核心思想:模块化与复用
在传统的前端开发中,我们通常按页面划分代码,代码耦合度高,复用性差。而 React 的组件化思想解决了这一问题:
- 单一职责:一个组件只负责完成一个特定的功能(比如一个按钮、一个列表、一个弹窗);
- 可复用:组件可以在不同页面、不同项目中重复使用;
- 可组合:组件可以嵌套、组合,形成更复杂的组件或页面;
- 可维护:组件独立存在,修改一个组件不会影响其他组件,便于后期维护。
3. 组件的本质:函数或类
无论哪种类型的 React 组件,其本质都是JavaScript 函数或类:
- 函数组件:本质是一个普通的 JavaScript 函数,接收 props 参数,返回 JSX;
- 类组件 :本质是继承自
React.Component的类,通过render方法返回 JSX。
举个最简单的例子,一个显示 "Hello React" 的组件:
scala
// 函数组件(主流)
const Hello = () => {
return <h1>Hello React</h1>;
};
// 类组件(旧版写法,现在极少使用)
import React from 'react';
class HelloClass extends React.Component {
render() {
return <h1>Hello React</h1>;
}
}
二、React 组件的分类:不同类型的组件及适用场景
React 组件可以根据实现方式、职责、特性分为不同类型,了解这些分类有助于我们在开发中选择合适的组件类型。
1. 按实现方式划分:函数组件 vs 类组件
这是最核心的分类方式,也是 React 发展的重要分水岭。
| 特性 | 函数组件(Function Component) | 类组件(Class Component) |
|---|---|---|
| 语法复杂度 | 简洁,普通 JS 函数 | 繁琐,需要继承 React.Component,写 render 方法 |
| 状态管理 | 依赖 Hooks(useState、useReducer) | 依赖 this.state 和 this.setState |
| 生命周期 | 依赖 Hooks(useEffect) | 有固定的生命周期方法(componentDidMount 等) |
| 性能 | 略优(无类实例化开销) | 略差(需要实例化类) |
| 适用场景 | 所有场景(React 16.8 + 推荐) | 老项目维护、特殊场景(如需要继承的组件) |
注意:React 16.8 版本引入 Hooks 后,函数组件已经能够实现类组件的所有功能,目前函数组件 + Hooks是 React 开发的主流方式,类组件仅在老项目中存在。
2. 按职责划分:展示组件 vs 容器组件
这是一种基于 "关注点分离" 的分类方式,用于区分组件的功能职责。
展示组件(Presentational Component)
- 职责:只负责 UI 的展示,不处理业务逻辑,也不管理状态;
- 数据来源:通过 Props 接收外部传入的数据和回调函数;
- 特点:纯函数式、可复用性高、无副作用。
javascript
// 展示组件:只渲染列表项,不处理数据逻辑
const TodoItem = ({ text, onDelete }) => {
return (
<li>
{text}
<button onClick={onDelete}>删除</button>
</li>
);
};
容器组件(Container Component)
- 职责:负责处理业务逻辑、管理状态、请求数据,不关心 UI 展示;
- 数据来源:自身的 State 或外部状态管理库(Redux、Mobx);
- 特点:通常不直接渲染 JSX,而是将数据和方法通过 Props 传递给展示组件。
javascript
// 容器组件:处理待办事项的逻辑,管理状态
import { useState } from 'react';
const TodoListContainer = () => {
const [todos, setTodos] = useState(['学习React组件', '写博客']);
const handleDelete = (index) => {
setTodos(todos.filter((_, i) => i !== index));
};
return (
<ul>
{todos.map((todo, index) => (
<TodoItem key={index} text={todo} onDelete={() => handleDelete(index)} />
))}
</ul>
);
};
补充:随着 Hooks 的普及,这种分类方式逐渐淡化,因为我们可以通过自定义 Hooks 将业务逻辑抽离,让组件同时兼具展示和逻辑功能。
3. 其他常见分类
- 纯组件(Pure Component) :类组件中的
React.PureComponent或函数组件中的React.memo,用于优化性能,避免不必要的重渲染; - 高阶组件(Higher-Order Component,HOC) :一个接收组件并返回新组件的函数,用于复用组件逻辑;
- 门户组件(Portal) :用于将组件渲染到 DOM 树的其他位置,如弹窗、提示框;
- 懒加载组件 :通过
React.lazy和Suspense实现的按需加载组件。
三、组件的创建与基础使用
本节将重点讲解函数组件的创建与使用(类组件仅作简单介绍),包括 Props 接收、默认值、类型校验等核心知识点。
1. 基础函数组件的创建与渲染
(1)创建函数组件
函数组件是一个普通的 JavaScript 函数,返回值为 JSX(或 null、false,表示不渲染任何内容)。
javascript
// 无参数的基础组件
const Button = () => {
return <button>点击我</button>;
};
// 箭头函数简写(无大括号时,return可省略)
const Text = () => <p>这是一段文本</p>;
// 普通函数写法(兼容旧版JS)
function Title() {
return <h1>这是标题</h1>;
}
(2)渲染组件
组件创建完成后,可以像使用 HTML 标签一样在其他组件中渲染,注意组件名必须以大写字母开头(React 的约定,用于区分原生 HTML 标签)。
javascript
// 根组件
const App = () => {
return (
<div className="app">
<Title />
<Text />
<Button />
</div>
);
};
// 渲染到DOM
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
2. Props:组件的输入参数
Props(Properties 的缩写)是组件的输入参数,用于从父组件向子组件传递数据。Props 是只读的,子组件不能修改 Props(这是 React 的单向数据流原则)。
(1)接收与使用 Props
javascript
// 子组件:接收props
const Greeting = (props) => {
// props是一个对象,包含父组件传递的所有属性
return <h1>Hello, {props.name}!</h1>;
};
// 父组件:传递props
const App = () => {
return (
<div>
{/* 传递字符串属性 */}
<Greeting name="React" />
{/* 传递非字符串属性(需用{}包裹) */}
<Greeting age={18} />
{/* 传递布尔值 */}
<Greeting isShow={true} />
{/* 传递函数 */}
<Greeting onButtonClick={() => alert('点击了')} />
{/* 传递JSX元素(对应props.children) */}
<Greeting>
<p>这是子元素</p>
</Greeting>
</div>
);
};
(2)Props 解构赋值
为了简化代码,通常使用解构赋值直接提取 Props 中的属性。
javascript
// 基础解构
const Greeting = ({ name, age }) => {
return (
<div>
<h1>Hello, {name}!</h1>
<p>年龄:{age}</p>
</div>
);
};
// 解构+默认值
const Greeting = ({ name = '游客', age = 0 }) => {
return (
<div>
<h1>Hello, {name}!</h1>
<p>年龄:{age}</p>
</div>
);
};
(3)默认 Props
除了使用解构赋值设置默认值,还可以通过defaultProps属性设置组件的默认 Props(适用于函数组件和类组件)。
javascript
const Greeting = ({ name, age }) => {
return (
<div>
<h1>Hello, {name}!</h1>
<p>年龄:{age}</p>
</div>
);
};
// 设置默认Props
Greeting.defaultProps = {
name: '游客',
age: 0
};
(4)Props 类型校验
为了提高代码的健壮性,我们可以使用prop-types库对 Props 的类型进行校验(React v15.5.0 后,PropTypes 从 React 核心库中移出,需单独安装)。
步骤 1:安装 prop-types
csharp
npm install prop-types --save
# 或
yarn add prop-types
步骤 2:使用 PropTypes 进行类型校验
javascript
import PropTypes from 'prop-types';
const Greeting = ({ name, age, isShow, onButtonClick, children }) => {
return (
<div>
<h1>Hello, {name}!</h1>
<p>年龄:{age}</p>
{children}
</div>
);
};
// 类型校验
Greeting.propTypes = {
// 字符串,必传
name: PropTypes.string.isRequired,
// 数字(也可以是字符串)
age: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
// 布尔值
isShow: PropTypes.bool,
// 函数
onButtonClick: PropTypes.func,
// 任意React节点
children: PropTypes.node
};
// 默认Props
Greeting.defaultProps = {
name: '游客',
age: 0,
isShow: false
};
3. 类组件的创建(仅作了解)
类组件需要继承React.Component,并实现render方法返回 JSX。
javascript
import React from 'react';
import PropTypes from 'prop-types';
class Greeting extends React.Component {
// 默认Props
static defaultProps = {
name: '游客',
age: 0
};
// 类型校验
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number
};
render() {
const { name, age } = this.props;
return (
<div>
<h1>Hello, {name}!</h1>
<p>年龄:{age}</p>
</div>
);
}
}