前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

React笔记|博客系列

# React笔记

# React的理念

快速响应:

  • 速度快
  • 响应自然

速度快:

由于React语法的灵活性,在编译时无法区分可能变化的部分,所以它为了速度快需要在运行时做更多的努力,例如:

  • 使用PureComponent或React.memo构建组件
  • 使用shouldComponentUpdate生命周期钩子
  • 渲染列表时使用key
  • 使用useCallback和useMemo缓存函数和变量

响应自然:

将同步更新变为可中断的异步更新,在浏览器每一帧的时间中,预留一些时间给JS线程,React利用这部分时间更新组件,源码中预留的时间是5ms。当预留的时间不够用时,React将线程控制权交还给浏览器使其有时间渲染UI,React则等待下一帧时间到来继续被中断的工作。

# React15架构及缺点

# 架构

  • Reconciler(协调器):负责找出变化的组件
  • Renderer(渲染器):负责将变化的组件渲染到页面上

# 更新流程

当有发生更新时,Reconciler会做:

  • 调用函数组件、或class组件的render方法,将返回的JSX转化为虚拟DOM
  • 将虚拟DOM和上次更新时的虚拟DOM对比
  • 通过对比找出本次更新中变化的虚拟DOM
  • 通知Renderer将变化的虚拟DOM渲染到页面上

# React15架构的缺点:

在Reconciler中,更新的方式是递归更新子组件,而对于递归更新,更新一旦开始,中途就无法中断,所以当层级很深时,递归更新时间就可能会超过16.6ms(主流浏览器刷新频率为60Hz,即每1000ms/60Hz,16.6ms刷新一次),这样用户交互就会卡顿。

而解决以上问题的关键就是用可中断的异步更新代替同步的更新,但是React15的架构却是不支持异步更新的。

因为在React15,Reconciler和Renderer是交替着工作,当前一轮更新的任务结束之后,第二轮再进行Renconciler,由于整个过程都是同步的,所以如果此时中途中断更新,就会使得后面的任务不执行,渲染也就停止了。

# React16架构

# 架构:

  • Scheduler(调度器):调度任务的优先级,高优先级的任务先进入Reconciler
  • Reconciler(协调器):负责找出变化的组件
  • Renderer(渲染器):负责将变化的组件渲染到页面上

相比于React15,新增了Scheduler。

# Scheduler:

一个独立于React的库,实际上是一个功能完备的requestIdleCallbackpolyfill。

它产生的原因是:

  • 浏览器需要用是否有剩余时间作为任务中断的标准,所以我们需要一种机制,当浏览器有剩余时间的时候通知我们。
  • 部分浏览器已经实现了这个API,也就是requestIdleCallback,不过由于它有以下缺点而被React放弃使用了:
    • 浏览器兼容性问题
    • 触发频率不稳定,受很多因素影响。比如当我们的浏览器切换tab后,之前tab注册的requestIdleCallback触发的频率会变得很低

所以React实现了一个功能更完备的requestIdleCallbackpolyfill:Scheduler。

作用:

  • 在空闲时间触发回调,以通知我们浏览器是否有剩余时间做任务中断
  • 提供了多种调度优先级供任务设置

# 更新流程

  • Reconciler与Renderer不再是交替工作
  • 当产生一个更新,先将更新内容交给Scheduler,Scheduler会判断有没有其它高优先级更新需要执行,若是没有的话则将更新的内容交给Reconcoler
  • 当Scheduler将任务交给Reconciler后,Reconciler会为变化的虚拟DOM打上代表增/删/更新的标记
  • 整个Scheduler与Reconciler的工作都在内存中进行。只有当所有组件都完成Reconciler的工作,才会统一交给Renderer
  • Renderer接收到通知,将变化的虚拟DOM渲染到页面上

# Fiber架构

# 代数效应

代数效应是函数式编程中的一个概念,用于将副作用从函数调用中分离。

代数效应在React中的应用:

  • useState
  • useRef
  • useReducer

代数效应与Generator:

异步可中断更新可以理解为:更新在执行过程中可能会被打断(浏览器时间分片用尽或有更高优任务插队),当可以继续执行时恢复之前执行的中间状态。

代数效应中try...handle的作用就是为了满足以上需求。

而此功能与浏览器原生的Generator很像,但它存在一些缺陷,因此不被使用:

  • 类似async,Generator也是传染性的,使用了Generator则上下文的其他函数也需要作出改变。这样心智负担比较重。
  • Generator执行的中间状态是上下文关联的。如果后面的计算需要依赖与前面的计算结果,就需要重新计算。

代数效应与Fiber:

Fiber:中文翻译为纤程,与进程(Process)、线程(Thread)、协程(Coroutine)同为程序执行过程。

可以理解为纤程是协程的一种实现,而在JS中协程的实现就是Generator。

所以,我们可以将纤程(Fiber)、协程(Generator)理解为代数效应思想在JS中的体现。

React Fiber可以理解为:

React内部实现的一套状态更新机制。支持任务不同优先级,可中断与恢复,并且恢复后可以复用之前的中间状态。

其中每个任务更新单元为React Element对应的Fiber节点。

# Fiber架构的工作原理

Fiber树是什么?它的构建和替换过程又是怎样的?

# 双缓存:

在内存中构建并直接替换的技术叫做双缓存。

# 双缓存Fiber树:

在React中最多同时存在两颗Fiber树:

  • 屏幕上显示内容对应着:current Fiber树
  • 正在内存中构建对应着:workInProgress Fiber树

Fiber树的节点:

  • current Fiber树中的节点:currentFiber
  • workInProgress Fiber树:workInProgressFiber

两种节点之间的关系,靠alternate属性连接:

currentFiber.alternate === workInProgressFiber;
workInProgressFiber.alternate === currentFiber;
@前端进阶之旅: 代码已经复制到剪贴板

而在React应用的根节点就是通过current指针在不同的Fiber树的rootFiber间切换来实现Fiber树的切换,这个过程伴随着DOM的更新。

FiberRootNode和rootFiber

在讲解切换更新流程时,需要明确几个概念:

如下的代码:

function App() {
  const [num, add] = useState(0);
  return (
    <p onClick={() => add(num + 1)}>{num}</p>
  )
}

ReactDOM.render(<App/>, document.getElementById('root'));
@前端进阶之旅: 代码已经复制到剪贴板

首次执行ReactDOM.render时会创建:

  • fiberRootNode(源码中为fiberRoot),它是整个应用的根节点;
  • rootFiber,它是<App />组件树的根节点。

在应用中我们可能会多次调用ReactDOM.render渲染不同的组件树,所以它们可能会有不同的rootFiber,但是fiberRootNode在整个应用中就只有一个。

它俩之间的关系,是靠fiberRootNode的一个名为current的属性来联系的:

fiberRootNode.current = rootFiber
@前端进阶之旅: 代码已经复制到剪贴板

mount和update阶段

而对应着生命周期来说,会分为mount和update阶段。

在mount时,会有三个阶段:

  1. 首屏渲染时,页面没有挂载任何的DOM,此时fiberRootNode.current = rootFiber,但是rootFiber并没有任何的子Fiber节点,所以此时currentFiber为空。
  1. 到了render阶段,根据组件的JSX在内存中依次构建了Fiber节点并连接成了Fiber树,这棵树也就是workInProgress Fiber树:
  1. 已构建完的workInProgress Fiber树在commit阶段渲染到页面,此时fiberRootNode的current指针指向workInProgress Fiber树使其变为current Fiber 树:

update阶段其实就像是我们前面说的了,当有节点触发状态改变:

  1. 会开启一次新的render阶段,构建一个新的workInProgress Fiber树:
  1. workInProgress Fiber 树在render阶段完成构建后进入commit阶段渲染到页面上。渲染完毕后,workInProgress Fiber 树变为current Fiber 树:

# React源码文件结果

根目录
├── fixtures        # 包含一些给贡献者准备的小型 React 测试项目
├── packages        # 包含元数据(比如 package.json)和 React 仓库中所有 package 的源码(子目录 src)
├── scripts         # 各种工具链的脚本,比如git、jest、eslint等
@前端进阶之旅: 代码已经复制到剪贴板

# packages

具体来看看packages文件夹:

  • react文件夹:React的核心,包含所有全局 React API,例如React.createElement、React.Component等
  • scheduler文件夹:Scheduler(调度器)的实现
  • share文件夹:源码中其他模块公用的方法和全局变量

Renderer相关文件夹:

- react-art
- react-dom                 # 注意这同时是DOM和SSR(服务端渲染)的入口
- react-native-renderer
- react-noop-renderer       # 用于debug fiber(后面会介绍fiber)
- react-test-renderer
@前端进阶之旅: 代码已经复制到剪贴板

试验性包的文件夹:

- react-server        # 创建自定义SSR流
- react-client        # 创建自定义的流
- react-fetch         # 用于数据请求
- react-interactions  # 用于测试交互相关的内部特性,比如React的事件模型
- react-reconciler    # Reconciler的实现,你可以用他构建自己的Renderer
@前端进阶之旅: 代码已经复制到剪贴板

辅助包的文件夹:

- react-is       # 用于测试组件是否是某类型
- react-client   # 创建自定义的流
- react-fetch    # 用于数据请求
- react-refresh  # “热重载”的React官方实现
@前端进阶之旅: 代码已经复制到剪贴板

比较重要的:

  • react-reconciler,他一边对接Scheduler,一边对接不同平台的Renderer,构成了整个 React16 的架构体系。

# 调试源码

说明:

  • 现在我们通过create-react-app创建的React项目中使用到的react源码不一定和 facebook/react 仓库中master分支的一样,因为给用户使用的必须是趋于稳定的一个版本。
  • 当我们在调试源码的时候可以选择仓库中的最新代码。

其实想要调试源码,大体来说只需要这么几个步骤:

← React新特性(memo、lazy、suspense、hooks)React设计模式和最佳实践总结 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合