21-组件的渲染机制

77 阅读3分钟

组件的渲染机制

  • render 渲染的时候,如果发现type是一个
    • 字符串: 创建元素标签
    • 函数:把函数执行,把解析出来的props当实参传递给函数
      • 单闭合调用: 不能书写子节点 [没有children]
      • 双闭合调用: 可以有children -> 实现出类似vue中插槽(slot)的概念, 有助于复用性
    • 类: 把类基于 new 执行,创造其它一个实例
  • children 的情况
    • 无: children -> undefined
    • 1个: children -> 等传过来的东西
    • 多个: children -> 是一个数组
  • props: 是被冻结的对象 -> Object.isFrozen(props) -> true
    • 不能删除/不能新增/不能劫持
    • 给组件设置默认值: 如下的 FuncComponent.defaultProps
    • 给props设置规则:
// => App.jsx
import FuncComponent from "./pages/funcComp";

function App() {
  const render = () => {
    return <div>我是render函数</div>
  }
  return (
    <div className="App">
      <FuncComponent 
        x={20} 
        y="30" 
        arr={[10,20,30]}
        render={render}
      >
        // => 命名插槽的的模拟
        <span className="slot" slot="header">我是children-头部</span>  
        <span className="slot" slot="footer">我是children-尾部</span>  
      </FuncComponent>  
    </div>
  );
}
export default App;
// => FuncComponent.jsx
import React from 'react'
import PropTypes from 'prop-types'

function FuncComponent(props) {
  const {
    x,
    y,
    arr,
    children,
    render,
    num
  } = props;
  console.log(Object.isFrozen(props)); // true
  // => 命名插槽的的模拟
  const headSlots = children.filter(item => item.props.slot === 'header');
  const footSlots = children.filter(item => item.props.slot === 'footer');

  return (
    <div>
      <h1>FuncComponent</h1>
      { headSlots }
      <h1>children: { children[0] }</h1>
      <p>x: {x}</p>
      <p>y: {y}</p>
      {
        arr && arr.map((item, index) => {
          return (
            <div key={index}>{ item }</div>
          )
        })
      }
      
      <div>
        render: { render() }
      </div>
      <h2>children: { children[1] }</h2>
      { footSlots }
    </div>
  )
}

// 默认值
FuncComponent.defaultProps = {
  num: 100
}

// 给props设置规则
FuncComponent.prototype = {
  x: PropTypes.number.isRequired,
  y: PropTypes.string,
  arr: PropTypes.array
}
export default FuncComponent

函数组件的视图更新: 就是让函数重新执行

  • 函数组件是静态组件
    • 不具备状态/生命周期/ref等
    • 第一次渲染完毕,除非父组件控制渲染,否则内容不会再更新
    • 优势渲染速度快
    • 弊端: 静态组件,无法实现组件动态更新

类组件

import React, { Component } from 'react'
import PropTypes from 'prop-types'

export default class ClassComp extends Component {
  // 默认值
  static defaultProps = {
    x: 0,
    y: 0
  }
  // 属性规则
  static propTypes = {
    x: PropTypes.number,
    y: PropTypes.number,
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log(this.props, this.state, nextProps, nextState, 'shouldComponentUpdate');
    return true;
  }
  state = {
    num: 10
  }
  constructor(props) {
    super();//写了constructor 必须super
    console.log(this.props, 'constructor');
  }
  render() {
    let { num } = this.state;
    console.log(this.props, this.state, 'render');
    return (
      <div>
        <p>{ num }</p>
        <button onClick={() => {
          this.setState({
            num : 200
          },() => {
            console.log(this.state.num, 'state callback')
          })
        }}>plus</button>
      </div>
    )
  }
}
// undefined 'constructor'
// {x: 0, y: 0} {num: 10} 'render'
// {x: 0, y: 0} {num: 10} {x: 0, y: 0} {num: 200} 'shouldComponentUpdate'
// {x: 0, y: 0} {num: 200} 'render'
// 200 'state callback'
  • 渲染流程总结
new ClassComp([props])
@1 getDefaultProps && 属性规则校验
@2 初始化
  + 把constructor执行,把处理好的props传递给constructor
    * super() 等价于 React.Component.call(this)
      * this.props = undefined
      * this.refs = {}
      * this.context = undefined
      * this.updater ={...}
    * super(props) 此处直接把传递进来的props挂载到实例上去
      * this.props = {...}
      * ...
@3 初始化结束后,会把props/context这些信息挂载到实例上去
  + this.props={...}
  + this.context = {...}
@4 触发一个生命周期 componentWillMount: 第一次render之前
  + 不安全的周期函数
  + UNSAFE_componentWillMount [在React.StrictMode严格模式下会报错]
@5 触发render函数
  + 把render执行返回的JSX元素对象(虚拟dom对象)进行渲染
  + **render函数必须有**,必须返回jsx元素
@6 触发componentDidMount生命周期函数: 第一次渲染完
  + 获取真实的DOM
  + 从服务器获取数据
  + 设置定时器或者监听器等
基于setState修改状态,通知视图更新
@a 触发shouldComponentUpdate:是否允许更新
  + 返回true: 允许更新,继续执行后续的步骤
  + 返回false: 停止更新,状态/属性值也不会进行修改,视图也不会更新
  + 可以基于这个生命周期做项目的性能优化(一般不会去动这个函数)
@b 触发componentWillUpdate: 不安全的周期函数
  + 进行到这一步,属性和状态还没修改为最新的值
@c 修改状态/属性为新的值,基于this.props/state访问,获取最新的值
@d 触发render: 视图重新渲染
@e 触发componentDidUpdate视图更新完毕
@f 如果setState有回调函数,则在视图更新完毕后(componentDidUpdate后),执行回调函数
  + 特殊: 即便shouldComponentUpdate返回false,此回调函数也会被触发 获取this.state.xxx是新的只是不刷新
组件销毁
@1 触发componentWillUnMount 生命周期函数: 销毁之前
  + 把监听器,定时器等手动释放掉
  + 把组件中的一些信息做缓存(信息草稿箱)
@2 销毁