原理

render 方法在react里表现的两种形式

  1. class类组件中的 render()

    1. export default class ClassRender extends React.Component {
    2. constructor(props) {
    3. super(props)
    4. this.state = {
    5. count: 1
    6. }
    7. }
    8. render(){
    9. return <div className='cn'>
    10. classRender
    11. <Header> hello </Header>
    12. <div> start </div>
    13. </div>
    14. }
    15. }
  2. 函数组件中的函数本身

    1. export default function HookRender(){
    2. return(
    3. <div> hookRender</div>
    4. )
    5. };

    1️⃣render后做了什么?
    我们在render中编写jsx,通过babel把jsx转译为一个名为React.createElement() 的函数调用。
    上述class babel编译后:

    1. return(
    2. React.createElement(
    3. 'div',
    4. {
    5. className:'cn'
    6. },
    7. 'classRender',
    8. React.createElement(
    9. 'Header',
    10. null,
    11. 'hello'
    12. ),
    13. React.createElemnt(
    14. 'div',
    15. null,
    16. 'start'
    17. )
    18. )
    19. )

    React.createElement,从名字上看是创建元素,实际上预先执行了一些检查,帮助编写无错误代码,并创建了一个对象

    1. (简化后的结构)
    2. const element = {
    3. type: 'div',
    4. props:{
    5. className: 'cn',
    6. children:[
    7. 'classRender',
    8. {
    9. type: 'Header',
    10. children: 'hello'
    11. },
    12. {
    13. type: 'div',
    14. children: 'start'
    15. }
    16. ]
    17. }
    18. }
  3. 这些 对象就是 React元素,(VNode)描述出了一个虚拟DOM树。

  4. 用render新返回的Dom树与旧Dom树进行对比,也就是diff算法。会对新旧两棵树做一个深度优先遍历,这样每一个节点会有一个标记,在深度遍历的时候,每遍历到一个节点,就把该节点和新节点进行对比,如果有差异会放在一个对象里,遍历差异对象,根据差异的类型,根据对应规则更新VDOM

React 的处理 render 的基本思维模式是每次一有变动就会去重新渲染整个应用。在 Virtual DOM 没有出现之前,最简单的方法就是直接调用 innerHTML。Virtual DOM厉害的地方并不是说它比直接操作 DOM 快,而是说不管数据怎么变,都会尽量以最小的代价去更新 DOM。React 将 render 函数返回的虚拟 DOM 树与老的进行比较,从而确定 DOM 要不要更新、怎么更新。当 DOM 树很大时,遍历两棵树进行各种比对还是相当耗性能的,特别是在顶层 setState 一个微小的修改,默认会去遍历整棵树。尽管 React 使用高度优化的 Diff 算法,但是这个过程仍然会损耗性能.

触发时机

2️⃣render的在什么时候被触发?

1、类组件setState调用时更新render
  1. export default class ClassRender extends React.Component {
  2. constructor(props) {
  3. super(props)
  4. console.log(props)
  5. this.state = {
  6. count: 1,
  7. tag: 'XXXX'
  8. }
  9. this.clickButton = this.clickButton.bind(this);
  10. }
  11. clickButton(){
  12. const { count } = this.state;
  13. const newCount = count < 3 ? count+1 : count;
  14. this.setState({
  15. count : newCount,
  16. })
  17. }
  18. render(){
  19. console.log('classRender', count);
  20. return <div>classRender
  21. <button onClick={this.clickButton}> 点击</button>
  22. {this.state.count}
  23. <TestRender tag={this.state.tag}/>
  24. </div>
  25. }
  26. }
  27. export default function TestRender(props){
  28. console.log('TestRender');
  29. return(
  30. <div> TestRender
  31. {props && props.tag}
  32. </div>
  33. )
  34. };

多次click‘点击’,控制台输出如下:
image.png

  1. setState调用时会触发本组件的render渲染,父组件render时会带着子组件一起render;
  2. setState值没有发生变化,set同样的值,也会触发本组件render,子组件不管有没有使用相关数据也会跟着渲染。
  3. ❗️❗️❗️为了避免不必要的渲染,可以使用shouldComponentUpdate返回false阻止

    2、函数组件useState调用时更新render,但是值不发生改变时不会触发render

    ```javascript export default function HookRender(){ const [count, setCount] = useState(1) const [tag, setTag] = useState(‘999’)

    const clickButton = ()=>{ const newCount = count < 3 ? count+1 : count; setCount(newCount) } console.log(‘hookRender’); return(

    hookRender {count}
    ) };

export default function TestRender(props){ console.log(‘TestRender’); return(

TestRender {props && props.tag}
) }; ``` 多次点击,控制台输出如下:
image.png

  1. useState时数值有变化,render会触发,否则不会触发render
  2. 父组件render时,子组件会跟着render

    结论

    综上,render函数里可以编写jsx,在babel作用下转化为createElement形式,用于生成虚拟DOM,最终转化为真实的DOM。
    在react中,类组件只要执行了setState方法,就会触发render函数执行。因为React中的shouldComponentUpdate方法默认返回true,当然,可以手动改写返回false阻止渲染。
    函数组件使用useState更改状态不一定·导致重新render。
    组件的props发生改变,不一定触发render函数执行,但是如果props的值来自于父组件或者祖先组件的state,在这种情况下父组件或者祖先组件的state发生了改变,就会导致子组件重新渲染。
    所以,一旦执行setState就会触发render,useState会判断当前值有无发生改变确定是否执行render方法;一旦父组件发生渲染,子组件也会渲染。