【React】源码遨游(一) createElement()
2021/7/19 1:05:12
本文主要是介绍【React】源码遨游(一) createElement(),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
React源码遨游
今天新开的专栏主要围绕着React源码进行学习,切入口主要围绕着React的顶层API(即React)。
createElement()
createElment()
隶属于顶层ApiReact
的 /src/ReactElement.js
中。
首先我们看一下创建组件的代码,然后逐句分析一下。
export function createElement(type, config, children) { let propName; // Reserved names are extracted const props = {}; let key = null; let ref = null; let self = null; let source = null; if (config != null) { if (hasValidRef(config)) { ref = config.ref; if (__DEV__) { warnIfStringRefCannotBeAutoConverted(config); } } if (hasValidKey(config)) { key = '' + config.key; } self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; // Remaining properties are added to a new props object for (propName in config) { if ( hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName) ) { 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 || 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, ); }
方法顶部首先定义了一些变量,先不管他们。
config
是参数传进来的配置信息。首先看如果这个变量不为空,我们会从配置信息中提取哪些要素:
// 如果配置不为空 if (config != null) { // 如果配置中的 ref 有效,则将前面定义的 ref 赋值为配置中的 ref 属性 if (hasValidRef(config)) { ref = config.ref; // 开发环境中 if (__DEV__) { // 如果配置中的 ref 属性是一个字符串,则报一个警告(未来不支持字符串传入 ref ) warnIfStringRefCannotBeAutoConverted(config); } } // 如果配置中的 key 有效,则将前面定义的 key 赋值为配置中的 key属性 if (hasValidKey(config)) { key = '' + config.key; } // 也是之前定义的 self 和 source, 如果配置中的私有变量 __self, __source不为空,则赋值。 self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; // Remaining properties are added to a new props object // 除了上述提到的属性之外,遍历其余属性,赋值到之前定义的 prop 属性中 for (propName in config) { if ( // 属性名要不是保留字 hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName) ) { // 满足条件的属性,赋值到 prop 变量中 props[propName] = config[propName]; } } }
接下来处理children
参数
// 首先确定子元素的数量,因为 children 不一定只有一个 const childrenLength = arguments.length - 2; if (childrenLength === 1) // 如果只有一个,那么就把这个 children 赋值给我们定义的 props 变量的 children 属性即可 props.children = children; } else if (childrenLength > 1) { // 如果大于一个,定义一个children的数组,将所有children都放进去 const childArray = Array(childrenLength); for (let i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } if (__DEV__) { // 如果再开发环境,则冻结这个childArray数组(性能提升) if (Object.freeze) { Object.freeze(childArray); } } // 将子元素数组赋值到 props 变量的 children 属性 props.children = childArray; }
接下来我们来处理第一个参数type
// Resolve default props // 如果 type 参数中的 defaultProps属性不为空 if (type && type.defaultProps) { // 新建一个 defaultProps 变量,赋值为参数 type 的 defaultProps const defaultProps = type.defaultProps; for (propName in defaultProps) { // 如果之前我们定义的 props 中有属性为 undefined,则我们从defaultProps中提取默认值 if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } if (__DEV__) { // 如果 key 或者 ref 不为空 if (key || ref) { // 如果传入的 type 是一个函数,则显示名设置为函数的显示名、名、未知。 // 否则就显示 type 名 const displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type; if (key) { // 如果 key 属性不为空 // 如果通过 props 对象获取 key 时报错 defineKeyPropWarningGetter(props, displayName); } if (ref) { // 如果 ref 不为空 // 如果通过 props 对象获取 ref 时报错 defineRefPropWarningGetter(props, displayName); } } } // 返回 ReactElement return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props, );
看一下最后返回的ReactElement
:
const ReactElement = function(type, key, ref, self, source, owner, props) { const element = { // 这个type能让我们判断这个元素是一个react元素 $$typeof: REACT_ELEMENT_TYPE, // 元素拥有的内构属性的值 type: type, key: key, ref: ref, props: props, // 记录创建元素的拥有者 _owner: owner, }; if (__DEV__) { // 开发模式 // validated是可更改的,所以用一个内部私有变量_store储存,从而可以冻结整个对象。 // 未来可以用一个WeakMap对象来代替 element._store = {}; // 在开发模式下,把validated设置为不可遍历,使测试更简单 Object.defineProperty(element._store, 'validated', { configurable: false, enumerable: false, writable: true, value: false, }); // _self和_source是内部私有变量 Object.defineProperty(element, '_self', { configurable: false, enumerable: false, writable: false, value: self, }); // 在测试环境中不同地方创建的元素应被认为平等,所以将_source定义为不可遍历 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.js
里面的其他方法,说明都在注释中:
function hasValidRef(config) { if (__DEV__) { // 如果是开发模式,查看 config 中定义的 get 方法, 如果 get 中的 isReactWarning // 为真(说明该 ref 有警告,在开发模式不有效,返回false if (hasOwnProperty.call(config, 'ref')) { const getter = Object.getOwnPropertyDescriptor(config, 'ref').get; if (getter && getter.isReactWarning) { return false; } } } // 如果不是开发模式,则直接判断 ref 是否为空 return config.ref !== undefined; }
// key 的验证方式和上述是一样的 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; }
// 定义属性的get方法 function defineKeyPropWarningGetter(props, displayName) { // 这个变量的意思是访问Key的时候是否有警告 const warnAboutAccessingKey = function() { if (__DEV__) { // 如果特殊属性key的警告没有显示过 if (!specialPropKeyWarningShown) { // 将这个flag先置为true specialPropKeyWarningShown = true; // 在开发者工具中输出error 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://reactjs.org/link/special-props)', displayName, ); } } }; // 将这个属性的 isReactWarning 置为 true warnAboutAccessingKey.isReactWarning = true; // 用对象的 defineProperty 方法,定义key的属性,get方法设置为 warnAboutAccessingKey Object.defineProperty(props, 'key', { get: warnAboutAccessingKey, configurable: true, }); }
// 定义 ref 的get方法也是一样的。 function defineRefPropWarningGetter(props, displayName) { const warnAboutAccessingRef = function() { if (__DEV__) { if (!specialPropRefWarningShown) { specialPropRefWarningShown = true; console.error( '%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://reactjs.org/link/special-props)', displayName, ); } } }; warnAboutAccessingRef.isReactWarning = true; Object.defineProperty(props, 'ref', { get: warnAboutAccessingRef, configurable: true, }); }
// 创建一个方法,生产提供类型的ReactElement export function createFactory(type) { const factory = createElement.bind(null, type); // 暴露Factory的type属性,这样就能轻松访问type属性。 // 这不是一个构造方法 factory.type = type; return factory; }
// 克隆元素,并且替换一个新的key export function cloneAndReplaceKey(oldElement, newKey) { const newElement = ReactElement( oldElement.type, newKey, oldElement.ref, oldElement._self, oldElement._source, oldElement._owner, oldElement.props, ); return newElement; }
// 克隆元素方法 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); // 下面和createElement的处理一致 let key = element.key; let ref = element.ref; // self 属性设置为元素的_self,因为owner保持为原来的owner不变 const self = element._self; // source设置为克隆元素的_souce,以更好的溯源 const source = element._source; // Owner也保留之前的owner,而不是被克隆的元素 let owner = element._owner; if (config != null) { if (hasValidRef(config)) { // 从配置中获取 ref, 并且将owner设置为 ReactCurrentOwner.current ref = config.ref; owner = ReactCurrentOwner.current; } // 下述操作和createElement一致 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]; } } } } // 下述操作和createElement一致 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); }
这篇关于【React】源码遨游(一) createElement()的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-18tcpdf可以等待vue动态页面加载完成后再生成pdf吗?-icode9专业技术文章分享
- 2024-11-16Vue3资料:新手入门必读教程
- 2024-11-16Vue3资料:新手入门全面指南
- 2024-11-16Vue资料:新手入门完全指南
- 2024-11-16Vue项目实战:新手入门指南
- 2024-11-16React Hooks之useEffect案例详解
- 2024-11-16useRef案例详解:React中的useRef使用教程
- 2024-11-16React Hooks之useState案例详解
- 2024-11-16Vue入门指南:从零开始搭建第一个Vue项目
- 2024-11-16Vue3学习:新手入门教程与实践指南