返回首页

说说 React render 方法的原理?在什么时候会被触发?

问题解析(面试官考察点)

面试官通过此问题主要考察:

  • 对 React 渲染机制的理解
  • 对 render 方法在整个组件生命周期中的位置和作用的理解
  • 对触发 render 的各种场景的了解
  • 对渲染优化相关知识的掌握

核心概念(基础知识点)

render 方法是什么

render 方法是 React 类组件中唯一必须实现的方法,它负责根据组件的 props 和 state 返回要渲染的 UI 结构(React 元素)。对于函数组件,整个函数就相当于 render 方法。

render 方法的特点

  1. 纯函数: render 应该是纯函数,相同的输入(props + state)应该返回相同的输出
  2. 无副作用: 不应该在 render 中执行副作用操作(如数据获取、订阅、手动修改 DOM)
  3. 返回 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>
  );
}

面试要点

  1. render 是什么: 类组件必须实现的方法,返回 JSX 描述 UI;函数组件本身即 render

  2. 触发 render 的时机:

    • 组件首次挂载
    • 调用 setState(无论值是否改变)
    • props 变化
    • 父组件重新渲染
    • 调用 forceUpdate
  3. render 特点:

    • 应该是纯函数
    • 不应该有副作用
    • 返回 Virtual DOM 描述
  4. 优化 render:

    • 使用 PureComponent 或 React.memo
    • 使用 shouldComponentUpdate
    • 避免在 render 中创建新对象/函数
    • 使用 useMemo 和 useCallback
  5. React 18 优化: 自动批处理(Automatic Batching)减少不必要的 render