buxuku / react-handwritten

手写实现React
0 stars 1 forks source link

1.实现React.createElement #1

Open buxuku opened 3 years ago

buxuku commented 3 years ago

因为从React 17开始,不再使用React环境下的createElement了,介绍全新的 JSX 转换. 所以我们先禁用这一特性.

yarn add cross-env

然后将package.json里面的start命令修改为:

"start": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts start",

我们接触React最首先就会接触到JSX,JSX实际上就是React里面的一个语法糖,因为我们知道HTML片段放在js里面本身它是没办法识别并运行的. 它需要借助babel转换为js脚本.

打开babel,输入下面这一块HTML片段

<h1 id="title">hello <span>world</span></h1>

可以看到babel将它转换成了可执行的js代码

"use strict";

/*#__PURE__*/
React.createElement("h1", {
  id: "title"
}, "hello ", /*#__PURE__*/React.createElement("span", null, "world"));

它这里就是调用了React里面的createElement这个API.

咱们都知道,React处理的是虚拟DOM, 所以React.createElement生成出来的是一个虚拟DOM,而不像Document.createElement生成出来的是一个真实的DOM节点.

那么这个虚拟DOM长什么样子呢,咱们在index.js文件里面打印一下这个element组件看看.

import React from 'react';
import ReactDOM from 'react-dom';

const element = <h1>hello <span>world!</span></h1>;

+ console.log('element', element);

ReactDOM.render(
    element, document.getElementById('root')
);

我们看到控制台输出了:

qFhJ2x

它其实就是一个对象,其中除了_source,_self,_store等这些由babel自动生成的值以外,我们看到这个虚拟DOM其实就是一个js对象,里面包含了咱们常见的key,ref,props,children.以及$$typeof这个字段来表示这是一个React组件,type代表了组件的类型.

从刚刚上面babel转换的结果可以看到React.createElement这个函数接收的参数,第一个参数就是type代表组件类型,第二个参数就是组件的配置集合,包括组件的props,ref, attribute属性等, 后面的所有参数就是该组件的子元素.有多少个子元素,就有多少个参数,所以后面的参数个数是不确定的.同时我们也发现了,children这个属性是挂在props上面的,并且父组件如果有多个子元素,那么这个children将会是一个数组.

好了, 虚拟DOM大概就是长这个样子,那么我们就可以实现一个React.createElement方法来返回这个虚拟DOM即可.

src/react/index.js新建一个文件,实现咱们第一个API,这里我们先不处理ref,key这些属性.

/**
 * 生成虚拟DOM
 * @param type
 * @param props
 * @param children
 * @returns {{ref: null, $$typeof: symbol, text: null, type, key: null, props: {}}}
 */
const createElement = (type, config = {}, ...children) => {
    const props = {...config};
    if (children.length) {
        props.children = children.length > 1 ? children : children[0];
    }
    return {
        $$typeof: Symbol.for('react.element'),
        type,
        props,
        key: null,
        ref: null,
    }
}

const React = {
    createElement
}

export default React;

src/index.js使用我们自己写的React

-import React from 'react';
+import React from './react';

试试看效果,居然能正常运行,这似乎是不是有点太简单了?

既然这个虚拟DOM是一个js对象,那么我们是不是也可以直接对这个虚拟DOM进行任意的增删改查呢?我们用官方的React,对element这个对象执行一下修改操作

element.type = 'p';

控制台报错了

TypeError: Cannot assign to read only property 'type' of object '#<Object>'

我们尝试增加一个属性

element.test = 'test';

控制台依然报错了

TypeError: Cannot add property test, object is not extensible

可以看到这个对象是被Object.freeze()处理过的,并且还是深遍历处理了的.当然,目前我们还是先不考虑这一步,只有先明白原生的虚拟DOM这样一个特性.

接下来咱们继续2.ReactDOM.render