一、JSX语法转换到Js语法
从 JSX 转换到 JS 会用到React.createElement()
,所以先熟悉下 JSX 到 JS 的转换。
这边是 JSX 语法:
<div id='one' class='two'> <span id="spanOne">this is spanOne</span> <span id="spanTwo">this is spanTwo</span></div>
这边是转化成的 js 语法:
React.createElement( "div", { id: "one", class: "two" }, React.createElement( "span", { id: "spanOne" }, "this is spanOne"), React.createElement("span", { id: "spanTwo" }, "this is spanTwo"));
React.createElement("标签名","Object,包含div的props",'children子节点1','children子节点2','...')
这边是 JSX 语法:
function Div(){ }<Div id='one' class='two'> <span id="spanOne">this is spanOne</span> <span id="spanTwo">this is spanTwo</span></Div>
这边是转化成的 js 语法:
React.createElement(Div, {} , xxx );
如果标签名大写,则表示组件 Div(也就是function
),小写表示 html 的标签 <div>
也就是说:自定义的组件必须大写字母开头
二、React.createElement()
源码地址:https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react/src/ReactElement.js
作用:
创建React.Element
,示例请看一、JSX语法转换到Js语法
源码:
//注意:react只写了3个参数,实际上,从第三个参数往后都是childrenexport 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; //赋给标签的props不为空时 if (config != null) { if (hasValidRef(config)) { ref = config.ref; } if (hasValidKey(config)) { //防止是Number key = '' + config.key; } //__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 for (propName in config) { //如果config中的属性不是标签原生属性,则放入props对象中 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); //依次将children push进array中 for (let i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } //如果是development环境的话 if (__DEV__) { //冻结array //未在微信发表 //https://www.jianshu.com/p/91e5dc520c0d?utm_campaign=hugo&utm_medium=reader_share&utm_content=note&utm_source=weixin-friends&from=singlemessage&isappinstalled=0 if (Object.freeze) { Object.freeze(childArray); } } //开发中写的this.props.children就是子元素的集合 props.children = childArray; } // Resolve default props //为传入的props设置默认值,比如: //class Comp extends React.Component{ // static defaultProps = { // aaa: 'one', // bbb: () => {}, // ccc: {}, // }; // // } if (type && type.defaultProps) { const defaultProps = type.defaultProps; for (propName in defaultProps) { //如果props数组中未设值,则设置默认值(注意:null也算设置了值) if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } if (__DEV__) { //一旦ref或key存在 if (key || ref) { //如果type是组件的话,赋值displayName const displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type; //可不看 if (key) { defineKeyPropWarningGetter(props, displayName); } if (ref) { defineRefPropWarningGetter(props, displayName); } } } return ReactElement( type, //'div' key, //null ref, //null self, //null source, //null ReactCurrentOwner.current, //null或Fiber props, //自定义的属性、方法,注意:props.children=childArray );}
解析:
(1)hasValidRef()
作用:
判断是否设置了ref的属性,true
有,false
没有
源码:
//判断是否设置了ref的属性,true有,false没有function hasValidRef(config) { //如果是development环境的话 if (__DEV__) { //如果config中存在ref属性的话 //在jQuery中 .call/.apply的更大作用是绑定this if (hasOwnProperty.call(config, 'ref')) { //Object.getOwnPropertyDescriptor() es5 //Object.getOwnPropertyDescriptors() es6 //https://blog.csdn.net/qq_30100043/article/details/53424963 //返回对象config的属性ref 的get对象 const getter = Object.getOwnPropertyDescriptor(config, 'ref').get; //如果isReactWarning,则忽略ref属性,返回false if (getter && getter.isReactWarning) { return false; } } } //<div ref={this.optionsTEchart} ></div> return config.ref !== undefined;}
① 注意:__DEV__
表示测试环境,是供React
内部测试的,可以不看,我简单地解释了下
② 在jQuery
中fn.call(xxx,a1,a2,...)
或fn.apply(xxx,array)
的更大作用是绑定this
③ Object.getOwnPropertyDescriptor()
的作用是返回某个对象属性的描述对象( descriptor )
比如:
var obj = { p: 'a' };Object.getOwnPropertyDescriptor(obj, 'p')//返回// Object { value: "a",// writable: true,// enumerable: true,// configurable: true}
关于Object.getOwnPropertyDescriptor()
和Object.getOwnPropertyDescriptors()
的区别,请看:https://blog.csdn.net/qq_30100043/article/details/53424963
(2)hasValidKey
作用:
判断是否设置了key
,同hasValidRef
,不解释了
源码:
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;}
(3)虽然React.createElement()
只传三个参数,但从第三个参数开始,利用arguments
来获取剩下的参数
(4)Object.freeze()
使用Object.freeze()
冻结的对象是最严格的防篡改级别,既不可扩展,也是密封的,不可修改属性。
对于 JS 库作者而言,冻结对象可防止有人修改库的核心对象。
关于 JS 冻结对象的方法,请看:JS红皮书解读之防篡改对象
(5)最后是 return 了ReactElement()
方法,注意props
中的children
属性就是React
组件的children
react组件的children属性不会被覆盖:
父组件:
return( <DashBoard children={'bbbb'}> aaaa </DashBoard>)
子组件:
console.log(this.props)
结果:
三、ReactElement()
源码地址:https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react/src/ReactElement.js
作用:
通过工厂模式创建React.Element
对象,你打印一个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, no instanceof check * will work. Instead test $$typeof field against Symbol.for('react.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 */// type, //'div'// key, //null// ref, //null// self, //null// source, //null// ReactCurrentOwner.current, //null或Fiber// props, //自定义的属性、方法,注意:props.children=childArrayconst ReactElement = function(type, key, ref, self, source, owner, props) { const element = { // This tag allows us to uniquely identify this as a React Element //标识element的类型 //因为jsx都是通过createElement创建的,所以ReactElement的类型固定:为REACT_ELEMENT_TYPE //重要!因为react最终渲染到DOM上时,需要判断$$typeof===REACT_ELEMENT_TYPE $$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. //记录创建react.element的组件(this?) _owner: owner, }; 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. //验证flag是不固定的.我们将其放置在一个store上,从而能冻结整个object //这样一旦它们被用在开发环境时,用WeakMap代替 //WeakMap // http://es6.ruanyifeng.com/#docs/set-map 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: false, }); // 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, }); if (Object.freeze) { Object.freeze(element.props); Object.freeze(element); } } return element;};
解析:
(1)通过$$typeof
确保是React.Element
类型,从而渲染到真正的DOM
树上
(2)__DEV__
注释中有提到WeakMap
,
简单说下WeakMap
的作用:
你往WeakMap
上的对象 a 添加数据,对象 b 引用 对象 a,之后对象 b 不引用 对象 a,a 就被垃圾回收,不用WeakMap
的话,即使对象 b 以后不引用对象 a了,a 也不会被垃圾回收,因为强引用是不会触发垃圾回收机制的,需要手动删除,很麻烦。
想更详细地了解的话,可以参考下这篇文章:
http://es6.ruanyifeng.com/#docs/set-map
关于垃圾回收机制,请看:浅谈下垃圾回收机制(1)
(3)该方法比较简单,就是初始化了一个对象,并将其标记为React.Element
对象($$typeof=REACT_ELEMENT_TYPE
)
(完)