源码解析

源码解析

  • ReactElement创建虚拟DOM节点对象

const ReactElement = function (type, key, ref, self, source, owner, props) {
    const element = {
        type: type,
        key: key,
        ref: ref,
        props: props,
        ...
    } // element结构
    ...
    if (Object.freeze) {
      Object.freeze(element.props) // 冻结对象
      Object.freeze(element)
    }
}
  • jsx解析代码并使用ReactElement创建节点对象。服务端渲染时selfsource是必传参数,客户端渲染传的是undefined

  • React.createElementReact.cloneElement2个方法基于ReactElement

  • Component创建组建

function Component(props, context, updater) {
    this.props = props
    this.context = context
    ...
    this.updater = updater || ReactNoopUpdateQueue
}

Component.prototype.setState = function(partialState, callback) {
    this.updater.enqueueSetState(this, partialState, callback, 'setState')
    // 创建此组件实例的renderer(可能是react-dom/react-native等等)来执行任务队列
}

Component.prototype.forceUpdate = function(callback) {
    this.updater.enqueueSetState(this, callback, 'forceUpdate')
}

虚拟DOM

虚拟DOM是描述DOM树的对象,目的是将所有操作统一处理完之后再进行DOM更新。(常规的树比较算法的时间复杂度为立方阶O(n3),react diff 为线性阶O(n))

  • tree diff对比2棵树的同一层次的节点

  • component diff通过不同组件类型生成不同的树形结构,同时允许设置shouldComponentUpdate()对进行优化

  • element diff通过唯一key进行比较

key的作用

  • 准确判断当前节点是否在旧集合中

  • 减少遍历次数

生命周期

  1. 初始化阶段

    1. constructor

    2. static getDerivedStateFromProps()

    3. UNSAFE_componentWillMount()

    4. render()

    5. componentDidMount()

  2. 更新阶段

    1. UNSAFE_componentWillReceiveProps()(onyl props改变)

    2. static getDerivedStateFromProps()(onyl props改变)

    3. shouldComponentUpdate()

    4. UNSAFE_componentWillUpdate()

    5. render()

    6. getSnapshotBreforeUpdate()

    7. componentDidUpdate()

  3. 卸载阶段: componentWillUnmount()

  4. 错误处理:

    1. static getDerivedStateFromError()

    2. componentDidCatch()

更新机制

  • setState后会进入到shouldComponentUpdate钩子函数,由该函数的返回值决定是否调用render

  • 如果包含子组件,那么父组件render完成后进入子组件的生命周期,直到子组件的生命周期完成后,回到父组件render的后续生命周期

  • render返回虚拟DOM,在其后进行diff计算,从而决定是否需要重新渲染

  • PureComponent是浅比较

// 父子组件初始化阶段的生命周期
Parent.constructor
Parent.getDerivedStateFromProps
Parent.render
Child.constructor
Child.getDerivedStateFromProps
Child.render
Child.componentDidMount
Parent.componentDidMount

getDerivedStateFromPros(nextProps, prevState)

返回null表示不做任何改变

shouldComponentUpdate(nextProps, nextState)

返回false控制组件不更新

getSnapshotBreforeUpdate(prevProps, prevState)

其返回值会传入componentDidUpdate(prevProps, prevState, fromSnapData)

setState

  • 第一个参数可以是对象,也可以是updater函数。

  • 第二个参数是回调函数,在componentDidUpdate后执行

  • setState更新过程是异步的,会合并所有的更新(如果是addEventListenersetTimeout里,则不会合并更新)

forceUpdate

跳过shouldComponentUpdate()直接调用render()钩子函数

性能优化

  • 谨慎分配state,避免不必要的render

  • 状态合并

  • 使用纯函数式组件

  • 使用高阶组件替代继承

  • 控制更新

  • 副作用代码放在commit阶段

Fiber

参考: 图解Fiber架构

除了React元素树(虚拟DOM)之外,有一个用于保存状态的内部实例树(Host Instance)。该内部实例树的新实现以及操作树的算法被成为Fiber。Fiber也是一个任务调和器:

  • 可分片(拆分任务)

  • 可中断(执行一个任务后可回头继续执行未完成的任务)

  • 具备优先级(任务优先级高的先执行)

function FiberNode(tag, pendingProps, key, mode) {
    this.tag = tag
    this.key = key
    ...

    // Fiber
    this.return = null
    this.child = null
    this.sibling = null
    this.index = 0
    ...
    // effects
    this.effectTag = NoEffect
    this.nextEffect = null
    ...
}

Fiber Tree的构建

通过child找子节点、sibling找兄弟节点、return找父节点的循环方法构建Fiber tree。

function List () {
    return [1,2,3].map((i) => <span key={i}>{i}</span>)
}
function App () {
    return [
        <button>btn</button>,
        <List />,
        <div></div>
    ]
}
ReactDom.render(<App />, document.getElementById('root'))

Hook

hooks的数据作为Fiber组件上的节点。

调用原理

基于FunctionalComponent

  1. 首先调用prepareToUseHooks初始化一些模块内的全局变量

  2. 调用finishHooks

事件合成

  • 事件委托:把所有事件绑定到最外层,使用一个统一的事件监听器

  • 自动绑定:每个事件的上下文均指向所属组件

setState

setState的更新是异步的。在非异步方法中,无论在多少个组件中调用多少个setState,它们都在最后一次setState后,全部放入enqueueSetState更新队列,然后执行一次统一的更新

// 更新过程
enqueueSetState(instance) {
    // 获取fiber
    const fiber = getInstance(instance)
    // 计算当前时间
    const currentTime = requestCurrentTime()
    // 计算fiber对象过期时间
    const expirationTime = computeExpirationForFiber(currentTime, fiber)
    // 创建update对象
    const update = createUpdate(expirationTime)
    // 进入待更新队列
    enqueueUpdate(fiber, update)
    // 任务调度
    scheduleWork(fiber, expirationTime)
}

forceUpdate

// 更新过程 和 setState一样,唯一不同是更新对象的tag值为ForceUpdate(2)
// UpdateState: 0 更新
// ReplaceState: 1 替换
// ForceUpdate: 2 强制更新
// CaptureUpdate: 3 捕获性更新
function createUpdate() {
    return {
        tag: UpdateState,
    }
}

ssr

renderToString

Last updated

Was this helpful?