react 源码解析 20. 总结 & 第一章的面试题解答
react 源码解析 20.总结 &第一章的面试题解答
视频讲解(高效学习):进入学习
往期文章:
总结
至此我们介绍了 react 的理念,如果解决 cpu 和 io 的瓶颈,关键是实现异步可中断的更新
我们介绍了 react 源码架构(ui=fn(state)),从 scheduler 开始调度(根据过期事件判断优先级),经过 render 阶段的深度优先遍历形成 effectList(中间会执行 reconcile|diff),交给 commit 处理真实节点(中间穿插生命周期和部分 hooks),而这些调度的过程都离不开 Fiber 的支撑,Fiber 是工作单元,也是节点优先级、更新 UpdateQueue、节点信息的载体,Fiber 双缓存则提供了对比前后节点更新的基础。我们还介绍了 jsx 是 React.createElement 的语法糖。Lane 模型则提供了更细粒度的优先级对比和计算,这一切都为 concurrent mode 提供了基础,在这之上变可以实现 Suspense 和 batchedUpdate(16、17 版本实现的逻辑不一样),18 章 context 的 valueStack 和 valueCursor 在整个架构中运行机制,19 章介绍了新版事件系统,包括事件生产、监听和触发
面试题简答(详见视频源码角度讲解)
jsx 和 Fiber 有什么关系
答:mount 时通过 jsx 对象(调用 createElement 的结果)调用 createFiberFromElement 生成 Fiber update 时通过 reconcileChildFibers 或 reconcileChildrenArray 对比新 jsx 和老的 Fiber(current Fiber)生成新的 wip Fiber 树
react17 之前 jsx 文件为什么要声明 import React from 'react',之后为什么不需要了
答:jsx 经过编译之后编程 React.createElement,不引入 React 就会报错,react17 改变了编译方式,变成了 jsx.createElement
Fiber 是什么,它为什么能提高性能
答:Fiber 是一个 js 对象,能承载节点信息、优先级、updateQueue,同时它还是一个工作单元。
Fiber 双缓存可以在构建好 wip Fiber 树之后切换成 current Fiber,内存中直接一次性切换,提高了性能
Fiber 的存在使异步可中断的更新成为了可能,作为工作单元,可以在时间片内执行工作,没时间了交还执行权给浏览器,下次时间片继续执行之前暂停之后返回的 Fiber
Fiber 可以在 reconcile 的时候进行相应的 diff 更新,让最后的更新应用在真实节点上
hooks
为什么 hooks 不能写在条件判断中
答:hook 会按顺序存储在链表中,如果写在条件判断中,就没法保持链表的顺序
状态/生命周期
setState 是同步的还是异步的
答:legacy 模式下:命中 batchedUpdates 时是异步 未命中 batchedUpdates 时是同步的
concurrent 模式下:都是异步的
componentWillMount、componentWillMount、componentWillUpdate 为什么标记 UNSAFE
答:新的 Fiber 架构能在 scheduler 的调度下实现暂停继续,排列优先级,Lane 模型能使 Fiber 节点具有优先级,在高优先级的任务打断低优先级的任务时,低优先级的更新可能会被跳过,所有以上生命周期可能会被执行多次,和之前版本的行为不一致。
组件
react 元素 $$typeof 属性什么
答:用来表示元素的类型,是一个 symbol 类型
react 怎么区分 Class 组件和 Function 组件
答:Class 组件 prototype 上有 isReactComponent 属性
函数组件和类组件的相同点和不同点
答:相同点:都可以接收 props 返回 react 元素
不同点:
编程思想:类组件需要创建实例,面向对象,函数组件不需要创建实例,接收输入,返回输出,函数式编程
内存占用:类组建需要创建并保存实例,占用一定的内存
值捕获特性:函数组件具有值捕获的特性 下面的函数组件换成类组件打印的 num 一样吗
可测试性:函数组件方便测试
状态:类组件有自己的状态,函数组件没有只能通过 useState
生命周期:类组件有完整生命周期,函数组件没有可以使用 useEffect 实现类似的生命周期
逻辑复用:类组件继承 Hoc(逻辑混乱 嵌套),组合优于继承,函数组件 hook 逻辑复用
跳过更新:shouldComponentUpdate PureComponent,React.memo
发展未来:函数组件将成为主流,屏蔽 this、规范、复用,适合时间分片和渲染
开放性问题
说说你对 react 的理解/请说一下 react 的渲染过程
答:是什么:react 是构建用户界面的 js 库
能干什么:可以用组件化的方式构建快速响应的 web 应用程序
如何干:声明式(jsx) 组件化(方便拆分和复用 高内聚 低耦合) 一次学习随处编写
做的怎么样: 优缺(社区繁荣 一次学习随处编写 api 简介)缺点(没有系统解决方案 选型成本高 过于灵活)
设计理念:跨平台(虚拟 dom) 快速响应(异步可中断 增量更新)
性能瓶颈:cpu io fiber 时间片 concurrent mode
渲染过程:scheduler render commit Fiber 架构
聊聊 react 生命周期 详见第 11 章
简述 diff 算法 详见第 9 章
react 有哪些优化手段
答:shouldComponentUpdate、不可变数据结构、列表 key、pureComponent、react.memo、useEffect 依赖项、useCallback、useMemo、bailoutOnAlreadyFinishedWork ...
react 为什么引入 jsx
答:jsx 声明式 虚拟 dom 跨平台
解释概念:jsx 是 js 语法的扩展 可以很好的描述 ui jsx 是 React.createElement 的语法糖
想实现什么目的:声明式 代码结构简洁 可读性强 结构样式和事件可以实现高内聚 低耦合 、复用和组合 不需要引入新的概念和语法 只写 js, 虚拟 dom 跨平
有哪些可选方案:模版语法 vue ag 引入了控制器 作用域 服务等概念
jsx 原理:babel 抽象语法树 classic 是老的转换 automatic 新的转换
说说 virtual Dom 的理解
答:是什么:React.createElement 函数返回的就是虚拟 dom,用 js 对象描述真实 dom 的 js 对象
优点:处理了浏览器的兼容性 防范 xss 攻击 跨平台 差异化更新 减少更新的 dom 操作
缺点:额外的内存 初次渲染不一定快
你对合成事件的理解
原生事件:全小写、事件处理函数(字符串)、阻止默认行为(返回 false)
合成事件:小驼峰、事件处理函数(函数对象)、阻止默认行为(event.preventDefault())
理解:
React 把事件委托到 document 上(v17 是 container 节点上)
先处理原生事件 冒泡到 document 上在处理 react 事件
React 事件绑定发生在 reconcile 阶段 会在原生事件绑定前执行
优势:
进行了浏览器兼容。顶层事件代理,能保证冒泡一致性(混合使用会出现混乱)
默认批量更新
避免事件对象频繁创建和回收,react 引入事件池,在事件池中获取和释放对象(react17 中废弃) react17 事件绑定在容器上了
我们写的事件是绑定在 dom 上么,如果不是绑定在哪里? 答:v16 绑定在 document 上,v17 绑定在 container 上
为什么我们的事件手动绑定 this(不是箭头函数的情况) 答:合成事件监听函数在执行的时候会丢失上下文
为什么不能用 return false 来阻止事件的默认行为? 答:说到底还是合成事件和原生事件触发时机不一样
react 怎么通过 dom 元素,找到与之对应的 fiber 对象的? 答:通过 internalInstanceKey 对应
解释结果和现象
点击 Father 组件的 div,Child 会打印 Child 吗
打印顺序是什么
useLayout/componentDidMount 和 useEffect 的区别是什么
答:他们在 commit 阶段不同时机执行,useEffect 在 commit 阶段结尾异步调用,useLayout/componentDidMount 同步调用
如何解释 demo_4、demo_8、demo_9 出现的现象 答:demo_4:useEffect 和 useLayoutEffect 的区别 demo_8:任务的优先级有关,见源码分析视频 demo_9:批量更新有关,见源码分析视频
评论