React源码--ReactElement学习笔记

464 阅读3分钟

ReactElement是什么?

// s是我们写的jsx
const s = '<div>hello <span>wo<span>rld</span></span></div>'
// 下面是将s转为react语法代码,返回的都是ReactElement
React.createElement("div", null, "hello ",
    React.createElement("span", null, "wo", 
        React.createElement("span", null, "rld")
    )
 );
 // 将react代码转为AST
 

![WechatIMG54.jpeg](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/92c22c86342c434f8983691130d098db~tplv-k3u1fbpfcp-watermark.image)
// ReactElement是描述DOM的js对象也就是虚拟DOM

ReactElement主要使用Object.defineProperty设置属性

Object.defineProperty

语法Object.defineProperty(obj,propkey,descriptor)

描述对象descriptor的属性有哪些
  • configurable:是否可修改属性,若为false则不能修改该属性的任何描述,也不可以删除,设置为true才可以对属性描述进行修改和删除;默认为false;
var a = Object.defineProperty({},'key',{})// 默认configurable没false
/**
以下调用会报错:
Uncaught TypeError: Cannot redefine property: key
    at Function.defineProperty (<anonymous>)
    at <anonymous>:1:8
*/
Object.defineProperty(a,'key',{configurable:true})
delete a.key // 输出false,删除失败

  • enumerable:是否可枚举,默认false.
//继续以上例子
for(var key in a){console.log(key)} // 输出undefined
Object.keys(a) // 输出[]
  • value:属性对应的值
  • writable:是否可以改变value的值,默认为false
//继续以上例子
a.key =1 
console.log(a) // {key: undefined}
  • get:存取描述,返回属性的值,默认为undefined
Object.defineProperty(a,'key1',{
    configurable:true,
    enumerable:true,
    get(){return '2'}
   })
console.log(a.key1) // 2
// 存取描述和value、wriable不能同时使用
Object.defineProperty(a,'key1',{
    configurable:true,
    enumerable:true,
    writable:true
    get(){return '2'}
   })
 /**
VM2292:1 Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>
    at Function.defineProperty (<anonymous>)
    at <anonymous>:1:8
*/


  • set:存取描述,设置属性值,默认返回undefined
var key1Value = '2'
// 存取描述和value、wriable不能同时使用
Object.defineProperty(a,'key1',{
    configurable:true,
    enumerable:true,
    get(){return key1Value},
    set(newV) {key1Value = newV}
   })
数据描述符

如果一个描述符不具有 value、writable、get 和 set 中的任意一个键,那么它将被认为是一个数据描述符。

ref和key均为数据描述符,因此react中判断ref和key是否有效是利用Object.getOwnPropertyDescriptor(a,'key').get来判断

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;
}
Object.freeeze

冻结的对象不能被修改,但被冻结的对象的属性是个对象,那这个属性可以被修改的,因此属性不想被修改也需要被冻结

// ReactElement返回被冻结的对象
if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
  }

属性遍历

  • for...in:循环遍历对象自身和继承的属性,(不包括:enumerable为false、Symbol属性)
for(let prop in config){
    // 自身属性  去掉预设的ref和key
    if(config.hasOwnProperty(prop) && !RESERVED_PROPS.hasOwnProperty(prop)){
      props[prop] = config[prop]
    }
  }
  • Object.keys:循环遍历对象自身的属性(不包括:继承、enumerable为false,Symbol属性)
  Object.keys(config).forEach(prop=>{
    if(!RESERVED_PROPS.hasOwnProperty(prop)){
      props[prop] = config[prop]
    }
  })
  • Object.getOwnPropertyNames:循环遍历对象自身属性,包括enumerable为false的(不包含:继承,Symbol)
Object.getOwnPropertyNames(config).forEach(prop=>{
    if(!RESERVED_PROPS.hasOwnProperty(prop) && Object.getOwnPropertyDescriptor(config,prop).enumerable){
      props[prop] = config[prop]
    }
  })

解构children参数

  • 通过ES6的rest参数方式获取children的参数
 // 获取children
  if(res.length>0){
    if(res.length===1)props.children = res[0]
    else props.children = [...res]
  }

  • 通过arguments获取children
  // 通过arguments获取children
  // 去掉头两个参数
  const children = [].slice.call(arguments,2)
  if(children.length>0){
    if(children.length===1)props.children = children[0]
    else props.children = [...children]
  }

手写源码


// 其他属性略

const REACT_ELEMENT_TYPE = typeof Symbol === 'function' && Symbol.for?Symbol.for('react.element'):0xeac7
/**
 * ReactElement 
 * 工厂函数,创建一个React element,它不是一个class,所以不能用new去调用,而且instanceof检查类型也是不起作用的,
 * 若想检查类型需要用$$type进行判断,react源码中是是使用的Symbol.for('react.element'),
 * 可以使用React.isValidElement来判断
 * 
 *  */ 
const MyReactElement =function(type,key,ref,props){
  const element = {
    $$typeof:REACT_ELEMENT_TYPE, // react中使用的是Symbol,确定唯一性
    type,
    key,
    ref,
    props
  }
  return element;
}

const RESERVED_PROPS={
  key:true,
  ref:true
}
/**
 * 
 * @param {*} type 
 * @param {*} config 
 * @param  {...any} children 
 * @returns 
 */
// 创建createElement
function CreateElement(type,config,...res){
  const props = {}
  let ref,key = null
  // 设置最外层的ref,key
  if(config!==null){
    if(hasValidDescript(config,'ref')){
      ref = config.ref
    }
    if(hasValidDescript(config,'key')){
      key = config.key
    }
  
  // 循环遍历对象自身的属性(不包括:继承、enumerable为false,Symbol属性)
  Object.keys(config).forEach(prop=>{
    if(!RESERVED_PROPS.hasOwnProperty(prop)){
      props[prop] = config[prop]
    }
  })
 }

 // 获取children
  if(res.length>0){
    if(res.length===1)props.children = res[0]
    else props.children = [...res]
  }

  return MyReactElement(
    type,
    key,
    ref,
    props
  )
}
// 判断是否为数据描述符
function hasValidDescript(config,key){
  if(config && Object.prototype.hasOwnProperty.call(config,key)){
      return !Object.getOwnPropertyDescriptor(config,key).get
  }
  return false
}

export default {
  createElement:CreateElement
}