Que's Blog

WebFrontEnd Development

0%

react-17.0.2

react 本身

react 不仅仅是浏览器端在用,而且服务器端和 reactNative 端其实也还是在使用。

这就决定了 react 库本身只是一个很纯粹的数据结构类型的库。

但是前端其实很难脱离 react-dom 去看源码,在这个不断的跳转中其实已经分不清什么是 react,什么是 react-dom。

但是说到底,react 就是 react, 而非 react-dom。虽然常规来说,如果不看 react-dom,其实很难处乎其外去理解 react。但是这篇,还是很克制的去单的分析 react 本身。

时间已经到了 21 年 5 月。所以这里的版本是 17.0.2。但是 react 这个库 实质很多地方已经一年多没有修改过了。所以如果早期有理解,这里依然还是那样。

整体结构

react17.0.2

这张图比较大,可以右键打开到特定地方点击放大镜查看(chrome)。
这里对导出的函数和变量做一点分析。

函数

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
createMutableSource,
createRef,
Component,
PureComponent,
createElement,
cloneElement,
createContext,
createFactory,
forwardRef,
lazy,
memo,
useCallback,
useContext,
useEffect,
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMemo,
useMutableSource,
useReducer,
useRef,
useState,
isValidElement,
useTransition,
startTransition,
useDeferredValue,
block,
createFundamental as unstable_createFundamental,
useOpaqueIdentifier as unstable_useOpaqueIdentifier,

变量

1
2
3
4
5
6
7
8
9
10
Children
REACT_FRAGMENT_TYPE as Fragment,
REACT_PROFILER_TYPE as Profiler,
REACT_STRICT_MODE_TYPE as StrictMode,
REACT_DEBUG_TRACING_MODE_TYPE as unstable_DebugTracingMode,
REACT_SUSPENSE_TYPE as Suspense,
ReactSharedInternals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
REACT_SUSPENSE_LIST_TYPE as SuspenseList,
REACT_LEGACY_HIDDEN_TYPE as unstable_LegacyHidden,
REACT_SCOPE_TYPE as unstable_Scope,

细节分析

这篇主要分析返回的数据结构,看情况记录一下以前写过这些数据是如何使用之类。

Fn#createMutableSource

这个函数过于简单 基本直接贴也可以:

1
2
3
4
5
6
7
8
9
10
11
12
export function createMutableSource<Source: $NonMaybeType<mixed>>(
source: Source,
getVersion: MutableSourceGetVersionFn,
): MutableSource<Source> {
const mutableSource: MutableSource<Source> = {
_getVersion: getVersion,
_source: source,
_workInProgressVersionPrimary: null,
_workInProgressVersionSecondary: null,
};
return mutableSource;
}

这个函数主要用于useMutableSource, 官方文档在这里0147-use-mutable-source.md

useMutableSource() enables React components to safely and efficiently read from a mutable external source in Concurrent Mode.

Fn#createRef

1
2
3
4
5
6
export function createRef(): RefObject {
const refObject = {
current: null,
};
return refObject;
}

这里超级常见了, 常常会用到的ref.current就是这里。

Fn#Component

这里Component说是一个函数其实不太合适,日常来说,我们都是把它作为一个组件使用的。它是react项目无法避开的一个知识点。

但是在纯粹的React库里面它是一个构造器。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.isReactComponent = {};
Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

当new一个Component时候,原型链上就自动有了setState这些东西。它们依托ReactNoopUpdateQueue进行更新。

当然,后面就全是细节了。这里不表。

Fn#PureComponent

PureComponent和Component基本相同。但是它没有了Component的原型上的setState和forceUpdate这些了。

1
2
3
4
5
6
function PureComponent(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}

Fn#createContext

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
export function createContext<T>(
defaultValue: T,
calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {
if (calculateChangedBits === undefined) {
calculateChangedBits = null;
} else {
if (__DEV__) { }
}

const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
_calculateChangedBits: calculateChangedBits,
_currentValue: defaultValue,
_currentValue2: defaultValue,
_threadCount: 0,
Provider: (null: any),
Consumer: (null: any),
};

context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};

let hasWarnedAboutUsingNestedContextConsumers = false;
let hasWarnedAboutUsingConsumerProvider = false;
let hasWarnedAboutDisplayNameOnConsumer = false;

if (__DEV__) {
// 略
} else {
context.Consumer = context;
}

return context;
}

这里context是早前redux之类库必须用到的东西。这里主要关注ReactContext数据定义。

Fn#forwardRef

1
2
3
4
5
6
7
8
9
export function forwardRef<Props, ElementType: React$ElementType>(
render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
const elementType = {
$$typeof: REACT_FORWARD_REF_TYPE,
render,
};
return elementType;
}

这里这个函数核心还是$$typeof和render组成的这个数据结构。正常来说render是一个(props)=>ReactNode的函数。

它主要作用还是为了方便使用ref方式获取dom结构,以便一些老旧js库基于dom来工作。

1
2
3
4
5
6
// 常规使用方式
const Child = React.forwardRef((props, ref)=>{
return (
<div ref={ref}>{props.txt}</div>
)
})

当要进行实例化、渲染的时候,需要执行这个render一次将ref传入。

Fn#lazy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export function lazy<T>(
ctor: () => Thenable<{default: T, ...}>,
): LazyComponent<T, Payload<T>> {
const payload: Payload<T> = {
// We use these fields to store the result.
_status: -1,
_result: ctor,
};

const lazyType: LazyComponent<T, Payload<T>> = {
$$typeof: REACT_LAZY_TYPE,
_payload: payload,
_init: lazyInitializer,
};

return lazyType;
}
1
2
// 常规的使用
const SomeComponent = React.lazy(() => import('./SomeComponent'));

这里lazy要求有一个<React.Suspense>容器 && 浏览器的Promise环境支持。

具体的lazy更多细节在ReactLazy.js里面。它的实现上和Promise非常相似。里面有多种状态

1
2
3
4
const Uninitialized = -1;
const Pending = 0;
const Resolved = 1;
const Rejected = 2;

而lazy数据结构中的lazyInitializer属性,执行之后会进行类似Promise这样的操作。

这个_init: lazyInitializer机制,大致就是为什么需要Promise(底层原理依赖它),为什么需要<React.Suspense>容器(_init:lazyInitializer需要有人来执行, 返回的数据结构和内部逻辑决定了它没有自执行的可能性)。

关于Promise底层依赖这个,访问babel repl,做一点尝试就很容易明白。

1
2
3
4
// from:
const OtherComponent = React.lazy(() => import('./OtherComponent'));
// to:
const OtherComponent = React.lazy(() => Promise.resolve().then(() => _interopRequireWildcard(require('./OtherComponent'))));

关于里面多出来的其他函数可能也无需去理解,看到转译出来的Promise.resolve()已经可以确定底层依赖的事实。

Value#Suspense

上文的lazy依赖于它。但是在react内部呢,Suspense仅仅是一个Symbol符号。在代码实现上,它是这样定义的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const symbolFor = Symbol.for;
REACT_SUSPENSE_TYPE = symbolFor('react.suspense');
// lazy返回结构对应的定义是
REACT_LAZY_TYPE = symbolFor('react.lazy');

// 使用场景
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<React.Suspense fallback={<Spinner />}>
<OtherComponent />
</React.Suspense>
);
}

// babel对React.Suspense的转译:
function MyComponent() {
return React.createElement(React.Suspense, {
fallback: React.createElement(Spinner, null)
}, React.createElement(OtherComponent, null));
}

考虑createElement的定义,这里发生的事情大致是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export function createElement(type, config, children) {
return ReactElement(
type, // 这里type = symbolFor('react.suspense') = REACT_SUSPENSE_TYPE
key,
ref,
self,
source,
ReactCurrentOwner.current,
props: { // 细节有忽略 大致如此
...config, // 这里基本等价 fallback 也就是那个Spinner组件
children, // 这里对应的LazyComponent
}
);
}

以下是ReactDom范畴:

简单看了一下新版本的ReactDom, render一路往下到了updateContainer。在这个函数中有一点点细节:

1
2
3
4
5
6
update={
callback: callback, // callback返回Spinner组件
payload: { element } // element是我们的React.Suspense容器组件
}
enqueueUpdate(current, update); // current是容器 Fiber形式
scheduleUpdateOnFiber(current, lane, eventTime);

enqueueUpdate将update挂载到了current这个fiber节点。scheduleUpdateOnFiber开始执行。

Fn#memo

来自ReactMemo文件。

返回的是这样的一个数据结构:

1
2
3
4
5
const elementType = {
$$typeof: REACT_MEMO_TYPE,
type, // type这里是组件
compare: compare === undefined ? null : compare,
};

常规使用上:

1
2
3
4
5
6
7
8
9
10
11
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);

它用于一些函数式组件的性能优化。解决shouldComponentUpdate只能用于classComponent的局限。

这个数据结构的设计还是很容易理解的,$$typeof用来判断类型,compare用来判断是否需要更新,type这是渲染方法。React这种数据驱动的View的模式, type作为render函数, 只要使用compare精细判别一下props变化, 一般是针对作为对象的props属性,就可以进行视图一致、业务精细化处理的刷新。

HookS#useXXX系列

这里太长, 单独拆开了说。点这里

主要分析了 useState 和 useEffect两个,写到最后其实有些兴味索然,不过还是可以看看吧。

block

这是一个当前尚未发布的功能。关联issue

“Blocks” fixture app which made by built-in API, ‘react-fetch’, Server Component combination that useful to play new features v18 later.

简单说这是一个[内置API+远程数据获取+SSR]的混合体。

实际上这属于一个资料很少的功能,当前实际上也很难找到demo可以参考,当前最合适用来理解这个功能的demo应当是ReactBlocks-test.js

我去大致看了一下这个test,大致是测试Block这个api,可以实现实现数据获取,数据缓存,带有Suspense安全边界这样的一个功能。

个人猜想它大致还是针对当前我们一些业务组件不得不在组件里面耦合远程api请求、以及复用组件产生的。

当前它还没有发布, 对它有个初步认知应该就可以了。

useOpaqueIdentifier

这是工具函数, 用来生成唯一id,从前为了这个id使用过uuid-v4这个库。
它比uuid-v4更优越的地方可能在于它是SSR-safe的,当这样当SSR环境生成的id和客户端生成的id就不会出现冲突的可能。

🌠 React is getting a new hook, useOpaqueIdentifier, which can be used to generate unique IDs (to be used for form labels, aria-labelledby, etc), in an SSR-safe way.

1
2
3
4
5
6
7
8
9
10
11
12
13
import React, { useOpaqueIdentifier } from 'react'

const TextField = ({ value, onChange, label }) => {
const id = useOpaqueIdentifier()

return (
<div className="text-field">
<label htmlFor={id}>{label}</label>
<input type="text" id={id} value={value} onChange={onChange} />
</div>
)
}
export default TextField