React 组件开发
更新: 8/6/2025 字数: 0 字 时长: 0 分钟
组件的类型
函数组件 vs 类组件
react
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 使用箭头函数
const Welcome = (props) => {
return <h1>Hello, {props.name}</h1>;
};
react
import React, { Component } from 'react';
class Welcome extends Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
组件的生命周期
挂载阶段
- constructor() - 初始化状态和绑定方法
- static getDerivedStateFromProps() - 在渲染前更新 state
- render() - 渲染组件
- componentDidMount() - 组件挂载后执行,适合进行网络请求、添加订阅等
更新阶段
- static getDerivedStateFromProps() - 在渲染前更新 state
- shouldComponentUpdate() - 决定组件是否需要更新
- render() - 重新渲染组件
- getSnapshotBeforeUpdate() - 在更新前获取 DOM 信息
- componentDidUpdate() - 组件更新后执行
卸载阶段
- componentWillUnmount() - 组件卸载前执行,适合清理订阅、定时器等
错误处理
- static getDerivedStateFromError() - 在子组件抛出错误后更新 state
- componentDidCatch() - 捕获子组件的错误
组件通信
父组件向子组件传递数据
通过 props 向子组件传递数据:
react
// 父组件
function Parent() {
const data = 'Hello from parent';
return <Child message={data} />;
}
// 子组件
function Child(props) {
return <p>{props.message}</p>;
}
// 使用解构赋值
function Child({ message }) {
return <p>{message}</p>;
}
react
// 父组件
class Parent extends Component {
render() {
const data = 'Hello from parent';
return <Child message={data} />;
}
}
// 子组件
class Child extends Component {
render() {
return <p>{this.props.message}</p>;
}
}
子组件向父组件传递数据
通过回调函数向父组件传递数据:
react
// 父组件
function Parent() {
const handleChildData = (data) => {
console.log('Data from child:', data);
};
return <Child onDataSend={handleChildData} />;
}
// 子组件
function Child({ onDataSend }) {
const sendData = () => {
const data = 'Hello from child';
onDataSend(data);
};
return <button onClick={sendData}>Send Data to Parent</button>;
}
react
// 父组件
class Parent extends Component {
handleChildData = (data) => {
console.log('Data from child:', data);
};
render() {
return <Child onDataSend={this.handleChildData} />;
}
}
// 子组件
class Child extends Component {
sendData = () => {
const data = 'Hello from child';
this.props.onDataSend(data);
};
render() {
return <button onClick={this.sendData}>Send Data to Parent</button>;
}
}
兄弟组件通信
通过共同的父组件进行通信:
react
import React, { useState } from 'react';
function Parent() {
const [data, setData] = useState('');
const handleDataFromA = (data) => {
setData(data);
};
return (
<div>
<ChildA onDataSend={handleDataFromA} />
<ChildB receivedData={data} />
</div>
);
}
function ChildA({ onDataSend }) {
const sendData = () => {
onDataSend('Hello from ChildA');
};
return <button onClick={sendData}>Send Data</button>;
}
function ChildB({ receivedData }) {
return <p>Data from ChildA: {receivedData}</p>;
}
组件复用
高阶组件 (HOC)
高阶组件是一个函数,接收一个组件并返回一个新组件:
react
import React, { useEffect } from 'react';
function withLogger(WrappedComponent) {
return function WithLogger(props) {
useEffect(() => {
console.log('Component rendered:', WrappedComponent.name);
return () => {
console.log('Component will unmount:', WrappedComponent.name);
};
}, []);
return <WrappedComponent {...props} />;
};
}
function MyComponent({ name }) {
return <div>Hello, {name}</div>;
}
const EnhancedComponent = withLogger(MyComponent);
// 使用增强后的组件
function App() {
return <EnhancedComponent name="World" />;
}
Render Props
Render Props 是一种通过函数 prop 共享代码的技术:
react
import React, { useState } from 'react';
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (event) => {
setPosition({
x: event.clientX,
y: event.clientY
});
};
return (
<div onMouseMove={handleMouseMove}>
{render(position)}
</div>
);
}
function App() {
return (
<div>
<h2>Render Props 示例</h2>
<MouseTracker
render={position => (
<p>Mouse position: {position.x}, {position.y}</p>
)}
/>
{/* 也可以使用 children 作为 render prop */}
<MouseTracker>
{position => (
<h3>鼠标坐标: ({position.x}, {position.y})</h3>
)}
</MouseTracker>
</div>
);
}
自定义 Hooks
自定义 Hooks 是一种在组件之间复用状态逻辑的方式:
react
import React, { useState, useEffect } from 'react';
// 自定义 Hook:鼠标位置追踪
function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (event) => {
setPosition({
x: event.clientX,
y: event.clientY
});
};
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []);
return position;
}
// 使用自定义 Hook
function MouseDisplay() {
const position = useMousePosition();
return (
<div>
<h3>鼠标位置</h3>
<p>X: {position.x}, Y: {position.y}</p>
</div>
);
}
// 另一个组件也可以使用同样的 Hook
function MouseFollower() {
const position = useMousePosition();
return (
<div
style={{
position: 'fixed',
left: position.x + 10,
top: position.y + 10,
pointerEvents: 'none',
background: 'red',
width: 10,
height: 10,
borderRadius: '50%'
}}
/>
);
}
return (
Mouse position: {position.x}, {position.y}
); }
## 组件性能优化
### React.memo
`React.memo` 是一个高阶组件,用于优化函数组件的性能:
```jsx
const MemoizedComponent = React.memo(function MyComponent(props) {
// 只有当 props 改变时才会重新渲染
return <div>{props.name}</div>;
});
shouldComponentUpdate
在类组件中,可以使用 shouldComponentUpdate
方法优化性能:
jsx
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// 只有当 props 或 state 改变时才重新渲染
return (
nextProps.name !== this.props.name ||
nextState.count !== this.state.count
);
}
render() {
return <div>{this.props.name}</div>;
}
}
PureComponent
React.PureComponent
自动实现了 shouldComponentUpdate
方法,进行浅比较:
jsx
class MyComponent extends React.PureComponent {
render() {
return <div>{this.props.name}</div>;
}
}
useMemo 和 useCallback
使用 useMemo
和 useCallback
避免不必要的计算和渲染:
jsx
function MyComponent({ data, onItemClick }) {
// 只有当 data 改变时才重新计算
const processedData = useMemo(() => {
return data.map(item => item.toUpperCase());
}, [data]);
// 只有当 onItemClick 改变时才创建新的回调函数
const handleClick = useCallback((item) => {
console.log('Item clicked:', item);
onItemClick(item);
}, [onItemClick]);
return (
<ul>
{processedData.map(item => (
<li key={item} onClick={() => handleClick(item)}>
{item}
</li>
))}
</ul>
);
}
组件样式
内联样式
jsx
function MyComponent() {
const style = {
color: 'blue',
fontSize: '16px',
fontWeight: 'bold'
};
return <div style={style}>Styled Component</div>;
}
CSS 类
jsx
import './MyComponent.css';
function MyComponent() {
return <div className="my-component">Styled Component</div>;
}
CSS Modules
jsx
import styles from './MyComponent.module.css';
function MyComponent() {
return <div className={styles.container}>Styled Component</div>;
}
Styled Components
jsx
import styled from 'styled-components';
const Container = styled.div`
color: blue;
font-size: 16px;
font-weight: bold;
`;
function MyComponent() {
return <Container>Styled Component</Container>;
}
错误处理
错误边界
错误边界是一种 React 组件,可以捕获子组件树中的 JavaScript 错误:
jsx
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state,下次渲染时显示备用 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 可以将错误日志上报给服务器
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
// 渲染备用 UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
);
}
try/catch
对于事件处理器中的错误,可以使用 try/catch:
jsx
function MyComponent() {
const handleClick = () => {
try {
// 可能会抛出错误的代码
throw new Error('Something went wrong');
} catch (error) {
console.error('Error in click handler:', error);
}
};
return <button onClick={handleClick}>Click Me</button>;
}
组件测试
Jest 和 React Testing Library
jsx
// Button.jsx
function Button({ onClick, children }) {
return <button onClick={onClick}>{children}</button>;
}
// Button.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';
test('renders button with correct text', () => {
render(<Button>Click Me</Button>);
const buttonElement = screen.getByText(/click me/i);
expect(buttonElement).toBeInTheDocument();
});
test('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click Me</Button>);
const buttonElement = screen.getByText(/click me/i);
fireEvent.click(buttonElement);
expect(handleClick).toHaveBeenCalledTimes(1);
});
最佳实践
- 保持组件小而专注:每个组件应该只做一件事。
- 使用函数组件和 Hooks:在大多数情况下,使用函数组件和 Hooks 比类组件更简洁。
- 提取重复逻辑:使用自定义 Hooks、高阶组件或 Render Props 提取重复逻辑。
- 使用 PropTypes 或 TypeScript:为组件添加类型检查。
- 避免过度优化:只在性能确实存在问题时才进行优化。
- 使用不可变数据:不要直接修改 props 或 state。
- 使用 key 属性:在渲染列表时,为每个元素提供唯一的 key。
- 避免内联函数:在渲染方法中避免创建内联函数,使用 useCallback 缓存函数。
- 使用 Fragment:避免添加不必要的 DOM 节点。
- 编写测试:为组件编写单元测试和集成测试。