跳到主要内容

React jsx 元素实现

一、作用

二、jsx 生产

备注
/**
* 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.
//
// 目前,key 可以作为 prop 被展开。这会导致一个潜在问题,如果 key 也被显式声明(例如
// <div {...props} key="Hi" /> 或 <div key="Hi" {...props} />)。我们希望弃用
// key 展开,但作为过渡步骤,除了 <div {...props} key="Hi" /> 之外,我们将对所有情况
// 使用 jsxDEV,因为我们目前无法判断 key 是否被显式声明为 undefined。
if (maybeKey !== undefined) {
if (enableOptimisticKey && maybeKey === REACT_OPTIMISTIC_KEY) {
key = REACT_OPTIMISTIC_KEY;
} else {
if (__DEV__) {
checkKeyStringCoercion(maybeKey);
}
key = '' + maybeKey;
}
}

if (hasValidKey(config)) {
if (enableOptimisticKey && maybeKey === REACT_OPTIMISTIC_KEY) {
key = REACT_OPTIMISTIC_KEY;
} else {
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.
//
// 如果 key 没有被展开,我们可以重用原始的 props 对象。这只适用于 `jsx`,而不适用于
// `createElement`,因为 `jsx` 是一个编译器目标,编译器总是会传递一个新对象。对于
// `createElement`,我们不能假设每次都会传递一个新对象,因为它可以被手动调用。
//
// 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 对象中删除展开键。(但我们
// 仍然会发出警告。)我们将始终直接传递该对象。
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.
//
// 我们需要移除保留的属性(key、prop、ref)。创建一个新的 props 对象,并复制所有非保留的
// 属性。我们不使用 `delete`,因为在 V8 中这会使对象退化为字典模式。
props = {};
for (const propName in config) {
// Skip over reserved prop names
// 跳过保留的属性名称
if (propName !== 'key') {
props[propName] = config[propName];
}
}
}

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

三、jsx 生产签名在开发环境中运行并带有动态子元素

// 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.
//
// 虽然在生产环境中绝不应该调用 `jsxDEV`,但在开发环境中,我们确实支持 `jsx` 和 `jsxs`。
// 这支持了这样的情况:第三方依赖提供了为生产环境编译的代码; 我们仍然希望在开发环境中提供警告。
//
// So these functions are the _dev_ implementations of the _production_
// API signatures.
// 所以这些函数是 _开发_ 版本的 _生产_ API 签名的实现。
//
// 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.)
//
// 由于这些函数仅用于开发,所以在这里添加一个间接调用是可以的。它们的存在只是为了提供
// `isStaticChildren` 的不同版本。(不过我们不应在生产版本中使用这种模式,因为它会增加调用栈。)
export function jsxProdSignatureRunningInDevWithDynamicChildren(
type,
config,
maybeKey,
) {
if (__DEV__) {
const isStaticChildren = false;
const trackActualOwner =
__DEV__ &&
ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit;
let debugStackDEV = false;
if (__DEV__) {
if (trackActualOwner) {
const previousStackTraceLimit = Error.stackTraceLimit;
Error.stackTraceLimit = ownerStackTraceLimit;
debugStackDEV = Error('react-stack-top-frame');
Error.stackTraceLimit = previousStackTraceLimit;
} else {
debugStackDEV = unknownOwnerDebugStack;
}
}

return jsxDEVImpl(
type,
config,
maybeKey,
isStaticChildren,
debugStackDEV,
__DEV__ &&
(trackActualOwner
? createTask(getTaskName(type))
: unknownOwnerDebugTask),
);
}
}

四、jsx 生产在开发环境中运行带有静态子元素的签名

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

五、jsx (开发)

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

六、创建元素

备注
/**
* Create and return a new ReactElement of the given type.
* See https://reactjs.org/docs/react-api.html#createelement
*
* 创建并返回指定类型的新 ReactElement。
* 参见 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.)
//
// 如果类型无效,则跳过 key 警告,因为我们的 key 验证逻辑并不期望非字符串/函数类型,并且
// 可能会抛出令人困惑的错误。我们不希望开发环境和生产环境的异常行为有所不同。(渲染时会抛出
// 有用的提示信息,一旦类型修正,key 警告就会出现。)
for (let i = 2; i < arguments.length; i++) {
validateChildKeys(arguments[i]);
}

// Unlike the jsx() runtime, createElement() doesn't warn about key spread.
// 与 jsx() 运行时不同,createElement() 不会对 key 扩展发出警告。
}

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,不要假设这是过时的 JSX 转换的结果,因为现代 JSX 转换有时会输出
// createElement 来保持静态 key 和扩展 key 之间的优先级。为了避免误报警告,如果
// 存在 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 (enableOptimisticKey && config.key === REACT_OPTIMISTIC_KEY) {
key = REACT_OPTIMISTIC_KEY;
} else {
if (__DEV__) {
checkKeyStringCoercion(config.key);
}
key = '' + config.key;
}
}

// Remaining properties are added to a new props object
// 剩余的属性被添加到一个新的 props 对象中
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.
//
// 即使我们在运行时不再使用这些,但我们不希望它们作为 props 出现,所以在 createElement
// 中我们会过滤掉它们。在 jsx() 运行时我们不必这样做,因为 jsx() 转换从未将这些作为
// props 传递;它使用的是单独的参数。
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.
// Children 可以有多个参数,这些参数会被传递到新分配的 props 对象上。
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;
let debugStackDEV = false;
if (__DEV__) {
if (trackActualOwner) {
const previousStackTraceLimit = Error.stackTraceLimit;
Error.stackTraceLimit = ownerStackTraceLimit;
debugStackDEV = Error('react-stack-top-frame');
Error.stackTraceLimit = previousStackTraceLimit;
} else {
debugStackDEV = unknownOwnerDebugStack;
}
}
return ReactElement(
type,
key,
props,
getOwner(),
debugStackDEV,
__DEV__ &&
(trackActualOwner
? createTask(getTaskName(type))
: unknownOwnerDebugTask),
);
}

七、克隆并替换键

export function cloneAndReplaceKey(oldElement, newKey) {
const clonedElement = ReactElement(
oldElement.type,
newKey,
oldElement.props,
!__DEV__ ? undefined : oldElement._owner,
__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.
* 使用 element 作为起点克隆并返回一个新的 ReactElement。
* 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 (enableOptimisticKey && config.key === REACT_OPTIMISTIC_KEY) {
key = REACT_OPTIMISTIC_KEY;
} else {
if (__DEV__) {
checkKeyStringCoercion(config.key);
}
key = '' + config.key;
}
}

// Remaining properties override existing props
// 剩余的属性会覆盖已有的属性
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.
//
// ……也许这些也是如此,虽然我们目前在开发中依赖它们来显示警告和调试信息。需要决定
// 是否可以接受放弃它们。在 jsx() 运行时这不是问题,因为数据会作为单独的参数传递,
// 而不是作为 props,但如果能完全停止依赖它们就好了,这样我们就可以从内部
// Fiber 字段中移除它们。
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.
//
// 未定义的 `ref` 会被 cloneElement 忽略。我们将其视为该属性不存在。这主要是为了
// 向后兼容。
!(propName === 'ref' && config.ref === undefined)
) {
props[propName] = config[propName];
}
}
}

// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
// Children 可以有多个参数,这些参数会被传递到新分配的 props 对象上。
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,
props,
owner,
__DEV__ && element._debugStack,
__DEV__ && element._debugTask,
);

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

return clonedElement;
}

九、判定是否是有效元素

/**
* Verifies the object is a ReactElement.
* * 验证对象是否为 React 元素。
* 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
);
}

十、判定是否是懒类型

export function isLazyType(object) {
return (
typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_LAZY_TYPE
);
}

十一、常量

1. 创建任务

const createTask =
__DEV__ && console.createTask ? console.createTask : () => null;

2. 所有者堆栈跟踪限制

// v8 (Chromium, Node.js) defaults to 10
// SpiderMonkey (Firefox) does not support Error.stackTraceLimit
// JSC (Safari) defaults to 100
// The lower the limit, the more likely we'll not reach react_stack_bottom_frame
// The higher the limit, the slower Error() is when not inspecting with a debugger.
// When inspecting with a debugger, Error.stackTraceLimit has no impact on Error() performance (in v8).
//
// v8(Chromium、Node.js)默认值为 10
// SpiderMonkey(Firefox)不支持 Error.stackTraceLimit
// JSC(Safari)默认值为 100
// 限制值越低,我们越有可能无法到达 react_stack_bottom_frame
// 限制值越高,当不使用调试器检查时,Error() 的速度越慢
// 使用调试器检查时,Error.stackTraceLimit 对 Error() 性能没有影响(在 v8 中)
const ownerStackTraceLimit = 10;

3. 创建假调用栈

const createFakeCallStack = {
react_stack_bottom_frame: function (callStackForError) {
return callStackForError();
},
};

4. 已警告过密钥扩散

备注

在源码中的 433 行

const didWarnAboutKeySpread = {};

十二、变量

1. 警告

备注

在源码中的 80 - 95 行

// 特殊属性键警告已显示
let specialPropKeyWarningShown;
// 已警告关于元素引用
let didWarnAboutElementRef;
// 已警告关于旧的 JSX 运行时
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));
}

十三、工具

1. 获取任务名称

备注
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 '<...>';
}
}

2. 获取所有者

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

3. 未知所有者

function UnknownOwner() {
return (() => Error('react-stack-top-frame'))();
}

4. 判定是否有有效引用

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;
}

5. 判定是否有有效键

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;
}

6. 定义键属性警告获取器

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,
});
}
}

7. React 元素

/**
* 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.
*
* 工厂方法用于创建一个新的 React 元素。它不再遵循类模式,因此不要使用 new 来调用它。此外,instanceof 检查将
* 不起作用。相反,可以通过检测 $$typeof 字段是否等于 Symbol.for('react.transitional.element') 来检查
* 某物是否是 React 元素。
*
* @internal
*/
function ReactElement(type, key, props, owner, 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.
// 忽略传入的 ref 参数,将 `props.ref` 视为真实来源。我们唯一使用它的地方是
// `element.ref`,访问时会显示弃用警告。在下一个版本中,我们可以同时移除
// `element.ref` 和 `ref` 参数。
const refProp = props.ref;

// An undefined `element.ref` is coerced to `null` for
// backwards compatibility.
// 未定义的 `element.ref` 被强制转换为 `null` 以保持向后兼容性。
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` 设置为不可枚举属性,并添加警告。它不可枚举,这样测试匹配器和
// 序列化器就不会访问它,也不会触发警告。
//
// `ref` will be removed from the element completely in a future release.
// 在未来的版本中,`ref` 将会从元素中完全移除。
element = {
// This tag allows us to uniquely identify this as a React Element
// 这个标签允许我们将其唯一地标识为 React 元素
$$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.
//
// 如果没有提供引用,则不要在访问时发出警告。这可以减少在测试序列化器使用
// getOwnPropertyDescriptors 比较对象时的误报情况,例如 Jest 会这样做,
// 这是一个问题,因为它绕过了非可枚举性。
//
// So unfortunately this will trigger a false positive warning in Jest
// when the diff is printed:
//
// 所以不幸的是,当打印差异时,这将在 Jest 中触发错误的警告:
//
// 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.
//
// 有点草率,但这就是我们多年来对 `props.key` 和 `props.ref` 访问器所做的处理,这
// 意味着它对 `element.ref` 也应该足够。看看是否有人会抱怨。
Object.defineProperty(element, 'ref', {
enumerable: false,
value: null,
});
}
} else {
// In prod, `ref` is a regular property and _owner doesn't exist.
// 在生产环境中,`ref` 是一个普通属性,_owner 不存在。
element = {
// This tag allows us to uniquely identify this as a React Element
// 这个标签允许我们将其唯一标识为一个 React 元素
$$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.
//
// 验证标志目前是可变的。我们将它放在外部存储上,以便可以冻结整个对象。
// 一旦在常用开发环境中实现了 WeakMap,这可以被替换。
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.
//
// 为了在测试中更容易比较 ReactElements,我们将验证标志设为不可枚举(在可能的情况下,
// 这应该包括我们运行测试的每个环境),以便测试框架忽略它。
Object.defineProperty(element._store, 'validated', {
configurable: false,
enumerable: false,
writable: true,
value: 0,
});
// debugInfo contains Server Component debug information.
// debugInfo 包含服务器组件的调试信息。
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;
}

8. jsx (开发实现)

备注
function jsxDEVImpl(
type,
config,
maybeKey,
isStaticChildren,
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.
//
// 如果类型无效,则跳过 key 警告,因为我们的 key 验证逻辑不期望非字符串/函数类型,并且可能
// 抛出令人困惑的错误。我们不希望开发环境和生产环境的异常行为有所不同。(渲染时会抛出带有有用
// 信息的消息,一旦类型被修复,key 警告就会出现。)有了 owner stack,我们在这里不再需要类型
// ,因此这条注释已经不适用。这就是为什么即使对于无效类型,我们也可以运行此代码。
const children = config.children;
if (children !== undefined) {
if (isStaticChildren) {
if (isArray(children)) {
for (let i = 0; i < children.length; i++) {
validateChildKeys(children[i]);
}

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);
}
}

// 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.
//
// 目前,key 可以作为 prop 被展开。这会导致一个潜在问题,如果 key 也被显式声明(例如
// <div {...props} key="Hi" /> 或 <div key="Hi" {...props} />)。我们希望弃用 key
// 展开,但作为过渡步骤,除了 <div {...props} key="Hi" /> 之外,我们将对所有情况使用
// jsxDEV,因为我们目前无法判断 key 是否被显式声明为 undefined。
if (maybeKey !== undefined) {
if (enableOptimisticKey && maybeKey === REACT_OPTIMISTIC_KEY) {
key = REACT_OPTIMISTIC_KEY;
} else {
if (__DEV__) {
checkKeyStringCoercion(maybeKey);
}
key = '' + maybeKey;
}
}

if (hasValidKey(config)) {
if (enableOptimisticKey && config.key === REACT_OPTIMISTIC_KEY) {
key = REACT_OPTIMISTIC_KEY;
} else {
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.
//
// 如果 key 没有被展开,我们可以重用原始的 props 对象。这只适用于 `jsx`,而不适用于
// `createElement`,因为 `jsx` 是一个编译器目标,编译器总是会传递一个新对象。对于
// `createElement`,我们不能假设每次都会传递一个新对象,因为它可以被手动调用。
//
// 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 对象中删除展开键。(但
// 我们仍然会发出警告。)我们将始终直接传递该对象。
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.
// 我们需要移除保留的属性(key、prop、ref)。创建一个新的 props 对象,并复制所有非保留
// 的属性。我们不使用 `delete`,因为在 V8 中这会使对象退化为字典模式。
props = {};
for (const propName in config) {
// Skip over reserved prop names
// 跳过保留的属性名称
if (propName !== 'key') {
props[propName] = config[propName];
}
}
}

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

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

9. 验证子键

/**
* 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.
*
* 确保每个元素要么在静态位置传入,要么在定义了明确 key 属性的数组中传入,
* 或者在具有有效 key 属性的对象字面量中传入。
*
* @internal
* @param {ReactNode} node Statically passed child of any type.
* @param {ReactNode} node 以静态方式传递的任何类型的子元素。
* @param {*} parentType node's parent's type.
* @param {*} parentType 节点父节点的类型。
*/
function validateChildKeys(node) {
if (__DEV__) {
// Mark elements as being in a valid static child position so they
// don't need keys.
//
// 将元素标记为处于有效的静态子位置,这样它们就不需要 key。
if (isValidElement(node)) {
if (node._store) {
node._store.validated = 1;
}
} else if (isLazyType(node)) {
if (node._payload.status === 'fulfilled') {
if (isValidElement(node._payload.value) && node._payload.value._store) {
node._payload.value._store.validated = 1;
}
} else if (node._store) {
node._store.validated = 1;
}
}
}
}