React render 方法原理与触发时机

理解 React render 方法的工作原理和触发条件

问题

React 的 render 方法是如何工作的?什么情况下会触发重新渲染?

解答

render 方法的两种形式

在类组件中,render 是一个方法:

class Foo extends React.Component {
  render() {
    return <h1>Foo</h1>;
  }
}

在函数组件中,整个函数就是 render:

function Foo() {
  return <h1>Foo</h1>;
}

JSX 到虚拟 DOM 的转换

JSX 代码:

return (
  <div className='cn'>
    <Header>hello</Header>
    <div>start</div>
    Right Reserve
  </div>
)

经过 Babel 编译后转换为:

return (
  React.createElement(
    'div',
    { className: 'cn' },
    React.createElement(Header, null, 'hello'),
    React.createElement('div', null, 'start'),
    'Right Reserve'
  )
)

createElement 接收三个参数:

  • type:标签类型
  • attributes:标签属性,无则为 null
  • children:子节点

这些调用生成虚拟 DOM 树,React 通过 diff 算法比较新旧虚拟 DOM,然后更新真实 DOM。

触发时机

类组件调用 setState

无论状态是否真正改变,都会触发 render:

class Foo extends React.Component {
  state = { count: 0 };

  increment = () => {
    const { count } = this.state;
    const newCount = count < 10 ? count + 1 : count;
    this.setState({ count: newCount });
  };

  render() {
    console.log("Foo render"); // 每次 setState 都会执行
    return (
      <div>
        <h1>{this.state.count}</h1>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

函数组件使用 useState

只有状态值真正改变时才触发 render:

function Foo() {
  const [count, setCount] = useState(0);

  function increment() {
    const newCount = count < 10 ? count + 1 : count;
    setCount(newCount); // 值不变时不会触发 render
  }

  console.log("Foo render");
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

父组件重新渲染

类组件:父组件 render 会导致所有子组件 render

class App extends React.Component {
  state = { name: "App" };
  
  render() {
    return (
      <div className="App">
        <Foo /> {/* App render 时,Foo 也会 render */}
        <button onClick={() => this.setState({ name: "App" })}>
          Change name
        </button>
      </div>
    );
  }
}

函数组件:使用 useState 时,只有首次和状态改变时才触发子组件 render

function App() {
  const [name, setName] = useState('App');

  return (
    <div className="App">
      <Foo /> {/* 首次渲染后,点击按钮不会触发 Foo render */}
      <button onClick={() => setName("aaa")}>
        {name}
      </button>
    </div>
  );
}

关键点

  • render 方法将 JSX 转换为 createElement 调用,生成虚拟 DOM,通过 diff 算法更新真实 DOM
  • 类组件执行 setState 必定触发 render,即使状态值未改变
  • 函数组件使用 useState 时,只有状态值真正改变才触发 render
  • 类组件的父组件重新渲染会导致所有子组件重新渲染
  • 函数组件的渲染行为更加优化,避免不必要的重复渲染