React 源码阅读计划
截止 11 月底,学习版 React 编写基本完成,很高兴终于基本把 16.8.6 版本进度完成,从 5 月至今已时过半年之久,陆陆续续算是把她完成了。(喝彩)
来记录一下最近学习过程中遇到的问题,那么先来看到一段代码:
class ExampleApplication extends React.Component {
constructor(props) {
super(props)
}
render() {
return <p>Example Application</p>
}
}
ReactDOM.render(
<ExampleApplication />,
document.getElementById('container')
)
我们知道,上面代码中,我们定义了一个 ClassComponent
类组件,但是在进入 beginWork
(ReactFiberBeginWork.js) 函数时,她却跳进了 IndeterminateComponent
判断分支,而没有进入她应该进入的 ClassComponent
分支,这使得我很困惑,为什么会这样呢(已忽略无关代码)?
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
switch (workInProgress.tag) {
case IndeterminateComponent: { // <== 出乎意料的进入此分支
const elementType = workInProgress.elementType;
return mountIndeterminateComponent(
current,
workInProgress,
elementType,
renderExpirationTime,
);
}
case ClassComponent: { // <== 为什么没有进入应该进入的分支?
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderExpirationTime);
case HostComponent:
return updateHostComponent(current, workInProgress, renderExpirationTime);
}
}
我开始查找原因,直到我看到 createFiberFromTypeAndProps
(ReactFiber.js) 函数(已忽略无关代码):
function shouldConstruct(Component: Function) {
const prototype = Component.prototype;
return !!(prototype && prototype.isReactComponent);
}
export function createFiberFromTypeAndProps(
type: any, // React$ElementType
key: null | string,
pendingProps: any,
owner: null | Fiber,
mode: TypeOfMode,
expirationTime: ExpirationTime,
): Fiber {
let fiber;
// 原来 IndeterminateComponent 是初始化 Fiber 时的初始 WorkTag
// 所以才会造成我上面产生的困惑
let fiberTag = IndeterminateComponent;
// The resolved type is set if we know what the final type will be. I.e. it's not lazy.
let resolvedType = type;
// 这里是 ClassComponent 需要进入的分支逻辑
if (typeof type === 'function') {
// 问题就出在我没有进入到这个分支, shouldConstruct 函数执行返回 false
// 导致完全跳过这里的逻辑判断,直接进入 createFiber 函数
// 上面准备了 shouldConstruct ,我相信看到源码的你,很容易就能发现问题所在
if (shouldConstruct(type)) {
fiberTag = ClassComponent;
}
}
fiber = createFiber(fiberTag, pendingProps, key, mode);
fiber.elementType = type;
fiber.type = resolvedType;
fiber.expirationTime = expirationTime;
return fiber;
}
到这里,我发现了是因为校验 isReactComponent
失败才出现上述问题的,那么着手修复即可。我们在 ReactBaseClasses.js 中加入 Component.prototype.isReactComponent = {};
即可。
在后续又遇到了一个很诡异的 state
丢失问题,我知道,我也很清楚肯定是我哪里代码漏写才导致的,我又开始了无尽的调试,考虑从类的 constructor
函数开始调试,其实可以直接看到 constructClassInstance
(ReactFiberClassComponent.js) 函数(已忽略无关代码):
function constructClassInstance(
workInProgress: Fiber,
ctor: any,
props: any,
renderExpirationTime: ExpirationTime,
): any {
let context = null;
const instance = new ctor(props, context);
// 因为在学习时,我是直接跳过 __DEV__ 情况下的分支的
// 所以不慎将下面这一行代码删除了
// 这行代码中除了声明 state ,还有一步赋值操作
// 就是对 workInProgress.memoizedState 值的赋值操作
const state = (workInProgress.memoizedState =
instance.state !== null && instance.state !== undefined
? instance.state
: null);
adoptClassInstance(workInProgress, instance);
return instance;
}
function mountClassInstance(
workInProgress: Fiber,
ctor: any,
newProps: any,
renderExpirationTime: ExpirationTime,
): void {
const instance = workInProgress.stateNode;
instance.props = newProps;
// 由于上面那一行代码的丢失,导致这一步赋值操作永远为 null
instance.state = workInProgress.memoizedState;
instance.refs = emptyRefsObject;
}
到此为止, state
丢失问题也得以解决,进入 mountClassInstance
函数其实就是在进入 ClassComponent
分支之后执行的代码,所以真的是环环相扣啊。
遇到的问题也分享完了,那么接下来安排一下《React 源码学习》系列文章的后续章节内容(暂定):
- React 源码学习(九):16.8.6 的概况与变化
- React 源码学习(十):Fiber 数据结构
- React 源码学习(十一):调度机制 Scheduler
- React 源码学习(十二):调和机制 Reconciler
- React 源码学习(未知): React Hook(因为没有读)
建立自己的前端知识体系
- 计划在新的一年逐步建立自己的前端知识体系,并进行不断完善和补全,从而全方面提升自己。
Ant Design Hank 小记
迭代过程中发现了一个 UI 样式的局限性,关于 Tabel 组件,官方提供了 scroll
属性可用于控制列宽,但是当我在列中配置 fixed
属性后,并且列的总宽度计算不及屏幕宽度时,就会出现 Table 撕裂现象,当然此情况是出现在 3.15.2
版本(我司目前固定为此版本),借此我能做的就是再此基础上打补丁,毕竟 fork 下来改不合适,要改的地方连锁反应太大, Tabel 组件 基于 rc-table
,当然我是去看了源码的(已忽略无关代码):
// https://github.com/react-component/table/blob/6.4.0/src/ColGroup.js#L25
cols = cols.concat(
leafColumns.map(c => {
return <col key={c.key || c.dataIndex} style={{ width: c.width, minWidth: c.width }} />;
}),
);
看到这里,我留下了眼泪,他只接受唯一一个样式参数 width
,我哭了,所以经过考虑,如果出现上述场景,我们考虑自动调整尾列宽度来修复此问题,借助 ref
来获取当前组件实际在屏幕中的宽度后进行补全计算,代码我就不贴了,因为很简单。
日语学习计划
截止 11 月底,近 2 周没学习日语,决定在后期进行计划,固定时间学习日语。
- 本文链接: https://zongzi531.com/2019/12/01/%E6%96%B0%E7%9A%84%E6%8C%91%E6%88%98%E5%8D%B3%E5%B0%86%E5%BC%80%E5%A7%8B/
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!