写点什么

react 源码解析 20. 总结 & 第一章的面试题解答

作者:zz1998
  • 2021 年 12 月 08 日
  • 本文字数:3777 字

    阅读完需:约 12 分钟

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,同时它还是一个工作单元。

  1. Fiber 双缓存可以在构建好 wip Fiber 树之后切换成 current Fiber,内存中直接一次性切换,提高了性能

  2. Fiber 的存在使异步可中断的更新成为了可能,作为工作单元,可以在时间片内执行工作,没时间了交还执行权给浏览器,下次时间片继续执行之前暂停之后返回的 Fiber

  3. Fiber 可以在 reconcile 的时候进行相应的 diff 更新,让最后的更新应用在真实节点上

hooks

  1. 为什么 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 操作

缺点:额外的内存 初次渲染不一定快

  1. 你对合成事件的理解

原生事件:全小写、事件处理函数(字符串)、阻止默认行为(返回 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);
复制代码
  • 打印顺序是什么

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/>;}
复制代码
  • useLayout/componentDidMount 和 useEffect 的区别是什么

class App extends React.Component {  componentDidMount() {    console.log('mount');  }}
useEffect(() => { console.log('useEffect');}, [])


复制代码

答:他们在 commit 阶段不同时机执行,useEffect 在 commit 阶段结尾异步调用,useLayout/componentDidMount 同步调用

  • 如何解释 demo_4、demo_8、demo_9 出现的现象 答:demo_4:useEffect 和 useLayoutEffect 的区别 demo_8:任务的优先级有关,见源码分析视频 demo_9:批量更新有关,见源码分析视频

用户头像

zz1998

关注

还未添加个人签名 2021.06.26 加入

还未添加个人简介

评论

发布
暂无评论
react源码解析20.总结&第一章的面试题解答