YuJian920
2 years ago
31 changed files with 778 additions and 333 deletions
@ -1,5 +1,5 @@ |
|||||||
{ |
{ |
||||||
"baseFontSize": 11, |
"baseFontSize": 16, |
||||||
"theme": "obsidian", |
"theme": "obsidian", |
||||||
"cssTheme": "Minimal" |
"cssTheme": "Minimal" |
||||||
} |
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@ |
|||||||
|
- 正则表达式匹配所有中文:`^[\u4e00-\u9fa5]{2}$` |
@ -0,0 +1,250 @@ |
|||||||
|
>beginWork 执行在递归节点的 Fiber 创建之前,主要是为传入的 Fiber 节点根据类型创建第一个子 Fiber 节点 |
||||||
|
|
||||||
|
[代码位置](https://github.com/facebook/react/blob/bd4784c8f8c6b17cf45c712db8ed8ed19a622b26/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L3818) |
||||||
|
|
||||||
|
```javascript |
||||||
|
function beginWork(current, workInProgress, renderLanes) { |
||||||
|
|
||||||
|
// 前 beginWork 阶段 |
||||||
|
if (current !== null) { |
||||||
|
...... |
||||||
|
} else { |
||||||
|
...... |
||||||
|
} |
||||||
|
|
||||||
|
// 正式 beginWork 阶段 |
||||||
|
switch (workInProgress.tag) { |
||||||
|
...... |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
beginWork 函数接受三个参数,分别是 current 节点,workInProgress 节点和 renderLanes 优先级,根据 beginWork 函数的结构,我们可以分成两个阶段,分别是:前 beginWork 阶段和正式 beginWork 阶段 |
||||||
|
|
||||||
|
## 前 beginWork 阶段 |
||||||
|
|
||||||
|
```javascript |
||||||
|
if (current !== null) { |
||||||
|
...... |
||||||
|
} else { |
||||||
|
didReceiveUpdate = false; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
在进入正式 beginWork 阶段之前,会先对传入的 current 节点进行空值判断,根据 current 是否为空进入不同的处理逻辑。 |
||||||
|
|
||||||
|
**那么为什么需要判断 current 是否为空呢?答案是为了性能,前面说到 beginWork 函数的主要任务就是给当前传入的 Fiber 节点创建它的第一个子 Fiber 节点,要是在上次更新和本次更新中当前的 Fiber 节点并没有发生变化,那么还需要再次创建一个新的 Fiber 节点吗?肯定是不需要的,所以我们只需要将这个已经存在的并没有发生变化的 Fiber 节点拿过来复用就行了,而这段逻辑正是在前 beginWork 阶段中判断并执行的** |
||||||
|
这一段代码的主要目的是为了赋值 didReceiveUpdate 变量,这个变量表示在本次更新中当前 current 节点是否存在变化 |
||||||
|
|
||||||
|
### current 不为空 |
||||||
|
|
||||||
|
在 current 不为空的逻辑中,会先取出先前存储在 Fiber 节点中的新旧 props,连同其他几个判断条件一起做 if 判断,判断条件如下: |
||||||
|
1. 用三等判断 props 是否发生变化 |
||||||
|
2. 检查 context 是否发生变化 |
||||||
|
3. 检查新旧 Fiber type 是否发生变化 |
||||||
|
|
||||||
|
如果判断条件为 true 则会给 didReceiveUpdate 变量赋值为 true,进入正式 beginWork 阶段 |
||||||
|
|
||||||
|
```javascript |
||||||
|
var oldProps = current.memoizedProps; |
||||||
|
var newProps = workInProgress.pendingProps; |
||||||
|
|
||||||
|
if ( |
||||||
|
// 对比新旧 props |
||||||
|
oldProps !== newProps || |
||||||
|
// 检查 context 是否发生变化 |
||||||
|
hasContextChanged() || |
||||||
|
// 判断 Fiber type 是否发生变化 |
||||||
|
(workInProgress.type !== current.type ) |
||||||
|
) { |
||||||
|
didReceiveUpdate = true; |
||||||
|
} else { |
||||||
|
...... |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
如果判断条件为 false,会进入后续判断逻辑 |
||||||
|
|
||||||
|
此时会先调用 checkScheduledUpdateOrContext 函数检查 current 是否存在优先级相关的更新,关于 React 优先级相关我们先暂且按表不谈,进入 if 判断 |
||||||
|
|
||||||
|
- 不存在优先级相关的更新且 workInProgress 节点不存在 DidCapture flag |
||||||
|
- True:跳过后续的正式 beginWork 阶段,进入 baliout 也就是组件复用逻辑 |
||||||
|
- False:判断当前 current 节点是否存在 ForceUpdateForLegacySuspense flag |
||||||
|
- True:didReceiveUpdate 赋值为 true |
||||||
|
- False:didReceiveUpdate 赋值为 false |
||||||
|
|
||||||
|
```javascript |
||||||
|
// props 和 context 都没有发生变化,检查优先级相关 |
||||||
|
var hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current, renderLanes); |
||||||
|
|
||||||
|
if ( |
||||||
|
// 不存在优先级相关的更新c |
||||||
|
!hasScheduledUpdateOrContext && |
||||||
|
// workInProgress 节点上不存在 DidCapture flag |
||||||
|
(workInProgress.flags & DidCapture) === NoFlags |
||||||
|
) { |
||||||
|
didReceiveUpdate = false; |
||||||
|
// 这里会跳过正式 beginWork 阶段,进入 baliout 逻辑也就是组件复用 |
||||||
|
return attemptEarlyBailoutIfNoScheduledUpdate( |
||||||
|
current, |
||||||
|
workInProgress, |
||||||
|
renderLanes |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// current 节点不存在 ForceUpdateForLegacySuspense flag |
||||||
|
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) { |
||||||
|
didReceiveUpdate = true; |
||||||
|
} else { |
||||||
|
didReceiveUpdate = false; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### current 为空 |
||||||
|
|
||||||
|
首先 didReceiveUpdate 会被赋值为 false,紧接着会进入一个似乎关于 SSR 服务端渲染的判断逻辑,代码内容如下,这一段代码的行为还不清楚,暂时先跳过 |
||||||
|
|
||||||
|
```javascript |
||||||
|
didReceiveUpdate = false; |
||||||
|
// 检查 hydrate 状态 和 是否存在 Forked flag |
||||||
|
if (getIsHydrating() && isForkedChild(workInProgress)) { |
||||||
|
// 后续的逻辑似乎和 SSR 服务端渲染有关 |
||||||
|
// 根据官方的注释,这边的代码似乎是为了给 React 的并发模式铺路 |
||||||
|
var slotIndex = workInProgress.index; |
||||||
|
var numberOfForks = getForksAtLevel(); |
||||||
|
pushTreeId(workInProgress, numberOfForks, slotIndex); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
至此整个前 beginWork 阶段就结束了,我们可以在这里做个小小的总结:**在前beginWork 阶段主要是判断当前组件是否发生变化需要更新, 是否可以复用,在满足复用条件情况下会跳过 正式beginWork 阶段进入 baliout 逻辑而不再创建新的 Fiber 节点,从中可以看到对于特殊组件如 Suspense 而言可能并不会进入 baliout 逻辑** |
||||||
|
|
||||||
|
在此之外我们也可以看到一些很有意思的点:**对于组件新旧 props 对比使用的是简单的三等判断** |
||||||
|
|
||||||
|
## 正式 beginWork 阶段 |
||||||
|
|
||||||
|
正式 beginWork 阶段开始,会将 workInProgress 的 lanes 清空,接着会进入一个 switch 逻辑,根据 tag 不同进入对应的 case 处理逻辑,代码如下: |
||||||
|
|
||||||
|
```javascript |
||||||
|
workInProgress.lanes = NoLanes; |
||||||
|
|
||||||
|
switch (workInProgress.tag) { |
||||||
|
case IndeterminateComponent: ... |
||||||
|
case LazyComponent: ... |
||||||
|
// Function Component 处理逻辑 |
||||||
|
case FunctionComponent: ... |
||||||
|
// Class Component 处理逻辑 |
||||||
|
case ClassComponent: ... |
||||||
|
case HostRoot: ... |
||||||
|
case HostComponent: ... |
||||||
|
case HostText: ... |
||||||
|
// Suspense 处理逻辑 |
||||||
|
case SuspenseComponent: ... |
||||||
|
case HostPortal: ... |
||||||
|
case ForwardRef: ... |
||||||
|
case Fragment: ... |
||||||
|
case Mode: ... |
||||||
|
case Profiler: ... |
||||||
|
case ContextProvider: ... |
||||||
|
case ContextConsumer: ... |
||||||
|
case MemoComponent: ... |
||||||
|
case SimpleMemoComponent: ... |
||||||
|
case IncompleteClassComponent: ... |
||||||
|
case SuspenseListComponent: ... |
||||||
|
case ScopeComponent: ... |
||||||
|
case OffscreenComponent: ... |
||||||
|
case LegacyHiddenComponent: ... |
||||||
|
case CacheComponent: ... |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
switch 中的 case 逻辑太多了,全部都写出来会让笔记显得特别繁杂,这里只写出几个常用的处理逻辑如 HostComponent、FunctionComponent,其他处理逻辑后边有时间再另起一篇文章阐述 |
||||||
|
|
||||||
|
### updateHostRoot |
||||||
|
|
||||||
|
首先会执行 pushHostRootContext 函数,这个函数与 context 有关,暂且不谈 |
||||||
|
|
||||||
|
然后会接着执行 cloneUpdateQueue 方法 |
||||||
|
|
||||||
|
**cloneUpdateQueue** |
||||||
|
|
||||||
|
这个方法比较简单,会判断 current 和 workInProgress 中的 updateQueue 是否相同,如果相同会创建新的对象重复赋值以清除引用,这里是为了保证后续对 workInProgress 的操作不会影响到 current |
||||||
|
|
||||||
|
```javascript |
||||||
|
function cloneUpdateQueue(current, workInProgress) { |
||||||
|
var queue = workInProgress.updateQueue; |
||||||
|
var currentQueue = current.updateQueue; |
||||||
|
|
||||||
|
// 用三等对比更新队列,如果为 true 表示 queue 还未清除引用 |
||||||
|
if (queue === currentQueue) { |
||||||
|
// 清除引用 |
||||||
|
var clone = { |
||||||
|
baseState: currentQueue.baseState, |
||||||
|
firstBaseUpdate: currentQueue.firstBaseUpdate, |
||||||
|
lastBaseUpdate: currentQueue.lastBaseUpdate, |
||||||
|
shared: currentQueue.shared, |
||||||
|
effects: currentQueue.effects |
||||||
|
}; |
||||||
|
// 赋值 |
||||||
|
workInProgress.updateQueue = clone; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
从 cloneUpdateQueue 函数出来回到 updateHostRoot 调用栈,会紧接着执行 processUpdateQueue 方法,这个方法和更新队列有关,暂时不展开讲 |
||||||
|
|
||||||
|
```javascript |
||||||
|
function updateHostRoot(current, workInProgress, renderLanes) { |
||||||
|
// context 相关 |
||||||
|
pushHostRootContext(workInProgress); |
||||||
|
// 取出 updateQueue |
||||||
|
var updateQueue = workInProgress.updateQueue; |
||||||
|
if (current === null || updateQueue === null) { |
||||||
|
throw new Error("..."); |
||||||
|
} |
||||||
|
|
||||||
|
// 取出组件新 props |
||||||
|
var nextProps = workInProgress.pendingProps; |
||||||
|
// 取出组件 state |
||||||
|
var prevState = workInProgress.memoizedState; |
||||||
|
// 从 state 中取出 element |
||||||
|
var prevChildren = prevState.element; |
||||||
|
cloneUpdateQueue(current, workInProgress); |
||||||
|
processUpdateQueue(workInProgress, nextProps, null, renderLanes); |
||||||
|
var nextState = workInProgress.memoizedState; |
||||||
|
var root = workInProgress.stateNode; |
||||||
|
|
||||||
|
{ |
||||||
|
var nextCache = nextState.cache; |
||||||
|
pushRootCachePool(root); |
||||||
|
pushCacheProvider(workInProgress, nextCache); |
||||||
|
|
||||||
|
if (nextCache !== prevState.cache) { |
||||||
|
propagateContextChange( |
||||||
|
workInProgress, |
||||||
|
CacheContext, |
||||||
|
renderLanes |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
...... |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
再往后是一些针对服务端渲染的一些处理逻辑,服务端渲染也不是这次讨论的目的,也先跳过 |
||||||
|
最终调用 reconcileChildren 为 FIber 创建一个子 Fiber 节点并返回 |
||||||
|
|
||||||
|
至此一个节点的 beginWork 流程就走完了,下一次会根据是否存在子 Fiber 节点判断是执行当前 WorkInProgress Fiber 节点的 completeWork ,还是继续对子节点执行 beginWork |
||||||
|
|
||||||
|
|
||||||
|
### beginWork |
||||||
|
|
||||||
|
第一次触发更新,进入这个函数的逻辑前边和首屏渲染是都是一样的,但是在判断组件是否发生修改的时候逻辑和首屏渲染并不同,在首屏渲染,beginWork 会走到 switch 逻辑,并根据 Fiber tag 不同分别执行不同的 mount l逻辑,在这里会经过一些判断最终进入 attemptEarlyBailoutIfNoScheduledUpdate 这个函数,这个函数内部又有着一个 switch 逻辑,也是根据 workInProgress tag 属性分别进入不同的 case,在这个例子中进入的是 HostRoot 对应的 case,首先会执行 pushHostRootContext 函数,Context 相关 ~~不谈~~,最后继续处理服务端渲染的逻辑最后返回交由 bailoutOnAlreadyFinishedWork 函数处理,bailoutOnAlreadyFinishedWork 会对 WorkInProgress 赋上 current 的 dependencies 属性,然后就是 bailoutOnAlreadyFinishedWork 最终的目的: 调用 cloneChildFibers |
||||||
|
|
||||||
|
### cloneChildFibers |
||||||
|
|
||||||
|
这个函数从名字上就能看出来,目的是为了 clone Fiber 的 child 属性,这个函数会调用 createWorkInProgress 函数,从上边可以知道,这个函数可以根据传入的 current 和 WorkInProgress 进行判断是否创建一个新的 Fiber 节点或者复用已有的 current Fiber 节点,并将其互相链接,最终将结果返回,cloneChildFiber 接收到返回的 Fiber 子节点,将其链接到当前 WorkInProgress Fiber 的 child 属性上,并把 Fiber 子节点的 return 赋值 WorkInProgress Fiber |
||||||
|
|
||||||
|
也就是说,如果在 beginWork 中当前,如果组件并没有发生变化且没有要执行的逻辑,那么他会直接 clone 已有的 Fiber 节点,而不会走到 reconcileChildren 函数创建新的 Fiber 节点 |
||||||
|
|
||||||
|
最后返回到 bailoutOnAlreadyFinishedWork 在由它返回到 beginWork,beginWork 流程执行结束 |
@ -1,85 +0,0 @@ |
|||||||
>beginWork 执行在递归节点的 Fiber 创建之前,主要是为传入的 Fiber 节点根据类型创建第一个子 Fiber 节点 |
|
||||||
|
|
||||||
[代码位置](https://github.com/facebook/react/blob/bd4784c8f8c6b17cf45c712db8ed8ed19a622b26/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L3818) |
|
||||||
|
|
||||||
```javascript |
|
||||||
function beginWork(current, workInProgress, renderLanes) { |
|
||||||
|
|
||||||
// 前 beginWork 阶段 |
|
||||||
if (current !== null) { |
|
||||||
...... |
|
||||||
} else { |
|
||||||
...... |
|
||||||
} |
|
||||||
|
|
||||||
// 正式 beginWork 阶段 |
|
||||||
switch (workInProgress.tag) { |
|
||||||
...... |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
beginWork 函数接受三个参数,分别是 current 节点,workInProgress 节点和 renderLanes 优先级,根据 beginWork 函数的结构,我们可以分成两个阶段,分别是:前 beginWork 阶段和正式 beginWork 阶段 |
|
||||||
|
|
||||||
## 前 beginWork 阶段 |
|
||||||
|
|
||||||
```javascript |
|
||||||
if (current !== null) { |
|
||||||
...... |
|
||||||
} else { |
|
||||||
didReceiveUpdate = false; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
在进入正式 beginWork 阶段之前,会先对传入的 current 节点进行空值判断,根据 current 是否为空进入不同的处理逻辑。 |
|
||||||
|
|
||||||
**那么为什么需要判断 current 是否为空呢?答案是为了性能,前面说到 beginWork 函数的主要任务就是给当前传入的 Fiber 节点创建它的第一个子 Fiber 节点,要是在上次更新和本次更新中当前的 Fiber 节点并没有发生变化,那么还需要再次创建一个新的 Fiber 节点吗?肯定是不需要的,所以我们只需要将这个已经存在的并没有发生变化的 Fiber 节点拿过来复用就行了,而这段逻辑正是在前 beginWork 阶段中判断并执行的** |
|
||||||
这一段代码的主要目的是为了赋值 didReceiveUpdate 变量, |
|
||||||
|
|
||||||
### current 不为空 |
|
||||||
|
|
||||||
### current 为空 |
|
||||||
|
|
||||||
didReceiveUpdate 赋值为 false |
|
||||||
|
|
||||||
## 正式 beginWork 阶段 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### beginWork |
|
||||||
|
|
||||||
>beginWork 执行在递归节点的 Fiber 创建之前,主要是为传入的 Fiber 节点根据类型创建第一个子 Fiber 节点 |
|
||||||
|
|
||||||
首先会对 current 做空值判断,因为对于首屏渲染而言,当前传入的 current Fiber 节点是 FiberNode,所以是存在的,会进入 current !== null 的逻辑,然后会判断当前 Fiber 节点是否发生变化,然后赋值 `didReceiveUpdate` 做更新标识,判断的条件如下: |
|
||||||
1. 新旧 props 是否相同 |
|
||||||
2. Context 是否发生变化 |
|
||||||
3. Fiber 节点 type 是否发生变化 |
|
||||||
在本次首屏渲染中,以上的条件都为否,会进入后续条件判断,在后续的条件判断中主要是为了应付一些特殊的场景,例如错误边界和 legacy 模式,可以看到官方留下的代码注释,在我们现在的场景下,最终会走到 didReceiveUpdate 为 false 的情况下,也就是当前 Fiber 并没有变化,因为在之前的 createWorkInProgress 中 WorkFiberProgress 被赋值上了 current Fiber 的同名属性,所以它们会是相同的两个 FIber 节点,在这里的代码可以看到:beginWork 中还有对优先值 lanes 的一些处理,在这里先不展开,后续再补充 |
|
||||||
最后会走入 switch 环节,这了主要根据传入的 WorkInProgress Fiber 节点的类型进入不同的处理逻辑,这也是 beginWork 最主要的任务:为当前 WorkInProgress Fiber 节点创建它的第一个 Fiber 子节点,其内部根据 Fiber 的 tag 不同有对应的: |
|
||||||
- updateClassComponent 处理 Class Component |
|
||||||
- updateHostComponent 处理 Host Component |
|
||||||
- updateSuspenseComponent 处理 Suspense Component |
|
||||||
本次的是首屏渲染,传入的 WorkInProgress Fiber 为 FiberNode,所以会进入 HostRoot 的 case,执行 updateHostRoot 方法 |
|
||||||
|
|
||||||
### updateHostRoot |
|
||||||
|
|
||||||
updateHostRoot 函数中首先会执行 pushHostRootContext 方法,这个方法和 Context 相关,现在暂且不谈 |
|
||||||
在 updateHostRoot 逻辑一开始,它会将 current 和 WorkInProgress 传递给 cloneUpdateQueue 这个函数,这个函数会将 Fiber 中的 queue 属性进行 clone,除非它们已经是 clone 之后的属性,那么具体是怎么做的判断是否是 clone 过的属性呢?其实很简单,它只是使用了 === 三个等于判断属性的引用是否相同,也就是说这个函数实际上做的是清除引用,保证在之后对 WorkInProgress 的操作不会影响到 current Fiber 树,判断两个 Fiber 树的 updateQueue 属性的引用完全相同,那么就会创建一个新的对象并重复赋值达到清除应用的目的,最后赋值给 WorkInProgress Fiber 节点 |
|
||||||
之后,再把 WorkInProgress Fiber 节点交给 processUpdateQueue 函数,这个函数主要是处理更新队列,在这里先不展开,TODO |
|
||||||
再往后是一些针对服务端渲染的一些处理逻辑,服务端渲染也不是这次讨论的目的,也先跳过 |
|
||||||
最终调用 reconcileChildren 为 FIber 创建一个子 Fiber 节点并返回 |
|
||||||
|
|
||||||
至此一个节点的 beginWork 流程就走完了,下一次会根据是否存在子 Fiber 节点判断是执行当前 WorkInProgress Fiber 节点的 completeWork ,还是继续对子节点执行 beginWork |
|
||||||
|
|
||||||
|
|
||||||
### beginWork |
|
||||||
|
|
||||||
第一次触发更新,进入这个函数的逻辑前边和首屏渲染是都是一样的,但是在判断组件是否发生修改的时候逻辑和首屏渲染并不同,在首屏渲染,beginWork 会走到 switch 逻辑,并根据 Fiber tag 不同分别执行不同的 mount l逻辑,在这里会经过一些判断最终进入 attemptEarlyBailoutIfNoScheduledUpdate 这个函数,这个函数内部又有着一个 switch 逻辑,也是根据 workInProgress tag 属性分别进入不同的 case,在这个例子中进入的是 HostRoot 对应的 case,首先会执行 pushHostRootContext 函数,Context 相关 ~~不谈~~,最后继续处理服务端渲染的逻辑最后返回交由 bailoutOnAlreadyFinishedWork 函数处理,bailoutOnAlreadyFinishedWork 会对 WorkInProgress 赋上 current 的 dependencies 属性,然后就是 bailoutOnAlreadyFinishedWork 最终的目的: 调用 cloneChildFibers |
|
||||||
|
|
||||||
### cloneChildFibers |
|
||||||
|
|
||||||
这个函数从名字上就能看出来,目的是为了 clone Fiber 的 child 属性,这个函数会调用 createWorkInProgress 函数,从上边可以知道,这个函数可以根据传入的 current 和 WorkInProgress 进行判断是否创建一个新的 Fiber 节点或者复用已有的 current Fiber 节点,并将其互相链接,最终将结果返回,cloneChildFiber 接收到返回的 Fiber 子节点,将其链接到当前 WorkInProgress Fiber 的 child 属性上,并把 Fiber 子节点的 return 赋值 WorkInProgress Fiber |
|
||||||
|
|
||||||
也就是说,如果在 beginWork 中当前,如果组件并没有发生变化且没有要执行的逻辑,那么他会直接 clone 已有的 Fiber 节点,而不会走到 reconcileChildren 函数创建新的 Fiber 节点 |
|
||||||
|
|
||||||
最后返回到 bailoutOnAlreadyFinishedWork 在由它返回到 beginWork,beginWork 流程执行结束 |
|
@ -1,7 +1,5 @@ |
|||||||
父组件命中性能优化,子组件才有可能命中性能优化 |
- 父组件命中性能优化,子组件才有可能命中性能优化 |
||||||
该如何比较 props |
- 该如何比较 props |
||||||
1. 全等比较 --- 高效、不易命中 |
1. 全等比较 --- 高效、不易命中 |
||||||
2. 浅比较 --- 不高效,易命中 |
2. 浅比较 --- 不高效,易命中 |
||||||
没有传递 props 的时候,子组件接收到的 props 实际上是一个空对象 |
- 没有传递 props 的时候,子组件接收到的 props 实际上是一个空对象 |
||||||
React.memo 的作用和实际发生了什么 |
|
||||||
useMemo 的前提是当前组件必须是浅比较,为什么? |
|
@ -0,0 +1,2 @@ |
|||||||
|
- [ ] React.memo 的作用和实际发生了什么? |
||||||
|
- [ ] useMemo 的前提是当前组件必须是浅比较,为什么? |
@ -0,0 +1,2 @@ |
|||||||
|
- 组件最好是独立存在,与其他代码之间的纠葛越少越好。 |
||||||
|
- 组件可以用于实现副作用,而并不一定需要显示页面内容。 |
@ -0,0 +1,4 @@ |
|||||||
|
在设计 React 组件时,要注意以下原则: |
||||||
|
1. 保持接口小,props 数量要少; |
||||||
|
2. 根据数据边界来划分组件,充分利用组合 (composition); |
||||||
|
3. 状态提升,让下层组件尽量只是一个纯函数 |
@ -0,0 +1,6 @@ |
|||||||
|
>栈是一种逻辑结构,从栈的操作特性来看,它是一种“操作受限”的线性表。 |
||||||
|
>后进者先出,先进者后出,这就是典型的“栈”结构。 |
||||||
|
|
||||||
|
当某个数据集合只涉及在一端插入和删除,并且满足后进先出、先进后出的特性,我们就应该首选“栈”这种数据结构。 |
||||||
|
|
||||||
|
由于栈只是一个逻辑结构,所以它既可以用数组实现,也可以用链表来实现,数组实现的栈,叫做**顺序栈**,而链表实现的栈,叫做**顺序栈**。 |
@ -0,0 +1,14 @@ |
|||||||
|
对于排序算法执行效率的分析,我们一般会从这几个方面来衡量: |
||||||
|
1. 最好情况、最坏情况、平均情况时间复杂度 |
||||||
|
2. 时间复杂度的系数、常数 、低阶 |
||||||
|
3. 比较次数和交换(或移动)次数 |
||||||
|
|
||||||
|
## 冒泡排序 |
||||||
|
|
||||||
|
## 插入排序 |
||||||
|
|
||||||
|
## 选择排序 |
||||||
|
|
||||||
|
## 归并排序 (Merge Sort) |
||||||
|
|
||||||
|
## 快速排序 (Quick Sort) |
@ -0,0 +1,10 @@ |
|||||||
|
只要同时满足以下三个条件,就可以用递归来解决: |
||||||
|
1. 一个问题的解可以分解为几个子问题的解 |
||||||
|
2. 这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样 |
||||||
|
3. 存在递归终止条件 |
||||||
|
|
||||||
|
写递归代码最关键的是写出递推公式,找到终止条件 |
||||||
|
|
||||||
|
编写递归代码的关键是,只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤 |
||||||
|
|
||||||
|
递归代码虽然简洁高效,但是,递归代码也有很多弊端。比如,堆栈溢出、重复计算、函数调用耗时多、空间复杂度高等,所以,在编写递归代码的时候,一定要控制好这些副作用。 |
@ -0,0 +1,13 @@ |
|||||||
|
在看 Antd 的源码时看到下面这一段,是有关 bind 的使用方法,代码不多但是深入研究的话还是有不少知识盲区的 |
||||||
|
|
||||||
|
```typescript |
||||||
|
const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/; |
||||||
|
const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar); |
||||||
|
isTwoCNChar("提交") |
||||||
|
``` |
||||||
|
|
||||||
|
1. 为什么需要使用 `bind` 方法改变作用域? |
||||||
|
`rxTwoCNChar.test` 这一行赋值相当于 `RegExp.prototype.test` 直接从 RegExp 原型中取 test 方法赋值 ,而不是通过 RegExp 实例 `rxTwoCNChar` 中调用函数,所以执行时的作用域并不会指向 `rxTwoCNChar`,需要使用 `bind` 函数生成并返回一个具有指定作用域的新函数。 |
||||||
|
1. 不使用 bind 时 `rxTwoCNChar.test` 作用域指向谁? |
||||||
|
1. 严格模式下 `this` 指向 `undefined` |
||||||
|
2. 非严格模式下 `this` 指向 `global` |
Loading…
Reference in new issue