写在前面 计划用半年的时间去深入 React
源码并记录下来,本文是系列文章第一章,前面大多数会以功能为主,不会涉及太多事务机制与流程,后半部分以架构、流程为主。这个是一个水到渠成的事情。看的越多,对其理解的广度就越大,深度也随之沉淀,在深入的同时站在作者的角度去思考,能够脱离源码照葫芦画瓢,才能算理解,读懂本身源码并不重要。可能没有什么休息时间,但是会尽量挤出来完善文章,也算是一种兴趣与习惯。
起源 2011 年,前端工程师 Jordan Walke
创建了ReactJS的早期原型FaxJS。
时间轴
,
从入口开始 时至今日(2019.9.28),五个小时前React
已经将版本更新到 16.10.0
了,预计大半年内将步入17大版本。希望在系列文章完结之后更新(免得我又得看一遍)。
React 与 Vue 的源码相同的使用 Facebook 开源的 Flow 静态类型检查工具,为什么要用 Flow 而不用 Typescript ? 原因可能是 React 诞生的时间较早,那时候还没有 Typescript,后来也由于 Typescript 15年被社区广泛接受才火起来。还一个原因是 Flow 没有 Typescript 那么“严格”,所有的检查都是可选的。
fork/clone/open三部曲,找到 packages/react/src/React.js
,剔除注释和空白行的源码还不到一百行,这个入口文件集成了所有的api暴露出去。
React中的源码与React-DOM分离,所以在packages/React内很多只是“形”上的API
import ReactVersion from '../../shared/ReactVersion' ;import { REACT_FRAGMENT_TYPE, REACT_PROFILER_TYPE, REACT_STRICT_MODE_TYPE, REACT_SUSPENSE_TYPE, REACT_SUSPENSE_LIST_TYPE, } from '../../shared/ReactSymbols' ;
最顶部导入了React当前版本号,ReactSymbols
文件管理着全局的 React
功能组件 Symbol 标志
Component import {Component, PureComponent} from './ReactBaseClasses' ;
Component
和 PureComponent
组件都是经常用的, 猜也能猜到都是定义一些初始化方法。
function Component (props, context, updater ) { this .props = props; this .context = context; this .refs = emptyObject; this .updater = updater || ReactNoopUpdateQueue; } Component.prototype.isReactComponent = {}; Component.prototype.setState = function (partialState, callback ) { invariant( typeof partialState === 'object' || typeof partialState === 'function' || partialState == null , 'setState(...): takes an object of state variables to update or a ' + 'function which returns an object of state variables.' , ); this .updater.enqueueSetState(this , partialState, callback, 'setState' ); }; Component.prototype.forceUpdate = function (callback ) { this .updater.enqueueForceUpdate(this , callback, 'forceUpdate' ); };
定义了Component类的 setState
和 forceUpdate
方法,以便在组件实例化后调用,将当前的props,context,refs进行绑定,并初始化更新。
每个组件内部都有一个 updater
,被用来驱动state更新的工具对象,执行更新队列,没传入updater时,this.updater
默认为 ReactNoopUpdateQueue
,但是它没什么意义,只是做警告用的。
const ReactNoopUpdateQueue = { isMounted: function (publicInstance ) { return false ; }, enqueueForceUpdate: function (publicInstance, callback, callerName ) { warnNoop(publicInstance, 'forceUpdate' ); }, enqueueReplaceState: function (publicInstance, completeState, callback, callerName ) { warnNoop(publicInstance, 'replaceState' ); }, enqueueSetState: function (publicInstance, partialState, callback, callerName ) { warnNoop(publicInstance, 'setState' ); }, };
isMounted
在组件未挂载的情况下isMounted
一直会返回 false
,例如在 constructor
里调用 setState
或者组件已卸载/未使用,其他方法的作用是在开发环境下警告用户不要在constructor
内调用this原型上的方法。因为实际上真正的 updater
都是在 renderer 后注入的。真正的updater:
const classComponentUpdater = { isMounted, enqueueSetState(inst, payload, callback) { const fiber = getInstance(inst); const currentTime = requestCurrentTimeForUpdate(); const suspenseConfig = requestCurrentSuspenseConfig(); const expirationTime = computeExpirationForFiber( currentTime, fiber, suspenseConfig, ); const update = createUpdate(expirationTime, suspenseConfig); update.payload = payload; if (callback !== undefined && callback !== null ) { if (__DEV__) { warnOnInvalidCallback(callback, 'setState' ); } update.callback = callback; } enqueueUpdate(fiber, update); scheduleWork(fiber, expirationTime); }, }
setState的任务调度以这种形式发出的,另外 与forceUpdate 、 replaceState 也差不多。
那上面提到的ExpirationTime是什么?
ExpirationTime ExpirationTime是一个“保险”,为防止某个update因为优先级的原因一直被打断而未能执行。React会设置一个ExpirationTime,当时间到了ExpirationTime的时候,如果某个update还未执行的话,React将会强制执行该update,这就是ExpirationTime的作用。它有两种计算方法,一种computeInteractiveExpiration
同步更新,与 computeAsyncExpiration
返回异步更新的expirationTime
export const Sync = MAX_SIGNED_31_BIT_INT; export const Batched = Sync - 1 ; const UNIT_SIZE = 10 ;const MAGIC_NUMBER_OFFSET = Batched - 1 ; function msToExpirationTime (ms ) { return MAGIC_NUMBER_OFFSET - (ms / UNIT_SIZE | 0 ); } function ceiling (num: number, precision: number ): number { return (((num / precision) | 0 ) + 1 ) * precision; } function computeExpirationBucket (currentTime, expirationInMs, bucketSizeMs ): ExpirationTime { return ( MAGIC_NUMBER_OFFSET - ceiling(MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE, bucketSizeMs / UNIT_SIZE) ); } export const LOW_PRIORITY_EXPIRATION = 5000 ;export const LOW_PRIORITY_BATCH_SIZE = 250 ;export function computeAsyncExpiration (currentTime: ExpirationTime ): ExpirationTime { return computeExpirationBucket( currentTime, LOW_PRIORITY_EXPIRATION, LOW_PRIORITY_BATCH_SIZE, ); } export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150 ;export const HIGH_PRIORITY_BATCH_SIZE = 100 ;export function computeInteractiveExpiration (currentTime: ExpirationTime ) { return computeExpirationBucket( currentTime, HIGH_PRIORITY_EXPIRATION, HIGH_PRIORITY_BATCH_SIZE, ); } export function computeSuspenseExpiration ( currentTime: ExpirationTime, timeoutMs: number, ): ExpirationTime { return computeExpirationBucket( currentTime, timeoutMs, LOW_PRIORITY_BATCH_SIZE, ); }
通过performance.now()
生成 currentTime,当不支持performance
时转利用Date.now()
,这个值不用太过关注,只需要理解成时间戳即可。
const now1 = performance.now(); const now2 = performance.now(); const now3 = performance.now(); const now4 = performance.now(); const now5 = performance.now(); const now6 = performance.now(); const now7 = performance.now(); const now8 = performance.now(); 1073741821 - (((1073741821 - now1 + 500 ) / 25 ) | 0 ) * 25 - 25 ; 1073741821 - (((1073741821 - now2 + 500 ) / 25 ) | 0 ) * 25 - 25 ; 1073741821 - (((1073741821 - now3 + 500 ) / 25 ) | 0 ) * 25 - 25 ; 1073741821 - (((1073741821 - now4 + 500 ) / 25 ) | 0 ) * 25 - 25 ; 1073741821 - (((1073741821 - now5 + 500 ) / 25 ) | 0 ) * 25 - 25 ; 1073741821 - (((1073741821 - now6 + 500 ) / 25 ) | 0 ) * 25 - 25 ; 1073741821 - (((1073741821 - now7 + 500 ) / 25 ) | 0 ) * 25 - 25 ; 1073741821 - (((1073741821 - now8 + 500 ) / 25 ) | 0 ) * 25 - 25 ; 1073741821 - (((1073741821 - now1 + 50 ) / 10 ) | 0 ) * 10 - 10 ; 1073741821 - (((1073741821 - now2 + 50 ) / 10 ) | 0 ) * 10 - 10 ; 1073741821 - (((1073741821 - now3 + 50 ) / 10 ) | 0 ) * 10 - 10 ; 1073741821 - (((1073741821 - now4 + 50 ) / 10 ) | 0 ) * 10 - 10 ; 1073741821 - (((1073741821 - now5 + 50 ) / 10 ) | 0 ) * 10 - 10 ; 1073741821 - (((1073741821 - now6 + 50 ) / 10 ) | 0 ) * 10 - 10 ; 1073741821 - (((1073741821 - now7 + 50 ) / 10 ) | 0 ) * 10 - 10 ; 1073741821 - (((1073741821 - now8 + 50 ) / 10 ) | 0 ) * 10 - 10 ;
通过规律,可以看到LOW优先级任务时,区间<25的,得到的都是同一个值,而HIGH高优先级任务的区间为10,单位为毫秒,这个有什么用呢?
如果触发了多次事件,每次难道都要丢enqueueUpdate里立即调度?React让两个相近(25ms内)的update得到相同的expirationTime,它可以将多次事件分批打包丢入 enqueueUpdate
里,假如我在24ms内触发了两个事件,那么React会将他们丢入同一批车
,目的就是让这两个update自动合并成一个Update
,并且只会触发一次更新,从而达到批量更新的目的。
从之前的代码看 computeInteractiveExpiration传入的是150、100,computeAsyncExpiration传入的是5000、250,前者的优先级更高,而过期执行时间为交互事件为 100/UNIT_SIZE = 10,异步事件则为 250/UNIT_SIZE = 25, 佐证了事实。
PureComponent function ComponentDummy ( ) {}ComponentDummy.prototype = Component.prototype; function PureComponent (props, context, updater ) { this .props = props; this .context = context; this .refs = emptyObject; this .updater = updater || ReactNoopUpdateQueue; } const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());pureComponentPrototype.constructor = PureComponent; Object .assign(pureComponentPrototype, Component.prototype);pureComponentPrototype.isPureReactComponent = true ;
与 Component
类相同属性的 PureComponent
类有所不同,首先创建了一个空函数 ComponentDummy
,并将通过共享原型继承的方式将实例原型指向了 Component
的原型,其构造函数指定为PureComponent
。其实就是在外面套了一层 pureComponentPrototype
的 Component
。
createRef import {createRef} from './ReactCreateRef' ;
export function createRef ( ): RefObject { const refObject = { current: null , }; if (__DEV__) { Object .seal(refObject); } return refObject; }
返回一个refObject,其current的属性在组件挂载时进行关联,与react-dom强相关,后面再了解。现在只需要知道它很简单。
Children import {forEach, map, count, toArray, only} from './ReactChildren' ;
React 将 children 的 API 暴露出来,这里最常使用的应该是 React.Children.map
和 React.Children.forEach
。
Children.map 适用于替代 this.props.children.map
,因为这种写法通常用来嵌套组件,但是如果嵌套的是一个函数就会报错。而 React.Children.map
则不会。当需要写一个 Radio
组件需要依赖其父组件 RadioGroup
的 props
值,那么this.props.children.map
配合 cloneElement
简直不能再完美。还可以用来过滤某些组件。
React.cloneElement(props.children, { name: props.name }) render(){ return ( <div> { React.Children.map(children, (child, i) => { if ( i < 1 ) return ; return child; }) } </div> ) }
mapChildren function mapChildren (children, func, context ) { if (children == null ) { return children; } const result = []; mapIntoWithKeyPrefixInternal(children, result, null , func, context); return result; }
mapIntoWithKeyPrefixInternal 第一步,如果子组件为null直接不处理。正常情况下申明一个数组,进行加工。
function mapIntoWithKeyPrefixInternal (children, array, prefix, func, context ) { let escapedPrefix = '' ; if (prefix != null ) { escapedPrefix = escapeUserProvidedKey(prefix) + '/' ; } const traverseContext = getPooledTraverseContext( array, escapedPrefix, func, context, ); traverseAllChildren(children, mapSingleChildIntoContext, traverseContext); releaseTraverseContext(traverseContext); }
getPooledTraverseContext 与 releaseTraverseContext 第二步,处理key的暂时不用管。最终通过 getPooledTraverseContext
到对象池里取一个对象,给 traverseAllChildren
进行处理,结束的时候通过 releaseTraverseContext
reset所有属性放回去,做到复用,避免了一次性创建大量对象和释放对象消耗性能造成的内存抖动。
getPooledTraverseContext 用来取。 releaseTraverseContext 用来清空后放回
const POOL_SIZE = 10 ;const traverseContextPool = [];function getPooledTraverseContext ( mapResult, keyPrefix, mapFunction, mapContext, ) { if (traverseContextPool.length) { const 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 , }; } } 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); } }
traverseAllChildren/traverseAllChildrenImpl 第三步,最重要的一步,在取出一个待复用对象后,traverseAllChildren
判断为null就没必要处理了。直接 return。
function traverseAllChildren (children, callback, traverseContext ) { if (children == null ) { return 0 ; } return traverseAllChildrenImpl(children, '' , callback, traverseContext); }
function traverseAllChildrenImpl ( children, // children nameSoFar, // 父级 key callback, // 如果是可渲染节点 traverseContext, // 对象池复用对象 ) { const type = typeof children; if (type === 'undefined' || type === 'boolean' ) { children = null ; } let invokeCallback = false ; if (children === null ) { invokeCallback = true ; } else { switch (type) { case 'string' : case 'number' : invokeCallback = true ; break ; case 'object' : switch (children.$$typeof ) { case REACT_ELEMENT_TYPE: case REACT_PORTAL_TYPE: invokeCallback = true ; } } } if (invokeCallback) { callback( traverseContext, children, nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0 ) : nameSoFar, ); return 1 ; } let child; let nextName; let subtreeCount = 0 ; const nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR; if (Array .isArray(children)) { for (let i = 0 ; i < children.length; i++) { child = children[i]; nextName = nextNamePrefix + getComponentKey(child, i); subtreeCount += traverseAllChildrenImpl( child, nextName, callback, traverseContext, ); } } else { const iteratorFn = getIteratorFn(children); if (typeof iteratorFn === 'function' ) { const iterator = iteratorFn.call(children); let step; let ii = 0 ; while (!(step = iterator.next()).done) { child = step.value; nextName = nextNamePrefix + getComponentKey(child, ii++); subtreeCount += traverseAllChildrenImpl( child, nextName, callback, traverseContext, ); } } else if (type === 'object' ) { let addendum = '' ; const childrenString = '' + children; invariant( false , 'Objects are not valid as a React child (found: %s).%s' , childrenString === '[object Object]' ? 'object with keys {' + Object .keys(children).join(', ' ) + '}' : childrenString, addendum, ); } } return subtreeCount; }
const Demo = ({ children } ) => { console .log(React.Children.map(children, c => [[[[c, c]]]])); return ( children ); }; const Children = ({ msg } ) => ( <span> { msg } </span> );
上面函数的核心作用就是通过把传入的 children 数组通过遍历摊平成单个节点,其中迭代的所有callback都是 mapSingleChildIntoContext
。
mapSingleChildIntoContext function mapSingleChildIntoContext (bookKeeping, child, childKey ) { const {result, keyPrefix, func, context} = bookKeeping; let mappedChild = func.call(context, child, bookKeeping.count++); if (Array .isArray(mappedChild)) { mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c); } else if (mappedChild != null ) { if (isValidElement(mappedChild)) { mappedChild = cloneAndReplaceKey( mappedChild, keyPrefix + (mappedChild.key && (!child || child.key !== mappedChild.key) ? escapeUserProvidedKey(mappedChild.key) + '/' : '' ) + childKey, ); } result.push(mappedChild); } }
最终的逻辑又回到了 mapIntoWithKeyPrefixInternal
,通过递归调用使返回的数组结果展开铺平。
整体流程 大概整体流程
`mapChildren` ===================> `mapIntoWithKeyPrefixInternal` ==================> `getPooledTraverseContext` (复用对象池) /\ || /||\ || || || || \||/ || \/ ||Yes `traverseAllChildren`(遍历children树) || || || || || \||/ || \/ No || (children是数组又会重新递归执行) `releaseTraverseContext` (释放对象池)<=====`mapSingleChildIntoContext` (铺平result)<=============`traverseAllChildrenImpl`
Children.forEach 相比 map
,forEachChildren
则简单的多,因为不用去返回一个新的结果,只需要对children做遍历,
function forEachChildren (children, forEachFunc, forEachContext ) { if (children == null ) { return children; } const traverseContext = getPooledTraverseContext( null , null , forEachFunc, forEachContext, ); traverseAllChildren(children, forEachSingleChild, traverseContext); releaseTraverseContext(traverseContext); } function forEachSingleChild (bookKeeping, child, name ) { const {func, context} = bookKeeping; func.call(context, child, bookKeeping.count++); }
Children.count function countChildren (children ) { return traverseAllChildren(children, () => null , null ); }
用来计算children的个数,平时用的较少。和前面2个方法的行为差不多,预置的callback也不会进行任何处理。最终返回当前children的子元素,并不会向下递归查找。
Children.toArray function toArray (children ) { const result = []; mapIntoWithKeyPrefixInternal(children, result, null , child => child); return result; }
用来将children
转化成普通的数组,原理和 mapChildren 一样,可以用来将传入的children
重新进行排序。
class Sort extends React .Component { render () { const children = React.Children.toArray(this .props.children); return <p>{children.sort((a,b)=>a-b).join('-')}</p> } } <Sort> {2 }{5 }{8 }{4 }{9 } </Sort> / / view 2-4-5-8-9
Children.only function onlyChild (children ) { invariant( isValidElement(children), 'React.Children.only expected to receive a single React element child.' , ); return children; }
校验children是否为ReactElement,是则返回,否则报错。可以用来制做一个只接受一个 children
的 <Single>
组件。
class Single extends Component { render(){ return React.Children.only(this .props.children) } } function App ( ) { return ( <Single> <div>first</div> <div>second</ div> {} </Single> ) }
ReactElement import { createElement, createFactory, cloneElement, isValidElement, jsx, } from './ReactElement' ; const React = { createElement: __DEV__ ? createElementWithValidation : createElement, cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement, createFactory: __DEV__ ? createFactoryWithValidation : createFactory, isValidElement: isValidElement }
开发环境下会自动validator,切到packages/react/src/ReactElement.js
,大纲如下
hasValidRef ------------------------------- 判断是否有合理的ref hasValidKey ------------------------------- 判断是否有合理的key defineRefPropWarningGetter ---------------- 锁定props.ref defineKeyPropWarningGetter ---------------- 锁定props.key ReactElement ------------------------------ 转化ReactElement jsx --------------------------------------- 使用jsx方式创建Element jsxDEV ------------------------------------ 使用jsx方式创建Element(DEV) createElement ----------------------------- 创建并返回指定类型的ReactElement createFactory ----------------------------- 工厂模式createElement构造器 cloneAndReplaceKey ------------------------ 替换新key cloneElement ------------------------------ 克隆Element isValidElement ---------------------------- 判定是否ReactElement
createElement 这个方法大家耳熟能详。React用的最多的方法,没有之一。它负责React内所有元素节点的创建及初始化。
该方法接受三个参数 type, config, children
,type
是标签或者组件的名称,div/span/ul,对应的自定义Component首字母一定是大写。config
则包含所有的属性配置,children
代表子节点。
export function createElement (type, config, children ) { let propName; const props = {}; let key = null ; let ref = null ; let self = null ; let source = null ; if (config != null ) { if (hasValidRef(config)) { ref = config.ref; } if (hasValidKey(config)) { key = '' + config.key; } self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; for (propName in config) { if ( hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName) ) { props[propName] = config[propName]; } } } 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; } if (type && type.defaultProps) { const defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined ) { props[propName] = defaultProps[propName]; } } } if (__DEV__) { if (key || ref) { const displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type; if (key) { defineKeyPropWarningGetter(props, displayName); } if (ref) { defineRefPropWarningGetter(props, displayName); } } } return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props, ); }
const ReactCurrentOwner = { current: (null : null | Fiber), };
createElement
仅仅起到为ReactElement
加工前过滤属性的作用。
create ReactElement const ReactElement = function (type, key, ref, self, source, owner, props ) { const element = { $$typeof : REACT_ELEMENT_TYPE, type: type, key: key, ref: ref, props: props, _owner: owner, }; if (__DEV__) { element._store = {}; Object .defineProperty(element._store, 'validated' , { configurable: false , enumerable: false , writable: true , value: false , }); Object .defineProperty(element, '_self' , { configurable: false , enumerable: false , writable: false , value: self, }); Object .defineProperty(element, '_source' , { configurable: false , enumerable: false , writable: false , value: source, }); if (Object .freeze) { Object .freeze(element.props); Object .freeze(element); } } return element; };
ReactElement
将其属性做二次处理,等待被渲染成DOM,在平常开发我们通过console.log
打印出自定义Component 属性与element
一致。
createFactory 工厂模式的 createElement
,通过预置 type
参数创建指定类型的节点。
var child1 = React.createElement('li' , null , 'First Text Content' );var child2 = React.createElement('li' , null , 'Second Text Content' );var factory = React.createFactory("li" );var child1 = factory(null , 'First Text Content' );var child2 = factory(null , 'Second Text Content' );var root = React.createElement('ul' , {className : 'list' }, child1, child2);ReactDOM.render(root,document .getElementById('root' ));
export function createFactory (type ) { const factory = createElement.bind(null , type); factory.type = type; return factory; }
cloneElement 通过传入新的element与props及children,得到clone后的一个新元素。element
为cloneElement,config
是 newProps,可以重新定义 key
和 ref
,children
子节点。 整体方法与createElement大致相同。
export function cloneElement (element, config, children ) { invariant( !(element === null || element === undefined ), 'React.cloneElement(...): The argument must be a React element, but you passed %s.' , element, ); let propName; const props = Object .assign({}, element.props); let key = element.key; let ref = element.ref; const self = element._self; const source = element._source; let owner = element._owner; if (config != null ) { if (hasValidRef(config)) { ref = config.ref; owner = ReactCurrentOwner.current; } if (hasValidKey(config)) { key = '' + config.key; } let defaultProps; if (element.type && element.type.defaultProps) { defaultProps = element.type.defaultProps; } for (propName in config) { if ( hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName) ) { if (config[propName] === undefined && defaultProps !== undefined ) { props[propName] = defaultProps[propName]; } else { props[propName] = config[propName]; } } } } 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; } return ReactElement(element.type, key, ref, self, source, owner, props); }
cloneAndReplaceKey 顾名思义,与 cloneElement
名字上虽然差不多,但实际返回的是通过ReactElement传入newKey重新创建的旧Element。
export function cloneAndReplaceKey (oldElement, newKey ) { const newElement = ReactElement( oldElement.type, newKey, oldElement.ref, oldElement._self, oldElement._source, oldElement._owner, oldElement.props, ); return newElement; }
isValidElement 也很简单,通过判断 $$typeof
是否为内置的ReactElement类型。
export function isValidElement (object ) { return ( typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE ); }
jsx/jsxDEV 没怎么使用过这个API,猜测应该是通过 react-dom
转化后使用的创建语法。
从代码逻辑上看,与 createElement
大致形同,jsxDEV
比 jsx
多了两个能自定义的属性,source
和 self
,按照代码注释,是为了防止出现 <div key="Hi" {...props} />
情况中 key
比 props
先定义,导致被覆盖的情况。将对<div {...props} key="Hi" />
之外的所有情况统一使用 jsxDEV
来强行赋值 key 与 ref。
export function jsxDEV (type, config, maybeKey, source, self ) { let propName; const props = {}; let key = null ; let ref = null ; if (maybeKey !== undefined ) { key = '' + maybeKey; } if (hasValidKey(config)) { key = '' + config.key; } if (hasValidRef(config)) { ref = config.ref; } for (propName in config) { if ( hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName) ) { props[propName] = config[propName]; } } if (type && type.defaultProps) { const defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined ) { props[propName] = defaultProps[propName]; } } } if (key || ref) { const displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type; if (key) { defineKeyPropWarningGetter(props, displayName); } if (ref) { defineRefPropWarningGetter(props, displayName); } } return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props, ); }
defineRefPropWarningGetter/defineKeyPropWarningGetter key
是用来优化React渲染速度的,而 ref
是用来获取到React渲染后的真实DOM节点。正常情况下应该将这两个属性置之世外,仿佛这两个属性都应该是React本身的API。所以这两个方法就是用来禁止获取和设置的。
let specialPropKeyWarningShown, specialPropRefWarningShown;function defineKeyPropWarningGetter (props, displayName ) { const warnAboutAccessingKey = function ( ) { if (!specialPropKeyWarningShown) { specialPropKeyWarningShown = true ; warningWithoutStack( 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, ); } }; warnAboutAccessingKey.isReactWarning = true ; Object .defineProperty(props, 'key' , { get : warnAboutAccessingKey, configurable: true, }); }
当在组件内尝试 console.log(props.key)
的时候,就会发现报错。
两个方法逻辑一模一样,就不写粘贴两遍了。
hasValidRef/hasValidKey 这两个方法差不多,在开发模式下多了一个校验,通过 Object.prototype.hasOwnProperty
检查当前对象属性上是否存在 ref/key
,并获取其访问器函数 get
,如果事先被defineKeyPropWarningGetter/defineRefPropWarningGetter
锁定则 getter.isReactWarning
就必然为 true
(注意锁定方法调用的时机)。
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 ; }