React16源码: JSX2JS及React.createElement源码实现

news/2024/7/15 18:58:35 标签: react.js, javascript, 前端

JSX 到 Javascript 的转换

  • React中的 JSX 类似于 Vue中的template模板文件,Vue是基于编译时将template模板转换成render函数
  • 在React中,JSX是类似于html和javascript混编的语法,而javascript是真的javascript, html并非真的html
  • 它的可阅读性可维护性都是要高很多的

1 )JSX2JS 原理

  • JSX 通过 babel 进行转换之后,生成了纯JS
    • JSX相对于JS来讲,它唯一的一个区别,就是它可以写类似于HTML的一个标签
    • 比如说我们通过写div 然后在 div 这种方式去声明HML的标签
    • 然后它会给我们返回在React当中需要使用的对象
  • 这就是JSX到JS的一个转化过程

2 ) 工具演示

  • 这个工具是 babel playground
    • babeljs.io/repl
  • 要做一些代码转化的一个测试,可以直接到这个playground上面来
    • 它会实时在为我们展现出我们写的代码转化出来的是什么样的样子
    • 下面的示例是使用较低版本的 babel 来配合 React 16.6 版本

示例1

jsx

<div></div>

js

React.createElement("div", null);

示例2

jsx

<div class="test"></div>

js

React.createElement("div", {
  class: "test"
});

示例3

jsx

<div id="div" key="key" class="test">
  <span>1</span>
  <span>1</span>
</div>

js

React.createElement("div", {
  id: "div",
  key: "key",
  class: "test"
}, React.createElement("span", null, "1"), React.createElement("span", null, "1"));

示例4

jsx

function Comp() {
  return <a>123</a>
}

<Comp id="div" key="key">
  <span>1</span>
  <span>1</span>
  <div id="box">
  	<span class='inner'>2</span>
  </div>
</Comp>

js

function Comp() {
  return React.createElement("a", null, "123");
}

React.createElement(Comp, {
  id: "div",
  key: "key"
}, 
	React.createElement("span", null, "1"),
	React.createElement("span", null, "1"),
	React.createElement("div", {
	  id: "box"
	}, 
		React.createElement("span", {
		  class: "inner"
		}, "2")
	)
);

3 )说明

  • 从上面我们可以看出来我们的一个类似html的标签,或者是组件的一个标签
  • 通过这种尖括号的方式来写的,它最终都会转换成 React.createElement
  • 我们写的这些标签或者一些props,或者它的 children 都会作为一个参数
    • props 是一个 key-value 形式的一个对象
    • 它可以支持多层,无限层的嵌套,也就是一个树形结构
  • 如果是一个函数式的组件作为参数
    • 这里要分两种情况
      • 1 ) 组件是大写的,这样会直接转换成变量(对象)
      • 2 ) 组件是小写的,这样会直接变成字符串类型的标记(组件将失效)
    • 注意
      • 如果变成字符串,那么在React中,它是会认为这是一个原生的dom节点的
      • 如果不存在这么一个dom节点,那么后续在运行的时候,可能就报错了
      • 所以自定义的组件必须使用大写的开头,这是一个规范
  • 综上,我们现在问题的重点就在 createElement 之上了

React.createElement 源码解析

  • 在上一步的 JSX2JS中,我们的标签,标签里的属性,标签的内容,都会变成各种类型的参数
  • 传到我们调用的 createElement 这个方法里面,这个方法内部如何实现的
  • 在 createElement 函数的内部,返回了一个 React Element, 我们来看看它具体的作用
  • 看源码肯定要从它的入口文件开始看,因为入口文件会给我们很多的信息告诉我们
  • 常用的使用这个包的时候的这些API它都来自于哪里,以及它是如何 export 出来的

1 )React 入口文件 packages/react/src/React.js

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import ReactVersion from 'shared/ReactVersion';
import {
  REACT_CONCURRENT_MODE_TYPE,
  REACT_FRAGMENT_TYPE,
  REACT_PROFILER_TYPE,
  REACT_STRICT_MODE_TYPE,
  REACT_SUSPENSE_TYPE,
} from 'shared/ReactSymbols';

import {Component, PureComponent} from './ReactBaseClasses';
import {createRef} from './ReactCreateRef';
import {forEach, map, count, toArray, only} from './ReactChildren';
import {
  createElement,
  createFactory,
  cloneElement,
  isValidElement,
} from './ReactElement';
import {createContext} from './ReactContext';
import {lazy} from './ReactLazy';
import forwardRef from './forwardRef';
import memo from './memo';
import {
  createElementWithValidation,
  createFactoryWithValidation,
  cloneElementWithValidation,
} from './ReactElementValidator';
import ReactSharedInternals from './ReactSharedInternals';
import {enableStableConcurrentModeAPIs} from 'shared/ReactFeatureFlags';

const React = {
  Children: {
    map,
    forEach,
    count,
    toArray,
    only,
  },

  createRef,
  Component,
  PureComponent,

  createContext,
  forwardRef,
  lazy,
  memo,

  Fragment: REACT_FRAGMENT_TYPE,
  StrictMode: REACT_STRICT_MODE_TYPE,
  Suspense: REACT_SUSPENSE_TYPE,

  createElement: __DEV__ ? createElementWithValidation : createElement,
  cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
  createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
  isValidElement: isValidElement,

  version: ReactVersion,

  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
};

if (enableStableConcurrentModeAPIs) {
  React.ConcurrentMode = REACT_CONCURRENT_MODE_TYPE;
  React.Profiler = REACT_PROFILER_TYPE;
} else {
  React.unstable_ConcurrentMode = REACT_CONCURRENT_MODE_TYPE;
  React.unstable_Profiler = REACT_PROFILER_TYPE;
}

export default React;
  • 上面全是 import 其他一些东西,import 进来之后, 它声明了 React 对象
  • 这个对象就是我们在外部去用 React 的时候,给我们提供的API
  • 然后最终它 export default React 把这个对象给它 export 出来
  • 这样的话我们就可以在外部使用
  • 我们回到 createElement 上面来,从上面可知,跟Element相关的一些代码
  • 都放在了 ./ReactElement 这个文件下面

2 )定位到 ReactElement.js 文件中

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import invariant from 'shared/invariant';
import warningWithoutStack from 'shared/warningWithoutStack';
import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';

import ReactCurrentOwner from './ReactCurrentOwner';

const hasOwnProperty = Object.prototype.hasOwnProperty;

const RESERVED_PROPS = {
  key: true,
  ref: true,
  __self: true,
  __source: true,
};

let specialPropKeyWarningShown, specialPropRefWarningShown;

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;
}

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;
}

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,
  });
}

function defineRefPropWarningGetter(props, displayName) {
  const warnAboutAccessingRef = function() {
    if (!specialPropRefWarningShown) {
      specialPropRefWarningShown = true;
      warningWithoutStack(
        false,
        '%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://fb.me/react-special-props)',
        displayName,
      );
    }
  };
  warnAboutAccessingRef.isReactWarning = true;
  Object.defineProperty(props, 'ref', {
    get: warnAboutAccessingRef,
    configurable: true,
  });
}

/**
 * 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 {*} key
 * @param {string|object} ref
 * @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.
 * @param {*} owner
 * @param {*} props
 * @internal
 */
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    $$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.
    _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.
    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;
};

/**
 * Create and return a new ReactElement of the given type.
 * See https://reactjs.org/docs/react-api.html#createelement
 */
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 (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,
  );
}

/**
 * Return a function that produces ReactElements of a given type.
 * See https://reactjs.org/docs/react-api.html#createfactory
 */
export function createFactory(type) {
  const factory = createElement.bind(null, type);
  // Expose the type on the factory and the prototype so that it can be
  // easily accessed on elements. E.g. `<Foo />.type === Foo`.
  // This should not be named `constructor` since this may not be the function
  // that created the element, and it may not even be a constructor.
  // Legacy hook: remove it
  factory.type = type;
  return factory;
}

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

  return newElement;
}

/**
 * Clone and return a new ReactElement using element as the starting point.
 * See https://reactjs.org/docs/react-api.html#cloneelement
 */
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;

  // Original props are copied
  const props = Object.assign({}, element.props);

  // Reserved names are extracted
  let key = element.key;
  let ref = element.ref;
  // Self is preserved since the owner is preserved.
  const self = element._self;
  // Source is preserved since cloneElement is unlikely to be targeted by a
  // transpiler, and the original source is probably a better indicator of the
  // true owner.
  const source = element._source;

  // Owner will be preserved, unless ref is overridden
  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;
    }

    // Remaining properties override existing 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[propName] = defaultProps[propName];
        } else {
          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];
    }
    props.children = childArray;
  }

  return ReactElement(element.type, key, ref, self, source, owner, props);
}

/**
 * Verifies the object is a ReactElement.
 * See https://reactjs.org/docs/react-api.html#isvalidelement
 * @param {?object} object
 * @return {boolean} True if `object` is a ReactElement.
 * @final
 */
export function isValidElement(object) {
  return (
    typeof object === 'object' &&
    object !== null &&
    object.$$typeof === REACT_ELEMENT_TYPE
  );
}
  • 在这个文件里面先找到 createElement 这个方法,我们可以看到它接收的三个参数
    • type
      • 就是我们的节点类型,如果是原生的节点,那么它是一个字符串
      • 那如果是我们自己声明的组件,它就是一个class component 或者是一个 functional component
      • 还会有其他的一些情况。比如 使用 React 原生的一些组件
        • 比如说 Fragment、 StrictMode、 Suspense
        • 这些都是 React 提供我们的一些原生组件,
        • 其实,上面三个它们默认就只是一个 Symbol
        • 它没有任何其他的功能,就仅仅是一个标志
    • config
      • 是我们写在这个JSX标签上面的所有的 attributes
      • 它们都会变成key value的形式存到这个config对象里面
      • 我们要从这个对象里面筛选出真正的props的内容
      • 还有特殊的,比如说 key,ref 这些属性
    • children
      • 就是我们标签中间我们放的一些内容
      • 它可能是一个子标签,或者它直接是文字 text
  • 我们来看一下它如何去创建一个 ReactElement, 还是回到 createElement 这个方法
    • 内部声明了一堆变量,找到有没有合理的REF,有没有合理的key
    • 我们把这些都给它读到一个单独的变量里面
    • 忽略 self 和 source 这两个东西,不是特别的重要
    • 接下去,要对剩下的config下面的 props 进行一个处理
      • 判断一下它是否是内建的 props,如果不是的话,就放到一个新建的 props 对象里面
      • 如果是内建的 props,就不放进去了,因为它不属于正常的 props 的范畴
      • 看一下这个内建的 props,它是什么东西
        const RESERVED_PROPS = {
          key: true,
          ref: true,
          __self: true,
          __source: true,
        };
        
      • key, ref, __self, __source, 这些都不会出现在我们使用class component 的场景下
        • 比如说我们的 this.props里面
        • 因为在处理props的过程当中,就已经把它处理掉了
    • 这边把 props 的属性全部拿出来,放到一个新的对象里面之后
    • 接下去要处理children
      • children 是可以有多个的,在一个节点下面
      • 它的 children 可能有很多的兄弟节点存在
      • 它是作为后续的参数传进来的,虽然在声明 createElement的时候只有三个参数
      • 但是它是可以传 3,4,5,6,7,8,9, … 多个参数的
      • 后续第三个参数之后的参数,我们都认为它们是 children
      • 在react当中把后续 arguments.length - 2 代表剩下的这个长度都是children
      • 然后会一个一个把它读出来,然后变成一个数组
      • 声明一个数组, 存放后续所有的 children 节点对象, 最终再把它放到 props.children
      • 通过 this.props.children 拿到的就是这部分的内容
    • 接下来,就是 defaultProps 的处理
      • 在声明 class Comp 的时候,比如说我们 extends React.Component
      • 我们可以通过 Comp.defaultProps 一个对象,给接收的这些props去设置一些默认值
      • 比如说, 这边它的默认值是 {value:1},当组件在被使用的时候,没有传value这个props
      • 在这里面就会使用 1 作为我们在组件内部 this.props.value 去拿到的这个值
      • 它就是把我们刚才上面处理过的那个props对象上面去读取对应的defaultProps里面的每一个key的值
      • 如果值是 undefined,就把它设置为 defaultProps 里面的属性
      • 如果它是有值的, 我们就不设置了
      • 注意
        • 它的判断条件是 undefined
        • 也就是说 null 也是一个不需要使用默认值的情况
    • 接着,下面 DEV 判断的代码,进行忽略
    • 最终 return了一个 ReactElement
      • 传入刚才处理过的这些内容
  • 关于 ReactElement
    • 它不是一个 class Comp, 而是一个 function
    • 最终会return一个Object, 这个Object也就几个主要的属性
      • $$typeofREACT_ELEMENT_TYPE
        • 是用来标识我们的 element 是什么类型的
        • 在写JSX代码的时候,所有的节点都是通过 createElement 进行创建的
        • 那么,它的 $$typeof 永远都是 REACT_ELEMENT_TYPE
        • 在后续React的更新渲染dom的过程中是经常被用到的
        • 大部分情况下,我们拿到的 $$typeof 都是 REACT_ELEMENT_TYPE
        • 有一些特殊情况是和平台相关
          • 在react-dom里面,它有一个API叫做 React.createPortal, 它返回的对象和这里的类似
          • 但是它的 $$typeofREACT_PORTAL_TYPE
      • type 是之前传进来的那个 type
        • 是在 createElement 的时候接收的那个 type
        • 用于记录节点的类型,是原生组件,还是 class Comp
      • key 就是上面处理过的 key
      • ref 就是 ref
      • props 就是 props
    • 综上,就是一个 ReactElement, 具体的方法,如何去操作,以及最终返回的类型

http://www.niftyadmin.cn/n/5296310.html

相关文章

Python数值型字符串校验

从键盘输入一行字符串&#xff0c;编写Python代码判定字符串是python“合法”数值。 (笔记模板由python脚本于2023年12月25日 18:00:52创建&#xff0c;本篇笔记适合熟悉Python符串基本数据类型的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.py…

2023.12.31 Python 词频统计

练习&#xff1a;使用Python中的filter、map、reduce实现词频统计 样例数据&#xff1a; hello world java python java java hadoop spark spark python 需求分析&#xff1a; 1- 文件中有如上的示例数据 2- 读取文件内容。可以通过readline() 3- 将一行内容切分得到多个单…

Elasticsearch-8.11.1 (2+1)HA(高可用)集群部署

目录 一、环境描述 二、安装 ES 2.1 下载Elasticsearch 2.2 解压Elasticsearch 2.3 创建es服务账号/密码 2.3 修改服务器配置 2.4 配置节点 2.4.1 配置说明 2.4.2 配置高可用集群 2.4.2.1 maser节点服务配置 2.4.2.2 node1 节点服务配置 2.4.2.3 node2 节点服务配置…

2023 年中国高校大数据挑战赛 赛题 A 中文文本纠错

中文文本纠错的任务主要是针对中文文本中出现的错误进行检测和纠正&#xff0c;属于人工智能自然语言处理的研究子方向。中文文本纠错通常使用的场景有政务公文、裁判文书、新闻出版等&#xff0c;中文文本纠错对于以中文作为母语的使用者更为适用。基于此&#xff0c;本赛题主…

unity控制摄像机几种视角实现方式

目录 1、按下鼠标右键可以实现摄像机上下左右旋转 2、自由视角 3、摄像头跟随视角 4、跟随自由视角 5、第一人称跟随视角 python学习汇总连接&#xff1a; 1、按下鼠标右键可以实现摄像机上下左右旋转 这段代码定义了一个名为CameraRotate的脚本&#xff0c;用于控制摄像…

数据结构程序设计——哈希表的应用(2)->哈希表解决冲突的方法

目录 实验须知 代码实现 实验报告 一&#xff1a;问题分析 二、数据结构 1.逻辑结构 2.物理结构 三、算法 &#xff08;一&#xff09;主要算法描述 1.用除留余数法构造哈希函数 2.线性探测再散列法 &#xff08;一&#xff09;主要算法实现代码 四、上机调试 实…

Byzer-LLM 支持同时开源和SaaS版通义千问

实际上很多模型 我们都已经同时支持开源和SaaS版本&#xff0c;不过通义千问我们目前支持的更完整些&#xff0c;具有如下特性&#xff1a; 可以使用相同方式部署开源大模型和SaaS大模型支持相同的调用接口同时支持block / stream chat 模式 来&#xff0c;一起看看 他到底都给…

工作记录----CountDownLatch(特别好用的一个工具类)

CountDownLatch 是 Java 并发包中的一个同步工具类,它可以让一个或多个线程等待其他线程完成操作。它的核心思想是,一个线程(或多个线程)在执行某个任务时,可以通过它等待其他线程的信号,直到其他线程的任务完成才继续执行。用于实现线程间的等待和通知机制。 主要特点:…