owned this note
owned this note
Published
Linked with GitHub
# React.js 簡介與環境建置
###### tags: `javascript`、`react`
## 簡介
React 是一個 JavaScript 的 library,專門處理前端 UI 呈現的結果。常常與另外兩個前端框架(Vue.js、Angular.js)並稱前端三大框架,但嚴格說起來 React 只是一個 library,因為它原先的功能使用起來算是輕量型的 library,僅有處理 UI 呈現的功能,如果要藉由 React 處理前端較完整的功能的話,還需藉助其它的得力 library,像是 Redux 、Router 之類的。
## 前置作業
### 安裝 webpack 和 babel
安裝 webpack:`npm install webpack webpack-cli`
安裝 babel: `npm install -D babel-loader @babel/core @babel/preset-env`。其中 `babel-loader` 是要用在 webpack 的 plug-in,`preset-env` 則是指定 babel 要執行的程度,需要支援到哪些瀏覽器的轉換與版本,都可以在`.babelrc` 檔案做微調。([參考資料](https://www.npmjs.com/package/babel-loader))
`.babelrc` 的內容需要輸入:
```
{
"presets": [
"@babel/preset-env" // 如果需要調整支援程度,就改變這行的內容
]
}
```
以下是使用 webpack 打包的範例(在 `webpack.config.js` 檔案中):
```
const path = require("path"); // 引入 path module
// 打包後輸出
module.exports = {
entry: "./src/index.js", // 來源輸入檔案
output: {
path: path.join(__dirname, "/dist"), // 輸出檔案要放的位置(當前資料夾下的 dist 資料夾)
filename: "bundle.js" // 輸出檔案的名稱
},
// 要使用的 module
module: {
rules: [
{
test: /\.js$/, // 只要是 .js 結尾的檔案都要進來這個測試
exclude: /node_modules/, // 無視資料夾位置在 node_modules 的檔案
use: {
loader: "babel-loader" // 針對符合 .js 結尾的檔案來使用 babel 處理
}
}
]
}
}
```
### 安裝 react library
安裝指令:` npm install react react-dom @babel/preset-react`
其中安裝了三個 library,分別是:
1. [react](https://www.npmjs.com/package/react):就是 react 本體
2. [react-dom](https://www.npmjs.com/package/react-dom):和 DOM 操作有關
3. [@babel/preset-react](https://www.npmjs.com/package/@babel/preset-react):和 react 需要用到 babel 語法有關(jsx 的部份)
安裝完之後,在 `.babelrc` 的檔案中加上 `"@babel/preset-react"`,變成下面這樣:
```
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
```
這樣子 react 的環境就建置完畢了。
## 簡單使用 React 實作出 render 畫面
新增一個檔案叫 `app.js`,以下是檔案裡面的內容:
```
// 也可以只 import React,但是這樣如果要使用 Component 的話要這樣寫:React.Component
import React, { Component } from 'react'
// 建立一個插入 Component class 的 App class
class App extends Component {
// 每個 Component 都可以建立 render()
render() {
// 要 render 的內容
return (
<h1>hello world</h1>
)
}
}
export default App
```
接著在 `index.js` 檔案(webpack 輸入檔)加入以下內容:
```
import React from 'react'
import ReactDOM from 'react-dom'
import App from './app.js'
// 當執行的時候,會把 App 的 Component,render 到 root 這個 DOM 節點上面去
ReactDOM.render(<App />, document.getElementById('root'))
```
`index.html` 檔案記得要加這個節點:`<div id='root'></div>`
接著在 `package.json` 檔案中的 `script` 物件加上 `"start": "webpack --mode development"`,輸入指令: `npm run start` 之後,webpack 即打包完畢。
`npm run start` 這個指令預設是會去尋找與 `package.json` 相同目錄的 `webpack.config.js` 檔案。如果 `webpack.config.js` 檔案與 `package.json` 檔案在不同目錄下,那麼,在 `package.json` 的 `"start": "webpack --mode development"` 的後面就要再附上要執行的檔案的路徑,像是 `"start": "webpack --mode development" --config test/webpack.config.js`,意思就是要執行當前目錄(`package.json` 檔案目錄)的下一層目錄 `test` 裡面的 `webpack.config.js` 檔案。
#### 參考資料
1. [[Webpack]No.5 第二份 webpack.config 設定](https://pvt5r486.github.io/webpack/20190504/374010663/)
## 優化
### 自動生成檔案亂碼與自動新增 index.html 檔案
有時候瀏覽器會把檔案給 catch 住,所以可以在 `webpack.confing.js` 要輸出的檔案中加上 [hash]:
```
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
path: path.join(__dirname, "/dist"),
filename: "bundle.[hash].js" // 這一行多了 [hash]
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
]
}
}
```
這樣輸出檔案的檔名後面就會有隨機的亂碼:
![filename](https://i.imgur.com/K7u5fvq.png)
只要更動了檔案的內容,這個亂碼就會換新的一組。
不過這樣又會衍伸出另外一個問題,就是負責呈現畫面的 `index.html` 的檔案,它的 `<script>` 標籤的 `src` 屬性每次都還要再更改引進的 `.js` 檔名。
因此我們可以安裝一個 `html-webpack-plugin` 的 plugin 來解決這個問題:
`npm install html-webpack-plugin`
這個套件可以幫我們自動生成 `index.html` 的檔案,安裝完成後,把 `webpack.config.js` 檔案改成這樣:
```
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin"); // 引進 html-webpack-plugin
module.exports = {
entry: "./src/index.js",
output: {
path: path.join(__dirname, "/dist"),
filename: "bundle.[hash].js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
]
},
// plugin 都是放在這邊,可以引進很多 plugins
plugins: [
new HtmlWebpackPlugin({
template: './index.html' // 把 index.html 當成樣板
})
]
}
```
接著輸入 `npm run start` 打包,打包完後,就會看到 `dist` 資料夾會多一個 `index.html` 檔案,它的檔案內容如下:
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id='root'></div>
<script type="text/javascript" src="bundle.46652ee2d6380a0b24a8.js"></script></body>
</html>
```
`<script>` 那一行是經過 webpack 處理後自動產生的,而其它內容都是原先作為 template 的那個 `index.html` 檔案的內容。
### 自動更新你更新的內容並開啟 localhost:8080 的頁面查看
接下來要設定的東西是整個對於 react 開發環境中,最方便的工具之一,它就是 `webpack-dev-server`,為什麼要使用它的原因在於,今天如果我們在 `app.js`(使用 react render 畫面的那個檔案)這個檔案更新了內容之後,我們必須再接著輸入 `npm run start`,然後要再接著開啟 `index.html` 檔案才能看到最新的更新內容,這樣的步驟非常多,如果一直增增減減內容會拖慢開發的速度,因此 `webpakc-dev-server` 這個工具就誕生了。
首先要先安裝它: `npm install webpack-dev-server`
安裝完畢之後,我們還得到 `package.json` 的檔案去更改在 `scripts` 當初設定的 `start`,原先的 `start` 我們是設定 `webpack --mode development`,把它改為 `webpack-dev-server --mode development --open --hot` 就大功告成。
接著後面我們只要一樣在 CLI 輸入 `npm run start`,那麼就會自動跑出 `localhost:8080` 的頁面,而這個頁面就是 webpack 打包過後的頁面,緊接著更酷的是,我們只要在 `app.js` 的檔案當中更改任何內容,一儲存, `localhost:8080` 頁面就會立即更新,如下顯示:
原先內容:
![hello world](https://i.imgur.com/svRG4Fo.png)
`app.js` 檔案的內容更改後儲存,立即變更:
![yoyoyo](https://i.imgur.com/1luFZhA.png)
在 CLI 的畫面,一儲存 `app.js` 更新過後的內容,它就會自動幫你打包:
![auto package](https://i.imgur.com/1K9nEDe.png)
不過實際上在 `dist` 資料夾裡,並沒有多出 `bundle.js` 的檔案和 `index.html`(因為我們上面有安裝 html-webpack-plugin 但也沒出現 `index.html` 的檔案),要使用 `npm run build` 也就是 `webpack --mode production` 這個指令才會產出最後的檔案。
而只要還沒關閉這個 `webpack-dev-server` 的指令以前它都會一直在運轉,如果要關閉指令,只要按下`ctrl + c` 就會問你要不要終止工作,在輸入 `Y` 即結束指令。
## create react app
如果你不想要這麼麻煩,從這篇文章最上面開始環境建置到最後的朋友,你有福了,React 官方網站提供了一個「一鍵懶人包指令」,只要輸入以下的指令,所有的環境建置即完成:
`npx create-react-app my-app`
對,你沒看錯,就只需要這個指令,你就可以使用了,其中 `my-app` 是整個環境的資料夾,你可以任意更改名稱。在 CLI 輸入 `npm run start`,如果有成功跳出 `loacal:3000` 這個頁面,並且有個 React 圖案一直在轉圈圈的話,表示你成功了。
不過這種懶人包所安裝的 modules 或一些插件可能不是你所需要的,甚至是你需要的沒有安裝到,因此建議的方式還是靠著客製化自己的 React 環境建置(也就是從這篇文章的開頭開始)才比較好掌握開發。
### 使用 async/await 出現的錯誤
在 async/await 時,如果出現 `regeneratorRuntime is not defined` 的錯誤,表示目前所用的 Babel 沒有支援 `regeneratorRuntime`,這時可以裝 `babel-polyfill` 或是 `@babel/plugin-transform-runtime` 來解決這種新語法的不支援。
#### 參考資料
1. [Babel 6 regeneratorRuntime is not defined](https://stackoverflow.com/questions/33527653/babel-6-regeneratorruntime-is-not-defined)
2. [@babel/plugin-transform-runtime](https://babeljs.io/docs/en/babel-plugin-transform-runtime)