cosyer's Blog

Blog


  • 首页

  • 友链

  • 留言板

  • 归档

  • 关于

  • 搜索

React 分类

11月
23
更新于
11月23
2020
React

react源码解析系列三(React Fiber)

发表于 2020-11-23 | 热度 ℃
| 字数统计: 1,206 (字) | 阅读时长: 7 (分钟)

Fiber

每一个ReactElement对应一个Fiber对象

记录节点的各种状态

串联整个应用形成的结构

例如:FiberRoot的current指向RootFiber 的 child —->App 的child —> div 的child—->input 的sibling

属性:

1
2
3
4
5
return // 指向父节点(每个节点只会有一个父节点)

child // 子节点

sibling // 兄弟节点

Update

用于记录组件状态的改变

存放于Fiber对象的UpdateQueue中:UpdateQue单项链表的结构

多个Update可以同时存在:例如一个事件里面存在三个setState,创建三个update放到UpdateQueue中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// 创建或更新updatequeue
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
// Update queues are created lazily.
const alternate = fiber.alternate;
let queue1;
let queue2;
if (alternate === null) {
// There's only one fiber.
queue1 = fiber.updateQueue;
queue2 = null;
if (queue1 === null) {
queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
}
} else {
// There are two owners.
queue1 = fiber.updateQueue;
queue2 = alternate.updateQueue;
if (queue1 === null) {
if (queue2 === null) {
// Neither fiber has an update queue. Create new ones.
queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
queue2 = alternate.updateQueue = createUpdateQueue(
alternate.memoizedState,
);
} else {
// Only one fiber has an update queue. Clone to create a new one.
queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);
}
} else {
if (queue2 === null) {
// Only one fiber has an update queue. Clone to create a new one.
queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);
} else {
// Both owners have an update queue.
}
}
}
if (queue2 === null || queue1 === queue2) {
// There's only a single queue.
appendUpdateToQueue(queue1, update);
} else {
// There are two queues. We need to append the update to both queues,
// while accounting for the persistent structure of the list — we don't
// want the same update to be added multiple times.
if (queue1.lastUpdate === null || queue2.lastUpdate === null) {
// One of the queues is not empty. We must add the update to both queues.
appendUpdateToQueue(queue1, update);
appendUpdateToQueue(queue2, update);
} else {
// Both queues are non-empty. The last update is the same in both lists,
// because of structural sharing. So, only append to one of the lists.
appendUpdateToQueue(queue1, update);
// But we still need to update the `lastUpdate` pointer of queue2.
queue2.lastUpdate = update;
}
}

if (__DEV__) {
if (
fiber.tag === ClassComponent &&
(currentlyProcessingQueue === queue1 ||
(queue2 !== null && currentlyProcessingQueue === queue2)) &&
!didWarnUpdateInsideUpdate
) {
warningWithoutStack(
false,
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
'from inside an update function. Update functions should be pure, ' +
'with zero side-effects. Consider using componentDidUpdate or a ' +
'callback.',
);
didWarnUpdateInsideUpdate = true;
}
}
}

expirationTime

详解见:https://react.jokcy.me/book/update/expiration-time.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ReactFiberReconciler.js中
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
const current = container.current; // Fiber对象
//!important
const currentTime = requestCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, current);

return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}

requestCurrentTime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 目前粗略的理解目前为止到js加载完成的时间差
function requestCurrentTime() {
// 已经进入到渲染阶段了
if (isRendering) {
// We're already rendering. Return the most recently read time.
return currentSchedulerTime;
}
// Check if there's pending work.
findHighestPriorityRoot(); // 从调度队列找到权限最高的root
if (
nextFlushedExpirationTime === NoWork ||
nextFlushedExpirationTime === Never
) {
// If there's no pending work, or if the pending work is offscreen, we can
// read the current time without risk of tearing.
recomputeCurrentRendererTime(); // 如下
currentSchedulerTime = currentRendererTime;
return currentSchedulerTime;
}
// There's already pending work. We might be in the middle of a browser
// event. If we were to read the current time, it could cause multiple updates
// within the same event to receive different expiration times, leading to
// tearing. Return the last read time. During the next idle callback, the
// time will be updated.
return currentSchedulerTime;
}

recomputeCurrentRendererTime

1
2
3
4
5
function recomputeCurrentRendererTime() {
// 从js加载完成到现在为止的时间间隔
const currentTimeMs = now() - originalStartTimeMs; // originalStartTimeMs: 初始的now()
currentRendererTime = msToExpirationTime(currentTimeMs);
}

msToExpirationTime

1
2
3
4
5
const UNIT_SIZE = 10
export function msToExpirationTime(ms: number): ExpirationTime {
// Always add an offset so that we don't clash with the magic number for NoWork.
return ((ms / UNIT_SIZE) | 0) + MAGIC_NUMBER_OFFSET;
}

computeExpirationForFiber

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
let expirationTime;
if (expirationContext !== NoWork) {
// An explicit expiration context was set;
expirationTime = expirationContext;
} else if (isWorking) {
if (isCommitting) {
// Updates that occur during the commit phase should have sync priority
// by default.
expirationTime = Sync;
} else {
// Updates during the render phase should expire at the same time as
// the work that is being rendered.
expirationTime = nextRenderExpirationTime;
}
} else {
// No explicit expiration context was set, and we're not currently
// performing work. Calculate a new expiration time.
if (fiber.mode & ConcurrentMode) {
if (isBatchingInteractiveUpdates) {
// This is an interactive update
expirationTime = computeInteractiveExpiration(currentTime); // 如下
} else {
// This is an async update
expirationTime = computeAsyncExpiration(currentTime); // 如下
}
// If we're in the middle of rendering a tree, do not update at the same
// expiration time that is already rendering.
if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
expirationTime += 1;
}
} else {
// This is a sync update
expirationTime = Sync;
}
}
if (isBatchingInteractiveUpdates) {
// This is an interactive update. Keep track of the lowest pending
// interactive expiration time. This allows us to synchronously flush
// all interactive updates when needed.
if (expirationTime > lowestPriorityPendingInteractiveExpirationTime) {
lowestPriorityPendingInteractiveExpirationTime = expirationTime;
}
}
return expirationTime;
}

computeInteractiveExpiration

1
2
3
4
5
6
7
8
9
10
export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150;
export const HIGH_PRIORITY_BATCH_SIZE = 100;

export function computeInteractiveExpiration(currentTime: ExpirationTime) {
return computeExpirationBucket(
currentTime,
HIGH_PRIORITY_EXPIRATION,
HIGH_PRIORITY_BATCH_SIZE,
);
}

computeAsyncExpiration

1
2
3
4
5
6
7
8
9
10
11
12
export const LOW_PRIORITY_EXPIRATION = 5000;
export const LOW_PRIORITY_BATCH_SIZE = 250;

export function computeAsyncExpiration(
currentTime: ExpirationTime,
): ExpirationTime {
return computeExpirationBucket(
currentTime,
LOW_PRIORITY_EXPIRATION,
LOW_PRIORITY_BATCH_SIZE,
);
}

computeExpirationBucket

1
2
3
4
5
6
7
8
9
10
11
12
13
function computeExpirationBucket(
currentTime,
expirationInMs,
bucketSizeMs,
): ExpirationTime {
return (
MAGIC_NUMBER_OFFSET +
ceiling(
currentTime - MAGIC_NUMBER_OFFSET + expirationInMs / UNIT_SIZE,
bucketSizeMs / UNIT_SIZE,
)
);
}

计算为了将固定时间间断的,多次setState数据的更新当成一次更新,在这个时间间断内优先级是一样的

11月
11
更新于
11月11
2020
React

react源码解析系列二(React Render)

发表于 2020-11-11 | 热度 ℃
| 字数统计: 2,272 (字) | 阅读时长: 11 (分钟)

创建更新的方式

  1. render || hydrate
  2. setState
  3. forceUpdate

render的步骤

  1. 创建 ReactRoot
  2. 创建FiberRoot RootFiber
  3. 创建更新

后续的是进入调度后,由调度器进行管理


阅读全文 »
11月
23
更新于
11月23
2020
React

react源码解析系列一(React相关API)

发表于 2020-11-03 | 热度 ℃
| 字数统计: 4,092 (字) | 阅读时长: 19 (分钟)

今天开始学习react源码相关的内容,源码基于版本v16.6.0。React16相较于之前的版本是核心上的一次重写,虽然主要的API都没有变化,但是增加了很多能力。并且首次引入了Fiber的概念,之后新的功能都是围绕Fiber进行实现,比如AsyncMode,Profiler等。

React与ReactDom的区别

问题:react仅仅1000多行代码,而react-dom却将近2w行

答: React主要定义基础的概念,比如节点定义和描述相关,真正的实现代码都是在ReactDom里面的,也就是“平台无关”概念,针对不同的平台有不同的实现,但 基本的概念都定义在React里。

React16.6.0使用FlowType做类型检查

Flow 是 facebook 出品的 JavaScript 静态类型检查⼯具。所谓类型检查,就是在编译期尽早发现(由类型错误引起的)bug,⼜不影响代码运⾏(不需要运⾏时动态检查类型),使编写 JavaScript 具有和编写 Java 等强类型语⾔相近的体验。

官方网站

简单示例🌰

1
2
3
npm install -g flow-bin
flow init
touch index.js

1
2
3
4
5
6
7
8
// index.js 进行类型注释
/*@flow*/
function add(x: number, y: number): number {
return x + y
}
add('Hello', 11)

// 报错

阅读全文 »
7月
28
更新于
7月28
2020
React

Docker

发表于 2020-07-28 | 热度 ℃
| 字数统计: 672 (字) | 阅读时长: 2 (分钟)

前言

react-native开发使用的是JS,但是并不是纯正的JS,而是一种JSX语言,就是在JS中嵌入XML语言,因此,只要有一些JS的语法基础的原生开发者,就可以肯容易理解JSX的语法了,在RN中,推荐使用ES6的语法。

性能

使用react-native开发的最大的有点在于开发效率,加入APP并不复杂的话,那么完全可以使用纯JS开发,也就是Android和iOS公用一套界面和逻辑。极大的提高了开发效率。在性能上,RN的表现比原生弱一些,但 是远好于H5。所以总体来看,其实RN的未来还是可以期待的。

运行机制

RN是运行JS的,Android是运行Java字节码的,所以,实际上JS代码的最终运行是通过一层封装,把JS的代码映射成原生代码,而界面上的元素最终使用的也是原生的组件,而不是自己渲染(所以在性能上,RN比H5要 好很多)。

Component简介

在Android中,主要交互容器是activity或Fragment,而在RN中,界面的交互容器是Component:组件。我觉得Component和原生的Fragment其实很像,都存在于activity中,都受制于activity的生命周期,都可卸 载和装载。


阅读全文 »
7月
22
更新于
7月22
2020
React

react SSR教程

发表于 2020-06-10 | 热度 ℃
| 字数统计: 2,361 (字) | 阅读时长: 10 (分钟)

前言

本文是基于react ssr的入门教程,在实际项目中使用还需要做更多的配置和优化,比较适合第一次尝试react ssr的小伙伴们。技术涉及到 koa2 + react,案例使用create-react-app创建。

客户端渲染与服务端渲染

客户端渲染

  • 客户端渲染,实际上就是客户端向服务端请求页面,服务端返回的是一个非常简单的 HTML 页面,在这个页面里,只有很少的一些 HTML 标签
  • 客户端渲染时,页面中有两个比较重要的点,第一个是 script 标签,可能会有好几个 script 标签,这个标签是打包后的 js 代码,用来生成 DOM 元素,发送请求,事件绑定等。但是,生成的 DOM 元素需要有一个在页面上展示的容器,所以另外一个点就是容器,一般是一个 id 名为 root 或 app 的 div 标签,类似于这样 <div id="root"></div> 或<div id="app"></div>
  • 客户端渲染的特点
    • 客户端加载所有的 js 资源,渲染 DOM 元素
    • 在浏览器页面上的所有资源,都由客户端主动去获取,服务端只负责静态资源的提供和 API 接口,不再负责页面的渲染,如果采用 CDN 的话,服务端仅仅需要提供 API 接口
    • 优点: 前后端分离,责任区分,前端专注页面的开发,后端专注接口的开发
    • 缺点: 首屏加载资源多,首屏加载时响应慢。页面上没有 DOM 元素,不利于 SEO 优化

服务端渲染

  • 服务端渲染,就是客户端页面上的 HTML 元素,都要由服务端负责渲染。服务端利用模板引擎,把数据填充到模板中,生成 HTML 字符串,最终把 HTML 字符串返回到浏览器,浏览器接收到 HTML 字符串,通过 HTML 引擎解析,最终生成 DOM 元素,显示在页面上
  • 比如 Node.js 可以渲染的模板引擎有 ejs,nunjucks,pug 等。Java 最常见的是 JSP 模板引擎。Python 最常见的是 Jinja2 模板引擎。
  • 服务端渲染的特点
    • 优点: 页面资源大多由服务端负责处理,所以页面加载速度快,缩短首屏加载时间。有利于 SEO 优化。无需占用客户端资源
    • 缺点: 不利于前后端分离,开发效率低。占用服务器资源

区分与选择

  • 客户端渲染和服务端渲染本质的区别就是,是谁负责 HTML 页面的拼接,那么就是谁渲染的页面
  • 如果对首屏加载时间有非常高的需求,或者是需要 SEO 优化,那么就选择服务端渲染
  • 如果对首屏加载时间没有要求,也不需要做 SEO 优化,类似于做后台管理系列的业务,那么就可以选择客户端渲染
  • 具体选择客户端渲染还是服务端渲染,没有强制的要求,具体要根据项目的需求来区分选择

阅读全文 »
8月
05
更新于
8月05
2020
React

快速了解React 16新特性

发表于 2018-06-11 | 热度 ℃
| 字数统计: 809 (字) | 阅读时长: 3 (分钟)

Error Boundary

Error Boundary可以看作是一种特殊的React组件,新增了componentDidCatch这个生命周期函数,它可以捕获自身及子树上的错误并对错误做优雅处理,包括上报错误日志、展示出错提示,而不是卸载整个组件树。(注:它并不能捕获runtime所有的错误,比如组件回调事件里的错误,可以把它想象成传统的try-catch语句)


阅读全文 »
11月
18
更新于
11月18
2020
React

react面试题记录

发表于 2018-06-07 | 热度 ℃
| 字数统计: 10,197 (字) | 阅读时长: 39 (分钟)
reactInterview
阅读全文 »
陈宇(cosyer)

陈宇(cosyer)

不去做的话永远也做不到。

159 日志
10 分类
51 标签
RSS
GitHub Twitter E-Mail FB Page
推荐阅读
  • Callmesoul
  • JsTips
  • Personal Site
  • Resume
© 2021 陈宇(cosyer)
终于等到你(UV):   |   欢迎再来(PV):
Blog总字数: 312.5k字
苏ICP备17005342号-1