使用JSX 建立 Markup 组件风格开发的示例(前端组件化)

目录
  • JSX 环境搭建
  • 初始化 NPM
  • 安装 webpack
  • 安装 Babel
  • 配置 webpack
  • 安装 Babel-loader
  • 模式配置
  • 引入 JSX
  • JSX 基本用法
  • JSX 基础原理
  • 实现 createElement 函数
  • 实现自定义标签
    • 它会包含以下方法:

这里我们一起从 0 开始搭建一个组件系统。首先通过上一篇《前端组件化基础知识》和《用 JSX 建立组件 Parser(解析器)》中知道,一个组件可以通过 Markup 和 JavaScript 访问的一个环境。

所以我们的第一步就是建立一个可以使用 markup 的环境。这里我们会学习使用 JSX 来建立 markup 的风格。这里我们基于与 React 一样的 JSX 去建立我们组件的风格。


JSX 环境搭建

JSX 在大家一般认知里面,它是属于 React 的一部分。其实 Facebook 公司会把 JSX 定义为一种纯粹的语言扩展。而这个 JSX 也是可以被其他组件体系去使用的。

甚至我们可以把它单独作为一种,快捷创建 HTML 标签的方式去使用。

建立项目

那么我们就从最基础的开始,首先我们需要创建一个新的项目目录:

mkdir jsx-component

初始化 NPM

当然在你们喜欢的目录下创建这个项目文件夹。建立好文件夹之后,我们就可以进入到这个目录里面并且初始化 npm

npm init

执行以上命令之后,会出现一些项目配置的选项问题,如果有需要可以自行填写。不过我们也可以直接一直按回车,然后有需要的同学可以后面自己打开 package.json 自行修改。

安装 webpack

Wepack 很多同学应该都了解过,它可以帮助我们把一个普通的 JavaScript 文件变成一个能把不同的 import 和 require 的文件给打包到一起。

所以我们需要安装 webpack ,当然我们也可以直接使用 npx 直接使用 webpack,也可以全局安装 webpack-cli。

那么这里我们就使用全局安装 webpack-cli:

npm install -g webpack webpack-cli

安装完毕之后,我们可以通过输入下面的一条命令来检车一下安装好的 webpack 版本。如果执行后没有报错,并且出来了一个版本号,证明我们已经安装成功了。

webpack --version

安装 Babel

因为 JSX 它是一个 babel 的插件,所以我们需要依次安装 webpack,babel-loader, babel 和 babel 的 plugin。

这里使用 Babel 还有一个用处,它可以把一个新版本的 JavaScript 编译成一个老版本的 JavaScript,这样我们就可以支持在更多老版本的浏览器中运行。

安装 Babel 我们只需要执行以下的命令即可。

npm install --save-dev webpack babel-loader

这里我们需要注意的是,我们需要加上 --save-dev,这样我们就会把 babel 加入到我们的开发依赖中。

执行完毕后,我们应该会看到上面图中的消息。

为了验证我们是正确安装好了,我们可以打开我们项目目录下的 package.json

{
 "name": "jsx-component",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
 "test": "echo \"Error: no test specified\" && exit 1"
 },
 "author": "",
 "license": "ISC",
 "devDependencies": {
 "babel-loader": "^8.1.0",
 "webpack": "^5.4.0"
 }
}

好,我们可以看到在 devDependencies 下方,确实是有我们刚刚安装的两个包。还是担心的同学,可以再和 package.json 确认一下眼神哈。

配置 webpack

到这里我们就需要配置一下 webpack 的配置。配置 webpack 我们需要创建一个 webpack.config.js 配置文件。

在我们项目的根目录创建一个 webpack.config.js 文件。

首先 webpack config 它是一个 nodejs 的模块,所以我们需要用 module.exports 来写它的设置。而这个是早期 nodejs 工具常见的一种配置方法,它用一个 JavaScript 文件去做它的配置,这样它在这个配置里面就可以加入一些逻辑。

module.exports = {}

Webpack 最基本的一个东西,就是需要设置一个 entry (设置它的入口文件)。这里我们就设置一个 main.js 即可。

module.exports = {
 entry: "./main.js"
}

这个时候,我们就可以先在我们的根目录下创建一下我们 main.js 的文件了。在里面我们先加入一个简单的 for 循环。

// main.js 文件内容
for (let i of [1, 2, 3]) {
 console.log(i);
}

这样我们 webpack 的基本配置就配置好了,我们在根目录下执行一下 webpack 来打包一下我们 main.js 的文件看看。我们只需要执行下面的这行命令:

webpack

执行完毕之后,我们就可以在命令行界面中看到上面这样的一段提示。

注意细节的同学,肯定要举手问到,同学同学!你的命令行中报错啦!黄色部分确实有给我们一个警告,但是不要紧,这个我们接下的配置会修复它的。

这个时候我们会发现,在我们的根目录中生成了一个新的文件夹 dist。这个就是 webpack 打包默认生成的文件夹,我们所有打包好的 JavaScript 和资源都会被默认放入这个文件夹当中。

这里我们就会发现,这个 dist 文件夹里面有一个打包好的 main.js 的文件,这个就是我们写的 main.js,通过 webpack 被打包好的版本。

然后我们打开它,就会看到它被 babel 编译过后的 JavaScript 代码。我们会发现我们短短的几行代码被加入了很多的东西,这些其实我们都不用管,Webpack 的 “喵喵力量”。

在代码的最后面,还是能看到我们编写的 for 循环的,只是被改造了一下,但是它的作用是一致的。

安装 Babel-loader

接下来我们来安装 babel-loader,其实 babel-loader 并没有直接依赖 babel 的,所以我们才需要另外安装 @babel/core@babel/preset-env。我们只需要执行下面的命令行来安装:

npm install --save-dev @babel/core @babel/preset-env

最终的结果就如上图一样,证明安装成功了。这个时候我们就需要在 webpack.config.js 中配置上,让我们打包的时候用上 babel-loader。

在我们上面配置好的 webpack.config.jsentry 后面添加一个选项叫做 module

然后模块中我们还可以加入一个 rules,这个就是我们构建的时候所使用的规则。而 rules 是一个数组类型的配置,这里面的每一个规则是由一个 test 和一个 use 组成的。

  • test:

test 的值是一个正则表达式,用于匹配我们需要使用这个规则的文件。这里我们需要把所有的 JavaScript 文件给匹配上,所以我们使用 /\.js/ 即可。

  • use:

loader: 只需要加入我们的 babel-loader 的名字即可

  • options:

presets:

  • 这里是 loader 的选项,这里我们需要加入 @babel/preset-env

最后我们的配置文件就会是这个样子:

module.exports = {
 entry: './main.js',
 module: {
 rules: [
 {
 test: /\.js$/,
 use: {
 loader: 'babel-loader',
 options: {
 presets: ['@babel/preset-env'],
 },
 },
 },
 ],
 },
};

这样配置好之后,我们就可以来跑一下 babel 来试一试会是怎么样的。与刚才一样,我们只需要在命令行执行 webpack 即可。

如果我们的配置文件没有写错,我们就应该会看到上面图中的结果。

然后我们进入 dist 文件夹,打开我们编译后的 main.js,看一下我们这次使用了 babel-loader 之后的编译结果。

编译后的结果,我们会发现 for of 的循环被编译成了一个普通的 for 循环。这个也可以证明我们的 babel-loader 起效了,正确把我们新版本的 JavaScript 语法编程成能兼容旧版浏览器的 JavaScript 语法。

到了这里我们已经把我们 JSX 所需的环境给安装和搭建完毕了。

模式配置

最后我们还需要在 webpack.config.js 里面添加一个环境配置,不过这个也可以说可加可不加的,但是我们为了平时开发中的方便。

所以我们需要在 webpack.config.js 中添加一个 mode,这个属性的值我们使用 development。这个配置表示我们是开发者模式。

一般来说我们在代码仓库里面写的 webpack 配置都会默认加上这个 mode: 'development' 的配置。当我们真正发布的时候,我们就会把它改成 mode: 'production'

module.exports = {
 entry: './main.js',
 mode: 'development',
 module: {
 rules: [
 {
 test: /\.js$/,
 use: {
 loader: 'babel-loader',
 options: {
 presets: ['@babel/preset-env'],
 },
 },
 },
 ],
 },
};

改好之后,我们在使用 webpack 编译一下,看看我们的 main.js 有什么区别。

显然我们发现,编译后的代码没有被压缩成一行了。这样我们就可以调试 webpack 生成的代码了。这里我们可以注意到,我们在 main.js 中的代码被转成字符串,并且被放入一个 eval() 的函数里面。我们的代码被放入了 eval 里面,那么我们就可以在调试的时候就可以把它作为一个单独的文件去使用了。

引入 JSX

万事俱备,只欠东风了,最后我们需要如何引入 JSX呢?在引入之前,我们来看看,如果就使用现在的配置在我们的 main.js 里面使用 JSX 语法会怎么样。作为程序员的我们,总得有点冒险精神!

所以我们在我们的 main.js 里面加入这段代码:

var a = <div/>

然后我们执行 webpack 看看!

好家伙!果然报错了。这里的报错告诉我们,在 = 后面不能使用 “小于号”,但是在正常的 JSX 语法中,这个其实是 HTML 标签的 “尖括号”,因为没有 JSX 语法的编译过程,所以 JavaScript 默认就会认为这个就是 “小于号”。

所以我们要怎么做让我们的 webpack 编译过程支持 JSX 语法呢?这里其实就是还需要我们加入一个最关键的一个包,而这个包名非常的长,叫做 @babel/plugin-transform-react-jsx。好那么我们就执行一段命令来安装一下这个包:

npm install --save-dev @babel/plugin-transform-react-jsx

安装好之后,我们还需要在 webpack 配置中给他加入进去。我们需要在 module 里面的 rules 里面的 use 里面加入一个 plugins 的配置中加入 ['@babel/plugin-transform-react-jsx']

然后最终我们的 webpack 配置文件就是这样的:

module.exports = {
 entry: './main.js',
 mode: 'development',
 module: {
 rules: [
 {
 test: /\.js$/,
 use: {
 loader: 'babel-loader',
 options: {
 presets: ['@babel/preset-env'],
 plugins: ['@babel/plugin-transform-react-jsx'],
 },
 },
 },
 ],
 },
};

配置好之后,我们再去执行一下 webpack。这时候我们发现没有再报错了。这样也就证明我们的代码现在是支持使用 JSX 语法编写了。

最后我们来围观一下,最后编程的效果是怎么样的。

我们会发现,在 eval 里面我们加入的 <div/> 被翻译成一个 React.createElement("div", null) 的函数调用了。

所有接下来我们就一起来看一下,我们应该怎么实现这个 React.createElement,以及我们能否把这个换成我们自己的函数名字。

JSX 基本用法

首先我们来尝试理解 JSX,JSX 其实它相当于一个纯粹在代码语法上的一种快捷方式。在上一部分的结尾我们看到,JSX语法在被编译后会出现一个 React.createElement 的一个调用。

JSX 基础原理

那么这里我们就先修改在 webpack 中的 JSX 插件,给它一个自定义的创建元素函数名。我们打开 webpack.config.js,在 plugins 的位置,我们把它修改一下。

module.exports = {
 entry: './main.js',
 mode: 'development',
 module: {
 rules: [
 {
 test: /\.js$/,
 use: {
 loader: 'babel-loader',
 options: {
 presets: ['@babel/preset-env'],
 plugins: [
				[
					'@babel/plugin-transform-react-jsx',
					{ pragma: 'createElement' }
				]
			],
 },
 },
 },
 ],
 },
};

上面我们只是把原来的 ['@babel/plugin-transform-react-jsx'] 参数改为了 [['@babel/plugin-transform-react-jsx', {pragma: 'createElement'}]]。加入了这个 pragma 参数,我们就可以自定义我们创建元素的函数名。

这么一改,我们的 JSX 就与 React 的框架没有任何联系了。我们执行一下 webpack 看一下最终生成的效果,就会发现里面的 React.createElement 就会变成 createElement

接下来我们加入一个 HTML 文件来执行我们的 main.js 试试。首先在根目录创建一个 main.html,然后输入一下代码:

<script src="./main.js"></script>

然后我们执行在浏览器打开这个 HTML 文件。

这个时候我们控制台会给我们抛出一个错误,我们的 createElement 未定义。确实我们在 main.js 里面还没有定义这个函数,所以说它找不到。

所以我们就需要自己编写一个 createElement 这个函数。我们直接打开根目录下的 main.js 并且把之前的 for 循环给删除了,然后加上以下代码:

function createElement() {
 return;
}

let a = <div />;

这里我们就直接返回空,先让这个函数可以被调用即可。我们用 webpack 重新编译一次,然后刷新我们的 main.html 页面。这个时候我们就会发现报错没有了,可以正常运行。

实现 createElement 函数

在我们的编译后的代码中,我们可以看到 JSX 的元素在调用 createElement 的时候是传了两个参数的。第一个参数是 div, 第二个是一个 null

这里第二个参数为什么是 null 呢?其实第二个参数是用来传属性列表的。如果我们在 main.js 里面的 div 中加入一个 id="a" ,我们来看看最后编译出来会有什么变化。

我们就会发现第二个参数变成了一个 以 Key-Value 的方式存储的JavaScript 对象。到这里如果我们想一下,其实 JSX 也没有那么神秘,它只是把我们平时写的 HTML 通过编译改写成了 JavaScript 对象,我们可以认为它是属于一种 “语法糖”。

但是 JSX 影响了代码的结构,所以我们一般也不会完全把它叫作语法糖。

接下来我们来写一些更复杂一些的 JSX,我们给我们原本的 div 加一些 children 元素。

function createElement() {
 return;
}

let a = (
 <div id="a">
 <span></span>
 <span></span>
 <span></span>
 </div>
);

最后我们执行一下 webpack 打包看看效果。

在控制台中,我们可以看到最后编译出来的结果,是递归的调用了 createElement 这个函数。这里其实已经形成了一个树形的结构。

父级就是第一层的 div 的元素,然后子级就是在后面当参数传入了第一个 createElement 函数之中。然后因为我们的 span 都是没有属性的,所以所有后面的 createElement 的第二个参数都是 null

根据我们这里看到的一个编译结果,我们就可以分析出我们的 createElement 函数应有的参数都是什么了。

  • 第一个参数 type —— 就是这个标签的类型
  • 第二个参数 attribute —— 标签内的所有属性与值
  • 剩余的参数都是子属性 ...children —— 这里我们使用了 JavaScript 之中比较新的语法 ...children 表示把后面所有的参数 (不定个数) 都会变成一个数组赋予给 children 变量

那么我们 createElement 这个函数就可以写成这样了:

function createElement(type, attributes, ...children) {
 return;
}

函数我们有了,但是这个函数可以做什么呢?其实这个函数可以用来做任何事情,因为这个看起来长的像 DOM API,所以我们完全可以把它做成一个跟 React 没有关系的实体 DOM。

比如说我们就可以在这个函数中返回这个 type 类型的 element 元素。这里我们把所有传进来的 attributes 给这个元素加上,并且我们可以给这个元素挂上它的子元素。

创建元素我们可以用 createElement(type),而加入属性我们可以使用 setAttribute(),最后挂上子元素就可以使用 appendChild()

function createElement(type, attributes, ...children) {
 // 创建元素
 let element = document.createElement(type);
 // 挂上属性
 for (let attribute in attributes) {
 element.setAttribute(attribute);
 }
 // 挂上所有子元素
 for (let child of children) {
 element.appendChild(child);
 }
 // 最后我们的 element 就是一个节点
 // 所以我们可以直接返回
 return element;
}

这里我们就实现了 createElement 函数的逻辑。最后我们还需要在页面上挂載上我们的 DOM 节点。所以我们可以直接挂載在 body 上面。

// 在 main.js 最后加上这段代码
let a = (
 <div id="a">
 <span></span>
 <span></span>
 <span></span>
 </div>
);

document.body.appendChild(a);

这里还需要注意的是,我们的 main.html 中没有加入 body 标签,没有的话我们是无法挂載到 body 之上的。所以这里我们就需要在 main.html 当中加入 body 标签。

<body></body>
<script src="dist/main.js"></script>

好,这个时候我们就可以 webpack 打包,看一下效果。

Wonderful! 我们成功的把节点生成并且挂載到 body 之上了。但是如果我们的 div 里面加入一段文字,这个时候就会有一个文本节点被传入我们的 createElement 函数当中。毋庸置疑,我们的 createElement 函数以目前的逻辑是肯定无法处理文本节点的。

接下来我们就把处理文本节点的逻辑加上,但是在这之前我们先把 div 里面的 span 标签删除,换成一段文本 “hello world”。

let a = <div id="a">hello world</div>;

在我们还没有加入文本节点的逻辑之前,我们先来 webpack 打包一下,在我们挂上子节点之前,判断看看具体会报什么错误。

首先我们可以看到,在 createElement 函数调用的地方,我们的文本被当成字符串传入,然后这个参数是接收子节点的,并且在我们的逻辑之中我们使用了 appendChild,这个函数是接收 DOM 节点的。显然我们的 文本字符串不是一个节点,自然就会报错。

通过这种调试方式我们可以马上定位到,我们需要在哪里添加逻辑去实现我们这个功能。这种方式也可以算是一种捷径吧。

所以接下来我们就回到 main.js,在我们挂上子节点之前,判断一下 child 的类型,如果它的类型是 “String” 字符串的话,就使用 createTextNode() 来创建一个文本节点,然后再挂載到父元素上。这样我们就完成了字符节点的处理了。

function createElement(type, attributes, ...children) {
 // 创建元素
 let element = document.createElement(type);
 // 挂上属性
 for (let name in attributes) {
 element.setAttribute(name, attributes[name]);
 }
 // 挂上所有子元素
 for (let child of children) {
 if (typeof child === 'string')
		child = document.createTextNode(child);
 element.appendChild(child);
 }
 // 最后我们的 element 就是一个节点
 // 所以我们可以直接返回
 return element;
}

let a = <div id="a">hello world</div>;

document.body.appendChild(a);

我们用这个最新的代码 webpack 打包之后,就可以在浏览器上看到我们的文字被显示出来了。

到了这里我们编写的 createElement 已经是一个比较有用的东西了,我们已经可以用它来做一定的 DOM 操作了。甚至它可以完全代替我们自己去写 document.createElement 的这种反复繁琐的操作了。

这里我们可以验证一下,我们在 div 当中重新加上我们之前的三个 span, 并且在每个 span 中加入文本。11

let a = (
 <div id="a">
 hello world:
 <span>a</span>
 <span>b</span>
 <span>c</span>
 </div>
);

然后我们重新 webpack 打包后,就可以看到确实是可以完整这种 DOM 的操作的。

现在的代码已经可以完成一定的组件化的基础能力。

实现自定义标签

之前我们都是在用一些,HTML 自带的标签。如果我们现在把 div 中的 d 改为大写 D 会怎么样呢?

let a = (
 <Div id="a">
 hello world:
 <span>a</span>
 <span>b</span>
 <span>c</span>
 </Div>
);

果不其然,就是会报错的。不过这就是我们找到问题的根源的关键,这里我们发现当我们把 div 改为 Div 的时候,传入我们 createElement 的 div 从字符串 ‘div' 变成了一个 Div 类。

当然我们的 JavaScript 中并没有定义 Div 类,这里自然就会报 Div 未定义的错误。知道问题的所在,我们就可以去解决它,首先我们需要先解决未定义的问题,所以我们先建立一个 Div 的类。

// 在 createElment 函数之后加入
class Div {}

然后我们就需要在 createElement 里面做类型判断,如果我们遇到的 type 是字符类型,就按原来的方式处理。如果我们遇到是其他情况,我们就实例化传过来的 type

function createElement(type, attributes, ...children) {
 // 创建元素
 let element;
 if (typeof type === 'string') {
 element = document.createElement(type);
 } else {
 element = new type();
 }

 // 挂上属性
 for (let name in attributes) {
 element.setAttribute(name, attributes[name]);
 }
 // 挂上所有子元素
 for (let child of children) {
 if (typeof child === 'string') child = document.createTextNode(child);
 element.appendChild(child);
 }
 // 最后我们的 element 就是一个节点
 // 所以我们可以直接返回
 return element;
}

这里我们还有一个问题,我们有什么办法可以让自定义标签像我们普通 HTML 标签一样操作呢?在最新版的 DOM 标准里面是有办法的,我们只需要去注册一下我们自定义标签的名称和类型。

但是我们现行比较安全的浏览版本里面,还是不太建议这样去做的。所以在使用我们的自定义 element 的时候,还是建议我们自己去写一个接口。

首先我们是需要建立标签类,这个类能让任何标签像我们之前普通 HTML 标签的元素一样最后挂載到我们的 DOM 树上。

它会包含以下方法:

  • mountTo() —— 创建一个元素节点,用于后面挂載到 parent 父级节点上
  • setAttribute() —— 给元素挂上所有它的属性
  • appendChild() —— 给元素挂上所有它的子元素

首先我们来简单实现我们 Div 类中的 mountTo 方法,这里我们还需要给他加入 setAttributeappendChild 方法,因为在我们的 createElement 中有挂載属性子元素的逻辑,如果没有这两个方法就会报错。但是这个时候我们先不去实现这两个方法的逻辑,方法内容留空即可。

class Div {
 setAttribute() {}
 appendChild() {}
 mountTo(parent) {
 this.root = document.createElement('div');
 parent.appendChild(this.root);
 }
}

这里面其实很简单首先给类中的 root 属性创建成一个 div 元素节点,然后把这个节点挂載到这个元素的父级。这个 parent 是以参数传入进来的。

然后我们就可以把我们原来的 body.appendChild 的代码改为使用 mountTo 方法来挂載我们的自定义元素类。

// document.body.appendChild(a);
a.mountTo(document.body);

用现在的代码,我们 webpack 打包看一下效果:

我们可以看到我们的 Div 自定义元素是有正确的被挂載到 body 之上。但是 Div 中的 span 标签都是没有被挂載上去的。如果我们想它与普通的 div 一样去工作的话,我们就需要去实现我们的 setAttributeappendChild 逻辑。

接下来我们就一起来尝试完成剩余的实现逻辑。在开始写 setAttribute 和 appendChild 之前,我们需要先给我们的 Div 类加入一个构造函数 constructor。在这里个里面我们就可以把元素创建好,并且代理到 root 上。

constructor() {
 this.root = document.createElement('div');
}

然后的 setAttribute 方法其实也很简单,就是直接使用 this.root 然后调用 DOM API 中的 setAttribute 就可以了。而 appendChild 也是同理。最后我们的代码就是如下:

class Div {
 // 构造函数
 // 创建 DOM 节点
 constructor() {
 this.root = document.createElement('div');
 }
 // 挂載元素的属性
 setAttribute(name, attribute) {
 this.root.setAttribute(name, attribute);
 }
 // 挂載元素子元素
 appendChild(child) {
 this.root.appendChild(child);
 }
 // 挂載当前元素
 mountTo(parent) {
 parent.appendChild(this.root);
 }
}

我们 webpack 打包一下看看效果:

我们可以看到,div 和 span 都被成功挂載到 body 上。也证明我们自制的 div 也能正常工作了。

这里还有一个问题,因为我们最后调用的是 a.mountTo(),如果我们的 变量 a 不是一个自定义的元素,而是我们普通的 HTML 元素,这个时候他们身上是不会有 mountTo 这个方法的。

所以这里我们还需要给普通的元素加上一个 Wrapper 类,让他们可以保持我们元素类的标准格式。也是所谓的标准接口。

我们先写一个 ElementWrapper 类,这个类的内容其实与我们的 Div 是基本一致的。唯有两个区别

在创建 DOM 节点的时候,可以通过传当前元素名 type到我们的构造函数,并且用这个 type 去建立我们的 DOM 节点appendChild 就不能直接使用 this.root.appendChild,因为所有普通的标签都被改为我们的自定义类,所以 appendChild 的逻辑需要改为 child.mountTo(this.root)

class ElementWrapper {
 // 构造函数
 // 创建 DOM 节点
 constructor(type) {
 this.root = document.createElement(type);
 }
 // 挂載元素的属性
 setAttribute(name, attribute) {
 this.root.setAttribute(name, attribute);
 }
 // 挂載元素子元素
 appendChild(child) {
 child.mountTo(this.root);
 }
 // 挂載当前元素
 mountTo(parent) {
 parent.appendChild(this.root);
 }
}

class Div {
 // 构造函数
 // 创建 DOM 节点
 constructor() {
 this.root = document.createElement('div');
 }
 // 挂載元素的属性
 setAttribute(name, attribute) {
 this.root.setAttribute(name, attribute);
 }
 // 挂載元素子元素
 appendChild(child) {
 child.mountTo(this.root);
 }
 // 挂載当前元素
 mountTo(parent) {
 parent.appendChild(this.root);
 }
}

这里我们还有一个问题,就是遇到文本节点的时候,是没有转换成我们的自定义类的。所以我们还需要写一个给文本节点,叫做 TextWrapper

class TextWrapper {
 // 构造函数
 // 创建 DOM 节点
 constructor(content) {
 this.root = document.createTextNode(content);
 }
 // 挂載元素的属性
 setAttribute(name, attribute) {
 this.root.setAttribute(name, attribute);
 }
 // 挂載元素子元素
 appendChild(child) {
 child.mountTo(this.root);
 }
 // 挂載当前元素
 mountTo(parent) {
 parent.appendChild(this.root);
 }
}

有了这些元素类接口后,我们就可以改写我们 createElement 里面的逻辑。把我们原本的 document.createElementdocument.createTextNode 都替换成实例化 new ElementWrapper(type)new TextWrapper(content)即可。

function createElement(type, attributes, ...children) {
 // 创建元素
 let element;
 if (typeof type === 'string') {
 element = new ElementWrapper(type);
 } else {
 element = new type();
 }

 // 挂上属性
 for (let name in attributes) {
 element.setAttribute(name, attributes[name]);
 }
 // 挂上所有子元素
 for (let child of children) {
 if (typeof child === 'string')
		child = new TextWrapper(child);
 element.appendChild(child);
 }
 // 最后我们的 element 就是一个节点
 // 所以我们可以直接返回
 return element;
}

然后我们 webpack 打包一下看看。

没有任何意外,我们整个元素就正常的被挂載在 body 的上了。同理如果我们把我们的 Div 改回 div 也是一样可以正常运行的。

当然我们一般来说也不会写一个毫无意义的这种 Div 的元素。这里我们就会写一个我们组件的名字,比如说 Carousel,一个轮播图的组件。

完整代码 —— 对你有用的话,就给我一个 ⭐️ 吧,谢谢!

到此这篇关于使用JSX 建立 Markup 组件风格开发的示例(前端组件化)的文章就介绍到这了,更多相关JSX脚本 JSX建立组组件 JSX风格内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解利用jsx写vue组件的方法示例

    前言 本文主要给大家介绍的是关于利用jsx写vue组件,下面话不多说,来一起看看详细的介绍吧. 我们平常写vue的组件时,一般都是用的是模版,这种方式看起来比较简洁,而且vue作者也推荐使用这个方式,但是这种方式也有一些它的弊端,例如模版调试麻烦,或者在一些场景下模版描述可能没那么简单和方便. 下面我们要讲的是如何在vue里面写jsx,知道react的人应该都知道jsx,jsx的一个特性就是非常灵活,虽然有的人觉得jsx很丑陋,把逻辑都写到模版的感觉,但萝卜青菜各有所爱,适合自己适合团队的就是最

  • vue jsx 使用指南及vue.js 使用jsx语法的方法

    vue  jsx  语法与 react  jsx  还是有些不一样,在这里记录下. let component = null// if 语句 if (true) { component = ( <div></div> ); } else { component = ( <div></div> ); } var ul = ( <ul> {component} </ul> ); // map 语句 var coms = limit.map

  • vue组件jsx语法的具体使用

    如果使用render函数来写比较复杂的vue组件,对于可读性和可维护性都很不友好,而使用jsx就会让我们回到更接近于模板的语法.babel转译器会将jsx转译为render函数渲染. 配置 需要用到babel插件 安装 npm install\ babel-plugin-syntax-jsx\ babel-plugin-transform-vue-jsx\ babel-helper-vue-jsx-merge-props\ babel-preset-env\ --save-dev .babelr

  • 在vue中使用jsx语法的使用方法

    什么是JSX? JSX就是Javascript和XML结合的一种格式.React发明了JSX,利用HTML语法来创建虚拟DOM.当遇到<,JSX就当HTML解析,遇到{就当JavaScript解析. 我为什么要在vue中用JSX? 想折腾一下呗,开玩笑.最开始是因为近期在学习react,在里面体验了一把jsx语法,发现也并没有别人说的很难受的感觉啊,于是就想尝试在vue中也试下,废话不多说,先来用代码来看下两者的区别吧. ps:vue中大部分场景是不需要用render函数的,还是用模板更简洁直观

  • 使用JSX 建立 Markup 组件风格开发的示例(前端组件化)

    目录 JSX 环境搭建 初始化 NPM 安装 webpack 安装 Babel 配置 webpack 安装 Babel-loader 模式配置 引入 JSX JSX 基本用法 JSX 基础原理 实现 createElement 函数 实现自定义标签 它会包含以下方法: 这里我们一起从 0 开始搭建一个组件系统.首先通过上一篇<前端组件化基础知识>和<用 JSX 建立组件 Parser(解析器)>中知道,一个组件可以通过 Markup 和 JavaScript 访问的一个环境. 所以

  • 使用JSX 建立组件 Parser(解析器)开发的示例

    目录 JSX 环境搭建 建立项目 初始化 NPM 安装 webpack 安装 Babel 配置 webpack 安装 Babel-loader 模式配置 引入 JSX JSX 基本用法 JSX 基础原理 实现 createElement 函数 实现自定义标签 这里我们一起从 0 开始搭建一个组件系统.首先通过上一篇<前端组件化基础知识>中知道,一个组件可以通过 Markup 和 JavaScript 访问的一个环境. 所以我们的第一步就是建立一个可以使用 markup 的环境.这里我们会学习使

  • 使用JSX实现Carousel轮播组件的方法(前端组件化)

    在我们用 JSX 建立组件系统之前,我们先来用一个例子学习一下组件的实现原理和逻辑.这里我们就用一个轮播图的组件作为例子进行学习.轮播图的英文叫做 Carousel,它有一个旋转木马的意思. 上一篇文章<使用 JSX 建立 Markup 组件风格>中我们实现的代码,其实还不能称为一个组件系统,顶多是可以充当 DOM 的一个简单封装,让我们有能力定制 DOM. 要做这个轮播图的组件,我们应该先从一个最简单的 DOM 操作入手.使用 DOM 操作把整个轮播图的功能先实现出来,然后在一步一步去考虑怎

  • Vue3 组件的开发详情

    目录 一.前言 二.组件的开发 1.组件的构成 2.header部分组件的开发 3.footer组件的开发 4.修改App.vue 5.移除Helloword组件及相关代码 6.重启服务查看 三.最后 一.前言 果然长时间坐着或站着,会给腰带来很大负担,声明下我不是腰脱,就是个穿刺手术而已,身上有多处缝针没长好,所以会给肚子和腰带来一定的负担. 上一篇文章已经写了关于布局的开发,传送门<Vue3(三)网站首页布局开发 >,但是我们写代码,肯定是继承了优秀的代码风格,封装的特性,所以这里我们再对

  • Vue.js 父子组件通讯开发实例

    vue.js,是一个构建数据驱动的 web 界面的库.Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件.(这是官方的一个解释!) 小编没使用过angularjs,也没使用过react.js,不能详细的说明三者的区别,想了解的话,在官方有一个分析,请点这里查看 小编从业前端开发也有了一年多的时间,刚开始的时候,前端开发展现的技术太多,小编有心无力,兼顾不过来,经过了解之后,还是选择了学原生js基础兼并jquery的学习上路.小编使用vue.js,也是因为业务的需求

  • 详解使用React进行组件库开发

    最近针对日常业务需求使用react封装了一套[组件库], 大概记录下整个开发过程中的心得.由于篇幅原因,在这里只对开发过程中比较纠结的选型和打包等进行讨论,后续再对具体组件的封装进行讨论. 概述 我们都知道,组件化的开发模式对于我们的开发效率有着极大的提升,针对我们日常使用的基本组件进行封装,可以大量的简化我们对于基本UI的关注度,让我们的工作聚焦在业务逻辑上,很好的分离业务与基础UI的代码,使得整个项目更有调理,这也是我们要进行本组件库开发的原因. 然而现有React开源组件有很多,像ant-

  • Vue.js组件使用开发实例教程

    组件 组件可以扩展HTML元素,封装可重用的代码,在较高的层面上,组件是自定义元素,vue.js的编译器为它添加特殊功能,在有些情况下,组件也可以是原生HTML元素的形式,以is特性扩展. Vue.js的组件可以理解为预先定义好了行为的ViewModel类.一个组件可以预定义很多选项,但最核心的是以下几个: 模板(template):模板声明了数据和最终展现给用户的DOM之间的映射关系. 初始数据(data):一个组件的初始数据状态.对于可复用的组件来说,这通常是私有的状态. 接受的外部参数(p

  • react开发教程之React 组件之间的通信方式

    这两天学习了React感觉组件通信这个地方知识点挺多的,而且很重要,所以,今天添加一点小笔记. 父子组件通讯 通讯手段 这是最常见的通信方式,父组件只需要将子组件需要的props传给子组件,子组件直接通过this.props来使用. 通讯内容 更多要提的是如何合理的设置子组件的props,要想将子组件设计成一个复用性强的通用组件,需要将能够复用的部分抽象出来,抽象出来的props有两种形成,一种是简单的变量,另一种是抽象出来处理某种逻辑函数. 以Header 组件为例 //HeaderBar.j

  • Vue-router 类似Vuex实现组件化开发的示例

    本文介绍了Vue-router 类似Vuex实现组件化开发的示例,分享给大家,具体如下: 随着项目越来越大,把所有route写在一个文件里就显得杂乱. #单个组件路由 import a from '../components/a' export default { path: '/a', name: 'a', component: a } import arouter from 'xxx' export default new Router({ routes: [ arouter ] }) #多

  • Vue.js弹出模态框组件开发的示例代码

    前言 在开发项目的过程中,经常会需要开发一些弹出框效果,但原生的alert和confirm往往都无法满足项目的要求.这次在开发基于Vue.js的读书WebApp的时候总共有两处需要进行提示的地方,因为一开始就没有引入其他的组件库,现在只好自己写一个模态框组件了.目前只是一个仅满足当前项目需求的初始版本,因为这个项目比较简单,也就没有保留很多的扩展功能.这个组件还是有很多扩展空间的,可以增加更多的自定义内容和样式.这里只介绍如何去开发一个模态框组件,有需要进行更多扩展的,可以根据自己的需求自行开发

随机推荐