React 组件与生命周期
1. 什么是有状态组件和无状态组件?
答案:
有状态组件(Stateful Components):
- 包含自身的state(状态)
- 通常是类组件,但使用useState Hook的函数组件也可以是有状态的
- 可以处理复杂的逻辑和数据操作
无状态组件(Stateless Components):
- 不包含自身的state
- 通常是函数组件
- 主要用于展示UI,接收props并渲染
示例:
// 有状态组件
class StatefulComponent extends React.Component {
state = { count: 0 };
render() {
return <div>{this.state.count}</div>;
}
}
// 无状态组件
function StatelessComponent(props) {
return <div>{props.count}</div>;
}
2. React 组件的生命周期方法有哪些?它们的调用顺序是什么?
答案:React 类组件的生命周期方法可以分为三个阶段:
挂载阶段:
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
更新阶段:
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
卸载阶段:
- componentWillUnmount()
调用顺序就是上述列出的顺序。注意,在 React 16.3 之后,一些生命周期方法被标记为不安全(如 componentWillMount, componentWillReceiveProps, componentWillUpdate),应避免使用。
3. componentDidMount() 方法有什么用途?
答案:componentDidMount() 方法在组件被挂载到 DOM 后立即调用。这个方法通常用于:
- 执行副作用操作,如设置订阅或定时器
- 发起网络请求获取数据
- 直接操作 DOM
示例:
class MyComponent extends React.Component {
componentDidMount() {
// 发起 API 请求
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => this.setState({ data }));
// 设置定时器
this.timer = setInterval(() => {
console.log('Timer ticking');
}, 1000);
}
componentWillUnmount() {
// 清除定时器
clearInterval(this.timer);
}
render() {
// 渲染逻辑
}
}
4. shouldComponentUpdate() 方法的作用是什么?如何优化性能?
答案:shouldComponentUpdate() 方法允许我们手动决定是否要重新渲染组件。它在 props 或 state 发生变化时,在渲染前被调用。
作用:
- 性能优��:通过比较新旧 props 和 state,决定是否需要重新渲染
- 避免不必要的渲染
优化性能的方法:
- 实现浅比较:比较新旧 props 和 state 的值
- 使用 React.PureComponent:自动实现浅比较的 shouldComponentUpdate
- 使用 React.memo 高阶组件(用于函数组件)
示例:
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// 只有当 title 改变时才重新渲染
return this.props.title !== nextProps.title;
}
render() {
return <div>{this.props.title}</div>;
}
}
// 使用 React.memo
const MyFunctionComponent = React.memo(function MyFunctionComponent(props) {
return <div>{props.title}</div>;
});
5. 什么是错误边界(Error Boundaries)?如何实现?
答案:错误边界是 React 组件,它可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。
实现错误边界:
- 定义 static getDerivedStateFromError() 或 componentDidCatch() 方法
- 使用 state 来控制是否应该显示错误 UI
示例:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.log('Error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// 使用
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
6. React 16.3 之后的生命周期变化是什么?为什么要进行这些变化?
答案:React 16.3 之后的主要生命周期变化:
引入了新的生命周期方法:
- getDerivedStateFromProps
- getSnapshotBeforeUpdate
废弃了一些生命周期方法(在 17.0 版本中彻底移除):
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
这些变化的原因:
- 为即将到来的异步渲染做准备
- 避免开发者在即将被移除的生命周期方法中编写副作用代码
- 鼓励更好的编码实践,如将副作用移到 componentDidMount 中
新的生命周期方法使用示例:
class MyComponent extends React.Component {
static getDerivedStateFromProps(props, state) {
// 根据 props 更新 state
return null;
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 获取更新前的一些信息
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 使用 getSnapshotBeforeUpdate 的返回值
}
}
7. 如何在函数组件中模拟生命周期方法?
答案:在函数组件中,我们可以使用 React Hooks 来模拟生命周期方法:
- componentDidMount: 使用 useEffect 并传入空数组作为依赖
- componentDidUpdate: 使用 useEffect 并传入依赖数组
- componentWillUnmount: 在 useEffect 中返回一个清理函数
示例:
import React, { useState, useEffect } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
// 模拟 componentDidMount
fetchData();
// 模拟 componentWillUnmount
return () => {
// 清理工作,如取消订阅
};
}, []); // 空数组表示只在挂载和卸载时运行
useEffect(() => {
// 模拟 componentDidUpdate
console.log('data has changed:', data);
}, [data]); // 当 data 变化时运行
const fetchData = () => {
// 获取数据的逻辑
};
return <div>{/* 组件内容 */}</div>;
}
8. React 中的 render 方法作用是什么?它应该是纯函数吗?
答案:render 方法是 class 组件中唯一必须实现的方法。它的作用是:
- 检查 this.props 和 this.state 的变化
- 返回以下类型之一:
- React 元素(通常通过 JSX 创建)
- 数组或 fragments
- Portals
- 字符串或数值类型
- Boolean 或 null
render 方法应该是纯函数,这意味着:
- 不应该修改组件的 state
- 每次调用时,相同的输入(props 和 state)应该返回相同的输出
- 不应该直接与浏览器交互(应该在 componentDidMount 或其他生命周期方法中执行这些操作)
示例:
class MyComponent extends React.Component {
render() {
return (
<div>
<h1>{this.props.title}</h1>
<p>{this.state.content}</p>
</div>
);
}
}
9. 什么是 React 中的受控组件和非受控组件?
答案:
受控组件:
- 表单数据由 React 组件控制
- 通过 props 接收当前值,并通过回调通知变化
非受控组件:
- 表单数据由 DOM 本身处理
- 通常使用 ref 来获取表单数据
示例:
// 受控组件
function ControlledInput() {
const [value, setValue] = useState('');
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
}
// 非受控组件
function UncontrolledInput() {
const inputRef = useRef(null);
function handleSubmit() {
console.log(inputRef.current.value);
}
return (
<>
<input ref={inputRef} />
<button onClick={handleSubmit}>Submit</button>
</>
);
}
10. 什么是高阶组件(HOC)?它们有什么用途?
答案:高阶组件(HOC)是一个函数,它接受一个组件作为参数并返回一个新的组件。HOC 是 React 中复用组件逻辑的高级技术。
HOC 的主要用途:
- 代码复用
- 渲染劫持
- State 抽象和操作
- Props 操作
示例:
function withLogging(WrappedComponent) {
return class extends React.Component {
componentDidMount() {
console.log('Component is mounted');
}
render() {
return <WrappedComponent {...this.props} />;
}
};
}
// 使用
const EnhancedComponent = withLogging(MyComponent);
高阶组件可以用来实现横切关注点,如日志记录、权限控制,而不需要修改原组件的代码。