使用JSX 建立 Markup 組件風格開發(fā)的示例(前端組件化)
這里我們一起從 0 開始搭建一個組件系統(tǒng)。首先通過上一篇《前端組件化基礎知識》和《用 JSX 建立組件 Parser(解析器)》中知道,一個組件可以通過 Markup 和 JavaScript 訪問的一個環(huán)境。
所以我們的第一步就是建立一個可以使用 markup 的環(huán)境。這里我們會學習使用 JSX 來建立 markup 的風格。這里我們基于與 React 一樣的 JSX 去建立我們組件的風格。
JSX 環(huán)境搭建
JSX 在大家一般認知里面,它是屬于 React 的一部分。其實 Facebook 公司會把 JSX 定義為一種純粹的語言擴展。而這個 JSX 也是可以被其他組件體系去使用的。
甚至我們可以把它單獨作為一種,快捷創(chuàng)建 HTML 標簽的方式去使用。
建立項目
那么我們就從最基礎的開始,首先我們需要創(chuàng)建一個新的項目目錄:
mkdir jsx-component
初始化 NPM
當然在你們喜歡的目錄下創(chuàng)建這個項目文件夾。建立好文件夾之后,我們就可以進入到這個目錄里面并且初始化 npm
。
npm init
執(zhí)行以上命令之后,會出現(xiàn)一些項目配置的選項問題,如果有需要可以自行填寫。不過我們也可以直接一直按回車,然后有需要的同學可以后面自己打開 package.json
自行修改。
安裝 webpack
Wepack 很多同學應該都了解過,它可以幫助我們把一個普通的 JavaScript 文件變成一個能把不同的 import 和 require 的文件給打包到一起。
所以我們需要安裝 webpack
,當然我們也可以直接使用 npx 直接使用 webpack,也可以全局安裝 webpack-cli。
那么這里我們就使用全局安裝 webpack-cli:
npm install -g webpack webpack-cli
安裝完畢之后,我們可以通過輸入下面的一條命令來檢車一下安裝好的 webpack 版本。如果執(zhí)行后沒有報錯,并且出來了一個版本號,證明我們已經(jīng)安裝成功了。
webpack --version
安裝 Babel
因為 JSX 它是一個 babel 的插件,所以我們需要依次安裝 webpack,babel-loader, babel 和 babel 的 plugin。
這里使用 Babel 還有一個用處,它可以把一個新版本的 JavaScript 編譯成一個老版本的 JavaScript,這樣我們就可以支持在更多老版本的瀏覽器中運行。
安裝 Babel 我們只需要執(zhí)行以下的命令即可。
npm install --save-dev webpack babel-loader
這里我們需要注意的是,我們需要加上 --save-dev
,這樣我們就會把 babel 加入到我們的開發(fā)依賴中。
執(zhí)行完畢后,我們應該會看到上面圖中的消息。
為了驗證我們是正確安裝好了,我們可以打開我們項目目錄下的 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 我們需要創(chuàng)建一個 webpack.config.js
配置文件。
在我們項目的根目錄創(chuàng)建一個 webpack.config.js
文件。
首先 webpack config 它是一個 nodejs 的模塊,所以我們需要用 module.exports 來寫它的設置。而這個是早期 nodejs 工具常見的一種配置方法,它用一個 JavaScript 文件去做它的配置,這樣它在這個配置里面就可以加入一些邏輯。
module.exports = {}
Webpack 最基本的一個東西,就是需要設置一個 entry (設置它的入口文件)。這里我們就設置一個 main.js
即可。
module.exports = { entry: "./main.js" }
這個時候,我們就可以先在我們的根目錄下創(chuàng)建一下我們 main.js
的文件了。在里面我們先加入一個簡單的 for
循環(huán)。
// main.js 文件內容 for (let i of [1, 2, 3]) { console.log(i); }
這樣我們 webpack 的基本配置就配置好了,我們在根目錄下執(zhí)行一下 webpack 來打包一下我們 main.js
的文件看看。我們只需要執(zhí)行下面的這行命令:
webpack
執(zhí)行完畢之后,我們就可以在命令行界面中看到上面這樣的一段提示。
注意細節(jié)的同學,肯定要舉手問到,同學同學!你的命令行中報錯啦!黃色部分確實有給我們一個警告,但是不要緊,這個我們接下的配置會修復它的。
這個時候我們會發(fā)現(xiàn),在我們的根目錄中生成了一個新的文件夾 dist
。這個就是 webpack 打包默認生成的文件夾,我們所有打包好的 JavaScript 和資源都會被默認放入這個文件夾當中。
這里我們就會發(fā)現(xiàn),這個 dist
文件夾里面有一個打包好的 main.js
的文件,這個就是我們寫的 main.js
,通過 webpack 被打包好的版本。
然后我們打開它,就會看到它被 babel 編譯過后的 JavaScript 代碼。我們會發(fā)現(xiàn)我們短短的幾行代碼被加入了很多的東西,這些其實我們都不用管,Webpack 的 “喵喵力量”。
在代碼的最后面,還是能看到我們編寫的 for
循環(huán)的,只是被改造了一下,但是它的作用是一致的。
安裝 Babel-loader
接下來我們來安裝 babel-loader,其實 babel-loader 并沒有直接依賴 babel 的,所以我們才需要另外安裝 @babel/core
和 @babel/preset-env
。我們只需要執(zhí)行下面的命令行來安裝:
npm install --save-dev @babel/core @babel/preset-env
最終的結果就如上圖一樣,證明安裝成功了。這個時候我們就需要在 webpack.config.js
中配置上,讓我們打包的時候用上 babel-loader。
在我們上面配置好的 webpack.config.js
的 entry
后面添加一個選項叫做 module
。
然后模塊中我們還可以加入一個 rules
,這個就是我們構建的時候所使用的規(guī)則。而 rules
是一個數(shù)組類型的配置,這里面的每一個規(guī)則是由一個 test
和一個 use
組成的。
- test:
test
的值是一個正則表達式,用于匹配我們需要使用這個規(guī)則的文件。這里我們需要把所有的 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 來試一試會是怎么樣的。與剛才一樣,我們只需要在命令行執(zhí)行 webpack
即可。
如果我們的配置文件沒有寫錯,我們就應該會看到上面圖中的結果。
然后我們進入 dist
文件夾,打開我們編譯后的 main.js
,看一下我們這次使用了 babel-loader 之后的編譯結果。
編譯后的結果,我們會發(fā)現(xiàn) for of
的循環(huán)被編譯成了一個普通的 for
循環(huán)。這個也可以證明我們的 babel-loader 起效了,正確把我們新版本的 JavaScript 語法編程成能兼容舊版瀏覽器的 JavaScript 語法。
到了這里我們已經(jīng)把我們 JSX 所需的環(huán)境給安裝和搭建完畢了。
模式配置
最后我們還需要在 webpack.config.js 里面添加一個環(huán)境配置,不過這個也可以說可加可不加的,但是我們?yōu)榱似綍r開發(fā)中的方便。
所以我們需要在 webpack.config.js 中添加一個 mode
,這個屬性的值我們使用 development
。這個配置表示我們是開發(fā)者模式。
一般來說我們在代碼倉庫里面寫的 webpack 配置都會默認加上這個 mode: 'development'
的配置。當我們真正發(fā)布的時候,我們就會把它改成 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
有什么區(qū)別。
顯然我們發(fā)現(xiàn),編譯后的代碼沒有被壓縮成一行了。這樣我們就可以調試 webpack 生成的代碼了。這里我們可以注意到,我們在 main.js
中的代碼被轉成字符串,并且被放入一個 eval()
的函數(shù)里面。我們的代碼被放入了 eval
里面,那么我們就可以在調試的時候就可以把它作為一個單獨的文件去使用了。
引入 JSX
萬事俱備,只欠東風了,最后我們需要如何引入 JSX呢?在引入之前,我們來看看,如果就使用現(xiàn)在的配置在我們的 main.js
里面使用 JSX 語法會怎么樣。作為程序員的我們,總得有點冒險精神!
所以我們在我們的 main.js
里面加入這段代碼:
var a = <div/>
然后我們執(zhí)行 webpack 看看!
好家伙!果然報錯了。這里的報錯告訴我們,在 =
后面不能使用 “小于號”,但是在正常的 JSX 語法中,這個其實是 HTML 標簽的 “尖括號”,因為沒有 JSX 語法的編譯過程,所以 JavaScript 默認就會認為這個就是 “小于號”。
所以我們要怎么做讓我們的 webpack 編譯過程支持 JSX 語法呢?這里其實就是還需要我們加入一個最關鍵的一個包,而這個包名非常的長,叫做 @babel/plugin-transform-react-jsx
。好那么我們就執(zhí)行一段命令來安裝一下這個包:
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'], }, }, }, ], }, };
配置好之后,我們再去執(zhí)行一下 webpack。這時候我們發(fā)現(xiàn)沒有再報錯了。這樣也就證明我們的代碼現(xiàn)在是支持使用 JSX 語法編寫了。
最后我們來圍觀一下,最后編程的效果是怎么樣的。
我們會發(fā)現(xiàn),在 eval
里面我們加入的 <div/>
被翻譯成一個 React.createElement("div", null)
的函數(shù)調用了。
所有接下來我們就一起來看一下,我們應該怎么實現(xiàn)這個 React.createElement
,以及我們能否把這個換成我們自己的函數(shù)名字。
JSX 基本用法
首先我們來嘗試理解 JSX,JSX 其實它相當于一個純粹在代碼語法上的一種快捷方式。在上一部分的結尾我們看到,JSX語法在被編譯后會出現(xiàn)一個 React.createElement
的一個調用。
JSX 基礎原理
那么這里我們就先修改在 webpack 中的 JSX 插件,給它一個自定義的創(chuàng)建元素函數(shù)名。我們打開 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']
參數(shù)改為了 [['@babel/plugin-transform-react-jsx', {pragma: 'createElement'}]]
。加入了這個 pragma
參數(shù),我們就可以自定義我們創(chuàng)建元素的函數(shù)名。
這么一改,我們的 JSX 就與 React 的框架沒有任何聯(lián)系了。我們執(zhí)行一下 webpack 看一下最終生成的效果,就會發(fā)現(xiàn)里面的 React.createElement
就會變成 createElement
。
接下來我們加入一個 HTML 文件來執(zhí)行我們的 main.js 試試。首先在根目錄創(chuàng)建一個 main.html
,然后輸入一下代碼:
<script src="./main.js"></script>
然后我們執(zhí)行在瀏覽器打開這個 HTML 文件。
這個時候我們控制臺會給我們拋出一個錯誤,我們的 createElement
未定義。確實我們在 main.js
里面還沒有定義這個函數(shù),所以說它找不到。
所以我們就需要自己編寫一個 createElement
這個函數(shù)。我們直接打開根目錄下的 main.js
并且把之前的 for
循環(huán)給刪除了,然后加上以下代碼:
function createElement() { return; } let a = <div />;
這里我們就直接返回空,先讓這個函數(shù)可以被調用即可。我們用 webpack 重新編譯一次,然后刷新我們的 main.html 頁面。這個時候我們就會發(fā)現(xiàn)報錯沒有了,可以正常運行。
實現(xiàn) createElement 函數(shù)
在我們的編譯后的代碼中,我們可以看到 JSX 的元素在調用 createElement 的時候是傳了兩個參數(shù)的。第一個參數(shù)是 div
, 第二個是一個 null
。
這里第二個參數(shù)為什么是 null
呢?其實第二個參數(shù)是用來傳屬性列表的。如果我們在 main.js 里面的 div 中加入一個 id="a"
,我們來看看最后編譯出來會有什么變化。
我們就會發(fā)現(xiàn)第二個參數(shù)變成了一個 以 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> );
最后我們執(zhí)行一下 webpack 打包看看效果。
在控制臺中,我們可以看到最后編譯出來的結果,是遞歸的調用了 createElement
這個函數(shù)。這里其實已經(jīng)形成了一個樹形的結構。
父級就是第一層的 div 的元素,然后子級就是在后面當參數(shù)傳入了第一個 createElement 函數(shù)之中。然后因為我們的 span 都是沒有屬性的,所以所有后面的 createElement 的第二個參數(shù)都是 null
。
根據(jù)我們這里看到的一個編譯結果,我們就可以分析出我們的 createElement 函數(shù)應有的參數(shù)都是什么了。
- 第一個參數(shù)
type
—— 就是這個標簽的類型 - 第二個參數(shù)
attribute
—— 標簽內的所有屬性與值 - 剩余的參數(shù)都是子屬性
...children
—— 這里我們使用了 JavaScript 之中比較新的語法...children
表示把后面所有的參數(shù) (不定個數(shù)) 都會變成一個數(shù)組賦予給 children 變量
那么我們 createElement
這個函數(shù)就可以寫成這樣了:
function createElement(type, attributes, ...children) { return; }
函數(shù)我們有了,但是這個函數(shù)可以做什么呢?其實這個函數(shù)可以用來做任何事情,因為這個看起來長的像 DOM API,所以我們完全可以把它做成一個跟 React 沒有關系的實體 DOM。
比如說我們就可以在這個函數(shù)中返回這個 type
類型的 element
元素。這里我們把所有傳進來的 attributes
給這個元素加上,并且我們可以給這個元素掛上它的子元素。
創(chuàng)建元素我們可以用 createElement(type)
,而加入屬性我們可以使用 setAttribute()
,最后掛上子元素就可以使用 appendChild()
。
function createElement(type, attributes, ...children) { // 創(chuàng)建元素 let element = document.createElement(type); // 掛上屬性 for (let attribute in attributes) { element.setAttribute(attribute); } // 掛上所有子元素 for (let child of children) { element.appendChild(child); } // 最后我們的 element 就是一個節(jié)點 // 所以我們可以直接返回 return element; }
這里我們就實現(xiàn)了 createElement
函數(shù)的邏輯。最后我們還需要在頁面上掛載上我們的 DOM 節(jié)點。所以我們可以直接掛載在 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! 我們成功的把節(jié)點生成并且掛載到 body 之上了。但是如果我們的 div
里面加入一段文字,這個時候就會有一個文本節(jié)點被傳入我們的 createElement
函數(shù)當中。毋庸置疑,我們的 createElement
函數(shù)以目前的邏輯是肯定無法處理文本節(jié)點的。
接下來我們就把處理文本節(jié)點的邏輯加上,但是在這之前我們先把 div 里面的 span 標簽刪除,換成一段文本 “hello world”。
let a = <div id="a">hello world</div>;
在我們還沒有加入文本節(jié)點的邏輯之前,我們先來 webpack 打包一下,在我們掛上子節(jié)點之前,判斷看看具體會報什么錯誤。
首先我們可以看到,在 createElement
函數(shù)調用的地方,我們的文本被當成字符串傳入,然后這個參數(shù)是接收子節(jié)點的,并且在我們的邏輯之中我們使用了 appendChild
,這個函數(shù)是接收 DOM 節(jié)點的。顯然我們的 文本字符串不是一個節(jié)點,自然就會報錯。
通過這種調試方式我們可以馬上定位到,我們需要在哪里添加邏輯去實現(xiàn)我們這個功能。這種方式也可以算是一種捷徑吧。
所以接下來我們就回到 main.js
,在我們掛上子節(jié)點之前,判斷一下 child 的類型,如果它的類型是 “String” 字符串的話,就使用 createTextNode()
來創(chuàng)建一個文本節(jié)點,然后再掛載到父元素上。這樣我們就完成了字符節(jié)點的處理了。
function createElement(type, attributes, ...children) { // 創(chuàng)建元素 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 就是一個節(jié)點 // 所以我們可以直接返回 return element; } let a = <div id="a">hello world</div>; document.body.appendChild(a);
我們用這個最新的代碼 webpack 打包之后,就可以在瀏覽器上看到我們的文字被顯示出來了。
到了這里我們編寫的 createElement
已經(jīng)是一個比較有用的東西了,我們已經(jīng)可以用它來做一定的 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 的操作的。
現(xiàn)在的代碼已經(jīng)可以完成一定的組件化的基礎能力。
實現(xiàn)自定義標簽
之前我們都是在用一些,HTML 自帶的標簽。如果我們現(xiàn)在把 div 中的 d 改為大寫 D 會怎么樣呢?
let a = ( <Div id="a"> hello world: <span>a</span> <span>b</span> <span>c</span> </Div> );
果不其然,就是會報錯的。不過這就是我們找到問題的根源的關鍵,這里我們發(fā)現(xiàn)當我們把 div 改為 Div 的時候,傳入我們 createElement
的 div 從字符串 ‘div' 變成了一個 Div
類。
當然我們的 JavaScript 中并沒有定義 Div 類,這里自然就會報 Div 未定義的錯誤。知道問題的所在,我們就可以去解決它,首先我們需要先解決未定義的問題,所以我們先建立一個 Div 的類。
// 在 createElment 函數(shù)之后加入 class Div {}
然后我們就需要在 createElement
里面做類型判斷,如果我們遇到的 type 是字符類型,就按原來的方式處理。如果我們遇到是其他情況,我們就實例化傳過來的 type
。
function createElement(type, attributes, ...children) { // 創(chuàng)建元素 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 就是一個節(jié)點 // 所以我們可以直接返回 return element; }
這里我們還有一個問題,我們有什么辦法可以讓自定義標簽像我們普通 HTML 標簽一樣操作呢?在最新版的 DOM 標準里面是有辦法的,我們只需要去注冊一下我們自定義標簽的名稱和類型。
但是我們現(xiàn)行比較安全的瀏覽版本里面,還是不太建議這樣去做的。所以在使用我們的自定義 element 的時候,還是建議我們自己去寫一個接口。
首先我們是需要建立標簽類,這個類能讓任何標簽像我們之前普通 HTML 標簽的元素一樣最后掛載到我們的 DOM 樹上。
它會包含以下方法:
mountTo()
—— 創(chuàng)建一個元素節(jié)點,用于后面掛載到parent
父級節(jié)點上setAttribute()
—— 給元素掛上所有它的屬性appendChild()
—— 給元素掛上所有它的子元素
首先我們來簡單實現(xiàn)我們 Div
類中的 mountTo
方法,這里我們還需要給他加入 setAttribute
和 appendChild
方法,因為在我們的 createElement
中有掛載屬性子元素的邏輯,如果沒有這兩個方法就會報錯。但是這個時候我們先不去實現(xiàn)這兩個方法的邏輯,方法內容留空即可。
class Div { setAttribute() {} appendChild() {} mountTo(parent) { this.root = document.createElement('div'); parent.appendChild(this.root); } }
這里面其實很簡單首先給類中的 root
屬性創(chuàng)建成一個 div 元素節(jié)點,然后把這個節(jié)點掛載到這個元素的父級。這個 parent
是以參數(shù)傳入進來的。
然后我們就可以把我們原來的 body.appendChild 的代碼改為使用 mountTo
方法來掛載我們的自定義元素類。
// document.body.appendChild(a); a.mountTo(document.body);
用現(xiàn)在的代碼,我們 webpack 打包看一下效果:
我們可以看到我們的 Div 自定義元素是有正確的被掛載到 body 之上。但是 Div 中的 span 標簽都是沒有被掛載上去的。如果我們想它與普通的 div 一樣去工作的話,我們就需要去實現(xiàn)我們的 setAttribute
和 appendChild
邏輯。
接下來我們就一起來嘗試完成剩余的實現(xiàn)邏輯。在開始寫 setAttribute 和 appendChild 之前,我們需要先給我們的 Div 類加入一個構造函數(shù) constructor
。在這里個里面我們就可以把元素創(chuàng)建好,并且代理到 root
上。
constructor() { this.root = document.createElement('div'); }
然后的 setAttribute
方法其實也很簡單,就是直接使用 this.root
然后調用 DOM API 中的 setAttribute
就可以了。而 appendChild
也是同理。最后我們的代碼就是如下:
class Div { // 構造函數(shù) // 創(chuàng)建 DOM 節(jié)點 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 是基本一致的。唯有兩個區(qū)別
在創(chuàng)建 DOM 節(jié)點的時候,可以通過傳當前元素名 type
到我們的構造函數(shù),并且用這個 type 去建立我們的 DOM 節(jié)點appendChild 就不能直接使用 this.root.appendChild
,因為所有普通的標簽都被改為我們的自定義類,所以 appendChild 的邏輯需要改為 child.mountTo(this.root)
class ElementWrapper { // 構造函數(shù) // 創(chuàng)建 DOM 節(jié)點 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 { // 構造函數(shù) // 創(chuàng)建 DOM 節(jié)點 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); } }
這里我們還有一個問題,就是遇到文本節(jié)點的時候,是沒有轉換成我們的自定義類的。所以我們還需要寫一個給文本節(jié)點,叫做 TextWrapper
。
class TextWrapper { // 構造函數(shù) // 創(chuàng)建 DOM 節(jié)點 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.createElement
和 document.createTextNode
都替換成實例化 new ElementWrapper(type)
和 new TextWrapper(content)
即可。
function createElement(type, attributes, ...children) { // 創(chuàng)建元素 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 就是一個節(jié)點 // 所以我們可以直接返回 return element; }
然后我們 webpack 打包一下看看。
沒有任何意外,我們整個元素就正常的被掛載在 body 的上了。同理如果我們把我們的 Div 改回 div 也是一樣可以正常運行的。
當然我們一般來說也不會寫一個毫無意義的這種 Div 的元素。這里我們就會寫一個我們組件的名字,比如說 Carousel
,一個輪播圖的組件。
完整代碼 —— 對你有用的話,就給我一個 ⭐️ 吧,謝謝!
到此這篇關于使用JSX 建立 Markup 組件風格開發(fā)的示例(前端組件化)的文章就介紹到這了,更多相關JSX腳本 JSX建立組組件 JSX風格內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
微信小程序+ECharts實現(xiàn)動態(tài)刷新的過程記錄
這篇文章主要給大家介紹了關于微信小程序+ECharts實現(xiàn)動態(tài)刷新的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-05-05通過Javascript創(chuàng)建一個選擇文件的對話框代碼
通過Javascript創(chuàng)建一個選擇文件的對話框代碼,需要的朋友可以參考下2012-06-06