共计 5101 个字符,预计需要花费 13 分钟才能阅读完成。
react 源码解析 20. 总结 & 第一章的面试题解答
视频解说(高效学习):进入学习
往期文章:
1. 开篇介绍和面试题
2.react 的设计理念
3.react 源码架构
4. 源码目录构造和调试
5.jsx& 外围 api
6.legacy 和 concurrent 模式入口函数
7.Fiber 架构
8.render 阶段
9.diff 算法
10.commit 阶段
11. 生命周期
12. 状态更新流程
13.hooks 源码
14. 手写 hooks
15.scheduler&Lane
16.concurrent 模式
17.context
18 事件零碎
19. 手写迷你版 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
function App() {return <h1>Hello World</h1>;} // 转换后 import {jsx as _jsx} from 'react/jsx-runtime'; function App() {return _jsx('h1', { children: 'Hello world'}); }
-
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 一样吗
export default function App() {const [num, setNum] = useState(0); const click = () => {setTimeout(() => {console.log(num); }, 3000); setNum(num + 1); }; return <div onClick={click}>click {num}</div>; } export default class App extends React.Component { state = {num: 0}; click = () => {setTimeout(() => {console.log(this.state.num); }, 3000); this.setState({num: this.state.num + 1}); }; render() {return <div onClick={this.click}>click {this.state.num}</div>; } }
可测试性:函数组件不便测试
状态:类组件有本人的状态,函数组件没有只能通过 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 吗
function Child() {console.log('Child'); return <div>Child</div>; } function Father(props) {const [num, setNum] = React.useState(0); return (<div onClick={() => {setNum(num + 1)}}> {num} {props.children} </div> ); } function App() { return ( <Father> <Child/> </Father> ); } const rootEl = document.querySelector("#root"); ReactDOM.render(<App/>, rootEl);
答:不会,源码中是否命中 bailoutOnAlreadyFinishedWork
-
打印程序是什么
function Child() {useEffect(() => {console.log('Child'); }, []) return <h1>child</h1>; } function Father() {useEffect(() => {console.log('Father'); }, []) return <Child/>; } function App() {useEffect(() => {console.log('App'); }, []) return <Father/>; }
答:Child,Father,App,render 阶段 mount 时深度优先遍历,commit 阶段 useEffect 执行机会
-
useLayout/componentDidMount 和 useEffect 的区别是什么
class App extends React.Component {componentDidMount() {console.log('mount'); } } useEffect(() => {console.log('useEffect'); }, [])
答:他们在 commit 阶段不同机会执行,useEffect 在 commit 阶段结尾异步调用,useLayout/componentDidMount 同步调用
![react 源码 20.1](https://img-blog.csdnimg.cn/img_convert/1ff919a924bc0b632a70f999ea357042.png)
-
如何解释 demo_4、demo_8、demo_9 呈现的景象
答:demo_4:useEffect 和 useLayoutEffect 的区别
demo_8:工作的优先级无关,见源码剖析视频
demo_9:批量更新无关,见源码剖析视频