React 源码阅读笔记

React 源码阅读笔记

flow

Flow 是一个静态类型检查器,由 Facebook 开发,用于 JavaScript 代码。在 React 项目中使用 Flow,可以帮助你在开发过程中发现潜在的类型错误,提高代码的健壮性和可维护性。虽然 TypeScript 在 React 生态中应用更广泛,但 Flow 仍然被一些老项目或特定团队所采用。

以下是在 React 项目中使用 Flow 的一些关键方面:

1. 安装和配置 Flow:

  • 首先,你需要安装 Flow:
1
npm install --save-dev flow-bin
  • 然后,在你的 package.json 文件中添加一个 Flow 脚本:
1
2
3
4
5
{
  "scripts": {
    "flow": "flow"
  }
}
  • 运行 npm run flow init 初始化 Flow,这会创建一个 .flowconfig 文件。

2. 在 React 组件中使用 Flow:

  • 在你的 React 组件文件顶部添加 // @flow 注释,告诉 Flow 对该文件进行类型检查。
  • 使用 Flow 的类型注解来声明变量、函数参数、组件 props 和 state 的类型。

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// @flow
import React from 'react';

type Props = {
  name: string,
  age?: number, // 可选属性
};

function Greeting(props: Props) {
  return (
    <div>
      {props.age ? `Hello, ${props.name}! You are ${props.age} years old.` : `Hello, ${props.name}!`}
    </div>
  );
}

export default Greeting;

3. Flow 的类型注解:

  • Flow 提供了丰富的类型注解,包括:
    • 基本类型:stringnumberbooleannullundefinedvoid
    • 对象类型:{ prop1: type1, prop2: type2 }
    • 数组类型:Array<type>type[]
    • 函数类型:(param1: type1, param2: type2) => returnType
    • 联合类型:type1 | type2
    • 交叉类型:type1 & type2
    • 泛型:<T>

4. 运行 Flow 检查:

  • 运行 npm run flow 来执行 Flow 的类型检查。
  • Flow 会扫描你的代码,并报告任何类型错误。

5. Flow 与 React 的集成:

  • Flow 能够很好地与 React 集成,可以检查组件的 props、state 和 context 的类型。
  • flow-typed 是一个社区维护的 Flow 类型定义库,提供了许多第三方库的类型定义,包括 React。

6. Flow 的优点和缺点:

  • 优点:
    • 静态类型检查,可以在开发阶段发现类型错误。
    • 提高代码的健壮性和可维护性。
    • 与 React 集成良好。
  • 缺点:
    • 学习曲线相对较陡峭。
    • 与 TypeScript 相比,生态系统较小。
    • typescript现在是react项目中的主流,flow现在在react项目中已经很少被使用了。

总结:

虽然 Flow 在 React 生态系统中不如 TypeScript 流行,但它仍然是一个强大的静态类型检查器。通过在 React 项目中使用 Flow,你可以提高代码的质量和可靠性。

index.js

 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
/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

export {
  __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
  __COMPILER_RUNTIME,
  Children,
  Component,
  Fragment,
  Profiler,
  PureComponent,
  StrictMode,
  Suspense,
  cloneElement,
  createContext,
  createElement,
  createRef,
  use,
  forwardRef,
  isValidElement,
  lazy,
  memo,
  cache,
  unstable_useCacheRefresh,
  startTransition,
  useId,
  useCallback,
  useContext,
  useDebugValue,
  useDeferredValue,
  useEffect,
  useImperativeHandle,
  useInsertionEffect,
  useLayoutEffect,
  useMemo,
  useReducer,
  useOptimistic,
  useRef,
  useState,
  useSyncExternalStore,
  useTransition,
  useActionState,
  version,
} from './src/ReactClient';

这段代码是一个 JavaScript 模块的导出语句,它导出了来自 './src/ReactClient' 文件中的一系列 React API。让我们逐一分析这些导出项,了解它们在 React 中的作用:

核心 React API:

  • Children: 提供用于处理 props.children 的实用方法,例如遍历、映射和计数。
  • Component: React 组件的基类,用于创建基于类的组件。
  • Fragment: 允许在不添加额外 DOM 节点的情况下,将多个元素组合在一起。
  • Profiler: 用于测量 React 应用的渲染性能。
  • PureComponent: 类似于 Component,但实现了浅层属性和状态比较,以优化渲染性能。
  • StrictMode: 用于在开发过程中突出显示应用中的潜在问题。
  • Suspense: 用于声明组件在加载时应显示加载状态。
  • cloneElement: 用于克隆并修改 React 元素。
  • createContext: 用于创建 React 上下文,以便在组件树中共享数据。
  • createElement: 用于创建 React 元素。
  • createRef: 用于创建对 DOM 元素或 React 组件的引用。
  • forwardRef: 用于将 ref 传递给子组件。
  • isValidElement: 用于检查值是否为有效的 React 元素。
  • lazy: 用于延迟加载 React 组件。
  • memo: 用于对函数组件进行记忆化,以优化渲染性能。
  • version: 导出了当前react的版本号。

React Hooks:

  • use: 用于读取 promise 或者 context的值。
  • useId: 用于生成跨客户端和服务器的唯一 ID。
  • useCallback: 用于记忆化回调函数。
  • useContext: 用于访问 React 上下文。
  • useDebugValue: 用于在 React DevTools 中显示自定义调试值。
  • useDeferredValue: 用于延迟更新值,以优化性能。
  • useEffect: 用于在组件渲染后执行副作用。
  • useImperativeHandle: 用于自定义组件的 ref 句柄。
  • useInsertionEffect: 在任何布局计算之前运行。
  • useLayoutEffect: 在布局计算之后、浏览器绘制之前运行。
  • useMemo: 用于记忆化计算结果。
  • useReducer: 用于管理复杂的状态逻辑。
  • useOptimistic: 返回一个乐观状态的值,以及一个用于设置新乐观状态的函数。
  • useRef: 用于创建可变的 ref 对象。
  • useState: 用于在函数组件中管理状态。
  • useSyncExternalStore: 用于订阅外部数据存储。
  • useTransition: 用于标记状态更新为非紧急的。
  • useActionState: 返回一个 action 函数和一个 pending 状态。

内部和实验性 API:

  • __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE: 这是一个内部 API,不应在用户代码中使用,因为它可能会在未来的 React 版本中更改或删除。
  • __COMPILER_RUNTIME: 这是一个与编译器相关的内部API。
  • cache: 实验性API,用于缓存函数返回值。
  • unstable_useCacheRefresh: 实验性API,用于刷新缓存。

总结:

这段代码导出了 React 客户端库的核心 API,包括组件、hooks 和一些内部/实验性 API。这些 API 共同构成了 React 应用的基础,使开发者能够构建复杂的、高性能的用户界面。

ReactClient.js

  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
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import ReactVersion from 'shared/ReactVersion';
import {
  REACT_FRAGMENT_TYPE,
  REACT_PROFILER_TYPE,
  REACT_STRICT_MODE_TYPE,
  REACT_SUSPENSE_TYPE,
  REACT_SUSPENSE_LIST_TYPE,
  REACT_LEGACY_HIDDEN_TYPE,
  REACT_ACTIVITY_TYPE,
  REACT_SCOPE_TYPE,
  REACT_TRACING_MARKER_TYPE,
  REACT_VIEW_TRANSITION_TYPE,
} from 'shared/ReactSymbols';

import {Component, PureComponent} from './ReactBaseClasses';
import {createRef} from './ReactCreateRef';
import {forEach, map, count, toArray, only} from './ReactChildren';
import {
  createElement,
  cloneElement,
  isValidElement,
} from './jsx/ReactJSXElement';
import {createContext} from './ReactContext';
import {lazy} from './ReactLazy';
import {forwardRef} from './ReactForwardRef';
import {memo} from './ReactMemo';
import {cache} from './ReactCacheClient';
import {postpone} from './ReactPostpone';
import {
  getCacheForType,
  useCallback,
  useContext,
  useEffect,
  useEffectEvent,
  useImperativeHandle,
  useDebugValue,
  useInsertionEffect,
  useLayoutEffect,
  useMemo,
  useSyncExternalStore,
  useReducer,
  useRef,
  useState,
  useTransition,
  useDeferredValue,
  useId,
  useCacheRefresh,
  use,
  useOptimistic,
  useActionState,
  useSwipeTransition,
} from './ReactHooks';
import ReactSharedInternals from './ReactSharedInternalsClient';
import {startTransition} from './ReactStartTransition';
import {addTransitionType} from './ReactTransitionType';
import {act} from './ReactAct';
import {captureOwnerStack} from './ReactOwnerStack';
import * as ReactCompilerRuntime from './ReactCompilerRuntime';

const Children = {
  map,
  forEach,
  count,
  toArray,
  only,
};

export {
  Children,
  createRef,
  Component,
  PureComponent,
  createContext,
  forwardRef,
  lazy,
  memo,
  cache,
  postpone as unstable_postpone,
  useCallback,
  useContext,
  useEffect,
  useEffectEvent as experimental_useEffectEvent,
  useImperativeHandle,
  useDebugValue,
  useInsertionEffect,
  useLayoutEffect,
  useMemo,
  useOptimistic,
  useActionState,
  useSyncExternalStore,
  useReducer,
  useRef,
  useState,
  REACT_FRAGMENT_TYPE as Fragment,
  REACT_PROFILER_TYPE as Profiler,
  REACT_STRICT_MODE_TYPE as StrictMode,
  REACT_SUSPENSE_TYPE as Suspense,
  createElement,
  cloneElement,
  isValidElement,
  ReactVersion as version,
  ReactSharedInternals as __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
  ReactCompilerRuntime as __COMPILER_RUNTIME,
  // Concurrent Mode
  useTransition,
  startTransition,
  useDeferredValue,
  REACT_SUSPENSE_LIST_TYPE as unstable_SuspenseList,
  REACT_LEGACY_HIDDEN_TYPE as unstable_LegacyHidden,
  REACT_ACTIVITY_TYPE as unstable_Activity,
  getCacheForType as unstable_getCacheForType,
  useCacheRefresh as unstable_useCacheRefresh,
  use,
  // enableScopeAPI
  REACT_SCOPE_TYPE as unstable_Scope,
  // enableTransitionTracing
  REACT_TRACING_MARKER_TYPE as unstable_TracingMarker,
  // enableViewTransition
  REACT_VIEW_TRANSITION_TYPE as unstable_ViewTransition,
  addTransitionType as unstable_addTransitionType,
  // enableSwipeTransition
  useSwipeTransition as unstable_useSwipeTransition,
  // DEV-only
  useId,
  act,
  captureOwnerStack,
};

这段代码导出了 React 库中的一系列 API,涵盖了组件操作、Hooks、上下文、性能分析、内部工具以及一些实验性的并发模式特性。让我们逐一分析这些导出项:

1. Children 对象:

  • map: 用于遍历 props.children 并对其进行映射操作。
  • forEach: 用于遍历 props.children 并执行回调函数。
  • count: 用于计算 props.children 中元素的数量。
  • toArray: 用于将 props.children 转换为数组。
  • only: 用于验证 props.children 是否只有一个子元素,并返回该元素。

2. 核心 React API:

  • createRef: 用于创建对 DOM 元素或 React 组件的引用。
  • Component: React 组件的基类,用于创建基于类的组件。
  • PureComponent: 类似于 Component,但实现了浅层属性和状态比较,以优化渲染性能。
  • createContext: 用于创建 React 上下文,以便在组件树中共享数据。
  • forwardRef: 用于将 ref 传递给子组件。
  • lazy: 用于延迟加载 React 组件。
  • memo: 用于对函数组件进行记忆化,以优化渲染性能。
  • cache: 实验性API,用于缓存函数返回值。
  • createElement: 用于创建 React 元素。
  • cloneElement: 用于克隆并修改 React 元素。
  • isValidElement: 用于检查值是否为有效的 React 元素。
  • version: 导出了当前react的版本号。
  • Fragment: React片段,允许在不添加额外 DOM 节点的情况下,将多个元素组合在一起。
  • Profiler: 用于测量 React 应用的渲染性能。
  • StrictMode: 用于在开发过程中突出显示应用中的潜在问题。
  • Suspense: 用于声明组件在加载时应显示加载状态。

3. React Hooks:

  • useCallback: 用于记忆化回调函数。
  • useContext: 用于访问 React 上下文。
  • useEffect: 用于在组件渲染后执行副作用。
  • useImperativeHandle: 用于自定义组件的 ref 句柄。
  • useDebugValue: 用于在 React DevTools 中显示自定义调试值。
  • useInsertionEffect: 在任何布局计算之前运行。
  • useLayoutEffect: 在布局计算之后、浏览器绘制之前运行。
  • useMemo: 用于记忆化计算结果。
  • useOptimistic: 返回一个乐观状态的值,以及一个用于设置新乐观状态的函数。
  • useActionState: 返回一个 action 函数和一个 pending 状态。
  • useSyncExternalStore: 用于订阅外部数据存储。
  • useReducer: 用于管理复杂的状态逻辑。
  • useRef: 用于创建可变的 ref 对象。
  • useState: 用于在函数组件中管理状态。
  • use: 用于读取 promise 或者 context的值。

4. 内部和实验性 API:

  • __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE: 这是一个内部 API,不应在用户代码中使用,因为它可能会在未来的 React 版本中更改或删除。
  • __COMPILER_RUNTIME: 这是一个与编译器相关的内部API。
  • experimental_useEffectEvent: 实验性API。
  • unstable_postpone: 实验性API。
  • unstable_SuspenseList: 实验性API。
  • unstable_LegacyHidden: 实验性API。
  • unstable_Activity: 实验性API。
  • unstable_getCacheForType: 实验性API。
  • unstable_useCacheRefresh: 实验性API。
  • unstable_Scope: 实验性API。
  • unstable_TracingMarker: 实验性API。
  • unstable_ViewTransition: 实验性API。
  • unstable_addTransitionType: 实验性API.
  • unstable_useSwipeTransition: 实验性API。
  • useId: 用于生成跨客户端和服务器的唯一 ID。
  • act: 用于在单元测试中模拟 React 事件循环。
  • captureOwnerStack: 捕捉组件的所有者堆栈。
  • useTransition, startTransition, useDeferredValue: 用于管理并发模式下的过渡和延迟渲染。

总结:

这段代码导出了 React 库的核心 API、Hooks、内部工具以及一些实验性的并发模式特性。这些 API 共同构成了 React 应用的基础,使开发者能够构建复杂的、高性能的用户界面。

ReactChildren.js

  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
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import type {
  ReactNodeList,
  Thenable,
  PendingThenable,
  FulfilledThenable,
  RejectedThenable,
} from 'shared/ReactTypes';

import isArray from 'shared/isArray';
import {
  getIteratorFn,
  REACT_ELEMENT_TYPE,
  REACT_LAZY_TYPE,
  REACT_PORTAL_TYPE,
} from 'shared/ReactSymbols';
import {checkKeyStringCoercion} from 'shared/CheckStringCoercion';

import {isValidElement, cloneAndReplaceKey} from './jsx/ReactJSXElement';

const SEPARATOR = '.';
const SUBSEPARATOR = ':';

/**
 * Escape and wrap key so it is safe to use as a reactid
 *
 * @param {string} key to be escaped.
 * @return {string} the escaped key.
 */
function escape(key: string): string {
  const escapeRegex = /[=:]/g;
  const escaperLookup = {
    '=': '=0',
    ':': '=2',
  };
  const escapedString = key.replace(escapeRegex, function (match) {
    // $FlowFixMe[invalid-computed-prop]
    return escaperLookup[match];
  });

  return '$' + escapedString;
}

/**
 * TODO: Test that a single child and an array with one item have the same key
 * pattern.
 */

let didWarnAboutMaps = false;

const userProvidedKeyEscapeRegex = /\/+/g;
function escapeUserProvidedKey(text: string): string {
  return text.replace(userProvidedKeyEscapeRegex, '$&/');
}

/**
 * Generate a key string that identifies a element within a set.
 *
 * @param {*} element A element that could contain a manual key.
 * @param {number} index Index that is used if a manual key is not provided.
 * @return {string}
 */
function getElementKey(element: any, index: number): string {
  // Do some typechecking here since we call this blindly. We want to ensure
  // that we don't block potential future ES APIs.
  if (typeof element === 'object' && element !== null && element.key != null) {
    // Explicit key
    if (__DEV__) {
      checkKeyStringCoercion(element.key);
    }
    return escape('' + element.key);
  }
  // Implicit key determined by the index in the set
  return index.toString(36);
}

function noop() {}

function resolveThenable<T>(thenable: Thenable<T>): T {
  switch (thenable.status) {
    case 'fulfilled': {
      const fulfilledValue: T = thenable.value;
      return fulfilledValue;
    }
    case 'rejected': {
      const rejectedError = thenable.reason;
      throw rejectedError;
    }
    default: {
      if (typeof thenable.status === 'string') {
        // Only instrument the thenable if the status if not defined. If
        // it's defined, but an unknown value, assume it's been instrumented by
        // some custom userspace implementation. We treat it as "pending".
        // Attach a dummy listener, to ensure that any lazy initialization can
        // happen. Flight lazily parses JSON when the value is actually awaited.
        thenable.then(noop, noop);
      } else {
        // This is an uncached thenable that we haven't seen before.

        // TODO: Detect infinite ping loops caused by uncached promises.

        const pendingThenable: PendingThenable<T> = (thenable: any);
        pendingThenable.status = 'pending';
        pendingThenable.then(
          fulfilledValue => {
            if (thenable.status === 'pending') {
              const fulfilledThenable: FulfilledThenable<T> = (thenable: any);
              fulfilledThenable.status = 'fulfilled';
              fulfilledThenable.value = fulfilledValue;
            }
          },
          (error: mixed) => {
            if (thenable.status === 'pending') {
              const rejectedThenable: RejectedThenable<T> = (thenable: any);
              rejectedThenable.status = 'rejected';
              rejectedThenable.reason = error;
            }
          },
        );
      }

      // Check one more time in case the thenable resolved synchronously.
      switch ((thenable: Thenable<T>).status) {
        case 'fulfilled': {
          const fulfilledThenable: FulfilledThenable<T> = (thenable: any);
          return fulfilledThenable.value;
        }
        case 'rejected': {
          const rejectedThenable: RejectedThenable<T> = (thenable: any);
          const rejectedError = rejectedThenable.reason;
          throw rejectedError;
        }
      }
    }
  }
  throw thenable;
}

function mapIntoArray(
  children: ?ReactNodeList,
  array: Array<React$Node>,
  escapedPrefix: string,
  nameSoFar: string,
  callback: (?React$Node) => ?ReactNodeList,
): number {
  const type = typeof children;

  if (type === 'undefined' || type === 'boolean') {
    // All of the above are perceived as null.
    children = null;
  }

  let invokeCallback = false;

  if (children === null) {
    invokeCallback = true;
  } else {
    switch (type) {
      case 'bigint':
      case 'string':
      case 'number':
        invokeCallback = true;
        break;
      case 'object':
        switch ((children: any).$$typeof) {
          case REACT_ELEMENT_TYPE:
          case REACT_PORTAL_TYPE:
            invokeCallback = true;
            break;
          case REACT_LAZY_TYPE:
            const payload = (children: any)._payload;
            const init = (children: any)._init;
            return mapIntoArray(
              init(payload),
              array,
              escapedPrefix,
              nameSoFar,
              callback,
            );
        }
    }
  }

  if (invokeCallback) {
    const child = children;
    let mappedChild = callback(child);
    // If it's the only child, treat the name as if it was wrapped in an array
    // so that it's consistent if the number of children grows:
    const childKey =
      nameSoFar === '' ? SEPARATOR + getElementKey(child, 0) : nameSoFar;
    if (isArray(mappedChild)) {
      let escapedChildKey = '';
      if (childKey != null) {
        escapedChildKey = escapeUserProvidedKey(childKey) + '/';
      }
      mapIntoArray(mappedChild, array, escapedChildKey, '', c => c);
    } else if (mappedChild != null) {
      if (isValidElement(mappedChild)) {
        if (__DEV__) {
          // The `if` statement here prevents auto-disabling of the safe
          // coercion ESLint rule, so we must manually disable it below.
          // $FlowFixMe[incompatible-type] Flow incorrectly thinks React.Portal doesn't have a key
          if (mappedChild.key != null) {
            if (!child || child.key !== mappedChild.key) {
              checkKeyStringCoercion(mappedChild.key);
            }
          }
        }
        const newChild = cloneAndReplaceKey(
          mappedChild,
          // Keep both the (mapped) and old keys if they differ, just as
          // traverseAllChildren used to do for objects as children
          escapedPrefix +
            // $FlowFixMe[incompatible-type] Flow incorrectly thinks React.Portal doesn't have a key
            (mappedChild.key != null &&
            (!child || child.key !== mappedChild.key)
              ? escapeUserProvidedKey(
                  // $FlowFixMe[unsafe-addition]
                  '' + mappedChild.key, // eslint-disable-line react-internal/safe-string-coercion
                ) + '/'
              : '') +
            childKey,
        );
        if (__DEV__) {
          // If `child` was an element without a `key`, we need to validate if
          // it should have had a `key`, before assigning one to `mappedChild`.
          // $FlowFixMe[incompatible-type] Flow incorrectly thinks React.Portal doesn't have a key
          if (
            nameSoFar !== '' &&
            child != null &&
            isValidElement(child) &&
            child.key == null
          ) {
            // We check truthiness of `child._store.validated` instead of being
            // inequal to `1` to provide a bit of backward compatibility for any
            // libraries (like `fbt`) which may be hacking this property.
            if (child._store && !child._store.validated) {
              // Mark this child as having failed validation, but let the actual
              // renderer print the warning later.
              newChild._store.validated = 2;
            }
          }
        }
        mappedChild = newChild;
      }
      array.push(mappedChild);
    }
    return 1;
  }

  let child;
  let nextName;
  let subtreeCount = 0; // Count of children found in the current subtree.
  const nextNamePrefix =
    nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;

  if (isArray(children)) {
    for (let i = 0; i < children.length; i++) {
      child = children[i];
      nextName = nextNamePrefix + getElementKey(child, i);
      subtreeCount += mapIntoArray(
        child,
        array,
        escapedPrefix,
        nextName,
        callback,
      );
    }
  } else {
    const iteratorFn = getIteratorFn(children);
    if (typeof iteratorFn === 'function') {
      const iterableChildren: Iterable<React$Node> & {
        entries: any,
      } = (children: any);

      if (__DEV__) {
        // Warn about using Maps as children
        if (iteratorFn === iterableChildren.entries) {
          if (!didWarnAboutMaps) {
            console.warn(
              'Using Maps as children is not supported. ' +
                'Use an array of keyed ReactElements instead.',
            );
          }
          didWarnAboutMaps = true;
        }
      }

      const iterator = iteratorFn.call(iterableChildren);
      let step;
      let ii = 0;
      // $FlowFixMe[incompatible-use] `iteratorFn` might return null according to typing.
      while (!(step = iterator.next()).done) {
        child = step.value;
        nextName = nextNamePrefix + getElementKey(child, ii++);
        subtreeCount += mapIntoArray(
          child,
          array,
          escapedPrefix,
          nextName,
          callback,
        );
      }
    } else if (type === 'object') {
      if (typeof (children: any).then === 'function') {
        return mapIntoArray(
          resolveThenable((children: any)),
          array,
          escapedPrefix,
          nameSoFar,
          callback,
        );
      }

      // eslint-disable-next-line react-internal/safe-string-coercion
      const childrenString = String((children: any));

      throw new Error(
        `Objects are not valid as a React child (found: ${
          childrenString === '[object Object]'
            ? 'object with keys {' +
              Object.keys((children: any)).join(', ') +
              '}'
            : childrenString
        }). ` +
          'If you meant to render a collection of children, use an array ' +
          'instead.',
      );
    }
  }

  return subtreeCount;
}

type MapFunc = (child: ?React$Node, index: number) => ?ReactNodeList;

/**
 * Maps children that are typically specified as `props.children`.
 *
 * See https://reactjs.org/docs/react-api.html#reactchildrenmap
 *
 * The provided mapFunction(child, index) will be called for each
 * leaf child.
 *
 * @param {?*} children Children tree container.
 * @param {function(*, int)} func The map function.
 * @param {*} context Context for mapFunction.
 * @return {object} Object containing the ordered map of results.
 */
function mapChildren(
  children: ?ReactNodeList,
  func: MapFunc,
  context: mixed,
): ?Array<React$Node> {
  if (children == null) {
    // $FlowFixMe limitation refining abstract types in Flow
    return children;
  }
  const result: Array<React$Node> = [];
  let count = 0;
  mapIntoArray(children, result, '', '', function (child) {
    return func.call(context, child, count++);
  });
  return result;
}

/**
 * Count the number of children that are typically specified as
 * `props.children`.
 *
 * See https://reactjs.org/docs/react-api.html#reactchildrencount
 *
 * @param {?*} children Children tree container.
 * @return {number} The number of children.
 */
function countChildren(children: ?ReactNodeList): number {
  let n = 0;
  mapChildren(children, () => {
    n++;
    // Don't return anything
  });
  return n;
}

type ForEachFunc = (child: ?React$Node) => void;

/**
 * Iterates through children that are typically specified as `props.children`.
 *
 * See https://reactjs.org/docs/react-api.html#reactchildrenforeach
 *
 * The provided forEachFunc(child, index) will be called for each
 * leaf child.
 *
 * @param {?*} children Children tree container.
 * @param {function(*, int)} forEachFunc
 * @param {*} forEachContext Context for forEachContext.
 */
function forEachChildren(
  children: ?ReactNodeList,
  forEachFunc: ForEachFunc,
  forEachContext: mixed,
): void {
  mapChildren(
    children,
    // $FlowFixMe[missing-this-annot]
    function () {
      forEachFunc.apply(this, arguments);
      // Don't return anything.
    },
    forEachContext,
  );
}

/**
 * Flatten a children object (typically specified as `props.children`) and
 * return an array with appropriately re-keyed children.
 *
 * See https://reactjs.org/docs/react-api.html#reactchildrentoarray
 */
function toArray(children: ?ReactNodeList): Array<React$Node> {
  return mapChildren(children, child => child) || [];
}

/**
 * Returns the first child in a collection of children and verifies that there
 * is only one child in the collection.
 *
 * See https://reactjs.org/docs/react-api.html#reactchildrenonly
 *
 * The current implementation of this function assumes that a single child gets
 * passed without a wrapper, but the purpose of this helper function is to
 * abstract away the particular structure of children.
 *
 * @param {?object} children Child collection structure.
 * @return {ReactElement} The first and only `ReactElement` contained in the
 * structure.
 */
function onlyChild<T>(children: T): T {
  if (!isValidElement(children)) {
    throw new Error(
      'React.Children.only expected to receive a single React element child.',
    );
  }

  return children;
}

export {
  forEachChildren as forEach,
  mapChildren as map,
  countChildren as count,
  onlyChild as only,
  toArray,
};

这段 JavaScript 代码定义了 React 中用于处理 props.children 的实用工具函数,这些函数构成了 React.Children API 的核心部分。让我们逐一分析这些函数:

1. 辅助函数:

  • escape(key: string): string:
    • 用于转义 key 字符串,使其可以安全地用作 React 内部的 reactid
    • 它替换了 key 字符串中的特定字符(=:),以避免冲突。
  • escapeUserProvidedKey(text: string): string:
    • 用于转义用户提供的 key,主要是为了防止 / 字符引起的错误。
  • getElementKey(element: any, index: number): string:
    • 生成一个用于标识元素在集合中的唯一 key 字符串。
    • 如果元素具有显式的 key 属性,则使用该属性;否则,使用元素在集合中的索引。
  • noop():
    • 一个空函数,主要用在处理thenable的时候使用。
  • resolveThenable<T>(thenable: Thenable<T>): T:
    • 处理thenable对象,如果是fulfilled状态,则返回value,如果是rejected状态,则抛出错误,如果是pending状态,则为thenable对象添加状态和处理函数。
  • mapIntoArray(...):
    • 一个内部辅助函数,用于递归地遍历 children 并将其映射到一个数组中。
    • 它处理不同类型的 children,包括元素、数组和迭代器。
    • 它还负责生成唯一的 key 字符串。

2. React.Children API 函数:

  • mapChildren(children: ?ReactNodeList, func: MapFunc, context: mixed): ?Array<React$Node>:
    • children 中的每个子元素映射到一个新的数组中。
    • 它接受一个 func 回调函数,该函数用于转换每个子元素。
    • 类似于 JavaScript 的 Array.prototype.map() 方法。
  • countChildren(children: ?ReactNodeList): number:
    • 计算 children 中子元素的数量。
    • 它通过遍历 children 并递增计数器来实现。
  • forEachChildren(children: ?ReactNodeList, forEachFunc: ForEachFunc, forEachContext: mixed): void:
    • 遍历 children 中的每个子元素,并对每个子元素执行 forEachFunc 回调函数。
    • 类似于 JavaScript 的 Array.prototype.forEach() 方法。
  • toArray(children: ?ReactNodeList): Array<React$Node>:
    • children 转换为一个数组。
    • 它通过调用 mapChildren() 并返回结果数组来实现。
  • onlyChild<T>(children: T): T:
    • 验证 children 是否只有一个子元素,并返回该子元素。
    • 如果 children 包含多个子元素,则抛出一个错误。

3. 核心功能和目的:

  • 这些函数提供了一种方便的方式来处理 props.children,它通常表示组件的子元素。
  • 它们允许你遍历、转换和操作子元素,以实现各种渲染和逻辑需求。
  • key 属性的处理对于 React 的高效更新机制至关重要。这些函数确保了子元素具有唯一的 key,以便 React 可以正确地跟踪和更新它们。
  • 处理了thenable对象,让react可以处理异步的子节点。
  • 处理了迭代器,让react可以处理迭代器返回的子节点。

总结:

这段代码定义了 React.Children API,它提供了一组用于处理 React 组件子元素的实用工具函数。这些函数简化了对 props.children 的操作,并确保了 React 的高效渲染和更新。

ReactHooks.js

  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
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
import type {
  ReactContext,
  StartTransitionOptions,
  Usable,
  Awaited,
  StartGesture,
} from 'shared/ReactTypes';
import {REACT_CONSUMER_TYPE} from 'shared/ReactSymbols';

import ReactSharedInternals from 'shared/ReactSharedInternals';

import {
  enableUseEffectCRUDOverload,
  enableSwipeTransition,
} from 'shared/ReactFeatureFlags';

type BasicStateAction<S> = (S => S) | S;
type Dispatch<A> = A => void;

function resolveDispatcher() {
  const dispatcher = ReactSharedInternals.H;
  if (__DEV__) {
    if (dispatcher === null) {
      console.error(
        'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
          ' one of the following reasons:\n' +
          '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
          '2. You might be breaking the Rules of Hooks\n' +
          '3. You might have more than one copy of React in the same app\n' +
          'See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.',
      );
    }
  }
  // Will result in a null access error if accessed outside render phase. We
  // intentionally don't throw our own error because this is in a hot path.
  // Also helps ensure this is inlined.
  return ((dispatcher: any): Dispatcher);
}

export function getCacheForType<T>(resourceType: () => T): T {
  const dispatcher = ReactSharedInternals.A;
  if (!dispatcher) {
    // If there is no dispatcher, then we treat this as not being cached.
    return resourceType();
  }
  return dispatcher.getCacheForType(resourceType);
}

export function useContext<T>(Context: ReactContext<T>): T {
  const dispatcher = resolveDispatcher();
  if (__DEV__) {
    if (Context.$$typeof === REACT_CONSUMER_TYPE) {
      console.error(
        'Calling useContext(Context.Consumer) is not supported and will cause bugs. ' +
          'Did you mean to call useContext(Context) instead?',
      );
    }
  }
  return dispatcher.useContext(Context);
}

export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

export function useReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useReducer(reducer, initialArg, init);
}

export function useRef<T>(initialValue: T): {current: T} {
  const dispatcher = resolveDispatcher();
  return dispatcher.useRef(initialValue);
}

export function useEffect(
  create: (() => (() => void) | void) | (() => {...} | void | null),
  createDeps: Array<mixed> | void | null,
  update?: ((resource: {...} | void | null) => void) | void,
  updateDeps?: Array<mixed> | void | null,
  destroy?: ((resource: {...} | void | null) => void) | void,
): void {
  if (__DEV__) {
    if (create == null) {
      console.warn(
        'React Hook useEffect requires an effect callback. Did you forget to pass a callback to the hook?',
      );
    }
  }

  const dispatcher = resolveDispatcher();
  if (
    enableUseEffectCRUDOverload &&
    (typeof update === 'function' || typeof destroy === 'function')
  ) {
    // $FlowFixMe[not-a-function] This is unstable, thus optional
    return dispatcher.useEffect(
      create,
      createDeps,
      update,
      updateDeps,
      destroy,
    );
  } else if (typeof update === 'function') {
    throw new Error(
      'useEffect CRUD overload is not enabled in this build of React.',
    );
  }
  return dispatcher.useEffect(create, createDeps);
}

export function useInsertionEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  if (__DEV__) {
    if (create == null) {
      console.warn(
        'React Hook useInsertionEffect requires an effect callback. Did you forget to pass a callback to the hook?',
      );
    }
  }

  const dispatcher = resolveDispatcher();
  return dispatcher.useInsertionEffect(create, deps);
}

export function useLayoutEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  if (__DEV__) {
    if (create == null) {
      console.warn(
        'React Hook useLayoutEffect requires an effect callback. Did you forget to pass a callback to the hook?',
      );
    }
  }

  const dispatcher = resolveDispatcher();
  return dispatcher.useLayoutEffect(create, deps);
}

export function useCallback<T>(
  callback: T,
  deps: Array<mixed> | void | null,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useCallback(callback, deps);
}

export function useMemo<T>(
  create: () => T,
  deps: Array<mixed> | void | null,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useMemo(create, deps);
}

export function useImperativeHandle<T>(
  ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
  create: () => T,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useImperativeHandle(ref, create, deps);
}

export function useDebugValue<T>(
  value: T,
  formatterFn: ?(value: T) => mixed,
): void {
  if (__DEV__) {
    const dispatcher = resolveDispatcher();
    return dispatcher.useDebugValue(value, formatterFn);
  }
}

export function useTransition(): [
  boolean,
  (callback: () => void, options?: StartTransitionOptions) => void,
] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useTransition();
}

export function useDeferredValue<T>(value: T, initialValue?: T): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useDeferredValue(value, initialValue);
}

export function useId(): string {
  const dispatcher = resolveDispatcher();
  return dispatcher.useId();
}

export function useSyncExternalStore<T>(
  subscribe: (() => void) => () => void,
  getSnapshot: () => T,
  getServerSnapshot?: () => T,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useSyncExternalStore(
    subscribe,
    getSnapshot,
    getServerSnapshot,
  );
}

export function useCacheRefresh(): <T>(?() => T, ?T) => void {
  const dispatcher = resolveDispatcher();
  // $FlowFixMe[not-a-function] This is unstable, thus optional
  return dispatcher.useCacheRefresh();
}

export function use<T>(usable: Usable<T>): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.use(usable);
}

export function useMemoCache(size: number): Array<mixed> {
  const dispatcher = resolveDispatcher();
  // $FlowFixMe[not-a-function] This is unstable, thus optional
  return dispatcher.useMemoCache(size);
}

export function useEffectEvent<Args, F: (...Array<Args>) => mixed>(
  callback: F,
): F {
  const dispatcher = resolveDispatcher();
  // $FlowFixMe[not-a-function] This is unstable, thus optional
  return dispatcher.useEffectEvent(callback);
}

export function useOptimistic<S, A>(
  passthrough: S,
  reducer: ?(S, A) => S,
): [S, (A) => void] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useOptimistic(passthrough, reducer);
}

export function useActionState<S, P>(
  action: (Awaited<S>, P) => S,
  initialState: Awaited<S>,
  permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useActionState(action, initialState, permalink);
}

export function useSwipeTransition<T>(
  previous: T,
  current: T,
  next: T,
): [T, StartGesture] {
  if (!enableSwipeTransition) {
    throw new Error('Not implemented.');
  }
  const dispatcher = resolveDispatcher();
  // $FlowFixMe[not-a-function] This is unstable, thus optional
  return dispatcher.useSwipeTransition(previous, current, next);
}

这段代码定义了 React Hooks 的实现,它通过 resolveDispatcher 函数获取当前渲染上下文的 Dispatcher 对象,并调用 Dispatcher 上的相应方法来执行 Hooks 逻辑。

以下是对这段代码的详细分析:

1. 导入类型和常量:

  • Dispatcher:来自 react-reconciler,用于管理 Hooks 的内部状态。
  • ReactContextStartTransitionOptionsUsableAwaitedStartGesture:来自 shared/ReactTypes,定义了 React 上下文、过渡选项、可用值和异步操作的类型。
  • REACT_CONSUMER_TYPE:来自 shared/ReactSymbols,用于标识 React 上下文的 Consumer 组件。
  • ReactSharedInternals:来自 shared/ReactSharedInternals,包含 React 内部共享的变量。
  • enableUseEffectCRUDOverloadenableSwipeTransition:来自 shared/ReactFeatureFlags,用于控制实验性特性。

2. resolveDispatcher() 函数:

  • 这个函数用于获取当前渲染上下文的 Dispatcher 对象。
  • ReactSharedInternals.H 存储了当前 Dispatcher 的引用。
  • 在开发模式下,如果 Dispatchernull,则会发出警告,提示 Hooks 只能在函数组件内部调用。
  • 返回 Dispatcher 对象。

3. Hooks 实现:

  • getCacheForType<T>(resourceType: () => T): T:
    • 获取给定资源类型的缓存。
    • 如果不存在 Dispatcher,则直接调用 resourceType()
  • useContext<T>(Context: ReactContext<T>): T:
    • 访问 React 上下文的值。
    • 在开发模式下,检查 Context 是否为 Consumer 组件,如果是,则发出警告。
    • 调用 dispatcher.useContext(Context) 获取上下文值。
  • useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>]:
    • 管理组件的状态。
    • 调用 dispatcher.useState(initialState) 获取状态和更新函数。
  • useReducer<S, I, A>(reducer: (S, A) => S, initialArg: I, init?: I => S): [S, Dispatch<A>]:
    • 管理复杂的状态逻辑。
    • 调用 dispatcher.useReducer(reducer, initialArg, init) 获取状态和 dispatch 函数。
  • useRef<T>(initialValue: T): {current: T}:
    • 创建可变的 ref 对象。
    • 调用 dispatcher.useRef(initialValue) 创建 ref。
  • useEffect(...):
    • 在组件渲染后执行副作用。
    • 支持 enableUseEffectCRUDOverload 特性。
    • 调用 dispatcher.useEffect(...) 执行副作用。
  • useInsertionEffect(...):
    • 在任何布局计算之前运行。
    • 调用 dispatcher.useInsertionEffect(...) 执行插入副作用。
  • useLayoutEffect(...):
    • 在布局计算之后、浏览器绘制之前运行。
    • 调用 dispatcher.useLayoutEffect(...) 执行布局副作用。
  • useCallback<T>(callback: T, deps: Array<mixed> | void | null): T:
    • 记忆化回调函数。
    • 调用 dispatcher.useCallback(callback, deps) 记忆化回调。
  • useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T:
    • 记忆化计算结果。
    • 调用 dispatcher.useMemo(create, deps) 记忆化结果。
  • useImperativeHandle<T>(ref: ..., create: () => T, deps: ...): void:
    • 自定义组件的 ref 句柄。
    • 调用 dispatcher.useImperativeHandle(ref, create, deps) 设置 ref 句柄。
  • useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void:
    • 在 React DevTools 中显示自定义调试值。
    • 在开发模式下,调用 dispatcher.useDebugValue(value, formatterFn)
  • useTransition(): [boolean, (callback: () => void, options?: StartTransitionOptions) => void]:
    • 标记状态更新为非紧急的。
    • 调用 dispatcher.useTransition() 获取过渡状态和启动函数。
  • useDeferredValue<T>(value: T, initialValue?: T): T:
    • 延迟更新值,以优化性能。
    • 调用 dispatcher.useDeferredValue(value, initialValue) 获取延迟值。
  • useId(): string:
    • 生成跨客户端和服务器的唯一 ID。
    • 调用 dispatcher.useId() 生成 ID。
  • useSyncExternalStore<T>(subscribe: ..., getSnapshot: ..., getServerSnapshot?: ...): T:
    • 订阅外部数据存储。
    • 调用 dispatcher.useSyncExternalStore(...) 订阅存储。
  • useCacheRefresh(): <T>(?() => T, ?T) => void:
    • 刷新缓存。
    • 调用 dispatcher.useCacheRefresh() 刷新缓存。
  • use<T>(usable: Usable<T>): T:
    • 读取 promise 或者 context的值。
    • 调用 dispatcher.use(usable) 读取值。
  • useMemoCache(size: number): Array<mixed>:
    • 创建记忆化缓存。
    • 调用 dispatcher.useMemoCache(size) 创建缓存。
  • useEffectEvent<Args, F: (...Array<Args>) => mixed>(callback: F): F:
    • 创建一个绑定到 effect 生命周期上的事件回调。
    • 调用 dispatcher.useEffectEvent(callback).
  • useOptimistic<S, A>(passthrough: S, reducer: ?(S, A) => S): [S, (A) => void]:
    • 返回一个乐观状态的值,以及一个用于设置新乐观状态的函数。
    • 调用 dispatcher.useOptimistic(passthrough, reducer).
  • useActionState<S, P>(action: (Awaited<S>, P) => S, initialState: Awaited<S>, permalink?: string): [Awaited<S>, (P) => void, boolean]:
    • 返回一个 action 函数和一个 pending 状态。
    • 调用 dispatcher.useActionState(action, initialState, permalink).
  • useSwipeTransition<T>(previous: T, current: T, next: T): [T, StartGesture]:
    • 用于管理滑动过渡。
    • 需要 enableSwipeTransition 特性启用。
    • 调用 dispatcher.useSwipeTransition(previous, current, next).

总结:

这段代码实现了 React Hooks 的核心逻辑,它通过 Dispatcher 对象来管理 Hooks 的内部状态,并提供了各种 Hooks API 供开发者使用。

shared 文件夹

在 React 的开源项目中,shared 文件夹包含 React 核心代码中多个模块共享的通用代码。这些代码片段被多个 React 内部包所使用,以避免重复和保持一致性。

以下是 shared 文件夹中一些重要部分的概述:

1. ReactSymbols.js:

  • 定义 React 内部使用的各种 Symbol。
  • Symbol 是一种唯一且不可变的数据类型,用于标识 React 元素、组件类型和其他内部对象。
  • 例如,REACT_ELEMENT_TYPEREACT_CONTEXT_TYPEREACT_FORWARD_REF_TYPE 等。

2. ReactSharedInternals.js:

  • 包含 React 内部共享的变量和方法。
  • 这些内部变量和方法不应在用户代码中使用,因为它们可能会在未来的 React 版本中更改或删除。
  • 例如,ReactSharedInternals.ReactCurrentDispatcher 用于在 Hooks 中访问当前渲染上下文的调度器。

3. ReactTypes.js:

  • 定义 React 内部使用的各种类型。
  • 这些类型用于确保 React 代码的类型安全性和一致性。
  • 例如,ReactNodeReactElementReactContext 等。

4. isArray.js:

  • 提供跨浏览器兼容的 Array.isArray() 实现。
  • 在不同的 JavaScript 环境中,Array.isArray() 的行为可能有所不同,因此 React 提供了一个统一的实现。

5. checkPropTypes.js:

  • 在开发模式下,用于验证组件的 props 是否符合 propTypes 定义。
  • 如果 props 类型不匹配,则会发出警告。

6. lowPriorityWarning.js:

  • 用于在开发模式下发出低优先级警告。
  • 这些警告通常用于提示潜在的性能问题或不推荐使用的 API。

7. objectIs.js:

  • 提供跨浏览器兼容的 Object.is() 实现。
  • Object.is() 用于比较两个值是否严格相等,包括 NaN-0 的处理。

8. warning.js:

  • 用于在开发模式下发出警告。
  • 这些警告通常用于提示潜在的错误或不推荐使用的 API。

主要目的:

  • 代码重用: shared 文件夹中的代码被多个 React 内部包所使用,避免了重复编写相同的代码。
  • 一致性: 共享的代码确保了 React 内部各个模块之间的一致性。
  • 内部实现: shared 文件夹中的代码主要用于 React 内部实现,不应在用户代码中使用。

总而言之,shared 文件夹包含 React 核心代码中多个模块共享的通用代码,这些代码对于 React 的内部实现至关重要。

ReactServer.js

 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
export {default as __SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE} from './ReactSharedInternalsServer';

import {forEach, map, count, toArray, only} from './ReactChildren';
import {
  REACT_FRAGMENT_TYPE,
  REACT_PROFILER_TYPE,
  REACT_STRICT_MODE_TYPE,
  REACT_SUSPENSE_TYPE,
} from 'shared/ReactSymbols';
import {
  cloneElement,
  createElement,
  isValidElement,
} from './jsx/ReactJSXElement';
import {createRef} from './ReactCreateRef';
import {use, useId, useCallback, useDebugValue, useMemo} from './ReactHooks';
import {forwardRef} from './ReactForwardRef';
import {lazy} from './ReactLazy';
import {memo} from './ReactMemo';
import {cache} from './ReactCacheServer';
import version from 'shared/ReactVersion';
import {captureOwnerStack} from './ReactOwnerStack';

const Children = {
  map,
  forEach,
  count,
  toArray,
  only,
};

export {
  Children,
  REACT_FRAGMENT_TYPE as Fragment,
  REACT_PROFILER_TYPE as Profiler,
  REACT_STRICT_MODE_TYPE as StrictMode,
  REACT_SUSPENSE_TYPE as Suspense,
  cloneElement,
  createElement,
  createRef,
  use,
  forwardRef,
  isValidElement,
  lazy,
  memo,
  cache,
  useId,
  useCallback,
  useDebugValue,
  useMemo,
  version,
  captureOwnerStack, // DEV-only
};

这段代码定义了 React 服务器端渲染(SSR)相关的 API 导出。它主要从不同的 React 模块中导入特定的功能,并将其组合成一个可供服务器端使用的 API 集合。

以下是对这段代码的详细分析:

1. 导入模块和常量:

  • __SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE:从 './ReactSharedInternalsServer' 导入,这是一个服务器端内部使用的模块,不应在用户代码中使用。
  • forEach, map, count, toArray, only:从 './ReactChildren' 导入,这些是用于处理 props.children 的实用工具函数。
  • REACT_FRAGMENT_TYPE, REACT_PROFILER_TYPE, REACT_STRICT_MODE_TYPE, REACT_SUSPENSE_TYPE:从 'shared/ReactSymbols' 导入,这些是 React 内部使用的 Symbol,用于标识不同的 React 元素类型。
  • cloneElement, createElement, isValidElement:从 './jsx/ReactJSXElement' 导入,这些是用于创建和操作 React 元素的函数。
  • createRef:从 './ReactCreateRef' 导入,用于创建对 DOM 元素或 React 组件的引用。
  • use, useId, useCallback, useDebugValue, useMemo:从 './ReactHooks' 导入,这些是 React Hooks。
  • forwardRef:从 './ReactForwardRef' 导入,用于将 ref 传递给子组件。
  • lazy:从 './ReactLazy' 导入,用于延迟加载 React 组件。
  • memo:从 './ReactMemo' 导入,用于对函数组件进行记忆化,以优化渲染性能。
  • cache:从 './ReactCacheServer' 导入,用于在服务器端缓存数据。
  • version:从 'shared/ReactVersion' 导入,表示 React 版本。
  • captureOwnerStack: 从'./ReactOwnerStack'导入,用于捕捉组件的所有者堆栈。

2. Children 对象:

  • forEach, map, count, toArray, only 函数组合成一个 Children 对象,方便使用。

3. 导出 API:

  • 导出 Children 对象,以及从其他模块导入的函数和常量。
  • 这些导出的 API 主要用于服务器端渲染 React 应用。

4. 关键点:

  • 这段代码主要为服务器端渲染环境提供了必要的 React API。
  • 其中,__SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE 是一个内部 API,不应在用户代码中使用。
  • 导出的 Hooks 和其他 API 允许在服务器端渲染 React 组件。
  • captureOwnerStack 只在开发模式下使用。

总结:

这段代码导出了 React 服务器端渲染所需的 API,包括组件操作、Hooks、元素创建和一些内部工具。这些 API 使得在服务器端渲染 React 应用成为可能。

ReactJSXElement.js

  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
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
import getComponentNameFromType from 'shared/getComponentNameFromType';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import hasOwnProperty from 'shared/hasOwnProperty';
import assign from 'shared/assign';
import {
  REACT_ELEMENT_TYPE,
  REACT_FRAGMENT_TYPE,
  REACT_LAZY_TYPE,
} from 'shared/ReactSymbols';
import {checkKeyStringCoercion} from 'shared/CheckStringCoercion';
import isArray from 'shared/isArray';
import {
  disableDefaultPropsExceptForClasses,
  ownerStackLimit,
} from 'shared/ReactFeatureFlags';

const createTask =
  // eslint-disable-next-line react-internal/no-production-logging
  __DEV__ && console.createTask
    ? // eslint-disable-next-line react-internal/no-production-logging
      console.createTask
    : () => null;

function getTaskName(type) {
  if (type === REACT_FRAGMENT_TYPE) {
    return '<>';
  }
  if (
    typeof type === 'object' &&
    type !== null &&
    type.$$typeof === REACT_LAZY_TYPE
  ) {
    // We don't want to eagerly initialize the initializer in DEV mode so we can't
    // call it to extract the type so we don't know the type of this component.
    return '<...>';
  }
  try {
    const name = getComponentNameFromType(type);
    return name ? '<' + name + '>' : '<...>';
  } catch (x) {
    return '<...>';
  }
}

function getOwner() {
  if (__DEV__) {
    const dispatcher = ReactSharedInternals.A;
    if (dispatcher === null) {
      return null;
    }
    return dispatcher.getOwner();
  }
  return null;
}

/** @noinline */
function UnknownOwner() {
  /** @noinline */
  return (() => Error('react-stack-top-frame'))();
}
const createFakeCallStack = {
  'react-stack-bottom-frame': function (callStackForError) {
    return callStackForError();
  },
};

let specialPropKeyWarningShown;
let didWarnAboutElementRef;
let didWarnAboutOldJSXRuntime;
let unknownOwnerDebugStack;
let unknownOwnerDebugTask;

if (__DEV__) {
  didWarnAboutElementRef = {};

  // We use this technique to trick minifiers to preserve the function name.
  unknownOwnerDebugStack = createFakeCallStack['react-stack-bottom-frame'].bind(
    createFakeCallStack,
    UnknownOwner,
  )();
  unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner));
}

function hasValidRef(config) {
  if (__DEV__) {
    if (hasOwnProperty.call(config, 'ref')) {
      const getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
      if (getter && getter.isReactWarning) {
        return false;
      }
    }
  }
  return config.ref !== undefined;
}

function hasValidKey(config) {
  if (__DEV__) {
    if (hasOwnProperty.call(config, 'key')) {
      const getter = Object.getOwnPropertyDescriptor(config, 'key').get;
      if (getter && getter.isReactWarning) {
        return false;
      }
    }
  }
  return config.key !== undefined;
}

function defineKeyPropWarningGetter(props, displayName) {
  if (__DEV__) {
    const warnAboutAccessingKey = function () {
      if (!specialPropKeyWarningShown) {
        specialPropKeyWarningShown = true;
        console.error(
          '%s: `key` is not a prop. Trying to access it will result ' +
            'in `undefined` being returned. If you need to access the same ' +
            'value within the child component, you should pass it as a different ' +
            'prop. (https://react.dev/link/special-props)',
          displayName,
        );
      }
    };
    warnAboutAccessingKey.isReactWarning = true;
    Object.defineProperty(props, 'key', {
      get: warnAboutAccessingKey,
      configurable: true,
    });
  }
}

function elementRefGetterWithDeprecationWarning() {
  if (__DEV__) {
    const componentName = getComponentNameFromType(this.type);
    if (!didWarnAboutElementRef[componentName]) {
      didWarnAboutElementRef[componentName] = true;
      console.error(
        'Accessing element.ref was removed in React 19. ref is now a ' +
          'regular prop. It will be removed from the JSX Element ' +
          'type in a future release.',
      );
    }

    // An undefined `element.ref` is coerced to `null` for
    // backwards compatibility.
    const refProp = this.props.ref;
    return refProp !== undefined ? refProp : null;
  }
}

/**
 * Factory method to create a new React element. This no longer adheres to
 * the class pattern, so do not use new to call it. Also, instanceof check
 * will not work. Instead test $$typeof field against Symbol.for('react.transitional.element') to check
 * if something is a React Element.
 *
 * @param {*} type
 * @param {*} props
 * @param {*} key
 * @param {string|object} ref
 * @param {*} owner
 * @param {*} self A *temporary* helper to detect places where `this` is
 * different from the `owner` when React.createElement is called, so that we
 * can warn. We want to get rid of owner and replace string `ref`s with arrow
 * functions, and as long as `this` and owner are the same, there will be no
 * change in behavior.
 * @param {*} source An annotation object (added by a transpiler or otherwise)
 * indicating filename, line number, and/or other information.
 * @internal
 */
function ReactElement(
  type,
  key,
  self,
  source,
  owner,
  props,
  debugStack,
  debugTask,
) {
  // Ignore whatever was passed as the ref argument and treat `props.ref` as
  // the source of truth. The only thing we use this for is `element.ref`,
  // which will log a deprecation warning on access. In the next release, we
  // can remove `element.ref` as well as the `ref` argument.
  const refProp = props.ref;

  // An undefined `element.ref` is coerced to `null` for
  // backwards compatibility.
  const ref = refProp !== undefined ? refProp : null;

  let element;
  if (__DEV__) {
    // In dev, make `ref` a non-enumerable property with a warning. It's non-
    // enumerable so that test matchers and serializers don't access it and
    // trigger the warning.
    //
    // `ref` will be removed from the element completely in a future release.
    element = {
      // This tag allows us to uniquely identify this as a React Element
      $$typeof: REACT_ELEMENT_TYPE,

      // Built-in properties that belong on the element
      type,
      key,

      props,

      // Record the component responsible for creating this element.
      _owner: owner,
    };
    if (ref !== null) {
      Object.defineProperty(element, 'ref', {
        enumerable: false,
        get: elementRefGetterWithDeprecationWarning,
      });
    } else {
      // Don't warn on access if a ref is not given. This reduces false
      // positives in cases where a test serializer uses
      // getOwnPropertyDescriptors to compare objects, like Jest does, which is
      // a problem because it bypasses non-enumerability.
      //
      // So unfortunately this will trigger a false positive warning in Jest
      // when the diff is printed:
      //
      //   expect(<div ref={ref} />).toEqual(<span ref={ref} />);
      //
      // A bit sketchy, but this is what we've done for the `props.key` and
      // `props.ref` accessors for years, which implies it will be good enough
      // for `element.ref`, too. Let's see if anyone complains.
      Object.defineProperty(element, 'ref', {
        enumerable: false,
        value: null,
      });
    }
  } else {
    // In prod, `ref` is a regular property and _owner doesn't exist.
    element = {
      // This tag allows us to uniquely identify this as a React Element
      $$typeof: REACT_ELEMENT_TYPE,

      // Built-in properties that belong on the element
      type,
      key,
      ref,

      props,
    };
  }

  if (__DEV__) {
    // The validation flag is currently mutative. We put it on
    // an external backing store so that we can freeze the whole object.
    // This can be replaced with a WeakMap once they are implemented in
    // commonly used development environments.
    element._store = {};

    // To make comparing ReactElements easier for testing purposes, we make
    // the validation flag non-enumerable (where possible, which should
    // include every environment we run tests in), so the test framework
    // ignores it.
    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: 0,
    });
    // debugInfo contains Server Component debug information.
    Object.defineProperty(element, '_debugInfo', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: null,
    });
    Object.defineProperty(element, '_debugStack', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: debugStack,
    });
    Object.defineProperty(element, '_debugTask', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: debugTask,
    });
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }

  return element;
}

/**
 * https://github.com/reactjs/rfcs/pull/107
 * @param {*} type
 * @param {object} props
 * @param {string} key
 */
export function jsxProd(type, config, maybeKey) {
  let key = null;

  // Currently, key can be spread in as a prop. This causes a potential
  // issue if key is also explicitly declared (ie. <div {...props} key="Hi" />
  // or <div key="Hi" {...props} /> ). We want to deprecate key spread,
  // but as an intermediary step, we will use jsxDEV for everything except
  // <div {...props} key="Hi" />, because we aren't currently able to tell if
  // key is explicitly declared to be undefined or not.
  if (maybeKey !== undefined) {
    if (__DEV__) {
      checkKeyStringCoercion(maybeKey);
    }
    key = '' + maybeKey;
  }

  if (hasValidKey(config)) {
    if (__DEV__) {
      checkKeyStringCoercion(config.key);
    }
    key = '' + config.key;
  }

  let props;
  if (!('key' in config)) {
    // If key was not spread in, we can reuse the original props object. This
    // only works for `jsx`, not `createElement`, because `jsx` is a compiler
    // target and the compiler always passes a new object. For `createElement`,
    // we can't assume a new object is passed every time because it can be
    // called manually.
    //
    // Spreading key is a warning in dev. In a future release, we will not
    // remove a spread key from the props object. (But we'll still warn.) We'll
    // always pass the object straight through.
    props = config;
  } else {
    // We need to remove reserved props (key, prop, ref). Create a fresh props
    // object and copy over all the non-reserved props. We don't use `delete`
    // because in V8 it will deopt the object to dictionary mode.
    props = {};
    for (const propName in config) {
      // Skip over reserved prop names
      if (propName !== 'key') {
        props[propName] = config[propName];
      }
    }
  }

  if (!disableDefaultPropsExceptForClasses) {
    // Resolve default props
    if (type && type.defaultProps) {
      const defaultProps = type.defaultProps;
      for (const propName in defaultProps) {
        if (props[propName] === undefined) {
          props[propName] = defaultProps[propName];
        }
      }
    }
  }

  return ReactElement(
    type,
    key,
    undefined,
    undefined,
    getOwner(),
    props,
    undefined,
    undefined,
  );
}

// While `jsxDEV` should never be called when running in production, we do
// support `jsx` and `jsxs` when running in development. This supports the case
// where a third-party dependency ships code that was compiled for production;
// we want to still provide warnings in development.
//
// So these functions are the _dev_ implementations of the _production_
// API signatures.
//
// Since these functions are dev-only, it's ok to add an indirection here. They
// only exist to provide different versions of `isStaticChildren`. (We shouldn't
// use this pattern for the prod versions, though, because it will add an call
// frame.)
export function jsxProdSignatureRunningInDevWithDynamicChildren(
  type,
  config,
  maybeKey,
  source,
  self,
) {
  if (__DEV__) {
    const isStaticChildren = false;
    const trackActualOwner =
      __DEV__ &&
      ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit;
    return jsxDEVImpl(
      type,
      config,
      maybeKey,
      isStaticChildren,
      source,
      self,
      __DEV__ &&
        (trackActualOwner
          ? Error('react-stack-top-frame')
          : unknownOwnerDebugStack),
      __DEV__ &&
        (trackActualOwner
          ? createTask(getTaskName(type))
          : unknownOwnerDebugTask),
    );
  }
}

export function jsxProdSignatureRunningInDevWithStaticChildren(
  type,
  config,
  maybeKey,
  source,
  self,
) {
  if (__DEV__) {
    const isStaticChildren = true;
    const trackActualOwner =
      __DEV__ &&
      ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit;
    return jsxDEVImpl(
      type,
      config,
      maybeKey,
      isStaticChildren,
      source,
      self,
      __DEV__ &&
        (trackActualOwner
          ? Error('react-stack-top-frame')
          : unknownOwnerDebugStack),
      __DEV__ &&
        (trackActualOwner
          ? createTask(getTaskName(type))
          : unknownOwnerDebugTask),
    );
  }
}

const didWarnAboutKeySpread = {};

/**
 * https://github.com/reactjs/rfcs/pull/107
 * @param {*} type
 * @param {object} props
 * @param {string} key
 */
export function jsxDEV(type, config, maybeKey, isStaticChildren, source, self) {
  const trackActualOwner =
    __DEV__ &&
    ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit;
  return jsxDEVImpl(
    type,
    config,
    maybeKey,
    isStaticChildren,
    source,
    self,
    __DEV__ &&
      (trackActualOwner
        ? Error('react-stack-top-frame')
        : unknownOwnerDebugStack),
    __DEV__ &&
      (trackActualOwner
        ? createTask(getTaskName(type))
        : unknownOwnerDebugTask),
  );
}

function jsxDEVImpl(
  type,
  config,
  maybeKey,
  isStaticChildren,
  source,
  self,
  debugStack,
  debugTask,
) {
  if (__DEV__) {
    // We don't warn for invalid element type here because with owner stacks,
    // we error in the renderer. The renderer is the only one that knows what
    // types are valid for this particular renderer so we let it error there.

    // Skip key warning if the type isn't valid since our key validation logic
    // doesn't expect a non-string/function type and can throw confusing
    // errors. We don't want exception behavior to differ between dev and
    // prod. (Rendering will throw with a helpful message and as soon as the
    // type is fixed, the key warnings will appear.)
    // With owner stacks, we no longer need the type here so this comment is
    // no longer true. Which is why we can run this even for invalid types.
    const children = config.children;
    if (children !== undefined) {
      if (isStaticChildren) {
        if (isArray(children)) {
          for (let i = 0; i < children.length; i++) {
            validateChildKeys(children[i], type);
          }

          if (Object.freeze) {
            Object.freeze(children);
          }
        } else {
          console.error(
            'React.jsx: Static children should always be an array. ' +
              'You are likely explicitly calling React.jsxs or React.jsxDEV. ' +
              'Use the Babel transform instead.',
          );
        }
      } else {
        validateChildKeys(children, type);
      }
    }

    // Warn about key spread regardless of whether the type is valid.
    if (hasOwnProperty.call(config, 'key')) {
      const componentName = getComponentNameFromType(type);
      const keys = Object.keys(config).filter(k => k !== 'key');
      const beforeExample =
        keys.length > 0
          ? '{key: someKey, ' + keys.join(': ..., ') + ': ...}'
          : '{key: someKey}';
      if (!didWarnAboutKeySpread[componentName + beforeExample]) {
        const afterExample =
          keys.length > 0 ? '{' + keys.join(': ..., ') + ': ...}' : '{}';
        console.error(
          'A props object containing a "key" prop is being spread into JSX:\n' +
            '  let props = %s;\n' +
            '  <%s {...props} />\n' +
            'React keys must be passed directly to JSX without using spread:\n' +
            '  let props = %s;\n' +
            '  <%s key={someKey} {...props} />',
          beforeExample,
          componentName,
          afterExample,
          componentName,
        );
        didWarnAboutKeySpread[componentName + beforeExample] = true;
      }
    }

    let key = null;

    // Currently, key can be spread in as a prop. This causes a potential
    // issue if key is also explicitly declared (ie. <div {...props} key="Hi" />
    // or <div key="Hi" {...props} /> ). We want to deprecate key spread,
    // but as an intermediary step, we will use jsxDEV for everything except
    // <div {...props} key="Hi" />, because we aren't currently able to tell if
    // key is explicitly declared to be undefined or not.
    if (maybeKey !== undefined) {
      if (__DEV__) {
        checkKeyStringCoercion(maybeKey);
      }
      key = '' + maybeKey;
    }

    if (hasValidKey(config)) {
      if (__DEV__) {
        checkKeyStringCoercion(config.key);
      }
      key = '' + config.key;
    }

    let props;
    if (!('key' in config)) {
      // If key was not spread in, we can reuse the original props object. This
      // only works for `jsx`, not `createElement`, because `jsx` is a compiler
      // target and the compiler always passes a new object. For `createElement`,
      // we can't assume a new object is passed every time because it can be
      // called manually.
      //
      // Spreading key is a warning in dev. In a future release, we will not
      // remove a spread key from the props object. (But we'll still warn.) We'll
      // always pass the object straight through.
      props = config;
    } else {
      // We need to remove reserved props (key, prop, ref). Create a fresh props
      // object and copy over all the non-reserved props. We don't use `delete`
      // because in V8 it will deopt the object to dictionary mode.
      props = {};
      for (const propName in config) {
        // Skip over reserved prop names
        if (propName !== 'key') {
          props[propName] = config[propName];
        }
      }
    }

    if (!disableDefaultPropsExceptForClasses) {
      // Resolve default props
      if (type && type.defaultProps) {
        const defaultProps = type.defaultProps;
        for (const propName in defaultProps) {
          if (props[propName] === undefined) {
            props[propName] = defaultProps[propName];
          }
        }
      }
    }

    if (key) {
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;
      defineKeyPropWarningGetter(props, displayName);
    }

    return ReactElement(
      type,
      key,
      self,
      source,
      getOwner(),
      props,
      debugStack,
      debugTask,
    );
  }
}

/**
 * Create and return a new ReactElement of the given type.
 * See https://reactjs.org/docs/react-api.html#createelement
 */
export function createElement(type, config, children) {
  if (__DEV__) {
    // We don't warn for invalid element type here because with owner stacks,
    // we error in the renderer. The renderer is the only one that knows what
    // types are valid for this particular renderer so we let it error there.

    // Skip key warning if the type isn't valid since our key validation logic
    // doesn't expect a non-string/function type and can throw confusing
    // errors. We don't want exception behavior to differ between dev and
    // prod. (Rendering will throw with a helpful message and as soon as the
    // type is fixed, the key warnings will appear.)
    for (let i = 2; i < arguments.length; i++) {
      validateChildKeys(arguments[i], type);
    }

    // Unlike the jsx() runtime, createElement() doesn't warn about key spread.
  }

  let propName;

  // Reserved names are extracted
  const props = {};

  let key = null;

  if (config != null) {
    if (__DEV__) {
      if (
        !didWarnAboutOldJSXRuntime &&
        '__self' in config &&
        // Do not assume this is the result of an oudated JSX transform if key
        // is present, because the modern JSX transform sometimes outputs
        // createElement to preserve precedence between a static key and a
        // spread key. To avoid false positive warnings, we never warn if
        // there's a key.
        !('key' in config)
      ) {
        didWarnAboutOldJSXRuntime = true;
        console.warn(
          'Your app (or one of its dependencies) is using an outdated JSX ' +
            'transform. Update to the modern JSX transform for ' +
            'faster performance: https://react.dev/link/new-jsx-transform',
        );
      }
    }

    if (hasValidKey(config)) {
      if (__DEV__) {
        checkKeyStringCoercion(config.key);
      }
      key = '' + config.key;
    }

    // Remaining properties are added to a new props object
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        // Skip over reserved prop names
        propName !== 'key' &&
        // Even though we don't use these anymore in the runtime, we don't want
        // them to appear as props, so in createElement we filter them out.
        // We don't have to do this in the jsx() runtime because the jsx()
        // transform never passed these as props; it used separate arguments.
        propName !== '__self' &&
        propName !== '__source'
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    if (__DEV__) {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }

  // Resolve default props
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  if (__DEV__) {
    if (key) {
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;
      defineKeyPropWarningGetter(props, displayName);
    }
  }
  const trackActualOwner =
    __DEV__ &&
    ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit;
  return ReactElement(
    type,
    key,
    undefined,
    undefined,
    getOwner(),
    props,
    __DEV__ &&
      (trackActualOwner
        ? Error('react-stack-top-frame')
        : unknownOwnerDebugStack),
    __DEV__ &&
      (trackActualOwner
        ? createTask(getTaskName(type))
        : unknownOwnerDebugTask),
  );
}

export function cloneAndReplaceKey(oldElement, newKey) {
  const clonedElement = ReactElement(
    oldElement.type,
    newKey,
    undefined,
    undefined,
    !__DEV__ ? undefined : oldElement._owner,
    oldElement.props,
    __DEV__ && oldElement._debugStack,
    __DEV__ && oldElement._debugTask,
  );
  if (__DEV__) {
    // The cloned element should inherit the original element's key validation.
    if (oldElement._store) {
      clonedElement._store.validated = oldElement._store.validated;
    }
  }
  return clonedElement;
}

/**
 * Clone and return a new ReactElement using element as the starting point.
 * See https://reactjs.org/docs/react-api.html#cloneelement
 */
export function cloneElement(element, config, children) {
  if (element === null || element === undefined) {
    throw new Error(
      `The argument must be a React element, but you passed ${element}.`,
    );
  }

  let propName;

  // Original props are copied
  const props = assign({}, element.props);

  // Reserved names are extracted
  let key = element.key;

  // Owner will be preserved, unless ref is overridden
  let owner = !__DEV__ ? undefined : element._owner;

  if (config != null) {
    if (hasValidRef(config)) {
      owner = __DEV__ ? getOwner() : undefined;
    }
    if (hasValidKey(config)) {
      if (__DEV__) {
        checkKeyStringCoercion(config.key);
      }
      key = '' + config.key;
    }

    // Remaining properties override existing props
    let defaultProps;
    if (
      !disableDefaultPropsExceptForClasses &&
      element.type &&
      element.type.defaultProps
    ) {
      defaultProps = element.type.defaultProps;
    }
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        // Skip over reserved prop names
        propName !== 'key' &&
        // ...and maybe these, too, though we currently rely on them for
        // warnings and debug information in dev. Need to decide if we're OK
        // with dropping them. In the jsx() runtime it's not an issue because
        // the data gets passed as separate arguments instead of props, but
        // it would be nice to stop relying on them entirely so we can drop
        // them from the internal Fiber field.
        propName !== '__self' &&
        propName !== '__source' &&
        // Undefined `ref` is ignored by cloneElement. We treat it the same as
        // if the property were missing. This is mostly for
        // backwards compatibility.
        !(propName === 'ref' && config.ref === undefined)
      ) {
        if (
          !disableDefaultPropsExceptForClasses &&
          config[propName] === undefined &&
          defaultProps !== undefined
        ) {
          // Resolve default props
          props[propName] = defaultProps[propName];
        } else {
          props[propName] = config[propName];
        }
      }
    }
  }

  // Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }

  const clonedElement = ReactElement(
    element.type,
    key,
    undefined,
    undefined,
    owner,
    props,
    __DEV__ && element._debugStack,
    __DEV__ && element._debugTask,
  );

  for (let i = 2; i < arguments.length; i++) {
    validateChildKeys(arguments[i], clonedElement.type);
  }

  return clonedElement;
}

/**
 * Ensure that every element either is passed in a static location, in an
 * array with an explicit keys property defined, or in an object literal
 * with valid key property.
 *
 * @internal
 * @param {ReactNode} node Statically passed child of any type.
 * @param {*} parentType node's parent's type.
 */
function validateChildKeys(node, parentType) {
  if (__DEV__) {
    // With owner stacks is, no warnings happens. All we do is
    // mark elements as being in a valid static child position so they
    // don't need keys.
    if (isValidElement(node)) {
      if (node._store) {
        node._store.validated = 1;
      }
    }
  }
}

/**
 * Verifies the object is a ReactElement.
 * See https://reactjs.org/docs/react-api.html#isvalidelement
 * @param {?object} object
 * @return {boolean} True if `object` is a ReactElement.
 * @final
 */
export function isValidElement(object) {
  return (
    typeof object === 'object' &&
    object !== null &&
    object.$$typeof === REACT_ELEMENT_TYPE
  );
}

这段代码定义了 React 元素(React Element)的创建、克隆和验证相关的函数,以及一些开发模式下的警告和调试功能。以下是对这段代码的详细分析:

1. 导入模块和常量:

  • getComponentNameFromType: 用于从组件类型中获取组件名称。
  • ReactSharedInternals: 包含 React 内部共享的变量和方法。
  • hasOwnProperty: 用于检查对象是否具有指定的属性。
  • assign: 用于浅拷贝对象。
  • REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE, REACT_LAZY_TYPE: React 内部使用的 Symbol,用于标识元素类型。
  • checkKeyStringCoercion: 用于检查 key 属性是否被强制转换为字符串。
  • isArray: 用于检查变量是否为数组。
  • disableDefaultPropsExceptForClasses, ownerStackLimit: React 特性标志。

2. 辅助函数:

  • createTask: 用于在开发模式下创建控制台任务。
  • getTaskName: 用于从组件类型中获取任务名称。
  • getOwner: 用于在开发模式下获取组件的所有者。
  • UnknownOwner: 一个空函数,用于生成错误堆栈。
  • createFakeCallStack: 一个包含 UnknownOwner 的对象。
  • hasValidRef, hasValidKey: 用于检查配置对象是否具有有效的 refkey 属性。
  • defineKeyPropWarningGetter: 用于在开发模式下定义 key 属性的警告 getter。
  • elementRefGetterWithDeprecationWarning: 在开发模式下,当访问element.ref时,触发一个弃用警告。

3. ReactElement 函数:

  • 用于创建 React 元素。
  • 接收 typekeyrefprops 等参数。
  • 在开发模式下,添加 _store_debugInfo_debugStack_debugTask 属性,用于调试和验证。
  • 返回一个 React 元素对象。

4. jsxProdjsxDEV 函数:

  • jsxProd: 用于在生产模式下创建 React 元素。
  • jsxDEV: 用于在开发模式下创建 React 元素,并添加调试信息。
  • 这两个函数都处理了 key 属性的传递,并且在开发模式下,对于key属性的传递,进行警告。
  • jsxProdSignatureRunningInDevWithDynamicChildrenjsxProdSignatureRunningInDevWithStaticChildren这两个方法,是为了兼容在开发模式下,使用生产模式打包的第三方库,而添加的。

5. createElement 函数:

  • 用于创建 React 元素,类似于 jsxProdjsxDEV
  • 处理 typeconfigchildren 参数。
  • 在开发模式下,验证子元素的 key 属性。

6. cloneAndReplaceKey 函数:

  • 用于克隆 React 元素并替换其 key 属性。

7. cloneElement 函数:

  • 用于克隆 React 元素并合并新的配置。
  • 处理 elementconfigchildren 参数。
  • 在开发模式下,验证子元素的 key 属性。

8. validateChildKeys 函数:

  • 用于验证子元素的 key 属性。
  • 在开发模式下,标记元素为已验证。

9. isValidElement 函数:

  • 用于验证对象是否为有效的 React 元素。

主要功能和目的:

  • 创建和克隆 React 元素。
  • 验证 React 元素的属性。
  • 在开发模式下,提供警告和调试功能。
  • 处理 key 属性的传递和验证。
  • 兼容不同的 JSX 转换模式。

这段代码是 React 元素创建和操作的核心部分,它确保了 React 元素的正确性和一致性,并提供了开发模式下的调试支持。

react-reconciler

react-reconciler 是 React 核心算法的实现,负责将 React 元素树转换为底层平台(如 DOM、Native)的渲染指令。它在 React 的架构中扮演着协调器的角色,负责比较新旧虚拟 DOM 树,计算出需要更新的部分,然后将这些更新应用到底层平台。

以下是 react-reconciler 的一些关键方面:

1. 协调(Reconciliation)算法:

  • react-reconciler 实现了 React 的协调算法,用于比较新旧虚拟 DOM 树,找出差异。
  • 协调算法的目标是尽可能高效地更新 UI,只更新需要更改的部分,以提高性能。
  • React 16 引入了 Fiber 架构,对协调算法进行了重大改进,使其具有更好的性能和响应能力。

2. Fiber 架构:

  • Fiber 是一种新的协调算法,它将渲染过程分解为可中断的小任务。
  • 这使得 React 可以在高优先级任务(如用户交互)需要时暂停渲染,并在空闲时间恢复渲染。
  • Fiber 架构提高了 React 应用的响应能力,特别是在处理复杂 UI 和动画时。

3. 平台无关性:

  • react-reconciler 是平台无关的,这意味着它可以用于不同的渲染目标,如 DOM、Native 和 Canvas。
  • 它提供了一组通用的 API,用于与底层平台进行交互。
  • React DOM 和 React Native 都使用 react-reconciler 来实现其渲染逻辑。

4. 自定义渲染器:

  • react-reconciler 允许开发者创建自定义渲染器,以支持新的渲染目标。
  • 这使得 React 能够应用于各种不同的场景,如虚拟现实、3D 渲染和终端界面。

5. 核心功能:

  • 虚拟 DOM 的比较和更新。
  • 组件的挂载、更新和卸载。
  • 事件处理。
  • 生命周期方法的调用。
  • Refs 的管理。
  • Hooks的实现。

总结:

react-reconciler 是 React 的核心组件之一,它实现了 React 的协调算法和 Fiber 架构,负责将 React 元素树转换为底层平台的渲染指令。它的平台无关性和可扩展性使得 React 能够应用于各种不同的场景。

ReactFiberTreeReflection

ReactFiberTreeReflection 是 React 协调器(reconciler)内部的一个模块,主要用于在 Fiber 树上进行导航和信息检索。它在开发工具(React DevTools)和错误边界(Error Boundaries)等场景中发挥着重要作用。

以下是对 ReactFiberTreeReflection 的详细解释:

1. Fiber 树导航:

  • ReactFiberTreeReflection 提供了一系列函数,用于在 Fiber 树上进行导航,例如:
    • getParent(fiber):获取给定 Fiber 节点的父 Fiber 节点。
    • getFirstChild(fiber):获取给定 Fiber 节点的第一个子 Fiber 节点。
    • getSibling(fiber):获取给定 Fiber 节点的下一个兄弟 Fiber 节点。
    • getHostParent(fiber):获取给定 Fiber 节点最近的宿主父 Fiber 节点(例如,DOM 节点)。
  • 这些函数允许 React DevTools 和错误边界等模块遍历 Fiber 树,以查找特定的 Fiber 节点或收集有关组件层次结构的信息。

2. 组件信息检索:

  • ReactFiberTreeReflection 还提供了一些函数,用于检索有关 Fiber 节点及其关联组件的信息,例如:
    • getComponentName(fiber):获取给定 Fiber 节点关联组件的名称。
    • getComponentStack(fiber):获取给定 Fiber 节点的组件堆栈。
    • getFiberType(fiber):获取给定 Fiber 节点的类型(例如,类组件、函数组件、宿主组件)。
  • 这些函数允许 React DevTools 显示组件层次结构和属性,以及错误边界捕获组件堆栈以进行调试。

3. 开发工具集成:

  • React DevTools 使用 ReactFiberTreeReflection 来显示 React 组件树、属性和状态。
  • 它允许开发者检查组件层次结构、属性和状态,以及调试 React 应用。

4. 错误边界支持:

  • 错误边界使用 ReactFiberTreeReflection 来捕获组件堆栈和组件名称。
  • 这有助于开发者在发生错误时调试 React 应用。

5. 内部模块:

  • ReactFiberTreeReflection 是 React 协调器的内部模块,不应在用户代码中使用。
  • 它的 API 可能会在未来的 React 版本中更改或删除。

总结:

ReactFiberTreeReflection 是 React 协调器内部的一个模块,用于在 Fiber 树上进行导航和信息检索。它在开发工具和错误边界等场景中发挥着重要作用,允许开发者检查组件层次结构、属性和状态,以及调试 React 应用。

ReactFiberReconciler.js

  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
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
import type {
  Fiber,
  FiberRoot,
  SuspenseHydrationCallbacks,
  TransitionTracingCallbacks,
} from './ReactInternalTypes';
import type {RootTag} from './ReactRootTags';
import type {
  Container,
  PublicInstance,
  RendererInspectionConfig,
} from './ReactFiberConfig';
import type {ReactNodeList, ReactFormState} from 'shared/ReactTypes';
import type {Lane} from './ReactFiberLane';
import type {SuspenseState} from './ReactFiberSuspenseComponent';

import {LegacyRoot} from './ReactRootTags';
import {
  findCurrentHostFiber,
  findCurrentHostFiberWithNoPortals,
} from './ReactFiberTreeReflection';
import {get as getInstance} from 'shared/ReactInstanceMap';
import {
  HostComponent,
  HostSingleton,
  ClassComponent,
  HostRoot,
  SuspenseComponent,
} from './ReactWorkTags';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import isArray from 'shared/isArray';
import {
  enableSchedulingProfiler,
  enableHydrationLaneScheduling,
  disableLegacyMode,
} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
  getPublicInstance,
  rendererVersion,
  rendererPackageName,
  extraDevToolsConfig,
} from './ReactFiberConfig';
import {
  findCurrentUnmaskedContext,
  processChildContext,
  emptyContextObject,
  isContextProvider as isLegacyContextProvider,
} from './ReactFiberContext';
import {createFiberRoot} from './ReactFiberRoot';
import {isRootDehydrated} from './ReactFiberShellHydration';
import {
  injectInternals,
  markRenderScheduled,
  onScheduleRoot,
  injectProfilingHooks,
} from './ReactFiberDevToolsHook';
import {startUpdateTimerByLane} from './ReactProfilerTimer';
import {
  requestUpdateLane,
  scheduleUpdateOnFiber,
  scheduleInitialHydrationOnRoot,
  flushRoot,
  batchedUpdates,
  flushSyncFromReconciler,
  flushSyncWork,
  isAlreadyRendering,
  deferredUpdates,
  discreteUpdates,
  flushPendingEffects,
} from './ReactFiberWorkLoop';
import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates';
import {
  createUpdate,
  enqueueUpdate,
  entangleTransitions,
} from './ReactFiberClassUpdateQueue';
import {
  isRendering as ReactCurrentFiberIsRendering,
  current as ReactCurrentFiberCurrent,
  runWithFiberInDEV,
} from './ReactCurrentFiber';
import {StrictLegacyMode} from './ReactTypeOfMode';
import {
  SyncLane,
  SelectiveHydrationLane,
  getHighestPriorityPendingLanes,
  higherPriorityLane,
  getBumpedLaneForHydrationByLane,
} from './ReactFiberLane';
import {
  scheduleRefresh,
  scheduleRoot,
  setRefreshHandler,
} from './ReactFiberHotReloading';
import ReactVersion from 'shared/ReactVersion';
export {createPortal} from './ReactPortal';
export {
  createComponentSelector,
  createHasPseudoClassSelector,
  createRoleSelector,
  createTestNameSelector,
  createTextSelector,
  getFindAllNodesFailureDescription,
  findAllNodes,
  findBoundingRects,
  focusWithin,
  observeVisibleRects,
} from './ReactTestSelectors';
export {startHostTransition} from './ReactFiberHooks';
export {
  defaultOnUncaughtError,
  defaultOnCaughtError,
  defaultOnRecoverableError,
} from './ReactFiberErrorLogger';
import {getLabelForLane, TotalLanes} from 'react-reconciler/src/ReactFiberLane';

type OpaqueRoot = FiberRoot;

let didWarnAboutNestedUpdates;
let didWarnAboutFindNodeInStrictMode;

if (__DEV__) {
  didWarnAboutNestedUpdates = false;
  didWarnAboutFindNodeInStrictMode = ({}: {[string]: boolean});
}

function getContextForSubtree(
  parentComponent: ?React$Component<any, any>,
): Object {
  if (!parentComponent) {
    return emptyContextObject;
  }

  const fiber = getInstance(parentComponent);
  const parentContext = findCurrentUnmaskedContext(fiber);

  if (fiber.tag === ClassComponent) {
    const Component = fiber.type;
    if (isLegacyContextProvider(Component)) {
      return processChildContext(fiber, Component, parentContext);
    }
  }

  return parentContext;
}

function findHostInstance(component: Object): PublicInstance | null {
  const fiber = getInstance(component);
  if (fiber === undefined) {
    if (typeof component.render === 'function') {
      throw new Error('Unable to find node on an unmounted component.');
    } else {
      const keys = Object.keys(component).join(',');
      throw new Error(
        `Argument appears to not be a ReactComponent. Keys: ${keys}`,
      );
    }
  }
  const hostFiber = findCurrentHostFiber(fiber);
  if (hostFiber === null) {
    return null;
  }
  return getPublicInstance(hostFiber.stateNode);
}

function findHostInstanceWithWarning(
  component: Object,
  methodName: string,
): PublicInstance | null {
  if (__DEV__) {
    const fiber = getInstance(component);
    if (fiber === undefined) {
      if (typeof component.render === 'function') {
        throw new Error('Unable to find node on an unmounted component.');
      } else {
        const keys = Object.keys(component).join(',');
        throw new Error(
          `Argument appears to not be a ReactComponent. Keys: ${keys}`,
        );
      }
    }
    const hostFiber = findCurrentHostFiber(fiber);
    if (hostFiber === null) {
      return null;
    }
    if (hostFiber.mode & StrictLegacyMode) {
      const componentName = getComponentNameFromFiber(fiber) || 'Component';
      if (!didWarnAboutFindNodeInStrictMode[componentName]) {
        didWarnAboutFindNodeInStrictMode[componentName] = true;
        runWithFiberInDEV(hostFiber, () => {
          if (fiber.mode & StrictLegacyMode) {
            console.error(
              '%s is deprecated in StrictMode. ' +
                '%s was passed an instance of %s which is inside StrictMode. ' +
                'Instead, add a ref directly to the element you want to reference. ' +
                'Learn more about using refs safely here: ' +
                'https://react.dev/link/strict-mode-find-node',
              methodName,
              methodName,
              componentName,
            );
          } else {
            console.error(
              '%s is deprecated in StrictMode. ' +
                '%s was passed an instance of %s which renders StrictMode children. ' +
                'Instead, add a ref directly to the element you want to reference. ' +
                'Learn more about using refs safely here: ' +
                'https://react.dev/link/strict-mode-find-node',
              methodName,
              methodName,
              componentName,
            );
          }
        });
      }
    }
    return getPublicInstance(hostFiber.stateNode);
  }
  return findHostInstance(component);
}

export function createContainer(
  containerInfo: Container,
  tag: RootTag,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  isStrictMode: boolean,
  // TODO: Remove `concurrentUpdatesByDefaultOverride`. It is now ignored.
  concurrentUpdatesByDefaultOverride: null | boolean,
  identifierPrefix: string,
  onUncaughtError: (
    error: mixed,
    errorInfo: {+componentStack?: ?string},
  ) => void,
  onCaughtError: (
    error: mixed,
    errorInfo: {
      +componentStack?: ?string,
      +errorBoundary?: ?React$Component<any, any>,
    },
  ) => void,
  onRecoverableError: (
    error: mixed,
    errorInfo: {+componentStack?: ?string},
  ) => void,
  transitionCallbacks: null | TransitionTracingCallbacks,
): OpaqueRoot {
  const hydrate = false;
  const initialChildren = null;
  return createFiberRoot(
    containerInfo,
    tag,
    hydrate,
    initialChildren,
    hydrationCallbacks,
    isStrictMode,
    identifierPrefix,
    onUncaughtError,
    onCaughtError,
    onRecoverableError,
    transitionCallbacks,
    null,
  );
}

export function createHydrationContainer(
  initialChildren: ReactNodeList,
  // TODO: Remove `callback` when we delete legacy mode.
  callback: ?Function,
  containerInfo: Container,
  tag: RootTag,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  isStrictMode: boolean,
  // TODO: Remove `concurrentUpdatesByDefaultOverride`. It is now ignored.
  concurrentUpdatesByDefaultOverride: null | boolean,
  identifierPrefix: string,
  onUncaughtError: (
    error: mixed,
    errorInfo: {+componentStack?: ?string},
  ) => void,
  onCaughtError: (
    error: mixed,
    errorInfo: {
      +componentStack?: ?string,
      +errorBoundary?: ?React$Component<any, any>,
    },
  ) => void,
  onRecoverableError: (
    error: mixed,
    errorInfo: {+componentStack?: ?string},
  ) => void,
  transitionCallbacks: null | TransitionTracingCallbacks,
  formState: ReactFormState<any, any> | null,
): OpaqueRoot {
  const hydrate = true;
  const root = createFiberRoot(
    containerInfo,
    tag,
    hydrate,
    initialChildren,
    hydrationCallbacks,
    isStrictMode,
    identifierPrefix,
    onUncaughtError,
    onCaughtError,
    onRecoverableError,
    transitionCallbacks,
    formState,
  );

  // TODO: Move this to FiberRoot constructor
  root.context = getContextForSubtree(null);

  // Schedule the initial render. In a hydration root, this is different from
  // a regular update because the initial render must match was was rendered
  // on the server.
  // NOTE: This update intentionally doesn't have a payload. We're only using
  // the update to schedule work on the root fiber (and, for legacy roots, to
  // enqueue the callback if one is provided).
  const current = root.current;
  let lane = requestUpdateLane(current);
  if (enableHydrationLaneScheduling) {
    lane = getBumpedLaneForHydrationByLane(lane);
  }
  const update = createUpdate(lane);
  update.callback =
    callback !== undefined && callback !== null ? callback : null;
  enqueueUpdate(current, update, lane);
  scheduleInitialHydrationOnRoot(root, lane);

  return root;
}

export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
  const current = container.current;
  const lane = requestUpdateLane(current);
  updateContainerImpl(
    current,
    lane,
    element,
    container,
    parentComponent,
    callback,
  );
  return lane;
}

export function updateContainerSync(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
  if (!disableLegacyMode && container.tag === LegacyRoot) {
    flushPendingEffects();
  }
  const current = container.current;
  updateContainerImpl(
    current,
    SyncLane,
    element,
    container,
    parentComponent,
    callback,
  );
  return SyncLane;
}

function updateContainerImpl(
  rootFiber: Fiber,
  lane: Lane,
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): void {
  if (__DEV__) {
    onScheduleRoot(container, element);
  }

  if (enableSchedulingProfiler) {
    markRenderScheduled(lane);
  }

  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }

  if (__DEV__) {
    if (
      ReactCurrentFiberIsRendering &&
      ReactCurrentFiberCurrent !== null &&
      !didWarnAboutNestedUpdates
    ) {
      didWarnAboutNestedUpdates = true;
      console.error(
        'Render methods should be a pure function of props and state; ' +
          'triggering nested component updates from render is not allowed. ' +
          'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
          'Check the render method of %s.',
        getComponentNameFromFiber(ReactCurrentFiberCurrent) || 'Unknown',
      );
    }
  }

  const update = createUpdate(lane);
  // Caution: React DevTools currently depends on this property
  // being called "element".
  update.payload = {element};

  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    if (__DEV__) {
      if (typeof callback !== 'function') {
        console.error(
          'Expected the last optional `callback` argument to be a ' +
            'function. Instead received: %s.',
          callback,
        );
      }
    }
    update.callback = callback;
  }

  const root = enqueueUpdate(rootFiber, update, lane);
  if (root !== null) {
    startUpdateTimerByLane(lane);
    scheduleUpdateOnFiber(root, rootFiber, lane);
    entangleTransitions(root, rootFiber, lane);
  }
}

export {
  batchedUpdates,
  deferredUpdates,
  discreteUpdates,
  flushSyncFromReconciler,
  flushSyncWork,
  isAlreadyRendering,
  flushPendingEffects as flushPassiveEffects,
};

export function getPublicRootInstance(
  container: OpaqueRoot,
): React$Component<any, any> | PublicInstance | null {
  const containerFiber = container.current;
  if (!containerFiber.child) {
    return null;
  }
  switch (containerFiber.child.tag) {
    case HostSingleton:
    case HostComponent:
      return getPublicInstance(containerFiber.child.stateNode);
    default:
      return containerFiber.child.stateNode;
  }
}

export function attemptSynchronousHydration(fiber: Fiber): void {
  switch (fiber.tag) {
    case HostRoot: {
      const root: FiberRoot = fiber.stateNode;
      if (isRootDehydrated(root)) {
        // Flush the first scheduled "update".
        const lanes = getHighestPriorityPendingLanes(root);
        flushRoot(root, lanes);
      }
      break;
    }
    case SuspenseComponent: {
      const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
      if (root !== null) {
        scheduleUpdateOnFiber(root, fiber, SyncLane);
      }
      flushSyncWork();
      // If we're still blocked after this, we need to increase
      // the priority of any promises resolving within this
      // boundary so that they next attempt also has higher pri.
      const retryLane = SyncLane;
      markRetryLaneIfNotHydrated(fiber, retryLane);
      break;
    }
  }
}

function markRetryLaneImpl(fiber: Fiber, retryLane: Lane) {
  const suspenseState: null | SuspenseState = fiber.memoizedState;
  if (suspenseState !== null && suspenseState.dehydrated !== null) {
    suspenseState.retryLane = higherPriorityLane(
      suspenseState.retryLane,
      retryLane,
    );
  }
}

// Increases the priority of thenables when they resolve within this boundary.
function markRetryLaneIfNotHydrated(fiber: Fiber, retryLane: Lane) {
  markRetryLaneImpl(fiber, retryLane);
  const alternate = fiber.alternate;
  if (alternate) {
    markRetryLaneImpl(alternate, retryLane);
  }
}

export function attemptContinuousHydration(fiber: Fiber): void {
  if (fiber.tag !== SuspenseComponent) {
    // We ignore HostRoots here because we can't increase
    // their priority and they should not suspend on I/O,
    // since you have to wrap anything that might suspend in
    // Suspense.
    return;
  }
  const lane = SelectiveHydrationLane;
  const root = enqueueConcurrentRenderForLane(fiber, lane);
  if (root !== null) {
    scheduleUpdateOnFiber(root, fiber, lane);
  }
  markRetryLaneIfNotHydrated(fiber, lane);
}

export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
  if (fiber.tag !== SuspenseComponent) {
    // We ignore HostRoots here because we can't increase
    // their priority other than synchronously flush it.
    return;
  }
  let lane = requestUpdateLane(fiber);
  if (enableHydrationLaneScheduling) {
    lane = getBumpedLaneForHydrationByLane(lane);
  }
  const root = enqueueConcurrentRenderForLane(fiber, lane);
  if (root !== null) {
    scheduleUpdateOnFiber(root, fiber, lane);
  }
  markRetryLaneIfNotHydrated(fiber, lane);
}

export {findHostInstance};

export {findHostInstanceWithWarning};

export function findHostInstanceWithNoPortals(
  fiber: Fiber,
): PublicInstance | null {
  const hostFiber = findCurrentHostFiberWithNoPortals(fiber);
  if (hostFiber === null) {
    return null;
  }
  return getPublicInstance(hostFiber.stateNode);
}

let shouldErrorImpl: Fiber => ?boolean = fiber => null;

export function shouldError(fiber: Fiber): ?boolean {
  return shouldErrorImpl(fiber);
}

let shouldSuspendImpl = (fiber: Fiber) => false;

export function shouldSuspend(fiber: Fiber): boolean {
  return shouldSuspendImpl(fiber);
}

let overrideHookState = null;
let overrideHookStateDeletePath = null;
let overrideHookStateRenamePath = null;
let overrideProps = null;
let overridePropsDeletePath = null;
let overridePropsRenamePath = null;
let scheduleUpdate = null;
let setErrorHandler = null;
let setSuspenseHandler = null;

if (__DEV__) {
  const copyWithDeleteImpl = (
    obj: Object | Array<any>,
    path: Array<string | number>,
    index: number,
  ): $FlowFixMe => {
    const key = path[index];
    const updated = isArray(obj) ? obj.slice() : {...obj};
    if (index + 1 === path.length) {
      if (isArray(updated)) {
        updated.splice(((key: any): number), 1);
      } else {
        delete updated[key];
      }
      return updated;
    }
    // $FlowFixMe[incompatible-use] number or string is fine here
    updated[key] = copyWithDeleteImpl(obj[key], path, index + 1);
    return updated;
  };

  const copyWithDelete = (
    obj: Object | Array<any>,
    path: Array<string | number>,
  ): Object | Array<any> => {
    return copyWithDeleteImpl(obj, path, 0);
  };

  const copyWithRenameImpl = (
    obj: Object | Array<any>,
    oldPath: Array<string | number>,
    newPath: Array<string | number>,
    index: number,
  ): $FlowFixMe => {
    const oldKey = oldPath[index];
    const updated = isArray(obj) ? obj.slice() : {...obj};
    if (index + 1 === oldPath.length) {
      const newKey = newPath[index];
      // $FlowFixMe[incompatible-use] number or string is fine here
      updated[newKey] = updated[oldKey];
      if (isArray(updated)) {
        updated.splice(((oldKey: any): number), 1);
      } else {
        delete updated[oldKey];
      }
    } else {
      // $FlowFixMe[incompatible-use] number or string is fine here
      updated[oldKey] = copyWithRenameImpl(
        // $FlowFixMe[incompatible-use] number or string is fine here
        obj[oldKey],
        oldPath,
        newPath,
        index + 1,
      );
    }
    return updated;
  };

  const copyWithRename = (
    obj: Object | Array<any>,
    oldPath: Array<string | number>,
    newPath: Array<string | number>,
  ): Object | Array<any> => {
    if (oldPath.length !== newPath.length) {
      console.warn('copyWithRename() expects paths of the same length');
      return;
    } else {
      for (let i = 0; i < newPath.length - 1; i++) {
        if (oldPath[i] !== newPath[i]) {
          console.warn(
            'copyWithRename() expects paths to be the same except for the deepest key',
          );
          return;
        }
      }
    }
    return copyWithRenameImpl(obj, oldPath, newPath, 0);
  };

  const copyWithSetImpl = (
    obj: Object | Array<any>,
    path: Array<string | number>,
    index: number,
    value: any,
  ): $FlowFixMe => {
    if (index >= path.length) {
      return value;
    }
    const key = path[index];
    const updated = isArray(obj) ? obj.slice() : {...obj};
    // $FlowFixMe[incompatible-use] number or string is fine here
    updated[key] = copyWithSetImpl(obj[key], path, index + 1, value);
    return updated;
  };

  const copyWithSet = (
    obj: Object | Array<any>,
    path: Array<string | number>,
    value: any,
  ): Object | Array<any> => {
    return copyWithSetImpl(obj, path, 0, value);
  };

  const findHook = (fiber: Fiber, id: number) => {
    // For now, the "id" of stateful hooks is just the stateful hook index.
    // This may change in the future with e.g. nested hooks.
    let currentHook = fiber.memoizedState;
    while (currentHook !== null && id > 0) {
      currentHook = currentHook.next;
      id--;
    }
    return currentHook;
  };

  // Support DevTools editable values for useState and useReducer.
  overrideHookState = (
    fiber: Fiber,
    id: number,
    path: Array<string | number>,
    value: any,
  ) => {
    const hook = findHook(fiber, id);
    if (hook !== null) {
      const newState = copyWithSet(hook.memoizedState, path, value);
      hook.memoizedState = newState;
      hook.baseState = newState;

      // We aren't actually adding an update to the queue,
      // because there is no update we can add for useReducer hooks that won't trigger an error.
      // (There's no appropriate action type for DevTools overrides.)
      // As a result though, React will see the scheduled update as a noop and bailout.
      // Shallow cloning props works as a workaround for now to bypass the bailout check.
      fiber.memoizedProps = {...fiber.memoizedProps};

      const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
      if (root !== null) {
        scheduleUpdateOnFiber(root, fiber, SyncLane);
      }
    }
  };
  overrideHookStateDeletePath = (
    fiber: Fiber,
    id: number,
    path: Array<string | number>,
  ) => {
    const hook = findHook(fiber, id);
    if (hook !== null) {
      const newState = copyWithDelete(hook.memoizedState, path);
      hook.memoizedState = newState;
      hook.baseState = newState;

      // We aren't actually adding an update to the queue,
      // because there is no update we can add for useReducer hooks that won't trigger an error.
      // (There's no appropriate action type for DevTools overrides.)
      // As a result though, React will see the scheduled update as a noop and bailout.
      // Shallow cloning props works as a workaround for now to bypass the bailout check.
      fiber.memoizedProps = {...fiber.memoizedProps};

      const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
      if (root !== null) {
        scheduleUpdateOnFiber(root, fiber, SyncLane);
      }
    }
  };
  overrideHookStateRenamePath = (
    fiber: Fiber,
    id: number,
    oldPath: Array<string | number>,
    newPath: Array<string | number>,
  ) => {
    const hook = findHook(fiber, id);
    if (hook !== null) {
      const newState = copyWithRename(hook.memoizedState, oldPath, newPath);
      hook.memoizedState = newState;
      hook.baseState = newState;

      // We aren't actually adding an update to the queue,
      // because there is no update we can add for useReducer hooks that won't trigger an error.
      // (There's no appropriate action type for DevTools overrides.)
      // As a result though, React will see the scheduled update as a noop and bailout.
      // Shallow cloning props works as a workaround for now to bypass the bailout check.
      fiber.memoizedProps = {...fiber.memoizedProps};

      const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
      if (root !== null) {
        scheduleUpdateOnFiber(root, fiber, SyncLane);
      }
    }
  };

  // Support DevTools props for function components, forwardRef, memo, host components, etc.
  overrideProps = (fiber: Fiber, path: Array<string | number>, value: any) => {
    fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value);
    if (fiber.alternate) {
      fiber.alternate.pendingProps = fiber.pendingProps;
    }
    const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
    if (root !== null) {
      scheduleUpdateOnFiber(root, fiber, SyncLane);
    }
  };
  overridePropsDeletePath = (fiber: Fiber, path: Array<string | number>) => {
    fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path);
    if (fiber.alternate) {
      fiber.alternate.pendingProps = fiber.pendingProps;
    }
    const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
    if (root !== null) {
      scheduleUpdateOnFiber(root, fiber, SyncLane);
    }
  };
  overridePropsRenamePath = (
    fiber: Fiber,
    oldPath: Array<string | number>,
    newPath: Array<string | number>,
  ) => {
    fiber.pendingProps = copyWithRename(fiber.memoizedProps, oldPath, newPath);
    if (fiber.alternate) {
      fiber.alternate.pendingProps = fiber.pendingProps;
    }
    const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
    if (root !== null) {
      scheduleUpdateOnFiber(root, fiber, SyncLane);
    }
  };

  scheduleUpdate = (fiber: Fiber) => {
    const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
    if (root !== null) {
      scheduleUpdateOnFiber(root, fiber, SyncLane);
    }
  };

  setErrorHandler = (newShouldErrorImpl: Fiber => ?boolean) => {
    shouldErrorImpl = newShouldErrorImpl;
  };

  setSuspenseHandler = (newShouldSuspendImpl: Fiber => boolean) => {
    shouldSuspendImpl = newShouldSuspendImpl;
  };
}

function getCurrentFiberForDevTools() {
  return ReactCurrentFiberCurrent;
}

function getLaneLabelMap(): Map<Lane, string> | null {
  if (enableSchedulingProfiler) {
    const map: Map<Lane, string> = new Map();

    let lane = 1;
    for (let index = 0; index < TotalLanes; index++) {
      const label = ((getLabelForLane(lane): any): string);
      map.set(lane, label);
      lane *= 2;
    }

    return map;
  } else {
    return null;
  }
}

export function injectIntoDevTools(): boolean {
  const internals: Object = {
    bundleType: __DEV__ ? 1 : 0, // Might add PROFILE later.
    version: rendererVersion,
    rendererPackageName: rendererPackageName,
    currentDispatcherRef: ReactSharedInternals,
    // Enables DevTools to detect reconciler version rather than renderer version
    // which may not match for third party renderers.
    reconcilerVersion: ReactVersion,
  };
  if (extraDevToolsConfig !== null) {
    internals.rendererConfig = (extraDevToolsConfig: RendererInspectionConfig);
  }
  if (__DEV__) {
    internals.overrideHookState = overrideHookState;
    internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
    internals.overrideHookStateRenamePath = overrideHookStateRenamePath;
    internals.overrideProps = overrideProps;
    internals.overridePropsDeletePath = overridePropsDeletePath;
    internals.overridePropsRenamePath = overridePropsRenamePath;
    internals.scheduleUpdate = scheduleUpdate;
    internals.setErrorHandler = setErrorHandler;
    internals.setSuspenseHandler = setSuspenseHandler;
    // React Refresh
    internals.scheduleRefresh = scheduleRefresh;
    internals.scheduleRoot = scheduleRoot;
    internals.setRefreshHandler = setRefreshHandler;
    // Enables DevTools to append owner stacks to error messages in DEV mode.
    internals.getCurrentFiber = getCurrentFiberForDevTools;
  }
  if (enableSchedulingProfiler) {
    // Conditionally inject these hooks only if Timeline profiler is supported by this build.
    // This gives DevTools a way to feature detect that isn't tied to version number
    // (since profiling and timeline are controlled by different feature flags).
    internals.getLaneLabelMap = getLaneLabelMap;
    internals.injectProfilingHooks = injectProfilingHooks;
  }
  return injectInternals(internals);
}

这段代码是 React 协调器(reconciler)的入口文件,它导出了用于创建和更新 React 容器(React Root)的 API,以及一些与开发工具集成、错误处理和调试相关的函数。

以下是对这段代码的详细分析:

1. 导入模块和类型:

  • 导入了各种类型定义、React 内部模块和工具函数,包括 Fiber 相关类型、Root 相关类型、渲染配置、React 节点类型、Lane 类型、Suspense 相关类型,以及一些共享的 React 内部模块。

2. 辅助函数:

  • getContextForSubtree: 获取给定组件子树的上下文。
  • findHostInstance: 查找给定组件的宿主实例(DOM 节点)。
  • findHostInstanceWithWarning: 在开发模式下,查找宿主实例并发出警告。

3. 创建和更新容器:

  • createContainer: 创建一个新的 React 容器。
  • createHydrationContainer: 创建一个用于服务端渲染(SSR)水合(hydration)的 React 容器。
  • updateContainer: 更新 React 容器中的元素。
  • updateContainerSync: 同步更新 React 容器中的元素。
  • updateContainerImpl: 内部使用的更新容器实现。

4. 导出 API:

  • 导出用于创建和更新容器的函数,以及一些用于控制更新和获取实例的函数。
  • 导出了一些 React 测试选择器(React Test Selectors)和 Host Transition API。
  • 导出错误处理和调试相关的函数。

5. 开发工具集成:

  • injectIntoDevTools: 将 React 内部信息注入到 React DevTools 中,以便进行调试和性能分析。
  • 在开发模式下,提供了一系列用于调试和修改 React 内部状态的函数,如 overrideHookStateoverridePropsscheduleUpdate
  • getCurrentFiberForDevTools:获取当前 Fiber 节点,用于调试。
  • getLaneLabelMap:如果启用了调度分析器(Scheduling Profiler),获取 Lane 标签映射。

6. 错误处理和调试:

  • shouldErrorshouldSuspend:用于控制错误边界和 Suspense 组件的行为。
  • 提供了一些默认的错误处理函数,如 defaultOnUncaughtErrordefaultOnCaughtErrordefaultOnRecoverableError

7. React Refresh:

  • 导出了 scheduleRefreshscheduleRootsetRefreshHandler,用于支持 React Refresh。

8. 重要概念:

  • FiberRoot: React 树的根节点,包含了 React 树的元数据和状态。
  • Lane: 用于表示更新的优先级。
  • Hydration: 服务端渲染(SSR)的水合过程,用于将服务端渲染的 HTML 与客户端 React 树进行匹配。
  • React DevTools: React 官方提供的调试工具。

总结:

这段代码是 React 协调器的入口文件,它导出了用于创建和更新 React 容器的 API,以及一些与开发工具集成、错误处理和调试相关的函数。它为 React 应用的渲染和更新提供了核心功能,并为开发者提供了强大的调试工具。

ReactReconcilerConstants.js

 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
export {
  NoEventPriority,
  DiscreteEventPriority,
  ContinuousEventPriority,
  DefaultEventPriority,
  IdleEventPriority,
} from './ReactEventPriorities';
export {ConcurrentRoot, LegacyRoot} from './ReactRootTags';
--- ReactRootTags
export type RootTag = 0 | 1;

export const LegacyRoot = 0;
export const ConcurrentRoot = 1;
--- ReactEventPriorities
import type {Lane, Lanes} from './ReactFiberLane';

import {
  NoLane,
  SyncLane,
  InputContinuousLane,
  DefaultLane,
  IdleLane,
  getHighestPriorityLane,
  includesNonIdleWork,
} from './ReactFiberLane';

export opaque type EventPriority = Lane;

export const NoEventPriority: EventPriority = NoLane;
export const DiscreteEventPriority: EventPriority = SyncLane;
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
export const DefaultEventPriority: EventPriority = DefaultLane;
export const IdleEventPriority: EventPriority = IdleLane;

export function higherEventPriority(
  a: EventPriority,
  b: EventPriority,
): EventPriority {
  return a !== 0 && a < b ? a : b;
}

export function lowerEventPriority(
  a: EventPriority,
  b: EventPriority,
): EventPriority {
  return a === 0 || a > b ? a : b;
}

export function isHigherEventPriority(
  a: EventPriority,
  b: EventPriority,
): boolean {
  return a !== 0 && a < b;
}

export function eventPriorityToLane(updatePriority: EventPriority): Lane {
  return updatePriority;
}

export function lanesToEventPriority(lanes: Lanes): EventPriority {
  const lane = getHighestPriorityLane(lanes);
  if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
    return DiscreteEventPriority;
  }
  if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
    return ContinuousEventPriority;
  }
  if (includesNonIdleWork(lane)) {
    return DefaultEventPriority;
  }
  return IdleEventPriority;
}

这段代码定义了 React 中的事件优先级(Event Priority)和根标签(Root Tag)相关的内容,主要用于控制 React 事件处理和渲染的优先级。

以下是对这段代码的详细分析:

1. ReactRootTags.js:定义 React 根标签

  • RootTag 类型:
    • 定义了 RootTag 类型,它是一个联合类型,可以是 01
  • LegacyRootConcurrentRoot 常量:
    • LegacyRoot:值为 0,表示传统的同步渲染模式。
    • ConcurrentRoot:值为 1,表示并发渲染模式(Fiber 架构)。
  • 导出:
    • ConcurrentRootLegacyRoot 常量导出,以便在 React 内部使用。

2. ReactEventPriorities.js:定义 React 事件优先级

  • 导入:
    • 导入了 LaneLanes 类型,以及一些 Lane 相关的常量和函数,这些来自ReactFiberLane
  • EventPriority 类型:
    • 定义了一个 EventPriority 类型,它是一个 Lane 的别名。
  • 事件优先级常量:
    • NoEventPriority:值为 NoLane,表示没有事件优先级。
    • DiscreteEventPriority:值为 SyncLane,表示离散事件优先级(如点击、按键)。
    • ContinuousEventPriority:值为 InputContinuousLane,表示连续事件优先级(如滚动、鼠标移动)。
    • DefaultEventPriority:值为 DefaultLane,表示默认事件优先级。
    • IdleEventPriority:值为 IdleLane,表示空闲事件优先级。
  • 比较事件优先级的函数:
    • higherEventPriority(a, b): 返回更高的事件优先级。
    • lowerEventPriority(a, b): 返回更低的事件优先级。
    • isHigherEventPriority(a, b): 判断 a 的优先级是否高于 b
  • 事件优先级和 Lane 之间的转换函数:
    • eventPriorityToLane(updatePriority): 将事件优先级转换为 Lane。
    • lanesToEventPriority(lanes): 将 Lanes 转换为事件优先级。

主要功能和目的:

  • 根标签(Root Tag):
    • 用于区分 React 应用的渲染模式,以便 React 能够根据不同的模式执行相应的渲染逻辑。
  • 事件优先级(Event Priority):
    • 用于控制 React 事件处理的优先级,以便 React 能够优先处理高优先级事件,从而提高用户体验。
    • 离散事件优先级最高,保证了交互的及时响应。
    • 连续事件优先级次之,保证连续交互的流畅性。
    • 默认事件优先级用于常规更新。
    • 空闲事件优先级最低,用于执行一些不紧急的任务。
  • Lane 转换:
    • 事件优先级和 Lane 之间的转换函数,用于在 React 内部进行优先级管理。

总结:

这段代码定义了 React 中的事件优先级和根标签,它们是 React 内部优先级管理和渲染控制的重要组成部分。通过这些定义,React 能够更好地管理事件处理和渲染任务,从而提高应用的性能和用户体验。

Licensed under CC BY-NC-SA 4.0
最后更新于 Mar 25, 2025 13:53 UTC