在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
这里我们一起从 0 开始搭建一个组件系统。首先通过上一篇《前端组件化基础知识》中知道,一个组件可以通过 Markup 和 JavaScript 访问的一个环境。 所以我们的第一步就是建立一个可以使用 markup 的环境。这里我们会学习使用两种建立 markup 的风格。 第一种是基于与 React 一样的 JSX 去建立我们组件的风格。第二种则是我们去建立基于类似 Vue 的这种,基于标记语言的 Parser 的一种风格。 JSX 环境搭建JSX 在大家一般认知里面,它是属于 React 的一部分。其实 Facebook 公司会把 JSX 定义为一种纯粹的语言扩展。而这个 JSX 也是可以被其他组件体系去使用的。
建立项目那么我们就从最基础的开始,首先我们需要创建一个新的项目目录: mkdir jsx-component 初始化 NPM在你们喜欢的目录下创建这个项目文件夹。建立好文件夹之后,我们就可以进入到这个目录里面并且初始化 npm init 执行以上命令之后,会出现一些项目配置的选项问题,如果有需要可以自行填写。不过我们也可以直接一直按回车,然后有需要的同学可以后面自己打开 安装 webpackWepack 很多同学应该都了解过,它可以帮助我们把一个普通的 JavaScript 文件变成一个能把不同的 import 和 require 的文件给打包到一起。 所以我们需要安装 那么这里我们就使用全局安装 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 这里我们需要注意的是,我们需要加上 执行完毕后,我们应该会看到上面图中的消息。 为了验证我们是正确安装好了,我们可以打开我们项目目录下的 { "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" } } 好,我们可以看到在 配置 webpack到这里我们就需要配置一下 webpack。配置 webpack 我们需要创建一个 在我们项目的根目录创建一个 首先 webpack config 它是一个 nodejs 的模块,所以我们需要用 module.exports 来写它的设置。而这个是早期 nodejs 工具常见的一种配置方法,它用一个 JavaScript 文件去做它的配置,这样它在这个配置里面就可以加入一些逻辑。 module.exports = {} Webpack 最基本的一个东西,就是需要设置一个 entry (设置它的入口文件)。这里我们就设置一个 module.exports = { entry: "./main.js" } 这个时候,我们就可以先在我们的根目录下创建一个 // main.js 文件内容 for (let i of [1, 2, 3]) { console.log(i); } 这样 webpack 的基本配置就配置好了,我们在根目录下执行一下 webpack 来打包一下 webpack 执行完毕之后,我们就可以在命令行界面中看到上面这样的一段提示。
这个时候我们会发现,在我们的根目录中生成了一个新的文件夹 这里我们就会发现,这个 然后我们打开它,就会看到它被 babel 编译过后的 JavaScript 代码。我们会发现我们短短的几行代码被加入了很多的东西,这些其实我们都不用管,那都是 Webpack 的 “喵喵力量”。 在代码的最后面,还是能看到我们编写的 安装 Babel-loader接下来我们来安装 babel-loader,其实 babel-loader 并没有直接依赖 babel 的,所以我们才需要另外安装 npm install --save-dev @babel/core @babel/preset-env 最终的结果就如上图一样,证明安装成功了。这个时候我们就需要在 在我们上面配置好的 然后模块中我们还可以加入一个 test:
use: loader:
options: presets:
最后我们的配置文件就会是这个样子: module.exports = { entry: './main.js', module: { rules: [ { test: /\.js$/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], }, }, }, ], }, }; 这样配置好之后,我们就可以来跑一下 babel 来试一试会是怎么样的。与刚才一样,我们只需要在命令行执行 如果我们的配置文件没有写错,我们就应该会看到上面图中的结果。 然后我们进入 编译后的结果,我们会发现 到了这里我们已经把 JSX 所需的环境给安装和搭建完毕了。 模式配置最后我们还需要在 webpack.config.js 里面添加一个环境配置,不过这个是可加也可不加的,但是我们为了平时开发中的方便。 所以我们需要在 webpack.config.js 中添加一个 一般来说我们在代码仓库里面写的 webpack 配置都会默认加上这个 module.exports = { entry: './main.js', mode: 'development', module: { rules: [ { test: /\.js$/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], }, }, }, ], }, }; 改好之后,我们在使用 显然我们发现,编译后的代码没有被压缩成一行了。这样我们就可以调试 webpack 生成的代码了。这里我们可以注意到,我们在 引入 JSX万事俱备,只欠东风了,最后我们需要如何引入 JSX呢?在引入之前,我们来看看,如果就使用现在的配置在我们的 所以我们在 var a = <div/> 然后大胆地执行 webpack 看看! 好家伙!果然报错了。这里的报错告诉我们,在 所以我们要怎么做让我们的 webpack 编译过程支持 JSX 语法呢?这里其实就是还需要我们加入一个最关键的一个包,而这个包名非常的长,叫做 npm install --save-dev @babel/plugin-transform-react-jsx 安装好之后,我们还需要在 webpack 配置中给他加入进去。我们需要在 然后最终我们的 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 语法了。 最后我们来围观一下,最后编程的效果是怎么样的。 我们会发现,在 所以接下来我们就一起来看一下,我们应该怎么实现这个 JSX 基本用法首先我们来尝试理解 JSX,JSX 其实它相当于一个纯粹在代码语法上的一种快捷方式。在上一部分的结尾我们看到,JSX语法在被编译后会出现一个 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' } ] ], }, }, }, ], }, }; 上面我们只是把原来的 这么一改,我们的 JSX 就与 React 的框架没有任何联系了。我们执行一下 webpack 看一下最终生成的效果,就会发现里面的 接下来我们加入一个 HTML 文件来执行我们的 main.js 试试。首先在根目录创建一个 <script src="./main.js"></script> 然后我们执行在浏览器打开这个 HTML 文件。 这个时候我们控制台会给我们抛出一个错误,我们的 所以我们就需要自己编写一个 function createElement() { return; } let a = <div />; 这里我们就直接返回空,先让这个函数可以被调用即可。我们用 webpack 重新编译一次,然后刷新我们的 main.html 页面。这个时候我们就会发现报错没有了,可以正常运行。 实现 createElement 函数在我们的编译后的代码中,我们可以看到 JSX 的元素在调用 createElement 的时候是传了两个参数的。第一个参数是 这里第二个参数为什么是 我们就会发现第二个参数变成了一个以 Key-Value 的方式存储的JavaScript 对象。到这里如果我们想一下,其实 JSX 也没有那么神秘,它只是把我们平时写的 HTML 通过编译改写成了 JavaScript 对象,我们可以认为它是属于一种 “[[语法糖]]”。
接下来我们来写一些更复杂一些的 JSX,我们给原本的 div 加一些 children 元素。 function createElement() { return; } let a = ( <div id="a"> <span></span> <span></span> <span></span> </div> ); 最后我们执行以下 webpack 打包看看效果。 在控制台中,我们可以看到最后编译出来的结果,是递归的调用了 父级就是第一层的 div 的元素,然后子级就是在后面当参数传入了第一个 createElement 函数之中。然后因为我们的 span 都是没有属性的,所以所有后面的 createElement 的第二个参数都是 根据我们这里看到的一个编译结果,我们就可以分析出我们的 createElement 函数应有的参数都是什么了。
那么我们 function createElement(type, attributes, ...children) { return; } 函数我们有了,但是这个函数可以做什么呢?其实这个函数可以用来做任何事情,因为这个看起来长的像 DOM API,所以我们完全可以把它做成一个跟 React 没有关系的实体 DOM。 比如说我们就可以在这个函数中返回这个 创建元素我们可以用 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; } 这里我们就实现了 // 在 main.js 最后加上这段代码 let a = ( <div id="a"> <span></span> <span></span> <span></span> </div> ); document.body.appendChild(a); 这里还需要注意的是,我们的 main.html 中没有加入 body 标签,没有 body 元素的话我们是无法挂載到 body 之上的。所以这里我们就需要在 main.html 当中加入 body 元素。 <body></body> <script src="dist/main.js"></script> 好,这个时候我们就可以 webpack 打包,看一下效果。 Wonderful! 我们成功的把节点生成并且挂載到 body 之上了。但是如果我们的 接下来我们就把处理文本节点的逻辑加上,但是在这之前我们先把 div 里面的 span 标签删除,换成一段文本 “hello world”。 let a = <div id="a">hello world</div>; 在我们还没有加入文本节点的逻辑之前,我们先来 webpack 打包一下,看看具体会报什么错误。 首先我们可以看到,在 通过这种调试方式我们可以马上定位到,我们需要在哪里添加逻辑去实现这个功能。这种方式也可以算是一种捷径吧。 所以接下来我们就回到 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 打包之后,就可以在浏览器上看到我们的文字被显示出来了。 到了这里我们编写的 这里我们可以验证以下,我们在 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 的时候,传入我们 当然我们的 JavaScript 中并没有定义 Div 类,这里自然就会报 Div 未定义的错误。知道问题的所在,我们就可以去解决它,首先我们需要先解决未定义的问题,所以我们先建立一个 Div 的类。 // 在 createElment 函数之后加入 class Div {} 然后我们就需要在 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 树上。 它会包含以下方法:
首先我们来简单实现以下我们 class Div { setAttribute() {} appendChild() {} mountTo(parent) { this.root = document.createElement('div'); parent.appendChild(this.root); } } 这里面其实很简单首先给类中的 然后我们就可以把我们原来的 body.appendChild 的代码改为使用 // document.body.appendChild(a); a.mountTo(document.body); 用现在的代码,我们 webpack 打包看一下效果: 我们可以看到我们的 Div 自定义元素是有正确的被挂載到 body 之上。但是 Div 中的 span 标签都是没有被挂載上去的。如果我们想它与普通的 div 一样去工作的话,我们就需要去实现我们的 接下来我们就一起来尝试完成剩余的实现逻辑。在开始写 setAttribute 和 appendChild 之前,我们需要先给我们的 Div 类加入一个构造函数 constructor() { this.root = document.createElement('div'); } 然后的 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 也能正常工作了。 这里还有一个问题,因为我们最后调用的是 所以这里我们还需要给普通的元素加上一个 Wrapper 类,让他们可以保持我们元素类的标准格式。也是所谓的标准接口。 我们先写一个
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); } } 这里我们还有一个问题,就是遇到文本节点的时候,是没有转换成我们的自定义类的。所以我们还需要写一个给文本节点,叫做 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); } } 有了这些元素类接口后,我们就可以改写我们 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 的元素。这里我们就会写一个我们组件的名字,比如说
我们在这里互相监督,互相鼓励,互相努力走上人生学习之路,让学习改变我们生活! 学习的路上,很枯燥,很寂寞,但是希望这样可以给我们彼此带来多一点陪伴,多一点鼓励。我们一起加油吧! (๑ •̀ㅂ•́)و
到此这篇关于使用JSX 建立组件 Parser(解析器)开发的示例的文章就介绍到这了,更多相关JSX建立组件Parser内容请搜索极客世界以前的文章或继续浏览下面的相关文章希望大家以后多多支持极客世界! |
请发表评论