React进阶课程学习笔记-持续更新

118 阅读9分钟

基础篇-jsx

  1. React JSX 在 babel 和 webpack 编译后是React.createElement()方法。因此老版本中需要引入import React from 'react'来防止报错。
  2. React element 会被处理成 fiber 对象,通过 sibling (兄弟节点)、return (父节点)、child (子节点)互相联系,这个过程称之为“调和”。
  3. React.Children.toArray()API,用于扁平化 children 数组。
  4. React.Children.forEach()API,可以遍历所有子节点数组,包括子节点。
  5. React.isValidElement()API,用于判断是否为 React Element元素,返回 boolean 。

基础篇-Components

  1. 在调和阶段,React 通过 fiber tag 来判断并选择组件的处理逻辑。
  2. 对于类组件,底层只需实例化一次,state 等状态保存在实例化的组件中,每次更新只需要调用 render 以及各生命周期函数;对于函数组件,每次更新都是一次新函数的执行,所有的东西都会重新声明。
  3. 主流的通信方式:
    • props 和 callback ,父子通信
    • ref
    • context 上下文
    • mobx、redux 状态管理
    • event bus 发布订阅(不推荐❌)

问与答

Q:如果没有在 constructor 的 super 函数中传递 props,那么接下来 constructor 执行上下文中就获取不到 props ,这是为什么呢?

/* 假设我们在 constructor 中这么写 */ 
constructor(){ 
    super() 
    console.log(this.props) // 打印 undefined 为什么? 
}

绑定 props 是在父类 Component 构造函数中,执行 super 等于执行 Component 函数,此时 props 没有作为第一个参数传给 super() ,在 Component 中就会找不到 props 参数,从而变成 undefined ,在接下来 constructor 代码中打印 props 为 undefined 。

A:

constructor(props){
    super(props) 
}

基础篇-state

  1. 在类组件中,一次 setState 的流程:
    • setState 产生当前更新的优先级( old : expirationTime; new : lane; )
    • 调和阶段:从 fiber root 根节点向下调和子节点,找出需要更新的组件,合并 state。
    • render阶段: 触发 render 函数,得到新的 UI 视图层。
    • commit阶段: 替换真实 Dom,并执行 setState 第二个参数的 callback。
  2. 优化更新次数的方法:
    • pureComponent:对 state 和 props 进行浅比较。
    • shouldComponentUpdate 生命周期,可自定义判断是否需要更新。
  3. 类组件中,通过 setState 的第二个参数,或者生命周期 ComponentDidMount 来检测 state 改变或者组件更新;函数组件中通过 useEffect() 来检测。
  4. 在函数组件中,在本次函数执行的上下文中,永远拿不到最新的 state 值,只能通过 useEffect 去监听值改变。

问与答

Q:demo中为何点击后 state 不更新?

export default function Index(){
    const [ state , dispatchState ] = useState({ name:'alien' }) 
    const handleClick = ()=>{ // 点击按钮,视图没有更新。
    state.name = 'Alien' 
    dispatchState(state) // 直接改变 `state`,在内存中指向的地址相同。 
    } 
    return <div> 
        <span> { state.name }</span> 
        <button onClick={ handleClick } >changeName++</button> 
    </div> 
}

如上例子🌰中,当点击按钮后,发现视图没有改变,为什么会造成这个原因呢?

在 useState 的 dispatchAction 处理逻辑中,会浅比较两次 state ,发现 state 相同,不会开启更新调度任务; demo 中两次 state 指向了相同的内存空间,所以默认为 state 相等,就不会发生视图更新了。

A:

把上述的 dispatchState 改成 dispatchState({...state}) 根本解决了问题,浅拷贝了对象,重新申请了一个内存空间。


Q:类组件中的 setState 和函数组件中的 useState 有什么异同?

A:

相同点:

  • 首先从原理角度出发,setState和 useState 更新视图,底层都调用了 scheduleUpdateOnFiber 方法,而且事件驱动情况下都有批量更新规则。

不同点:

  • 在不是 pureComponent 组件模式下, setState 不会浅比较两次 state 的值,只要调用 setState,在没有其他优化手段的前提下,就会执行更新。但是 useState 中的 dispatchAction 会默认比较两次 state 是否相同,然后决定是否更新组件。
  • setState 有专门监听 state 变化的回调函数 callback,可以获取最新state;但是在函数组件中,只能通过 useEffect 来执行 state 变化引起的副作用。
  • setState 在底层处理逻辑上主要是和老 state 进行合并处理,而 useState 更倾向于重新赋值。

问:setState是同步还是异步

解决问题:

React 的 setState 是同步还是异步? - 掘金 (juejin.cn)


基础篇-props


基础篇-生命周期

React lifecycle methods diagram (wojtekmaj.pl)

  1. react 渲染流程:

    • jsx 语法解析为 React.createElement
    • 生成 virtual dom
    • 进入 render 阶段:virtual dom 调和成 fiber 对象
    • 深度遍历 fiber 树,通过 diff 算法查找需要更新的节点
    • 对于需要变化的组件,执行 render 函数
    • render 阶段结束,进入 commit 阶段
    • 创建并修改真实 DOM 节点
  2. fiber 阶段会生成双缓冲树:current树和 workInProgress树(当前正在调和的树)。在初始化时,current = null,在第一次 fiber 调和后,将 workInProgress 赋值给 current ,通过两树来保证 React 在一次更新中快速构建并保持状态不丢失

  3. class instance(组件实例)上通过 _reactinternals 属性访问对应的 class fiber 对象,class fiber对象上通过 stateNode 属性来访问 class instance

  4. getDerivedStateFromProps,getSnapshotBeforeUpdate生命周期是在 render 阶段执行

  5. componentDidMount,componentDidUpdate 声明周期是在 commit 阶段执行的

问与答

Q:React.useEffect 回调函数 和 componentDidMount / componentDidUpdate 执行时机有什么区别 ?

A:useEffect 对 React 执行栈来看是异步执行的,而 componentDidMount / componentDidUpdate 是同步执行的,useEffect代码不会阻塞浏览器绘制。在时机上 ,componentDidMount / componentDidUpdate 和 useLayoutEffect 更类似。


基础篇-Ref

  1. 类组件中使用React.createRef();函数组件中使用React.useRef(null)
  2. 类组件中,ref 被实例 instance 维护;函数组件中 ref 对象挂载在 fiber 对象上
  3. 函数组件中,使用 forwardRefuseImperativeHandle 实现 ref 通信

优化篇-渲染控制

  1. React.useMemo(create,deps)第一个参数为一个函数,函数的返回值作为缓存值,如上 demo 中把 Children 对应的 element 对象,缓存起来。第二个参数为一个数组,存放当前 useMemo 的依赖项,在函数组件下一次执行的时候,会对比 deps 依赖项里面的状态,是否有改变,如果有改变重新执行 create ,得到新的缓存值。
  2. useMemo 原理:useMemo 会记录上一次执行 create 的返回值,并把它绑定在函数组件对应的 fiber 对象上,只要组件不销毁,缓存值就一直存在,但是 deps 中如果有一项改变,就会重新执行 create ,返回值作为新的值记录到 fiber 对象上。
  3. useMemo 是将数据缓存在fiber上的。
  4. React.memo(Component,compare) 接受两个参数,第一个参数 Component 原始组件本身,第二个参数 compare 是一个函数,可以根据一次更新中 props 是否相同决定原始组件是否重新渲染。第二个参数 返回 true 组件不渲染 , 返回 false 组件重新渲染。

问与答

Q:useCallback 和 useMemo 有什么区别?

A: useCallback 第一个参数就是缓存的内容,useMemo 需要执行第一个函数,返回值为缓存的内容,比起 useCallback , useMemo 更像是缓存了一段逻辑,或者说执行这段逻辑获取的结果。那么对于缓存 element 用 useCallback 可以吗,答案是当然可以了。

原理篇-调度与时间片

问与答

Q: 异步调度原理?

A: React 发生一次更新,

Q: React为什么不用settimeout?

A: setTimeout(fn,0)虽然可以是宏任务,满足让出主线程的要求,但是递归 setTimeout(fn,0) 的时候,最后时间差会有 4ms,而一帧时间也就 16ms,比较浪费。

Q: 说一说 React 的时间分片?

A: 根据每秒60帧的频率来划分时间片。浏览器执行一次事件循环的时间为16ms(一帧),一帧内浏览器会做如下事情:处理事件、执行js、渲染动画、布局 Layout 、绘制 Paint。React 的异步事件就是通过类似requestIdleCallback()的方法一帧一帧的去请求,如果浏览器有空闲时间,就开始执行异步任务,以此来防止卡顿。

Q: React 如何模拟 requestIdleCallback?

A: MessageChannel接口。在一次更新中,React 会调用 requestHostCallback ,把更新任务赋值给 scheduledHostCallback ,然后 channel.port2 向 channel.port1 发送消息通知, port1 通过 onmessage 接收消息后执行更新任务 scheduledHostCallback ,然后置空 scheduledHostCallback ,以此来达到异步目的。

Q: 简述一下调度流程?

A:


原理篇-调和与 fiber

问与答

Q: 什么是fiber ? Fiber 架构解决了什么问题?

A: Fiber 是 React 中 的虚拟 DOM。没有 Fiber 之前,React 一次渲染需要从根节点开始,并且不能打断,大型项目就会因此卡顿。 Fiber 作为执行的单元,可以根据 lane 优先级来中断,让浏览器可以及时渲染,等到浏览器空余时间时,再恢复更新 Fiber,提高了用户体验。

Q: Fiber root 和 root fiber 有什么区别?

A: 首次构建时,React 会创建一个Fiber root 作为整个应用的根基(应用根节点);Root fiber 可以理解为,例如一个 Index 组件, Index 就是一个 root fiber。

Q: 不同fiber 之间如何建立起关联的?

A: fiber 通过 return 指向父节点,通过 sibling 指向下一个兄弟节点,通过 child 指向子节点。

Q: React 调和流程?

A:

  1. 初始化
    • 创建fiber root 和 root fiber
    • 生成 workInProgress 和 current 树
    • 深度调和子节点
    • 渲染视图
  2. 更新
    • 生成 workInProgress 和 current 树

Q: 两大阶段 commit 和 render 都做了哪些事情?

A:

  1. render 阶段

    • beginWork() 向下调和子节点
    • completeUnitOfWork() 向上归并兄弟节点
    • 完成调和
  2. commit 阶段

    a. Before mutation(执行 DOM 操作前)

    • getSnapshotBeforeUpdate() 生命周期
    • 异步调用 useEffect

    b. mutation(执行 DOM 操作)

    • 置空 ref ,在 ref 章节讲到对于 ref 的处理
    • 对新增元素,更新元素,删除元素。进行真实的 DOM 操作

    c. layout(执行 DOM 操作后)

    • commitLayoutEffectOnFiber 对于类组件,会执行生命周期,setState 的callback,对于函数组件会执行 useLayoutEffect 钩子
    • 如果有 ref ,会重新赋值 ref

Q: 什么是双缓冲树? 有什么作用?

A: workInProgress (内存中构建的树) 和 current (渲染树); workInProgress 是在内存中构建的 fiber 树,在一次更新中,更新都是再 workInProgress fiber 树上进行的。更新结束后,会赋值给 current 树,用于渲染视图。

两棵树用 alternate 指针相互指向,防止更新状态丢失并加快了 DOM 节点的替换与更新。

Q: Fiber 深度遍历流程?

A:

Q: Fiber的调和能中断吗? 如何中断?

A: