【从0实现React18】 (二) JSX 的转换 jsx到底是什么?React是如何把jsx转换为ReactElement?

react项目结构

  • React(宿主环境的公用方法)
  • React-reconciler(协调器的实现,宿主环境无关)
  • 各种宿主环境的包
  • shared(公用辅助方法,宿主环境无关)

当前实现的JSX转换属于 react****包

初始化react包

先创建react package并初始化

更新package.json文件:

{
  "name": "react",
  "version": "1.0.0",
  "description": "react公用方法",
  "module": "index.ts",
  "keywords": [],
  "author": "",
  "license": "ISC"
}

JSX转换是什么

jsx在线转换

包括两部分

  • 编译时
  • 运行时:jsx方法或react.createElement方法的实现(包括dev、prod两个环境)

实现运行时 jsx 转换

编译时由babel编译实现,我们实现运行时,工作量包括:

  • 实现jsx方法
  • 实现打包流程
  • 实现调试打包结果的环境
  1. 实现jsx方法

包括:

  • jsxDEV方法(dev环境)
  • jsx方法(prod环境)
  • React.craeteElement方法

实现:react/src/jsx.ts:

import { REACT_ELEMENT_TYPE } from '@/shared/ReactSymbols'
import {
  Type,
  Key,
  Ref,
  Props,
  ElementType,
  ReactElementType,
} from '@/shared/ReactTypes'

// ReactElement 构造函数实现
const ReactElement = function (
  type: Type,
  key: Key,
  ref: Ref,
  props: Props
): ReactElementType {
  const element = {
    $$typeof: REACT_ELEMENT_TYPE, // 内部字段, 指明当前字段是reactElement
    type,
    key,
    ref,
    props,
    __mark: 'khs', // 该字段是为了与真实的react项目区分开
  }

  return element
}

// jsx 函数实现
export const jsx = (type: ElementType, config: any, ...maybeChildren: any) => {
  let key: Key = null
  let ref: Ref = null
  const props: Props = {}

  // 遍历config
  for (const prop in config) {
    const val = config[prop]
    // 1. 单独找出 key和ref字段
    if (prop === 'key') {
      if (val !== undefined) {
        key = '' + val
      }
      continue
    }
    if (prop === 'ref') {
      if (val !== undefined) {
        ref = val
      }
      continue
    }
    // 2. 剩下的如果是config自身的prop, 则正常取出
    if ({}.hasOwnProperty.call(config, prop)) {
      props[prop] = val
    }
  }

  const maybeChildrenLength = maybeChildren.length
  if (maybeChildrenLength) {
    // [child] 或 [child, child, child]
    if (maybeChildrenLength === 1) {
      props.child = maybeChildren[0]
    } else {
      props.child = maybeChildren
    }
  }
  return ReactElement(type, key, ref, props)
}

// jsxDEV 函数实现
export const jsxDEV = (type: ElementType, config: any) => {
  let key: Key = null
  let ref: Ref = null
  const props: Props = {}

  // 遍历config
  for (const prop in config) {
    const val = config[prop]
    // 1. 单独找出 key和ref字段
    if (prop === 'key') {
      if (val !== undefined) {
        key = '' + val
      }
      continue
    }
    if (prop === 'ref') {
      if (val !== undefined) {
        ref = val
      }
      continue
    }
    // 2. 剩下的如果是config自身的prop, 则正常取出
    if ({}.hasOwnProperty.call(config, prop)) {
      props[prop] = val
    }
  }

  return ReactElement(type, key, ref, props)
}

react/index.tsx:

/**
 * 打包出的React包
 */

import { jsxDEV } from './src/jsx'
export default {
  version: '0.0.0',
  createElement: jsxDEV,
}

同时因为react引入了shared包,所以为react/package.json添加依赖:

{
  "name": "react",
  "version": "1.0.0",
  "description": "react公用方法",
  "module": "index.ts",
  "dependencies": {
    "shared": "workspace: *"  
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
  1. 实现打包流程

对应上述3方法,打包对应文件

  • react/jsx-dev-runtime.js(dev环境)
  • react/jsx-runtime.js(prod环境)
  • react

1、安装rollup Plugin

  • 兼容commonjs: @rollup/plugin-commonjs
  • ts解析为js: rollup-plugin-typescript2
  • 生成package.json文件:rollup-plugin-generate-package-json
pnpm i -D -w rollup-plugin-typescript2

pnpm i -D -w @rollup/plugin-commonjs

pnpm i -D -w rollup-plugin-generate-package-json
  1. react包rollup打包配置:

scripts/rollup/react.config.js:

import { getBaseRollupPlugins, getPackageJSON, resolvePkgPath } from './utils'

import generatePackageJson from 'rollup-plugin-generate-package-json'

const { name, module } = getPackageJSON('react')
// react包的路径
const pkgPath = resolvePkgPath(name)
//react产物路径
const pkgDistPath = resolvePkgPath(name, true)

export default [
  // react 的包
  {
    input: `${pkgPath}/${module}`,
    output: {
      file: `${pkgDistPath}/index.js`,
      name: 'index.js',
      format: 'umd', // 该格式能够兼容commonjs
    },
    plugins: [
      ...getBaseRollupPlugins(),
      // 生成package.json文件
      generatePackageJson({
        inputFolder: pkgPath,
        outputFolder: pkgDistPath,
        baseContents: ({ name, description, version }) => ({
          name,
          description,
          version,
          main: 'index.js',
        }),
      }),
    ],
  },
  // jsx-runtime 和 jsx-dev-runtime 的包
  {
    input: `${pkgPath}/src/jsx.ts`,
    output: [
      // jsx-runtime
      {
        file: `${pkgDistPath}/jsx-runtime.js`,
        name: 'jsx-runtime',
        format: 'umd',
      },
      // jsx-dev-runtime
      {
        file: `${pkgDistPath}/jsx-dev-runtime.js`,
        name: 'jsx-dev-runtime.js',
        format: 'umd',
      },
    ],
    plugins: getBaseRollupPlugins(),
  },
]
  1. 测试打包

安装清除上次打包的文件工具:rimraf

pnpm i -D -w rimraf

添加脚本:

"build:dev": "rimraf dist && rollup --bundleConfigAsCjs --config scripts/rollup/react.config.js"

运行脚本

pnpm build:dev

打包结果:

  1. 调试打包结果

Pnpm link

npm link是一种把包链接到包文件夹的方式,即:可以在不发布npm模块的情况下,调试该模块,并且修改模块后会实时生效,不需要通过npm install进行安装

  • 优点:可以模拟实际项目引用React的清空
  • 缺点:略显繁琐,达不到热更新的效果

先去到模块目录,把它 link 到全局:

cd .\dist\node_modules\react\

pnpm link --global

在外部新建一个react项目,然后将我们实现的react link到该项目

npx create-react-app react-demo 
 
cd ./react-demo

pnpm link react --global

修改react-demo/index.js:

import React from 'react'

const jsx = (
  <div key={123} ref={'khs'}>
    hello
    <span>big-react</span>
  </div>
)

console.log(React)
console.log(jsx)

控制台打印调试后的结果:

  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值