当前位置: 首页 > news >正文

React 深度学习:React Core—ReactElement

path:packages/react/src/ReactElement.js

ReactElement 下面导出了多个方法:

  • createElement 用于创建并返回一个新的ReactElement 元素
  • createFactory 用于返回固定类的 createElement 方法 【已废弃】
  • cloneElement 克隆一个元素
  • isValidElement 验证一个对象是否为 ReactElement 。
  • cloneAndReplaceKey 使用给定 key 返回一个新的 ReactElement 类型

ReactElement

ReactElement 是用来创建 React 元素的工厂方法。

ReactElemen 不再遵循类的模式,并未从 ReactElement 模块导出,因此不能使用 new 来调用它,并且无法使用 instanceOf 检查。 取而代之,可以检测 $$typeof 字段与 Symbol.for('react.element') 是否匹配来判断是否是一个 React 元素

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__) {
    // do something
  }

  return element;
};
复制代码

从上面的源码中可以看到 React 元素是一个拥有以下属性的普通的对象:

  • $$typeof

    这个标记允许我们唯一地将其标识为 React 元素,其值为 REACT_ELEMENT_TYPE

    我们可以通过判断对象的 $$typeof 是否等于 REACT_ELEMENT_TYPE 来判断其是否为一个 React 元素。

  • type

  • key

  • ref

  • props

  • _owner

createElement

React.createElement 函数在 React 中具有举足轻重的地位,我们的组件最后都会编译成它。

React.createElement 使用给定的 type 创建并返回的新的 React 元素,参见 React 官方文档。

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;
  
  // 不为 undefined 和 null
  if (config != null) {
    // config 是否含有有效的 ref 属性
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    
    // config 是否含有有效的 key 属性
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // 剩余的属性被添加到一个新的 props 对象中
    // RESERVED_PROPS 包含四个属性 ref、key、__self、__source
    // 这里就是拷贝除这四个属性之外的其他属性
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // children 可以是多个参数,这些参数被转移到新分配的 props 对象上。
  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__) {
      // do somthing
    }
    props.children = childArray;
  }

  // 解析默认 props
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  if (__DEV__) {
    // do something
  }
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}
复制代码

示例

来看看一个示例:

React.createElement('div');
复制代码

返回一个对象,对象如下:

可以看到句代码创建了一个普通的 javascript 对象。这时候,产生了疑问:

  • DOM 层级如此之多,信息之复杂,React 又是如何实现的?

React 如何实现 DOM 层级

来看下面的 Hello 组件:

function Hello(props) {
  function sayHi() { return (<div>Hi</div>); }
  
  function handleClick(e) {
    console.log('click');
  }
  return (
    <div>
      { sayHi() }
      Hello { props.name }
      <p className="title" onClick={ handleClick }>React</p>
      <p className="content">React is cool!</p>
      <MyFooter className="footer">
        <div className="concat">concat</div>
        <div className="info">company info</div>
      </MyFooter>
    </div>
  );
}
复制代码

MyFooter 组件:

function MyFooter(props) {
  return (
    <div className="footer">
      { props.children }
    </div>
  );
}
复制代码

我们采用了函数式组件的方式,他们将分别被编译为:

// Hello 组件
function Hello(props) {
  function sayHi() {
    return React.createElement("div", null, "Hi");
  }
  
  function handleClick(e) {
    console.log('click');
  }
  
  return React.createElement("div", null, sayHi(), "Hello ", props.name, React.createElement("p", {
    className: "title",
    onClick: handleClick
  }, "React"), React.createElement("p", {
    className: "content"
  }, "React is cool!"), React.createElement(MyFooter, {
    className: "footer"
  }, React.createElement("div", {
    className: "concat"
  }, "concat"), React.createElement("div", {
    className: "info"
  }, "company info")));
}

// MyFooter 组件
function MyFooter(props) {
  return React.createElement("div", {
    className: "footer"
  }, props.children);
}
复制代码

首先,从上面的代码我们可以看出下面几点:

  • 组件都会被编译成 React.createElement 不论是自定义组件还是原始的 html 标签,都会被编译器编译。

  • React.createElement 方法的参数个数是可变的,在上面源码分析中,我们已经看到从第三个参数开始的所有参数会打包为一个数组,存入 React 元素的 props 属性的 children 中。

  • 不论从组件的哪一级部分开始划分,其子元素都是通过函数参数传递进父级的,并最后都会存放于 props 属性的 children 中。

  • 在处理 children 时,编译器会很智能地区分字符串、{} 表达式和 JSX,不同的部分都会被转换成一个 React.createElement 的参数,因此在上面的代码中,会产生 6 个参数:

    • sayHi()
    • "Hello"
    • props.nameReact.createElement(...)
    • React.createElement(...)
    • React.createElement(...)
  • 自定义组件的 type 参数值是组件的直接引用,而不是组件名的字符串。MyFooter 组件的 type 属性是一个函数,是它本身,而不是 "MyFooter" 字符串。

  • 只要是写在 JSX 上的属性,都被当做 config 的一部分传递给了 React.createElement() ,包括事件,例如这里的 onClick

再看看生成的 JavaScript 对象:

所谓的层级结构就是通过 React 元素的 props 属性中的 children 属性层层嵌套得来的。

createFactory

此辅助函数已废弃,建议使用 JSX 或直接调用 React.createElement() 来替代它。

cloneAndReplaceKey

使用新的 key 克隆一个 React 元素。

export function cloneAndReplaceKey(oldElement, newKey) {
  const newElement = ReactElement(
    oldElement.type,
    newKey,
    oldElement.ref,
    oldElement._self,
    oldElement._source,
    oldElement._owner,
    oldElement.props,
  );

  return newElement;
}
复制代码

cloneElement

克隆一个旧的 react 元素,并返回一新的个 React 元素,参见 React 官方文档。


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;

  // 复制原始 props
  const props = Object.assign({}, element.props);

  // 提取保留名称
  let key = element.key;
  let ref = element.ref;
  // Self 被保存,因为 owner 被保存。
  const self = element._self;
  // 保存 Source,因为 cloneElement 不太可能被 transpiler 定位,
  // 而原始源 source 可能是真正 owner 的更好指示器。
  const source = element._source;

  // Owner 将被保留,除非 ref 被覆盖
  let owner = element._owner;

  if (config != null) {
    if (hasValidRef(config)) {
      // Silently steal the ref from the parent.
      ref = config.ref;
      owner = ReactCurrentOwner.current;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }
    
    // 其余属性覆盖现有的 props
    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) {
          // Resolve default props
          // 解析默认 props
          props[propName] = defaultProps[propName];
        } else {
          props[propName] = config[propName];
        }
      }
    }
  }

  // Children 可以是多个参数,这些参数被转移到新分配的 props 对象上。
  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);
}
复制代码

cloneElement 方法会拷贝对旧 React 元素的属性进行拷贝,并使用新传入的 configchildren 参数对这些属性进行更新。然后使用这些新的属性作为参数调用 ReactElement 构造方法,生成并返回新的 React 元素。

isValidElement

通过判断对象的 $$typeof 属性与 Symbol.for('react.element') 是否相同来,验证一个对象是否为 React 元素。

通过 React.createElement 方法创建的对象,都含有一个值完全相同的 $$typeof 属性,标识其为一个 React 元素。

export function isValidElement(object) {
  return (
    typeof object === 'object' &&
    object !== null &&
    object.$$typeof === REACT_ELEMENT_TYPE
  );
}
复制代码

其他

RESERVED_PROPS

保留属性

const RESERVED_PROPS = {
  key: true,
  ref: true,
  __self: true,
  __source: true,
};
复制代码

hasValidRef

hasValidRef 判断一个对象中是否含有 ref 属性

function hasValidRef(config) {
  if (__DEV__) {
    // do something
  }
  return config.ref !== undefined;
}
复制代码

hasValidKey

hasValidKey 判断一个对象中是否含有 key 属性

function hasValidKey(config) {
  if (__DEV__) {
    // do something
  }
  return config.key !== undefined;
}
复制代码

遗留问题

  • 普通的 javascript 对象(React 元素)是如何变成了我们页面的 DOM 结构的

写文章的时候再阅读 React 的官方文档,发现这份文档制作很用心,循序渐进,由浅入深。

借用文档的一句话:

我们将在下一章节中探讨如何将 React 元素渲染为 DOM。


React 深度学习:React Core

转载于:https://juejin.im/post/5d10f2c06fb9a07eb15d62af

相关文章:

  • 12、rpm
  • 分布式配置
  • 20年研发管理经验谈(十一)
  • 数据之路 - Python爬虫 - 动态页面
  • JavaScript抽象语法树英文对照
  • vue 子组件接收父组件的另一种方法
  • MySQL存储过程例子
  • sql一关联多查询时否定筛选出现的问题的解决
  • 浅复制和深复制
  • JAVA-WEB-错误之-'OPTION SQL_SELECT_LIMIT=DEFAULT'
  • SpringBoot:spring boot使用Druid和监控配置
  • linux uniq去重,awk输出(可用于爆破字典优化)
  • Linux内核简介、子系统及分类
  • [转载]浅谈JavaScript函数重载
  • 2019-7-2 作业1 2 3
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • Apache的80端口被占用以及访问时报错403
  • CODING 缺陷管理功能正式开始公测
  • CSS盒模型深入
  • Java的Interrupt与线程中断
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • Making An Indicator With Pure CSS
  • Mocha测试初探
  • Promise面试题,控制异步流程
  • Vue2.0 实现互斥
  • 如何借助 NoSQL 提高 JPA 应用性能
  • 使用SAX解析XML
  • 微信小程序:实现悬浮返回和分享按钮
  • 限制Java线程池运行线程以及等待线程数量的策略
  • 一道闭包题引发的思考
  • ​LeetCode解法汇总2808. 使循环数组所有元素相等的最少秒数
  • ​ubuntu下安装kvm虚拟机
  • ​批处理文件中的errorlevel用法
  • # 达梦数据库知识点
  • #14vue3生成表单并跳转到外部地址的方式
  • #pragma once与条件编译
  • $NOIp2018$劝退记
  • (2015)JS ES6 必知的十个 特性
  • (附源码)spring boot车辆管理系统 毕业设计 031034
  • (七)Knockout 创建自定义绑定
  • (原)Matlab的svmtrain和svmclassify
  • .NET CORE 2.0发布后没有 VIEWS视图页面文件
  • .NET Reactor简单使用教程
  • .NET 使用 ILRepack 合并多个程序集(替代 ILMerge),避免引入额外的依赖
  • .net 桌面开发 运行一阵子就自动关闭_聊城旋转门家用价格大约是多少,全自动旋转门,期待合作...
  • .netcore 6.0/7.0项目迁移至.netcore 8.0 注意事项
  • .NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数
  • .net中应用SQL缓存(实例使用)
  • @ResponseBody
  • @transaction 提交事务_【读源码】剖析TCCTransaction事务提交实现细节
  • [.NET]桃源网络硬盘 v7.4
  • [\u4e00-\u9fa5] //匹配中文字符
  • [AHOI2009]中国象棋 DP,递推,组合数
  • [ARM]ldr 和 adr 伪指令的区别
  • [bzoj1006]: [HNOI2008]神奇的国度(最大势算法)