资讯专栏INFORMATION COLUMN

从头实现一个简易版React(一)

meislzhua / 3083人阅读

摘要:既然看不懂,那就看看社区前辈们写的一些源码分析文章以及实现思路吧,又这么过了几天,总算是摸清点思路,于是在参考了前辈们的基础上,实现了一个简易版的。总结以上就是实现一个的总体思路,下节我们重点放在不同的上。

写在开头

工作中使用react也很长一段时间了,虽然对它的用法,原理有了一定的了解,但是总感觉停留在表面。本着知其然知其所以然的态度,我试着去看了react源码,几天下来,发现并不能看懂,反而更加云里雾里了- -!。既然看不懂,那就看看社区前辈们写的一些源码分析文章以及实现思路吧,又这么过了几天,总算是摸清点思路,于是在参考了前辈们的基础上,实现了一个简易版的react。
这个系列我打算分为3节,第一节介绍下实现的思路以及结构,第二节讲渲染,第三节讲更新。

进入正题

众所周知,react的核心是Virtual DOM,所以,我们的思路也是围绕着Virtual DOM展开,包含Virtual DOM模型的建立,生命周期的管理,对比差异的diff算法,将Virtual DOM转化为原生DOM并展示的patch方法等,setState异步机制以及react合成事件由于还没有研究到,暂时先忽略,事件处理跟某位前辈的思路一样,也是使用jquery事件代替,这里我们主要以实现渲染,更新为主,相信你在看完这个系列后,能对react的运行原理有一定理解。
项目地址:https://github.com/LuSuguru/f...,以下的所有代码都是通过es6编写,切勿用在生产环境。

Virtual DOM的实现

React的一切都基于Virtual DOM,我们第一步自然先实现它,如下:

/**
 * @param type :代表当前的节点属性
 * @param key :用来标识element,用于优化以后的更新
 * @param props:节点的属性
 */
function VDom(type, key, props) {
  this.type = type
  this.key = key
  this.props = props
}
// 代码地址:src/react/reactElement.js  

实现了vDom后,理所需要一个方法来将我们写的元素转化为vDom。一般我们都是JSX来创建元素的,但它只不过是React.createElment的语法糖。所以,接下来,我们要实现的就是createElement方法:

function createElement(type, config, ...children) {
  const props = {}

  config = config || {}
  // 获取key,用来标识element,方便以后高效的更新
  const { key = null } = config
 
  let propName = ""

  // 复制config里的内容到props
  for (propName in config) {
    if (config.hasOwnProperty(propName) && propName !== "key") {
      props[propName] = config[propName]
    }
  }

  // 转化children
  if (children.length === 1 && Array.isArray(children[0])) {
    props.children = children[0]
  } else {
    props.children = children
  }

  return new VDom(type, key, props)
}
// 代码地址:src/react/reactElement.js  

这段代码也非常简单,根据我们传入的参数,生成对应的vDom

ReactComponent的实现

我们所创建的VDom类型分为3种:

文本类型

原生DOM类型

自定义类型

不同的类型,肯定有不同的渲染和更新逻辑,我们把这些逻辑与vDom一起,封装成对应的ReactComponent类,通过ReactComponent类控制vDom,这里我把它们命名为ReactTextComponent,ReactDomComponent,ReactCompositeComponent,分别对应三种类型。
首先是基类ReactComponet:

// component基类,用来处理不同的虚拟dom更新,渲染
class Component {
  constructor(element) {
    this._vDom = element
    // 用来标识当前component
    this._rootNodeId = null
  }
}
// 代码地址:src/react/component/ReactComponent.js  

接着再让不同类型的component继承这个基类,每种component类型都有mount和update两个方法,用来执行渲染和更新

class ReactDomComponent extends ReactComponent {
    // 渲染
  mountComponent() {}

  // 更新
  updateComponent() {}
}
class ReactCompositeComponent extends ReactComponent {
    // 渲染
  mountComponent() {}

  // 更新
  updateComponent() {}
}
class ReactTextComponent extends ReactComponent {
    // 渲染
  mountComponent() {}

  // 更新
  updateComponent() {}
}
入口的实现

实现了ReactComponent后,我们自然需要一个入口去得到ReactComponent并调用它的mount。在使用React时,通常都是通过

import React from "react"
import ReactDOM from "react-dom"

class App extends React.Component {
}

ReactDOM.render(, document.getElementById("root"))

这段代码来充当渲染的入口,下面我们来实现这个入口,(为了方便说明,我把render方法也放在了React对象中)

import Component from "./Component"
import createElement from "./ReactElement"
import instantiateReactComponent from "./component/util"
import $ from "jquery"

const React = {
  nextReactRootIndex: 0, // 标识id,确定每个vDom的唯一性
  Component, // 所有自定义组件的父类
  createElement, // 创建vdom

  render(vDom, container) { // 入口
    var componentInstance = instantiateReactComponent(vDom) //通过vDom生成Component
    var markup = componentInstance.mountComponent(this.nextReactRootIndex++)

    container.innerHTML = markup
    $(document).trigger("mountReady")
  }
}
// 代码地址:src/react/index.js  

由于渲染和更新都已经封装在不同的ReactComponent里,所以,这里也需要一个方法,根据不同的vDom类型生成对应的ReactComponent,下面我们就来实现这个方法:

// component工厂,用来返回一个component实例
function instantiateReactComponent(node) {
  // 文本节点的情况
  if (typeof node === "string" || typeof node === "number") {
    return new ReactTextComponent(node)
  }

  // 浏览器默认节点的情况
  if (typeof node === "object" && typeof node.type === "string") {
    return new ReactDomComponent(node)
  }

  // 自定义的元素节点
  if (typeof node === "object" && typeof node.type === "function") {
    return new ReactCompositeComponent(node)
  }
}

然后再调用入口ReactComponent的mount方法,获取渲染内容,再将其渲染出来就行。

总结

以上就是实现一个react的总体思路,下节我们重点放在不同ReactComponet的mount上。
下一节地址:https://segmentfault.com/a/11...

参考资料,感谢几位前辈的分享:
https://www.cnblogs.com/sven3...
https://github.com/purplebamb...
陈屹 《深入React技术栈》

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93118.html

相关文章

  • 从头实现简易React(二)

    摘要:写在开头从头实现一个简易版一地址上一节,我们详细介绍了实现一个简易的思路以及整体的结构,但是对于渲染和更新的原理,却还没有提及,因此,本节我们将重点放在的渲染上。 写在开头 从头实现一个简易版React(一)地址:https://segmentfault.com/a/11...上一节,我们详细介绍了实现一个简易React的思路以及整体的结构,但是对于渲染和更新的原理,却还没有提及,因此...

    vvpvvp 评论0 收藏0
  • 从头实现简易React(三)

    摘要:写在开头从头实现一个简易版二地址在上一节,我们的已经具备了渲染功能。参考资料,感谢几位前辈的分享陈屹深入技术栈 写在开头 从头实现一个简易版React(二)地址:https://segmentfault.com/a/11...在上一节,我们的react已经具备了渲染功能。在这一节我们将着重实现它的更新,说到更新,大家可能都会想到React的diff算法,它可以说是React性能高效的保...

    yvonne 评论0 收藏0
  • 基于react native的登录界面demo 超简易教程 redux

    摘要:登录视图登陆失败用户名或密码不能为空弹出提示框成功是点击登录按钮后调用的函数,这里的功能比较简单。通过把发出去密码登录声明组件需要整个中的哪一部分数据作为自己的将和组件联系在一起编写是负责生成的,所以在大项目中还会用到合并。 本猪说 本猪猪刚学react,也刚看RN,就叫写这个,苦不堪言,搭环境就搭了好久。看网上教程也是改了好多小地方才写完了。本着雷锋精神手把手教你写(假的)。 sho...

    scq000 评论0 收藏0
  • 借助Docker和Kodexplorer实现简易化部署Discuz类论坛网站

    摘要:在主机输入框中输入自己的云服务器分配的公网,其他设置不变,点击连接之后会出现安全警告,如果是在自己的电脑上操作的话,接受并保存即可。 基于DiscuzX系列构建的论坛可以说是大家在日常浏览各类BBS网站中接触最多的论坛类型了。本教程旨在引导读者通过简单明确的一些步骤就打造出自己的论坛网站,做一个实实在在的站长。 工具/原料 腾讯云服务器 CentOS 7.2 64位;远程控制程序 ...

    happen 评论0 收藏0
  • 借助Docker和Kodexplorer实现简易化部署Discuz类论坛网站

    摘要:在主机输入框中输入自己的云服务器分配的公网,其他设置不变,点击连接之后会出现安全警告,如果是在自己的电脑上操作的话,接受并保存即可。 基于DiscuzX系列构建的论坛可以说是大家在日常浏览各类BBS网站中接触最多的论坛类型了。本教程旨在引导读者通过简单明确的一些步骤就打造出自己的论坛网站,做一个实实在在的站长。 工具/原料 腾讯云服务器 CentOS 7.2 64位;远程控制程序 ...

    LeexMuller 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<