搭建簡易React
一步一個腳印搭建簡易React
準備環境:
- node環境
- vscode編輯器
初始化專案
// 建立一個資料夾,當然你也可以手動建立
mkdir my-react
// 進入到專案目錄
cd my-react
// 生成pakeage.json檔案,這個檔案主要是用來記錄這個專案的詳細資訊的,它會將我們在專案開發中所要用到的包,以及專案的詳細資訊等記錄在這個專案中
npm init
複製程式碼
搭建專案環境
由於我們的目的是實現一個簡易的React,為了能把更多的重心花在React上,我們需要藉助一些工具來幫助我們處理一些React之外的問題。
- webpack: 我們需要使用webapck將我們的專案打包,最終生成一個js檔案
- babel:能幫助我們將高階的ES語法向下轉化成瀏覽器識別的低版本的ES語法
webpack
cnpm install --save-dev webapck webapck-cli
複製程式碼
然後在我們專案的根目錄新建webapck.config.js檔案來告訴webapck如何打包我們的js檔案
module.exports = {
entry: './src/main.js',
};
複製程式碼
同時建立src目錄,並且新建main.js作為專案的入口檔案
// main.js
const array = [1, 2, 3, 4, 5];
array.find((item) => item === 1);
複製程式碼
babel
babel需要安裝的包比較多
cnpm install --save-dev babel-loader @babel/core @babel/preset-env
複製程式碼
- babel-loader: 使用babel-loader處理js檔案,會將es5以上的語法進行轉義
- @babel/core: 封裝了babel-loader需要用到的api
- @babel/preset-env: babel 內部經歷了「解析 - 轉換 - 生成」三個步驟。而
@babel/core
這個庫則負責「解析」,具體的「轉換」和「生成」步驟則交給各種外掛(plugin)和預設(preset)來完成
tips1:
tips2:
@babel/preset-*實際上就是各種外掛的打包組合,也就是說各種轉譯規則的統一設定,目的是告訴loader要以什麼規則來轉化成對應的js版本
好了,回到我們的專案本身,我們需要告訴webapck,用babel-loader去打包我們的js檔案,以及babel-loader對應的配置。
module.exports = {
entry: {
main: "./src/main.js",
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env"],
},
},
},
],
},
};
複製程式碼
第一次打包編譯
在終端執行npx webpack,就可以在dist檔案加下檢視打包出來的檔案了
tips:
使用npx可以保證我們執行的是當前專案
my-react
│
│
└───dist
│ │ main.js
└───src
│ │ main.js
└───package.json
|
└───webpack.config.js
複製程式碼
引入babel外掛支援jsx語法
現在我們嘗試在main.js中宣告一個不一樣的變數,然後再執行npx webpack
const array = [1, 2, 3, 4, 5];
array.find((item) => item === 1);
const ele = <div id="id" class="mr5" >
<span> zaoren </span>
</div>;
console.log(ele);
複製程式碼
我們看到報錯了,原因是我們不能解析jsx語法
因此,我們引入另一款babel外掛來幫助我們解析jsx語法 - @babel/plugin-transform-react-jsx
cnpm install --save-dev @babel/plugin-transform-react-jsx
複製程式碼
然後在我們的webapck.config.js中引入外掛
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env"],
plugins: ['@babel/plugin-transform-react-jsx']
},
},
},
複製程式碼
然後再用webapck打包一下,發現沒有報錯,打包成功了!
然後我們嘗試著用一個main.html來引入我們打包後的main.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./main.js"></script>
</body>
</html>
複製程式碼
但是當我們在瀏覽器中執行的時候發現報了個錯誤
咦?React未定義,想想我們這篇文章的主題是什麼?不就是實現一個簡易的React嗎?所以,到現在為止,我們不進一步使用工具來幫助我們簡化工作量。完全靠自己了!!!
開始動手寫React
從打包後的程式碼可以看出,首先,我們需要一個React變數,React變數的createElement方法接受了三個引數
- DOM節點型別
- DOM節點上的屬性物件
- DOM上的子節點children
我們來簡單動手實現一下React這個物件,並且把建立好的DOM物件掛載到html頁面的body上https://www.douban.com/group/topic/198335398/
// main.js
let React = {
createElement: (tagName, attributes, ...children) => {
let ele = document.createElement(tagName);
Object.keys(attributes || {}).forEach(key => {
ele.setAttribute(key, attributes[key]);
});
children.forEach(child => {
ele.appendChild(child);
})
return ele;
},
};
const ele = <div id="id" style="background: red" >
<span>zaoren1</span>
<span>zaoren2</span>
</div>;
document.body.appendChild(ele);
複製程式碼
執行npx webpack之後,我們開啟瀏覽器發現報錯了!
除錯後發現,我們這個t為文字“zaoren”,並且原生的Web API中createElement這個方法是不支援新增文字節點的,需要使用createTextNode方法(詳情見MDN)
因此在建立子節點的時候,需要對是否是文字節點進行判斷。
children.forEach(child => {
if (typeof child === "string") {
child = document.createTextNode(child);
}
ele.appendChild(child);
})
複製程式碼
重新npx webpack,檢視效果 可以看到我們的<div>
子節點<span>
以及我們的一些屬性都能成功設定啦!恭喜你,已經完成了第一步!
與屬性相對應對的是函式,但目前我們的React還是沒有處理事件的能力的,那麼只需要在attribute中過濾出函式屬性,然後增加事件監聽器(這裡簡單用正則去匹配on
字串)
... 省略部分
Object.keys(attributes || {}).forEach(key => {
if (key.match(/^on/)) {
let eventType = key.replace(/^on/, '').toLocaleLowerCase();
ele.addEventListener(eventType, attributes[key]);
return
}
ele.setAttribute(key, attributes[key]);
});
... 省略部分
const ele = <div id="id" style="background: red" >
<span onClick={() => {console.log('add event success!')}}>zaoren1</span>
<span>zaoren2</span>
</div>;
複製程式碼
點選zaoren1,可以看到我們已經成功新增click事件啦! https://www.douban.com/group/topic/198377234/
專案原始碼
本文使用 mdnice 排版
作者:棗仁,
連結:https://juejin.im/post/6886708531903496205
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。