React源码解析与实现(1)--环境配置与React和ReactDOM的实现

配置环境
  1. 新建react_simple目录,并新建index.html和index.js两个文件。
  2. 全局安装parcel,一个比webpack更简单的打包工具。
npm install parcel -g

然后在开发环境中安装parcel-bundler

npm install parcel-bundler --save-dev
  1. 安装babel,将jsx转换为js对象(虚拟DOM)
npm i babel-core babel-preset-env babel-plugin-transform-react-jsx --save-dev
  1. 新建.babelrc,配置如下信息
{
    "presets": ["env"],
    "plugins": [
        ["transform-react-jsx",{
            "prama":"React.createElement"
        }]
    ]
}
  1. 在package.json里面加入scripts,用来启动parcel
{
  "scripts": {
    "start":"parcel index.html"
  },
  "dependencies": {
    "babel-core": "^6.26.3",
    "babel-plugin-transform-react-jsx": "^6.24.1",
    "babel-preset-env": "^1.7.0",
    "parcel-bundler": "^1.12.4"
  }
}
写个简单的element

在index.html中写入基本的html代码,并引入index.js文件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="box"></div>
    <script src="./index.js"></script>
</body>
</html>
React和createElement的实现

在react/index.js中加入如下代码:

const React = {
    createElement
}

function createElement(tag, attrs, ...childrens) {
    return{
        tag,
        attrs,
        childrens
    }
}
const ele = (
    <div className="active" title="123">
        hello, <span>react</span>
    </div>
)

console.log(ele)

命令窗口中运行:

npm start

就可以看到console输出的

{tag: "div", attrs: {}, childrens: Array(2)}
attrs: {className: "active", title: "123"}
childrens: (2) ["hello, ", {}]
tag: "div"
__proto__: Object

由于ele先被babel解析成:

const ele = React.createElement("div", {
  className: "active",
  title: "123"
}, "hello, ", React.createElement("span", null, "react"));

通过我们自己createElement,再把ele解析成xml的格式

{tag: "div", attrs: {}, childrens: Array(2)}
attrs: {className: "active", title: "123"}
childrens: (2) ["hello, ", {}]
tag: "div"
__proto__: Object

在这里插入图片描述
再创建一个react文件夹,里面放入index.js, 把根目录中index.js中的下列代码挪过来,再加上export default导出

const React = {
    createElement
}

function createElement(tag, attrs, ...childrens) {
    return{
        tag,
        attrs,
        childrens
    }
}

export default React

别忘记在根目录中index.js中导入这个React

import  React  from "./react";
ReactDOM的实现

创建react-dom文件夹,同样在里面创建index.js
在react-dom/index.js中写入如下代码

const ReactDOM = {
    render
}

function render(vnode, container) {
    console.log(vnode)
    if(vnode === undefined) return;
    //如果vnode是字符串
    if(typeof vnode === 'string'){
        //创建文本节点
        const textNode = document.createTextNode(vnode);
        return container.appendChild(textNode);
    }

    //否则就是个虚拟DOM对象
    console.log("----v node is ---", vnode.tag, vnode.attrs)
    const {tag, attrs} = vnode; //解构vnode
    //创建节点对象
    const dom = document.createElement(tag);
    if(attrs){
        //有属性
        Object.keys(attrs).forEach(key=>{
            const value = attrs[key];
            setAttribute(dom, key, value)
        })
    }

    //递归--渲染子节点
    vnode.childrens.forEach(child=>render(child, dom))
    return container.appendChild(dom);

}

function setAttribute(dom, key, value) {
    //将className转为class
    if(key === 'className'){
        key = 'class';
    }

    //如果是事件,onClick,onBlur等
    if(/on\w+/.test(key)){
        //转小写
        key.toLowerCase();
        dom[key] = value || '';
    }else if(key === 'style'){
        if(!value || typeof value === 'string'){
            dom.style.cssText = value || '';
        }else if(value && typeof value === 'object'){
            //for example: {width:20}
            for(let k in value){
                if(typeof value[k] === 'number'){
                    dom.style[k] = value[k] + 'px';
                }else{
                    dom.style[k] = value[k]
                }
            }
        }
    }else if(key in dom){
        dom[key] = value || '';
    }if(value){
        dom.setAttribute(key, value);

    }else{
        dom.removeAttribute(key);
    }
}

export default ReactDOM

在根目录中引入

import ReactDOM from "./react-dom"

回顾一下在render函数里面做了哪些事情:

  1. 判断vnode类型

    • vnode如果是undefined,则什么都不做
    • vnode如果是string,追加到container作为节点
    • vnode如果是虚拟DOM对象,把vnode结构成tag, attrs, children…
  2. 用createElement新建一个以tag命名节点,判断attrs中是否有值,如果有值,遍历所有的值,用setAttribute(dom, key, value)将属性添加到节点中去。

  3. setAttribute(dom, key, value)中

    • 先将className转为class
    • 对于onClick等以on开始的事件名称,先转为小写,然后添加到dom[key]中
    • 对于key为style样式:
      • 如果为空,或者为string类型,则直接添加到dom.style.cssText中去
      • 如果为对象, 先遍历对象:\
        • 如果是数值,则加上‘px’之后赋给相应dom.style
        • 如果是其他,则直接赋值给相应dom.style中
    • 如果是其他属性,比如id,title等等,
      • 如果dom中有key,直接赋值
      • 如果有新值,用dom.setAttribute(key, value)
      • 如果没有,直接移除,dom.removeAttribute
  4. 递归–渲染子节点 vnode.childrens.forEach(child=>render(child, dom))

两个问题:

  1. 为什么ReactDOM.render()必须要引入React
  2. 组件分为哪两种?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值