资讯专栏INFORMATION COLUMN

亲手撸一个React(一):JSX与虚拟DOM

aaron / 1357人阅读

摘要:前言的火热程度已经达到了个,本系列文章主要用简单的代码来实现一个,来了解虚拟算法以及和的设计。要想将虚拟转成真实并渲染到页面上,就需要调用,比如这段代码转换后的样子这时,会将挂载到为的下,从而在页面上显示出来。

前言

react的火热程度已经达到了94.5k个start,本系列文章主要用简单的代码来实现一个react,来了解JSX、虚拟DOM、diff算法以及state和setState的设计。

提到react,当然少不了vue,vue的api设计十分简单 上手也非常容易,但黑魔法很多,使用起来有点虚, 而react没有过多的api,它的深度体现在设计思想上,使用react开发则让人比较踏实、能拿捏的住,这也是我喜欢react的原因之一。

JSX

react怎么少的了JSXJSX是什么,让我来看个例子
现在有下面这段代码:

const el = 

Hello Javascript

这样的js代码如果不经过处理会报错,jsx是语法糖,它让这段代码合法化,通过babel转化后是这样的:

const el = React.createElement(
    "h3",
    { className: "title" },
    "Hello Javascript"
)

这种例子官网首页也有demo

准备开始

开始编码之前,先介绍两个东西:parcelbabel-plugin-transform-jsx,等会我们用parcel搭建一个开发工程,babel-plugin-transform-jsxbabel的一个插件,它可以将jsx语法转成React.createElement(...)

下面我们开始

简单的搭建

parcel这里就不介绍了,一句话概况就是为你生成一个零配置的开发环境。

yarn global add parcel-bundlernpm install -g parcel-bundler

新建项目文件夹,这里取名为simple-react

simple-react中执行 yarn init -ynpm init -y 生成package.json

创建一个index.html

创建src文件夹 再在src下创建index.js 然后再index.html中引入index.js

如果你先麻烦,可以直接下载源码修改。

以上步骤完可能不完整,最好参考parcel里的内容。以上工作完成后,我们需要安装babel-plugin-transform-jsx

npm insatll babel-plugin-transform-jsx --save-dev
或者
yarn add babel-plugin-transform-jsx --dev

然后添加.babelrc文件,并在该文件中加入下面这段代码:

{
  "presets": ["env"],
  "plugins": [["transform-jsx", { "function": "React.createElement" }]]
}

上面代码的意思是 使用transform-jsx插件,并配置为通过React.createElement方法来解析JSX,当然你也可以不用React.createElement和自定义方法,比如preact使用的h方法。

React.createElement()

现在我们在index.js里开始编码。
首先写入代码:

const el = 

Hello Javascript

; console.log(el);

我们在什么都不写的情况下,打印看看el是什么。

打印报错:React没有定义。 这是因为在.babelrc文件中,我们使用的这段代码起了作用:

["transform-jsx", { "function": "React.createElement" }]

上面说过,它会通过React.createElement方法来转译JSX,那么我们就给出这个方法:
我们把刚才那段代码改变一下:

const React = {
  createElement: function(...args) {
    return args[0];
  }
};

const el = 

Hello Javascript

; console.log(el);

上面代码添加了一个React对象,并在其中添加一个createElement方法,现在再执行一下看看打印出什么:

由打印结果可以看出,jsx在使用React.createElement方法转译时,createElement方法应该是这样的:

createElement({ elementName, attributes, children });

elementName: dom对象的标签名 比如div、span等等

attributes: 当前dom对象的属性集合 比如class、id等等

children: 所有子节点

现在我们改写一下createElement方法,让key的名称简单一点:

const React = {
  createElement: function({ elementName, attributes, children }) {
    return {
      tag: elementName,
      attrs: attributes,
      children
    };
  }
};

现在可以看到打印结果是:

我们再打印个复杂点的DOM结构:

const el = (
  
Hello JavaScript
); console.log(el);

和我们想要的结构一样。
其实上面打印出来的就是虚拟DOM,现在我们要做的就是如何把虚拟DOM转成真正的DOM对象并显示在浏览器上。

ReactDOM.render()

要想将虚拟dom转成真实dom并渲染到页面上,就需要调用ReactDOM.render,比如:

ReactDOM.render(

Hello World

, document.getElementById("root"));

这段代码转换后的样子:

ReactDOM.render(
  React.createElement("h1", null, "Hello World"),
  document.getElementById("root")
);

这时,react会将

Hello World

挂载到id为root的dom下,从而在页面上显示出来。

现在我们实现render方法:

function render(vnode, container) {
  const dom = createDom(vnode); //将vnode转成真实DOM
  container.appendChild(dom);
}

上面代码中先调用createDom将虚拟dom转成真实DOM然后挂载到container下。

我们来实现createDom方法:

function createDom(vnode) {
  if (vnode === undefined || vnode === null || typeof vnode === "boolean") {
    vnode = "";
  }

  if (typeof vnode === "string" || typeof vnode === "number") {
    return document.createTextNode(String(vnode));
  }

  const dom = document.createElement(vnode.tag);

    //设置属性
  if (vnode.attrs) {
    for (let key in vnode.attrs) {
      const value = vnode.attrs[key];
      setAttribute(dom, key, value);
    }
  }
    //递归render子节点
  vnode.children.forEach(child => render(child, dom));
  return dom;
}

由于属性的种类比较多,我们抽出一个setAttribute方法来设置属性:

function setAttribute(dom, key, value) {
  //className
  if (key === "className") {
    dom.setAttribute("class", value);

    //事件
  } else if (/onw+/.test(key)) {
    key = key.toLowerCase();
    dom[key] = value || "";
    //style
  } else if (key === "style") {
    if (typeof value === "string") {
      dom.style.cssText = value || "";
    } else if (typeof value === "object") {
      // {width:"",height:20}
      for (let name in value) {
      //如果是数字可以忽略px
        dom.style[name] =
          typeof value[name] === "number" ? value[name] + "px" : value[name];
      }
    }

    //其他
  } else {
    dom.setAttribute(key, value);
  }
}

现在render方法已经完整的实现了,我们将创建ReactDOM对象,将render方法挂上去:

const ReactDOM = {
  render: function(vnode, container) {
    container.innerHTML = "";
    render(vnode, container);
  }
};

这里在调用render之前加了一句container.innerHTML = "",就不解释了,相信大家都明白。

那么万事具备,我们来测试一下,直接上一个比较复杂的dom结构并加上属性:

const element = (
  
alert(1)} style={{ color: "red", fontSize: 30 }} > Hello javascript!
); ReactDOM.render(element, document.getElementById("root"));

打开页面,是我们想要的结果:

再看看控制台的dom:

很完美,这是我们想要的东西

后语

该demo代码在这里哟~~

本文叙述了JSX和虚拟DOM,以及将虚拟DOM转成真实DOM的过程,后面的文章会继续叙述react中的组件、生命周期、diff算法和异步setState,敬请期待~

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

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

相关文章

  • 去哪儿网迷你React的研发心得

    摘要:市面上竟然拥有多个虚拟库。虚拟库,就是出来后的一种新式库,以虚拟与算法为核心,屏蔽操作,操作数据即操作视图。及其他虚拟库已经将虚拟的生成交由与处理了,因此不同点是,虚拟的结构与算法。因此虚拟库是分为两大派系算法派与拟态派。 去哪儿网迷你React是年初立项的新作品,在这前,去哪儿网已经深耕多年,拥有QRN(react-native的公司制定版),HY(基于React的hybird方案)...

    pekonchan 评论0 收藏0
  • 言不合造轮子--ReactTimePicker

    摘要:时间选择的表盘其实有两个,一个是小时的选择,另一个则是分钟的选择。也就是说,第一步选择小时,第二部选择分钟它是一个小时制的时间选择器。而则用于处理拖拽事件,标记着当前是否处于被拖拽状态。 本文的源码全部位于github项目仓库react-times,如果有差异请以github为准。最终线上DEMO可见react-times github page 文章记录了一次创建独立React组件...

    lifesimple 评论0 收藏0
  • 拥抱 JSX,它是伟大的尝试

    摘要:是一个看起来像的语法扩展。有人觉得看起来太怪异了,但是我觉得是一个伟大的尝试,是科学进步的表现,我们不应该对他有任何偏见。所以有一个口号,就是所以,的是一个伟大的尝试,我们应该拥抱。 原文: http://eyasweb.com/#/blog/detail/12 react 带来了新的语法,JSX。是一个看起来像XML的JavaScript语法扩展。 有些同学因为不喜欢或不习惯JSX语...

    xorpay 评论0 收藏0
  • 拥抱 JSX,它是伟大的尝试

    摘要:是一个看起来像的语法扩展。有人觉得看起来太怪异了,但是我觉得是一个伟大的尝试,是科学进步的表现,我们不应该对他有任何偏见。所以有一个口号,就是所以,的是一个伟大的尝试,我们应该拥抱。 原文: http://eyasweb.com/#/blog/detail/12 react 带来了新的语法,JSX。是一个看起来像XML的JavaScript语法扩展。 有些同学因为不喜欢或不习惯JSX语...

    blair 评论0 收藏0

发表评论

0条评论

aaron

|高级讲师

TA的文章

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