不到一百行代码,我们来实现一个简简简简简简简简简简版react库

good evening everybody!这是一篇关于react故事的文章,这个故事主要是讲在一个夜黑风高晚上,react从一个VDOM变成真实DOM的过程。

这个过程react经历了从JSX->React.createElement->VDOM->ReactDom.render->真实DOM的大体一个流程。

使用过react的开发者都知道,react一开编写的就是JSX。

以上这段看上去是HTML,但其实它是JSX。什么是JSX?为什么用JSX?它有什么好?

  • JSX是对JS的一个拓展。JSX将HTMl语法直接加入到JS代码中,再通过编译器(Babel-loader)转换成纯JS给浏览器执行。
  • JSX只要用于构建React的View层,类HTML语法,学习成本几乎为零。
  • JSX相对于直接用React.createElement更快,更容易维护,更简洁。

卧槽,怎么说到了JSX里面去了,成吉思汗你真不是人,当大佬们什么都不懂啊。

React.createElement又是什么?它能做什么?又是怎样做的???

  • React.createElement是react里面提供的一个核心方法,它可以将JSX转换成VDMO;说是核心方法,但这个放法实现非常简单,就是返回了一个JS对象。

ReactDom.render又是什么?它能做什么?又是怎样做的???

  • ReactDom.render是ReactDom提供的一个核心方法;ReactDom是FB团队故意从react中抽离出来的,但它只能渲染在浏览器。ReactDom.render可以将React.createElement转换成的VDOM,变成真实DOM挂载在浏览器上。

好了,react从VDOM到真实DOM的转换流程概念大概就是这样,现在通过手写React.createElement,React.Component,ReactDom.render三个API(这里实现的三个API,只是负责这个流程的实现,不带任何属性(class,key,ref...)的处理,和diff算法(tree diff,component diff,element diff)之类的东西,是三个极简版的API)

首先,我们通过create-react-app创建一个工程,然后将其他文件删掉,只留下index.js

在src文件在创建一个文件夹(custom)存放自己实现的react(customReact),和react-dom(coutomReactDom)。

index.js

import React from './custom/customReact';
import ReactDOM from './custom/customReactDom';

let arr = ['su', 'yue', 'feng']

function Container(props) {
    return <div>{props.name}</div>
}
class Test extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        return <div>
            {this.props.name}
            <ul>
                {
                    arr.map(item => <li key={item}>{item}</li>)
                }
            </ul>
        </div>
    }
}
const JSX = (
    <div>
        <Container name="函数组件" />
        <span>普通文本</span>
        <Test name="类组件" />
    </div>
)

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

以上index.js文件我们写上两个组件,(一个函数组件也叫无状态组件,之从hook出来之后,就不能再叫无状态组件了;一个类组件),还有一些普通标签组件。构成reactVDOM无非就这几种类型。

当我们打包编译的时候,以上的JSX就会被React.createElement转换成VDOM;所以我们在customReact中起码要实现一个createElement方法;如果使用了类组件,还有实现一个Component方法。

function createElement(type, props, ...children) {

    let vtype

    props.children = children

    if (typeof type === 'string') {
        vtype = 1
    } else if (typeof type === 'function') {
        if (type.prototype.render) {
            vtype = 2
        } else {
            vtype = 3
        }
    }
    return { vtype, type, props }
}

class Component {

    constructor(props) {
        this.props = props
        this.state = {}
        this.refs = {}
    }
    setState() { }
    
    forceUpdate(){}
}

export default { createElement, Component }

createElement方法接受三个参数,type(标签类型),prop(属性),children(子标签,子属性,这有可能是多个,所以通过es6展开符...,将它们搞成一个数组)。

在createElement里面我们通过type的类型判断他是普通标签,还是组件。如果type是string类型的话,就是普通标签,我们用1表示;如果type是function类型的话,就是组件,但是组件又分函数组件,和类组件,这两种组件,我们用render方法区分,如果又render方法的就是类组件,我们用2表示,没有render方法的就是函数组件,我们用3表示。最后返回一个JS对象。

在Component方法里面,我们什么也不做就是初始换一个变量,setState(),forceUpdate()这两个方法应该大家都不陌生。这里就不去实现,因为这部分的内容太多,涉及到diff,执行机制,更新机制等等,有兴趣的自己去看源码。

最后我们导出这两个方法。

在打包编译的时候,当JSX就会被React.createElement转换成VDOM后,就会执行,react-dom的render方法,这个放法会将VDMO变成真实DOM,所以我们在实现自己的react-dom的时候起码要有一个render方法。

coutomReactDom.js

function render(vnode, container) {
    container.appendChild(initVnode(vnode))
}

function initVnode(vnode) {
    if (!vnode.vtype) {
        return document.createTextNode(vnode)
    }
    if (vnode.vtype === 1) {
        return createElement(vnode)
    } else if (vnode.vtype === 2) {
        return createClassCom(vnode)
    } else if (vnode.vtype === 3) {
        return createFunctionCom(vnode)
    }
}

function createElement(vnode) {
    let { type, props: { key, children } } = vnode

    let node = document.createElement(type)
    
    children.forEach(element => {
        if (Array.isArray(element)) {
            element.forEach(c => {
                node.appendChild(initVnode(c))
            })
        } else {
            node.appendChild(initVnode(element))
        }
    });

    return node
}

function createClassCom(vnode) {
    let { type, props } = vnode
    let instance = new type(props)
    return initVnode(instance.render())
}

function createFunctionCom(vnode) {
    let { type, props } = vnode
    return initVnode(type(props))
}

export default { render }

render方法接收两个参数,vnode(虚拟DOM),container(挂载节点)

在render里面,我们通过传进来的虚拟DOM的vtype(也就是我们刚才通过1,2,3,标记的类型)来创建真实的DOM。如果vnode没有vtype的话,我们就通过document.createTextNode(vnode)来创建该文本,如果是1的话,我们就通过createElement方法来创建,createElement方法,只要是通过document.createElement()和递归的方式实现创建节点的流程。如果是2的话,我们就通过createClassCom()方法去创建节点;如果是3的话就通过createFunctionCom()方法去创建节点。这两个方法及其简单加起来就五行代码,这两个方法只要是去拿到组件的虚拟节点,然后给initVnode方法就看可以。因为无论什么组件,到最后还是跑回到创建普通标签的流程,也就是最后还是回到createElement方法里面。

好了,这样就大功告成了,这样就实现了自己的一个react库,俩个文件加起来也只有70行代码。(真正的react里面的做的东西,处理的东西会比这个复杂上千万赔,这里只不过是实现是它从VDOM到真实DOM的转换大概流程)

这是跑起来的效果(npm start)

有不足的地方,希望大佬们指出来。
image.png

Image placeholder
couldtt
未设置
  52人点赞

没有讨论,发表一下自己的看法吧

推荐文章
如何下载react库

如何下载react库一、使用浏览器直接下载react.js和react-dom.js在浏览器中输入https://cdn.staticfile.org/react/16.4.0/umd/react.d

从300万行到50万行代码,遗留系统的微服务改造

在传统企业甚至互联网企业中往往存在大量的遗留系统,这些遗留系统大多都能够正常工作,有的可能还运行着关键业务或者持有核心数据。但是,大部分遗留系统通常经常存在技术陈旧、代码复杂、难以修改等特点。笔者曾经

跨平台开发的救星-让我们来了解一下flutter

第一次看文章的朋友可以关注我,会不定期发布Android面试内容、进阶专题等等。简介很多人已经用上了flutter,今天就来介绍一下Flutter架构Flutter框架分三层 Framework,En

PHP 中使用 TUS 协议来实现大文件的断点续传

你是否曾经为大文件上传而苦恼?如果文件上传的过程中,因为某种原因中断了,是否可以从中断的位置继续上传,而不用重新上传整个文件?如果你有这样的困惑,那么请继续阅读下面的内容。 在现代网站应用中,上传文

一行代码解决求重问题

对需求的深刻认知,决定了解决问题的技巧高度 问题例如:vara=[[1,2,3,5],[2,3,4],[2,3],[2,3,6,7]]; 输出:[2,3] 例如:vara=[[0,1,2,3,5],

我的天!这是史上最烂的项目:苦撑12年,600多万行代码…

编译:欧剃来源:projectfailures.wordpress.com转载自:Java技术栈你见过最烂的项目,撑了多长时间才完蛋?六个月?一年?今天介绍的这个奇葩项目,不但一开始就烂得透透的,还硬

html和css不换行代码是什么?

html和css不换行代码是什么?一、HTML不换行代码:在网页排版布局中比如文章列表标题排版,无论多少文字均不希望换行显示,需要强制在一行显示完内容。这就可以nobr标签来实现。语法:内容不换行内容

把 14 亿中国人都拉到一个微信群,程序员在技术上能实现吗?

根据国家统计局的数据,截至2017年末,中国大陆总人口为13亿9008万人(包括31个省、自治区、直辖市和中国人民解放军现役军人,不包括香港、澳门和台湾以及海外华侨人数),早已超过13亿。目前,微信群

一个简单的基于 Redis 的分布式任务调度器 —— Java 语言实现

折腾了一周的JavaQuartz集群任务调度,很遗憾没能搞定,网上的相关文章也少得可怜,在多节点(多进程)环境下Quartz似乎无法动态增减任务,恼火。无奈之下自己撸了一个简单的任务调度器,结果只花了

算法题:设计和实现一个 LRU Cache 缓存机制

理论基础 LRU算法、Cache 实现LRUCache缓存机制 题目描述 设计和实现一个LRUCache缓存机制 解题思路 leastrecentlyused最近最少使用(被淘汰) Doubl

算法题:设计和实现一个 LRU Cache 缓存机制

题目来源于力扣 理论基础 LRU算法、Cache 实现LRUCache缓存机制题目描述 设计和实现一个LRUCache缓存机制 解题思路leastrecentlyused最近最少使用(被淘汰) Do

使用vue实现一个电子签名组件

使用vue实现一个电子签名组件在生活中我们使用到电子签名最多的地方可能就是银行了,每次都会让你留下大名。今天我们就要用vue实现一个电子签名的面板想要绘制图形,第一步想到的就是使用canvas标签,在

使用vue实现一个电子签名组件

在生活中我们使用到电子签名最多的地方可能就是银行了,每次都会让你留下大名。今天我们就要用vue实现一个电子签名的面板想要绘制图形,第一步想到的就是使用canvas标签,在之前的文章里我们使用canva

“听完你的评价,我们决定拒绝这位明天入职的技术经理”

每个工作日的中午,只要天气晴朗,我都会在午餐后去附近的公园溜达溜达,一来可以帮助肠胃消化,二来则有助于我静心思考工作总结,从而增强写作主题构思的能力。所以,我比较厌烦在这个时间段里聊工作。上周的某天,

判菜系、调众囗、打分数,这一回,我们用大数据烧菜?

大数据文摘投稿作品作者:blmoistawinde年前,文摘菌曾经扒下了全网所有“年夜饭”菜谱,找到了最有年味的一道菜的一文,对于菜谱数据分析产生了浓厚的兴趣,遂自己也写了个爬虫爬取了某美食网站的一些

在头条和百度搜索了100个关键词之后,我们发现……

作者|闫丽娇苏琦编辑|苏琦• 常用名词搜索方面,百度站外内容占比更高,内容来源比头条更多元。头条搜索的信息流广告目前还没有接入;• 疑问解答类搜索,百度的内容发散性更杂,而头条在信息准确度上更能理解用

快看,我们的分布式缓存就是这样把注册中心搞崩塌

写公众号两年以来,每当有机会写故障类主题的时候,我都会在开始前静静地望着显示器很久,经过多次煎熬和挣扎之后才敢提起笔来,为什么呢?因为这样的话题很容易招来吐槽,比如“说了半天,不就是配置没配好吗?”,

面向回家编程!GitHub标星两万的”Python抢票教程”,我们先帮你跑了一遍

盼望着,盼望着,春节的脚步近了,然而,每年到这个时候,最难的,莫过于一张回家的火车票。据悉,今年春运期间,全国铁路发送旅客人次同比将增长8.0%。达到4.4亿人次,2020年铁路春运自1月10日开始,

12306遭用户吐槽,我们该支持还是反对?

时近年底,春运火车票已进入销售高峰期,在线购票系统12306成为舆论漩涡,被大众推上风口浪尖。虽然,网络购票为大家带来了便利,省去了很多线下购票的麻烦,速度也比以往快了许多,但还是少不了遭用户吐槽。具

56岁潘石屹下决心学Python,60岁程序语言之父们还在敲代码,你呢

比你成功的人,比你还努力。上周,SOHO中国董事长、地产大亨 潘石屹,56岁生日当天发布微博宣布进军编程语言Python。 紧接着第二天,又更新微博解释为何会做出此举。潘石屹给出的解释大致就是,在不断

可自动生成代码,5款基于AI的顶级开发工具

如今,对机器学习潜力感兴趣的程序员都在讨论,如何使用人工智能和基于人工智能的软件开发工具构建应用程序。例如PyTorch和TensorFlow之类的解决方案。除此之外,机器学习技术正以另一种有趣的方式

再见微服务,从100多个问题儿童到一个超级明星

翻译| 马岛本文翻译自AlexandraNoonan的 GoodbyeMicroservices:From100sofproblemchildrento1 superstar。内容是描述 Segmen

使用 Vue.js 和 Iris 共建一个简单的 Todo MVC 应用

本文用Golang的Iris框架作为后端服务,vuejs渲染前端UI,用websocket通信。基于监听hash变化director.js库实现简单路由,axios库与后方沟通,netoffos.j

使用 Vue.js 和 Iris 共建一个简单的 Todo MVC 应用

数据服务 packagetodo import"sync" //Item条目数据 typeItemstruct{ SessionIDstring`json:"-"` IDint64`json:"i

我们走访了900名微软员工,为你揭秘全球最大软件公司的代码评审机制

大数据文摘出品来源:michaelagreiler编译:倪倪、钱天培、毅航全球最大的软件公司之一微软拥有约140,000名员工,其中大约44%,即超过60,000名员工,是工程师。Office、Vis