说说 React render 方法的原理?在什么时候会被触发?
问题解析(面试官考察点)
面试官通过此问题主要考察:
- 对 React 渲染机制的理解
- 对 render 方法在整个组件生命周期中的位置和作用的理解
- 对触发 render 的各种场景的了解
- 对渲染优化相关知识的掌握
核心概念(基础知识点)
render 方法是什么
render 方法是 React 类组件中唯一必须实现的方法,它负责根据组件的 props 和 state 返回要渲染的 UI 结构(React 元素)。对于函数组件,整个函数就相当于 render 方法。
render 方法的特点
- 纯函数: render 应该是纯函数,相同的输入(props + state)应该返回相同的输出
- 无副作用: 不应该在 render 中执行副作用操作(如数据获取、订阅、手动修改 DOM)
- 返回 JSX: 返回 JSX 元素(会被编译为 React.createElement 调用)
详细解答(代码示例)
render 方法的基本使用
// 类组件的 render 方法
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
// 函数组件(本身就是 render)
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// render 可以返回多种类型
class MyComponent extends React.Component {
render() {
// 1. React 元素
return <div>Hello</div>;
// 2. 数组或 Fragment
return [
<li key="1">Item 1</li>,
<li key="2">Item 2</li>
];
// 3. Portals
return ReactDOM.createPortal(
<div>Modal</div>,
document.getElementById('modal-root')
);
// 4. 字符串或数字
return "Hello World";
// 5. null 或 boolean(不渲染)
return null;
}
}
render 何时被触发
1. 初始渲染(Mount)
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
console.log('constructor'); // 1. 先执行
}
static getDerivedStateFromProps() {
console.log('getDerivedStateFromProps'); // 2. 再执行
return null;
}
render() {
console.log('render'); // 3. 最后执行 render
return <div>{this.state.count}</div>;
}
componentDidMount() {
console.log('componentDidMount'); // 4. render 完成后执行
}
}
// 输出顺序:
// constructor
// getDerivedStateFromProps
// render
// componentDidMount
2. 状态更新(setState)
class Counter extends React.Component {
state = { count: 0 };
handleClick = () => {
// 调用 setState 会触发 render
this.setState({ count: this.state.count + 1 });
};
render() {
console.log('render:', this.state.count);
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.handleClick}>+1</button>
</div>
);
}
}
3. Props 变化
class Child extends React.Component {
render() {
console.log('Child render:', this.props.value);
return <div>{this.props.value}</div>;
}
}
class Parent extends React.Component {
state = { value: 0 };
handleClick = () => {
this.setState({ value: this.state.value + 1 });
};
render() {
return (
<div>
<Child value={this.state.value} />
<button onClick={this.handleClick}>Update</button>
</div>
);
}
}
4. 父组件重新渲染
class Parent extends React.Component {
state = { parentValue: 0 };
handleClick = () => {
// 只更新 parentValue,但 Child 也会重新 render
this.setState({ parentValue: this.state.parentValue + 1 });
};
render() {
console.log('Parent render');
return (
<div>
<p>Parent: {this.state.parentValue}</p>
<Child childValue="不变" /> {/* 这个组件也会重新 render */}
<button onClick={this.handleClick}>Update Parent</button>
</div>
);
}
}
class Child extends React.Component {
render() {
console.log('Child render'); // 即使 props 没变,也会执行
return <div>Child: {this.props.childValue}</div>;
}
}
5. 强制更新(forceUpdate)
class MyComponent extends React.Component {
handleClick = () => {
// 强制触发 render,即使 state 和 props 都没变
this.forceUpdate();
};
render() {
console.log('render');
return <button onClick={this.handleClick}>Force Update</button>;
}
}
render 触发时机总结
// 触发 render 的所有场景
class Example extends React.Component {
// 1. 组件首次挂载
componentDidMount() {
// 初始渲染已完成
}
// 2. setState 调用(无论值是否改变)
handleStateUpdate = () => {
this.setState({ a: 1 }); // 触发 render
this.setState({ a: 1 }); // 同样会触发 render(React 18 自动批处理优化)
};
// 3. props 变化
// 父组件传递新的 props
// 4. 父组件重新渲染
// 即使 props 没变,子组件也会 render
// 5. forceUpdate
handleForceUpdate = () => {
this.forceUpdate(); // 强制 render
};
render() {
return <div>Example</div>;
}
}
深入理解(原理剖析)
render 的执行流程
┌─────────────────────────────────────────────────────────────┐
│ React 渲染流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 触发渲染 │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 1. Render 阶段 │ │
│ │ │ │
│ │ - 调用 render() │ │
│ │ - 创建 React 元素 │
│ │ - 构建 Virtual DOM │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 2. Reconciliation│ │
│ │ │ │
│ │ - Diff 算法对比 │
│ │ - 计算最小更新 │
│ │ - 生成更新队列 │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 3. Commit 阶段 │ │
│ │ │ │
│ │ - 更新真实 DOM │
│ │ - 执行生命周期 │
│ │ - 执行副作用 │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
render 与 Virtual DOM
// render 方法返回的是 React 元素(Virtual DOM 描述)
class MyComponent extends React.Component {
render() {
// JSX 会被编译为 React.createElement 调用
return (
<div className="container">
<h1>Title</h1>
<p>Content</p>
</div>
);
}
}
// 编译后等价于:
class MyComponent extends React.Component {
render() {
return React.createElement(
'div',
{ className: 'container' },
React.createElement('h1', null, 'Title'),
React.createElement('p', null, 'Content')
);
}
}
// 返回的 React 元素结构:
{
type: 'div',
props: {
className: 'container',
children: [
{ type: 'h1', props: { children: 'Title' } },
{ type: 'p', props: { children: 'Content' } }
]
}
}
React 18 的自动批处理
// React 18 之前:每次 setState 都触发一次 render
function BeforeReact18() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
const handleClick = () => {
setCount(c => c + 1); // 触发 render
setFlag(f => !f); // 再触发 render
};
// 总共触发 2 次 render
}
// React 18:自动批处理,只触发一次 render
function React18() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
const handleClick = () => {
setCount(c => c + 1); // 不立即触发
setFlag(f => !f); // 不立即触发
};
// 事件处理结束后,只触发 1 次 render
const handleAsync = () => {
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React 18 中这里也会自动批处理
}, 1000);
};
}
最佳实践
1. 避免不必要的 render
// 使用 PureComponent
class MyComponent extends React.PureComponent {
render() {
console.log('render');
return <div>{this.props.value}</div>;
}
}
// 使用 React.memo(函数组件)
const MyComponent = React.memo(({ value }) => {
console.log('render');
return <div>{value}</div>;
});
// 自定义比较函数
const MyComponent = React.memo(
({ value }) => {
return <div>{value}</div>;
},
(prevProps, nextProps) => {
// 返回 true 表示不重新 render
return prevProps.value === nextProps.value;
}
);
2. 使用 shouldComponentUpdate
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// 只有 value 变化时才重新 render
return nextProps.value !== this.props.value;
}
render() {
return <div>{this.props.value}</div>;
}
}
3. 避免在 render 中创建新对象
// 不好的做法:每次 render 都创建新对象
function BadExample() {
return (
<Child
config={{ a: 1, b: 2 }} // 每次都是新对象
onClick={() => {}} // 每次都是新函数
items={[1, 2, 3]} // 每次都是新数组
/>
);
}
// 好的做法:使用 useMemo
function GoodExample() {
const config = useMemo(() => ({ a: 1, b: 2 }), []);
const handleClick = useCallback(() => {}, []);
const items = useMemo(() => [1, 2, 3], []);
return (
<Child
config={config}
onClick={handleClick}
items={items}
/>
);
}
4. 条件渲染优化
// 使用短路运算符时注意
function Component({ items }) {
// 如果 items 是空数组,会渲染 "0"
return (
<div>
{items.length && items.map(item => <Item key={item.id} {...item} />)}
</div>
);
}
// 正确做法
function Component({ items }) {
return (
<div>
{items.length > 0 && items.map(item => <Item key={item.id} {...item} />)}
</div>
);
}
面试要点
-
render 是什么: 类组件必须实现的方法,返回 JSX 描述 UI;函数组件本身即 render
-
触发 render 的时机:
- 组件首次挂载
- 调用 setState(无论值是否改变)
- props 变化
- 父组件重新渲染
- 调用 forceUpdate
-
render 特点:
- 应该是纯函数
- 不应该有副作用
- 返回 Virtual DOM 描述
-
优化 render:
- 使用 PureComponent 或 React.memo
- 使用 shouldComponentUpdate
- 避免在 render 中创建新对象/函数
- 使用 useMemo 和 useCallback
-
React 18 优化: 自动批处理(Automatic Batching)减少不必要的 render