想想react会怎么做(1)之 jsx方法实现

45 阅读2分钟

前言

书接上文,我们已经了解了jsx是如何编译成jsx方法的过程了,效果如图:

// 编译前
<div key="key" ref="ref" name="name">
  <div key="key1" ref="ref1" name="name1">1</div>
  <div key="key2" ref="ref2" name="name2">2</div>
</div>

// 编译后
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/*#__PURE__*/_jsxs("div", {
  ref: "ref",
  name: "name",
  children: [/*#__PURE__*/_jsx("div", {
    ref: "ref1",
    name: "name1",
    children: "1"
  }, "key1"), /*#__PURE__*/_jsx("div", {
    ref: "ref2",
    name: "name2",
    children: "2"
  }, "key2")]
}, "key");

那么react中的jsx方法到底是干什么的呢?我们这一章来探索一下react中的jsx方法实现

目录:packages/react/src/jsx/ReactJSX.js

jsx方法实现

入口

代码在packages/react/src/jsx/ReactJSX.js中,其中导出了Fragment,jsx,jsxs,jsxDev,其实刚好就对应上了我们上一节中说过的jsx编译之后的那几个方法。

import {REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
import {
  jsxProd,
  jsxProdSignatureRunningInDevWithDynamicChildren,
  jsxProdSignatureRunningInDevWithStaticChildren,
  jsxDEV as _jsxDEV,
} from './ReactJSXElement';

const jsx: any = __DEV__
  ? jsxProdSignatureRunningInDevWithDynamicChildren
  : jsxProd;
const jsxs: any = __DEV__
  ? jsxProdSignatureRunningInDevWithStaticChildren
  : jsxProd;
const jsxDEV: any = __DEV__ ? _jsxDEV : undefined;

export {REACT_FRAGMENT_TYPE as Fragment, jsx, jsxs, jsxDEV};

环境判断

我们可以看到这里的jsx方法是有开发版本和生产版本的,根据__DEV__这个标识来判断是开发环境还是生产环境,为true则是开发环境,反之则是生产环境,这个__DEV___是怎么来的呢?

我们可以找到项目的rollup打包配置,目录在scripts/rollup/build.js,是根据配置的bundleType得到是否是开发模式,然后通过@rollup/plugin-replace插件进行对__DEV__全局替换。


const replace = require('@rollup/plugin-replace');

function isProductionBundleType(bundleType) {
  switch (bundleType) {
    case NODE_ES2015:
      return true;
    case ESM_DEV:
    case NODE_DEV:
    case BUN_DEV:
    case FB_WWW_DEV:
    case RN_OSS_DEV:
    case RN_FB_DEV:
      return false;
    case ESM_PROD:
    case NODE_PROD:
    case BUN_PROD:
    case NODE_PROFILING:
    case FB_WWW_PROD:
    case FB_WWW_PROFILING:
    case RN_OSS_PROD:
    case RN_OSS_PROFILING:
    case RN_FB_PROD:
    case RN_FB_PROFILING:
    case BROWSER_SCRIPT:
      return true;
    default:
      throw new Error(`Unknown type: ${bundleType}`);
  }
}

const isProduction = isProductionBundleType(bundleType);

replace({
  preventAssignment: true,
  values: {
    __DEV__: isProduction ? 'false' : 'true',
    // ...
  },
}),

jsxProd

那现在我们就主要来看一下生产模式的jsx方法,jsxProd方法的实现了

目录:packages/react/src/jsx/ReactJSXElement.js。

可以看到,这个方法接受3个参数,type,config,maybeKey,其实就是对应上一节编译结果的三个参数,元素tag,元素属性(包括子元素),元素的key。

比如说一个元素为

123

那么传入jsxProd的参数就是

jsxProd("div", {
  name: "name",
  children: "123"
}, "key");

关键逻辑源码:

export function jsxProd(type, config, maybeKey) {
  // 初始化key,ref
  let key = null;
  let ref = null;
  // 如果key存在就将它转换成字符串
  if (maybeKey !== undefined) {
    key = '' + maybeKey;
  }
  // 判断config中是否有key,有key的话覆盖一下
  if (hasValidKey(config)) {
    key = '' + config.key;
  }

  // 判断config中是否有ref,有的赋值
  if (hasValidRef(config)) {
      ref = config.ref;
    }
  }

  // 初始化props
  let props;
  for (const propName in config) {
    // 如果不是key和ref就赋值给props,因为key和ref之前已经处理过了
    if (propName !== 'key' && propName !== 'ref') {
        props[propName] = config[propName];
      }
  }

  // 如果不禁用defaultProps且有defaultProps时,给props一个初始值
  if (!disableDefaultPropsExceptForClasses) {
    // Resolve default props
    if (type && type.defaultProps) {
      const defaultProps = type.defaultProps;
      for (const propName in defaultProps) {
        if (props[propName] === undefined) {
          props[propName] = defaultProps[propName];
        }
      }
    }
  }

  // 将处理之后的数据传给ReactElement方法
  return ReactElement(
    type,
    key,
    ref,
    undefined,
    undefined,
    getOwner(),
    props,
    undefined,
    undefined,
  );
}

// 判断config中是否有key
function hasValidKey(config) {
  return config.key !== undefined;
}

// 判断config中是否有ref
function hasValidRef(config) {
  return config.ref !== undefined;
}

我们可以看出来,jsxProd方法主要就是将入参进行了一些分类和初始化,从config中提取出来了key和ref,然后将type,key,ref,props传给了ReactElement方法

ReactElement

目录:packages/react/src/jsx/ReactJSXElement.js。

import {
  REACT_ELEMENT_TYPE,
} from 'shared/ReactSymbols';
function ReactElement(
  type,
  key,
  _ref,
  self,
  source,
  owner,
  props,
  debugStack,
  debugTask,
) {
  let ref;
  let ref;
  // 是否允许ref在props中传递,如果允许就直接取props中的ref
  if (enableRefAsProp) {
    const refProp = props.ref;
    ref = refProp !== undefined ? refProp : null;
  } else {
    ref = _ref;
  }
  let element;
  element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type,
    key,
    ref,
    props,
    _owner: owner,
  };

  return element;
}

可以看到,ReactElement的核心逻辑其实简单,就是将传进来的参数组成一个数据结构,这个数据结构就是ReactElement,这个数据结构有一个标识( $$typeof )为REACT_ELEMENT_TYPE,REACT_ELEMENT_TYPE是一个symbol:Symbol.for('react.transitional.element')。

所以我们可以说,所有的jsx最后都会被转换成一个ReactElement结构的js对象,而ReactElement就是后续react中的最基础的数据结构

参考

github.com/facebook/re…