React16源码分析(2):react.development.js源码注释
Author:[email protected] Date:
react虚拟DOM实现的核心
react.development.js的核心讲解,跳到 336行
react Component
/** function Component(props, context, updater) { this.props = props; this.context = context; // If a component has string refs, we will assign a different object later. // 该属性用于存储类组件实例的引用信息 // 在React中我们可以有多种方式来创建引用 // 通过字符串的方式,如:<input type="text" ref="inputRef" /> // 通过回调函数的方式,如:<input type="text" ref={(input) => this.inputRef = input;} /> // 通过React.createRef()的方式, // 如:this.inputRef = React.createRef(null); <input type="text" ref={this.inputRef} /> // 通过useRef()的方式,如:this.inputRef = useRef(null); <input type="text" ref={this.inputRef} /> this.refs = emptyObject; // We initialize the default updater but the real one gets injected by the renderer. // 初始化默认的更新器,真实的更新器将会在渲染器内注入 // 当state发生变化的时候,需要updater对象去处理后续的更新调度任务 // 这部分涉及到任务调度的内容,在后续分析到任务调度阶段的时候再来细看 this.updater = updater || ReactNoopUpdateQueue; } // 定义原型属性 Component.prototype.isReactComponent = {};
然后设置 Component各种属性方法
跳转到337行
Component.prototype.setState
设置状态的子集,始终使用this去改变状态,需要把this.sate当成正常状态下不可变的
并不保证this.state会马上更新,先设置再取值可能返回的是老值
并不保证setState的调用是同步的,因为多个调用最终会被合并,你可以提供一个可选的回调,在setStates事实上完成之后调用
当一个函数传递给setState时,它将在未来的某个时间点被调用
并且使用最新的参数(state,props,context等)这新值可能跟this.xxx不一样
因为你的函数是在receiveProps之后,shouldComponentUpdate之前调用的
在这个新阶段,props和context并没有赋值给this
/** * Sets a subset of the state. Always use this to mutate * state. You should treat `this.state` as immutable. * * There is no guarantee that `this.state` will be immediately updated, so * accessing `this.state` after calling this method may return the old value. * * There is no guarantee that calls to `setState` will run synchronously, * as they may eventually be batched together. You can provide an optional * callback that will be executed when the call to setState is actually * completed. * * When a function is provided to setState, it will be called at some point in * the future (not synchronously). It will be called with the up to date * component arguments (state, props, context). These values can be different * from this.* because your function may be called after receiveProps but before * shouldComponentUpdate, and this new state, props, and context will not yet be * assigned to this. * * @param {object|function} partialState 表示下次需要更新的状态 Next partial state or function to * produce next partial state to be merged with current state. * @param {?function} 在组件更新之后需要执行的回调 callback Called after state is updated. */ Component.prototype.setState = function (partialState, callback) { // void 0 === undefined 省字节,同时防止undefined被注入 // partialState需要是对象,函数或者null !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? invariant(false, 'setState(...): 第一个参数只许传入函数货对象去更新state') : void 0; // 进入队列状态更新 this.updater.enqueueSetState(this, partialState, callback, 'setState'); };
Component.prototype.forceUpdate
强制刷新,该方法只能在非DOM转化态的时候调用
在一些深层状态改变但是setState没有被调用的时候使用,该方法不会调用shouldComponentUpdate,但componentWillUpdate和componentDidUpdate会被调用
/** 用于强制重新渲染 * Forces an update. This should only be invoked when it is known with * certainty that we are **not** in a DOM transaction. * * You may want to call this when you know that some deeper aspect of the * component's state has changed but `setState` was not called. * * This will not invoke `shouldComponentUpdate`, but it will invoke * `componentWillUpdate` and `componentDidUpdate`. * * @param {?function} callback Called after update is complete. * @final * @protected */ Component.prototype.forceUpdate = function (callback) { this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); };
上述内容中涉及到任务调度的会在后续讲解到调度阶段的时候再来细讲,现在我们知道可以通过原型上的isReactComponent属性来区分函数定义组件和类组件。事实上,在源码中就是通过这个属性来区分Class Component和Function Component的,可以找到以下方法:
// 返回true则表示类组件,否则表示函数定义组件function shouldConstruct(Component) { return !!(Component.prototype && Component.prototype.isReactComponent); }
遗弃api的告警函数
废弃的api,这些api只会在经典的类组件上存在,我们将会遗弃它们。我们并不会直接移除他们,而是定义getter,并抛错
/** * Deprecated APIs. These APIs used to exist on classic React classes but since * we would like to deprecate them, we're not going to move them over to this * modern base class. Instead, we define a getter that warns if it's accessed. */ { var deprecatedAPIs = { isMounted: ['isMounted', 'Instead, make sure to clean up subscriptions and pending requests in ' + 'componentWillUnmount to prevent memory leaks.'], replaceState: ['replaceState', 'Refactor your code to use setState instead (see ' + 'https://github.com/facebook/react/issues/3236).'] }; // 定义遗弃api的告警函数 var defineDeprecationWarning = function (methodName, info) { Object.defineProperty(Component.prototype, methodName, { get: function () { lowPriorityWarning$1(false, '%s(...) is deprecated in plain JavaScript React classes. %s', info[0], info[1]); return undefined; } }); }; // 依次注入getter for (var fnName in deprecatedAPIs) { if (deprecatedAPIs.hasOwnProperty(fnName)) { defineDeprecationWarning(fnName, deprecatedAPIs[fnName]); } } }
427行
ComponentDummy
假组件,原型是真组件的原型
function ComponentDummy() {} ComponentDummy.prototype = Component.prototype;
PureComponent
PureComponent Vs Component 它们几乎完全相同,但是PureComponent通过prop和state的浅比较来实现shouldComponentUpdate,某些情况下可以用PureComponent提升性能,不需要开发者自己实现shouldComponentUpdate
可能会因深层的数据不一致而产生错误的否定判断,从而shouldComponentUpdate结果返回false,界面得不到更新。
shallowEqual方法源码中看到,浅对比只是用Object.is()对Object的value做了一个基本数据类型的比较。
/** * Convenience component with default shallow equality check for sCU. */ function PureComponent(props, context, updater) { this.props = props; this.context = context; // 如果有字符串类型的ref,我们将在稍后指派一个不同的对象 // If a component has string refs, we will assign a different object later. this.refs = emptyObject; this.updater = updater || ReactNoopUpdateQueue; } // 纯粹的组件原型 将PureComponent的原型指向借用构造函数的实例 var pureComponentPrototype = PureComponent.prototype = new ComponentDummy(); // 定义纯粹组件原型的构造函数 重新设置构造函数的指向 pureComponentPrototype.constructor = PureComponent; // Avoid an extra prototype jump for these methods. // 原型上再次注入Component的原型 // 将Component.prototype和PureComponent.prototype进行合并,减少原型链查找所浪费的时间(原型链越长所耗费的时间越久) _assign(pureComponentPrototype, Component.prototype); // 标志位,判断是纯粹组件 这里是与Component的区别之处,PureComponent的原型上拥有一个isPureReactComponent属性 pureComponentPrototype.isPureReactComponent = true;
这里定义了组件的构造函数Component,然后定义了一系列IDE原型方法,比如setState、forceUpdate等等,然后针对一些已经废弃的api,给Component的原型添加get方法,以便在用户调用的时候抛错。接下来定义了一个过渡组件ComponentDummy,其原型为Component的原型,其实例为PureComponent的原型
442行
createRet和组件调用堆栈
被封闭对象仍旧全等该对象本
可以通过Object.isSealed来判断当前对象是否被封闭
不能为被封闭对象添加任何未知属性, 也不能为其已知属性添加访问者
可以修改已知的属性
// an immutable object with a single mutable value // 一个不可变的对象,一个不可变的值 function createRef() { // 只有current一个属性 var refObject = { current: null }; { Object.seal(refObject);// https://www.jianshu.com/p/96220f921272 } return refObject; } /** * Keeps track of the current dispatcher. */ // 跟踪当前的分发者 var ReactCurrentDispatcher = { /** * @internal * @type {ReactComponent} */ current: null }; /** * Keeps track of the current owner. * * The current owner is the component who should own any components that are * currently being constructed. */ // 跟踪react当前的所有者,指的是所有正在构造的组件的父组件 var ReactCurrentOwner = { /** * @internal * @type {ReactComponent} */ current: null }; // 正则,匹配任意内容加正反斜杆 // 括号内的内容是分组 https://www.jianshu.com/p/f09508c14e65 // match如果是全局匹配,返回的是所有的匹配项,如果不是返回的是匹配字符串,位置,原始输入,如果有分组,第二项是匹配的分组 var BEFORE_SLASH_RE = /^(.*)[\\\/]/; // 描述组件的引用位置 var describeComponentFrame = function (name, source, ownerName) { var sourceInfo = ''; if (source) { var path = source.fileName; // 解析出文件名 var fileName = path.replace(BEFORE_SLASH_RE, ''); { // In DEV, include code for a common special case: // prefer "folder/index.js" instead of just "index.js". // 在开发环境下,如果文件名为index 输出带上一级路径的文件名 if (/^index\./.test(fileName)) { // 解析出反斜杠前的文件名 var match = path.match(BEFORE_SLASH_RE); if (match) { var pathBeforeSlash = match[1]; if (pathBeforeSlash) { // 获得文件名前的文件夹的名字 var folderName = pathBeforeSlash.replace(BEFORE_SLASH_RE, ''); fileName = folderName + '/' + fileName; } } } } // 获取最近的文件夹名和文件名,拼上代码行数 sourceInfo = ' (at ' + fileName + ':' + source.lineNumber + ')'; } else if (ownerName) { sourceInfo = ' (created by ' + ownerName + ')'; } return '\n in ' + (name || 'Unknown') + sourceInfo; }; var Resolved = 1; // 细化解析惰性组件 function refineResolvedLazyComponent(lazyComponent) { // 如果已经resolved,返回结果 return lazyComponent._status === Resolved ? lazyComponent._result : null; } // 获取外层组件的名字 function getWrappedName(outerType, innerType, wrapperName) { var functionName = innerType.displayName || innerType.name || ''; // 优先是outerType的displayName,否则是wrapperName和functionName的组合 return outerType.displayName || (functionName !== '' ? wrapperName + '(' + functionName + ')' : wrapperName); }
这里首先定义了createRef的实现,其次定义了ReactCurrentDispatcher,react16.8.3版本后新增的hooks系列api,其机制类似于redux中的dispatcher,这里的ReactCurrentDispatcher在后续hooks api的定义中也会用到。接下来是
describeComponentFrame方法,其核心是获得文件源的出处(主要是文件名和代码行数),接下来定义了获得组件父组件的组件名的方法getWrappedName
507行
getComponentName与react debug模式下当前组件调用堆栈详情
// 获取组件名 function getComponentName(type) { if (type == null) { // Host root, text node or just invalid type. // 如果是根,文字节点或不存在的类型,返回null return null; } { // 如果type的tag是数字 if (typeof type.tag === 'number') { warningWithoutStack$1(false, '告警:接收到了预料之外的对象,这可能是react内部的bug,请提issue'); } } // 如果是构造函数,看他的静态属性displayName或者name if (typeof type === 'function') { return type.displayName || type.name || null; } // 如果是字符串直接返回 if (typeof type === 'string') { return type; } switch (type) { // 如果是react当前的节点,这些都是当初symbol定义的 case REACT_CONCURRENT_MODE_TYPE: return 'ConcurrentMode'; case REACT_FRAGMENT_TYPE: return 'Fragment'; // 如果是入口 case REACT_PORTAL_TYPE: return 'Portal'; // 如果是分析器 case REACT_PROFILER_TYPE: return 'Profiler'; case REACT_STRICT_MODE_TYPE: return 'StrictMode'; case REACT_SUSPENSE_TYPE: return 'Suspense'; } // 如果type是对象 if (typeof type === 'object') { // 按照$$typeof来判断 switch (type.$$typeof) { case REACT_CONTEXT_TYPE: return 'Context.Consumer'; case REACT_PROVIDER_TYPE: return 'Context.Provider'; // 如果是前向ref case REACT_FORWARD_REF_TYPE: return getWrappedName(type, type.render, 'ForwardRef'); // 如果是memo类型,递归调用自己 case REACT_MEMO_TYPE: return getComponentName(type.type); // 如果是lazy类型 case REACT_LAZY_TYPE: { var thenable = type; // 细化解析惰性组件 var resolvedThenable = refineResolvedLazyComponent(thenable); if (resolvedThenable) { return getComponentName(resolvedThenable); } } } } // 最后返回null return null; } // react正在debug的frame,可以理解为一个对象里面有一些方法可供调取当前组件的调用栈 var ReactDebugCurrentFrame = {}; // 当前正在验证的元素 var currentlyValidatingElement = null; // 设置当前正在验证的元素 function setCurrentlyValidatingElement(element) { { currentlyValidatingElement = element; } } { // 堆栈的实现是通过当前的renderer注入的 // Stack implementation injected by the current renderer. ReactDebugCurrentFrame.getCurrentStack = null; // 增加枚举的方法 // 本质上是返回调用堆栈的附录 ReactDebugCurrentFrame.getStackAddendum = function () { var stack = ''; // Add an extra top frame while an element is being validated // 增加一个额外的顶层框架,如果当前有元素正在被验证 if (currentlyValidatingElement) { // 获取元素的名字 var name = getComponentName(currentlyValidatingElement.type); // 获取元素所有者 var owner = currentlyValidatingElement._owner; // 获取源的目录位置 stack += describeComponentFrame(name, currentlyValidatingElement._source, owner && getComponentName(owner.type)); } // Delegate to the injected renderer-specific implementation // 转交给renderer中的特殊实现来获取堆栈 // 如果getCurrentStack被复写,追加该方法提供的信息 var impl = ReactDebugCurrentFrame.getCurrentStack; if (impl) { stack += impl() || ''; } return stack; }; }
getComponentName根据传入的react 元素(Element)的类型来获得组件的名称,通过判断type和type上的$$typeof的值来判断返回的结果。$$typeof是react Element内部定义的一个变量,负责记录元素的类型,后续的代码中会有提及。setCurrentlyValidatingElement将在后续的一些validate的方法中被反复调用,设置当前正在校验的元素,以便后续输出抛错时的调用栈。
594行
ReactSharedInternals 内部共享的控制构件
var ReactSharedInternals = { // 当前的分发者 ReactCurrentDispatcher: ReactCurrentDispatcher, // 当前的所有者 ReactCurrentOwner: ReactCurrentOwner, // Object.assign避免在UMD下被打包两次 // Used by renderers to avoid bundling object-assign twice in UMD bundles: assign: _assign }; { _assign(ReactSharedInternals, { // 在生产环境不应该有 // These should not be included in production. ReactDebugCurrentFrame: ReactDebugCurrentFrame, // Shim for React DOM 16.0.0 which still destructured (but not used) this. // TODO: remove in React 17.0. // react树形钩子 ReactComponentTreeHook: {} }); } // 类似于不变性的警告,只有在条件不满足的时候才打印 /** * Similar to invariant but only logs a warning if the condition is not met. * This can be used to log issues in development environments in critical * paths. Removing the logging code for production environments will keep the * same logic and follow the same code paths. */ // 首先赋值warningWithoutStack$1 var warning = warningWithoutStack$1; // 本质上是带调用栈的warning方法 { // 第二个条件是格式化 warning = function (condition, format) { if (condition) { return; } // 获取当前的调用序列 var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame; var stack = ReactDebugCurrentFrame.getStackAddendum(); // eslint-disable-next-line react-internal/warning-and-invariant-args // 拼装后面的参数 for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { args[_key - 2] = arguments[_key]; } // 复用原来的warning方法,前面的内容照旧,后面的内容拼上调用序列的信息 warningWithoutStack$1.apply(undefined, [false, format + '%s'].concat(args, [stack])); }; } var warning$1 = warning; // 定义hasOwnProperty方法 var hasOwnProperty = Object.prototype.hasOwnProperty; // 属性保留字 var RESERVED_PROPS = { key: true, ref: true, __self: true, __source: true }; // 特殊的属性key展示告警 // void 0 就是undefined var specialPropKeyWarningShown = void 0; var specialPropRefWarningShown = void 0; // 排除ref是warning的情况,判断是否存在ref function hasValidRef(config) { { // 如果config有ref自有属性属性 if (hasOwnProperty.call(config, 'ref')) { // 获取get方法 var getter = Object.getOwnPropertyDescriptor(config, 'ref').get; // 如果这个getter是warning的,返回false if (getter && getter.isReactWarning) { return false; } } } // 否则根据是否undefined来判断 return config.ref !== undefined; } // 是否具有可用属性 // 逻辑跟ref的很相似 function hasValidKey(config) { { if (hasOwnProperty.call(config, 'key')) { var getter = Object.getOwnPropertyDescriptor(config, 'key').get; if (getter && getter.isReactWarning) { return false; } } } return config.key !== undefined; } // 定义key属性的warning Getter function defineKeyPropWarningGetter(props, displayName) { // 关于访问key的告警 var warnAboutAccessingKey = function () { if (!specialPropKeyWarningShown) { specialPropKeyWarningShown = true; // key不能作为参数获取,被react内部征用了 // key不能作为参数,尝试去获取它只会返回undefined,如果你想获取子组件上的这个值,你应该传另一个名字的属性 warningWithoutStack$1(false, '%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://fb.me/react-special-props)', displayName); } }; // 这个函数定义为react warngin warnAboutAccessingKey.isReactWarning = true; // 给入参的key属性定义getter,避免外界访问 Object.defineProperty(props, 'key', { get: warnAboutAccessingKey, configurable: true }); } // 这部分内容跟key的非常类似 // 定义ref的获取方法 function defineRefPropWarningGetter(props, displayName) { var warnAboutAccessingRef = function () { if (!specialPropRefWarningShown) { specialPropRefWarningShown = true; warningWithoutStack$1(false, '%s: `ref` 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://fb.me/react-special-props)', displayName); } }; warnAboutAccessingRef.isReactWarning = true; Object.defineProperty(props, 'ref', { get: warnAboutAccessingRef, configurable: true }); }
接下来react内部定义了一个对象ReactSharedInternals,其内部包含了一些方法获取全局公用的一些方法,比如ReactCurrentDispatcher(hooks相关功能)等,接下来又定义了带有堆栈信息的warning方法,其实就是getStackAddendum的结果拼装warningWithoutStack$1。接下来通过一个常量定义了react Element(元素)属性的保留字:key,ref,__self和__source。接下来定义了验证是否有可用key或ref的方法。
React元素创建
跳到 ,722行
reactElement的构造函数
定义一个创建react 元素的构造函数,这跟class模式的组建不一样,请不要使用new来调用,所有instanceof来检查是失效的,不要使用要用Symbol.for('react.element'),而要用$$typeof来检查,来判断是否是react组件
self是一个暂时的变量,是用来判断当React.createElement被调用的时候this和owner是否一致,以便我们告警。我们打算摆脱owner这个概念并且
使用箭头函数,只要这个二者一致,组件就没有变化
source是一个注释对象(被转译器或者其他文件名,行数,等信息所添加)
/** 为一个工厂函数,每次执行都会创建并返回一个ReactElement对象 * 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, no instanceof check * will work. Instead test $$typeof field against Symbol.for('react.element') to check * if something is a React Element. * * @param {*} 表示节点所对应的类型,与React.createElement方法的第一个参数保持一致 * @param {*} key 表示节点所对应的唯一标识,一般在列表渲染中我们需要为每个节点设置key属性 * @param {string|object} ref 表示对节点的引用,可以通过React.createRef()或者useRef()来创建引用 * @param {*} self 该属性只有在开发环境才存在 * @param {*} source 该属性只有在开发环境才存在 * @param {*} owner 一个内部属性,指向ReactCurrentOwner.current,表示一个Fiber节点 * @param props 表示该节点的属性信息,在React.createElement中通过config,children参数和defaultProps静态属性得到 * @returns 返回一个ReactElement对象 */ // react元素构造函数 返回的其实是element对象 var ReactElement = function (type, key, ref, self, source, owner, props) { var element = { // 通过这个标签来识别react的元素 这里仅仅加了一个$$typeof属性,用于标识这是一个React 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: type, key: key, ref: ref, props: props, // 记录创建这个组件的组件 // Record the component responsible for creating this element. _owner: owner }; { // 这个验证标志是可变的,我们把这个放在外部支持存储,以便我们能够冻结整个对象, // 这个可以被若映射替代,一旦在开发环境下实现了 // 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. // 给_store设置validated属性false Object.defineProperty(element._store, 'validated', { configurable: false, enumerable: false, writable: true, value: false }); // self和source都是开发环境才存在的 // self and source are DEV only properties. Object.defineProperty(element, '_self', { configurable: false, enumerable: false, writable: false, value: self }); // 两个再不同地方创建的元素从测试的角度来看是相等的,我们在列举的时候忽略他们 // Two elements created in two different places should be considered // equal for testing purposes and therefore we hide it from enumeration. Object.defineProperty(element, '_source', { configurable: false, enumerable: false, writable: false, value: source }); // 如果Object有freeze的实现,我们冻结元素和它的属性 if (Object.freeze) { Object.freeze(element.props); Object.freeze(element); } } return element; };
这里首先引出ReactElement的构造函数,注意react内部是使用$$typeof来判断react 元素的类型的。使用_store来记录内部的状态,后面会有用到。为了方便测试框架,_store中定义了不可配置不可枚举的validated属性。类似的,框架内部定义了self和source的副本_self和_source,他们都是不可配置不可枚举不可写的。
一个ReactElement对象的结构相对而言还是比较简单,主要是增加了一个$$typeof属性用于标识该对象是一个React Element类型。REACT_ELEMENT_TYPE在支持Symbol类型的环境中为symbol类型,否则为number类型的数值。与REACT_ELEMENT_TYPE对应的还有很多其他的类型,均存放在shared/ReactSymbols目录中,这里我们可以暂且只关心这一种,后面遇到其他类型再来细看。
创建一个给定类型的ReactElement
// 创建并返回指定类型的reactElement /** * Create and return a new ReactElement of the given type. * See https://reactjs.org/docs/react-api.html#createelement * @param type 表示当前节点的类型,可以是原生的DOM标签字符串,也可以是函数定义组件或者其它类型 * @param config 表示当前节点的属性配置信息 * @param children 表示当前节点的子节点,可以不传,也可以传入原始的字符串文本,甚至可以传入多个子节点 * @returns 返回的是一个ReactElement对象 */ function createElement(type, config, children) { // 属性名 void 0就是undefined var propName = void 0; // Reserved names are extracted // 被保护的名字被屏蔽 用于存放config中的属性,但是过滤了一些内部受保护的属性名 var props = {}; // 将config中的key和ref属性使用变量进行单独保存 var key = null; var ref = null; var self = null; var source = null; // 根据confi的内容来初始化 config为null表示节点没有设置任何相关属性 if (config != null) { // 如果有可用的ref,将其赋值给ref变量 if (hasValidRef(config)) { ref = config.ref; } // 如果有可用的key,将其赋值给key 有效性判断,判断 config.ref !== undefined if (hasValidKey(config)) { key = '' + config.key; } // 再给self赋值 self = config.__self === undefined ? null : config.__self; // 给source赋值 source = config.__source === undefined ? null : config.__source; // Remaining properties are added to a new props object // 用于将config中的所有属性在过滤掉内部受保护的属性名后,将剩余的属性全部拷贝到props对象中存储 // const RESERVED_PROPS = { // key: true, // ref: true, // __self: true, // _ for (propName in config) { // 如果不是保留字的话就复制属性 if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) { props[propName] = config[propName]; } } } // 子元素会有不止一个,这些将会通过一个属性对象向下传递 由于子节点的数量不限,因此从第三个参数开始,判断剩余参数的长度 // 具有多个子节点则props.children属性存储为一个数组 // Children can be more than one argument, and those are transferred onto // the newly allocated props object. // 复制子元素 // 给props属性添加children属性 // var childrenLength = arguments.length - 2; if (childrenLength === 1) { // 单节点的情况下props.children属性直接存储对应的节点 props.children = children; } else if (childrenLength > 1) { // 多节点的情况下则根据子节点数量创建一个数组 var childArray = Array(childrenLength); for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } { // 冻结子元素列表 if (Object.freeze) { Object.freeze(childArray); } } props.children = childArray; } // Resolve default props // 解析默认属性,如果type上存在默认属性 此处用于解析静态属性defaultProps // 针对于类组件或函数定义组件的情况,可以单独设置静态属性defaultProps // 如果有设置defaultProps,则遍历每个属性并将其赋值到props对象中(前提是该属性在props对象中对应的值为undefined) if (type && type.defaultProps) { var defaultProps = type.defaultProps; // 如果没有属性值,采用type类型默认属性上的默认值 for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } { if (key || ref) { // 这里的type估计是个构造函数对象 // 如果type是个构造函数 var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type; if (key) { // 避免保护参数被错误取到,提供警告 defineKeyPropWarningGetter(props, displayName); } if (ref) { defineRefPropWarningGetter(props, displayName); } } } // 返回创建的元素 type是直接透传的 // key,ref等等都是从config里面解析出来的,props是由config上的参数,type上的参数(如果有的话),children等组合而成 return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props); }
创建一个ReactElement时,首先根据config中的值,依次给key,ref,ref,source赋值,然后将congfig中的其他属性依次赋值给props(前提是非react属性保留字RESERVED_PROPS中的属性,定义在上一篇洞悉细节!react 16.8.6源码分析-2 组件构造与获取调用栈),接下来将children赋值给props。接下来将传入的type上面的默认属性赋值给props。然后针对key和ref这两个属性设置取值报警。最后调用ReactElement来构造元素。这里我们截取一个react组件console后的结果。
react元素克隆 cloneAndReplaceKey
返回一个可以创建指定类型的react元素的函数
/** * Return a function that produces ReactElements of a given type. * See https://reactjs.org/docs/react-api.html#createfactory */ // 克隆并且替换key function cloneAndReplaceKey(oldElement, newKey) { // 其实就是替换调key,其他不变 var newElement = ReactElement(oldElement.type, newKey, oldElement.ref, oldElement._self, oldElement._source, oldElement._owner, oldElement.props); return newElement; } // 克隆并返回一个新的react元素,目标元素将作为起始点 /** * Clone and return a new ReactElement using element as the starting point. * See https://reactjs.org/docs/react-api.html#cloneelement */ function cloneElement(element, config, children) { // 如果element是null或者undefined,抛出不可用的错误 !!(element === null || element === undefined) ? invariant(false, 'React.cloneElement(...): The argument must be a React element, but you passed %s.', element) : void 0; // 属性名 var propName = void 0; // Original props are copied // 复制原始属性 var props = _assign({}, element.props); // Reserved names are extracted // 受保护的属性被单独提取出来 var key = element.key; var ref = element.ref; // self受保护是因为owner受保护 // Self is preserved since the owner is preserved. var self = element._self; // source受保护是因为克隆一个元素并不是一个转译操作,原始的源对真实的父元素来说可能是一个更好的标志 // Source is preserved since cloneElement is unlikely to be targeted by a // transpiler, and the original source is probably a better indicator of the // true owner. var source = element._source; // Owner will be preserved, unless ref is overridden // owner将会被保护,除非ref被复写 var owner = element._owner; if (config != null) { // 如果存在config,那么其中的值将会覆盖刚才定义的变量 if (hasValidRef(config)) { // Silently steal the ref from the parent. // 静默封装从父元素存底来的ref ref = config.ref; // 修改owner owner = ReactCurrentOwner.current; } if (hasValidKey(config)) { key = '' + config.key; } // Remaining properties override existing props // 剩下的属性将会复现现存的属性 var defaultProps = void 0; if (element.type && element.type.defaultProps) { // element.type上的默认属性赋值给defaultProps defaultProps = element.type.defaultProps; } // 属性复制 for (propName in config) { // 如果该属性是config自有的并且不是react的保留属性 if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) { // 如果config中没有值并且默认属性的值存在就从默认属性中赋值 if (config[propName] === undefined && defaultProps !== undefined) { // Resolve default props props[propName] = defaultProps[propName]; } else { // 否则复制config中的值 props[propName] = config[propName]; } } } } // Children can be more than one argument, and those are transferred onto // the newly allocated props object. // 复制子元素,逻辑类似先前 // children挂在props上,透传 var childrenLength = arguments.length - 2; if (childrenLength === 1) { props.children = children; } else if (childrenLength > 1) { var childArray = Array(childrenLength); for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } props.children = childArray; } return ReactElement(element.type, key, ref, self, source, owner, props); }
这里的核心逻辑是将原始元素内部的_source,_self,_owner等属性依次赋给source,self,owner,将它们传给ReactElement,内部还进行了属性复制,子元素复制等等操作。
reactElement元素验证与元素遍历池上下文维护
// 判断一个对象是否是react元素 /** * 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 */ // 首先是对象,其次不是null,再次$$typeoff为REACT_ELEMENT_TYPE function isValidElement(object) { return typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE; } var SEPARATOR = '.'; var SUBSEPARATOR = ':'; // 提取并且包裹key,使他可以用为reactid /** * Escape and wrap key so it is safe to use as a reactid * * @param {string} key to be escaped. * @return {string} the escaped key. */ // 替换key function escape(key) { var escapeRegex = /[=:]/g; var escaperLookup = { '=': '=0', ':': '=2' }; var escapedString = ('' + key).replace(escapeRegex, function (match) { return escaperLookup[match]; }); // 开头贴个$,=和冒号分别变成=0和=2 return '$' + escapedString; } /** * TODO: Test that a single child and an array with one item have the same key * pattern. */ // 关于映射的警告 // 控制这种报错只出现一次 var didWarnAboutMaps = false; // 匹配一个或多个/符号 给所有的/符号加一个/ var userProvidedKeyEscapeRegex = /\/+/g; function escapeUserProvidedKey(text) { // $&表示之前匹配中的串 return ('' + text).replace(userProvidedKeyEscapeRegex, '$&/'); } // // 维护一个池子 这玩意儿感觉是共用的,每次调用的时候把函数往下传,或者返回一个空的 var POOL_SIZE = 10; var traverseContextPool = []; // 获得合并的传递的上下文 // mapResult其实是处理过后的子元素的数组 function getPooledTraverseContext(mapResult, keyPrefix, mapFunction, mapContext) { // 如果有上下文,返回最后一个 if (traverseContextPool.length) { var traverseContext = traverseContextPool.pop(); // 将相应的值改成传入的值 traverseContext.result = mapResult; traverseContext.keyPrefix = keyPrefix; traverseContext.func = mapFunction; traverseContext.context = mapContext; traverseContext.count = 0; return traverseContext; } else { // 否则根据入参返回一个新的 return { result: mapResult, keyPrefix: keyPrefix, func: mapFunction, context: mapContext, count: 0 }; } } // 释放一个上下文,小于上限的话就往池子里push function releaseTraverseContext(traverseContext) { traverseContext.result = null; traverseContext.keyPrefix = null; traverseContext.func = null; traverseContext.context = null; traverseContext.count = 0; // 小于上限的话就往里推 if (traverseContextPool.length < POOL_SIZE) { traverseContextPool.push(traverseContext); } }
判断一个元素是否是react元素,核心是根据$$typeof这个属性来判断,然后是escape函数,react内部通过这个函数生成安全的reactid,将=和:分别替换为=0和=2,然后将开头拼接上$成为reactid。之后定义了一个数组,作为遍历元素时的上下文堆栈,然后定义getPooledTraverseContext,来获取遍历元素时的上下文。
参考文章:
洞悉细节!react 16.8.6源码分析-2 组件构造与获取调用栈 https://blog.csdn.net/xiaohulidashabi/article/details/106896357
仓库地址:react16.8.3源码注释仓库
React16源码解读:开篇带你搞懂几个面试考点 https://www.cnblogs.com/tangshiwei/p/12100306.html
React源码解析(一):这些React的API你都知道吗 https://zhuanlan.zhihu.com/p/84962848
React 16 源码瞎几把解读 【二】 react组件的解析过程 https://www.cnblogs.com/JhoneLee/p/9482911.html
转载本站文章《React16源码分析(2):react.development.js源码注释》,
请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/jsBase/2016_0517_7829.html