其他章節請看:
react 腳手架建立專案
前面我們一直通過 script 的方式學習 react 基礎知識,而真實專案通常是基於腳手架進行開發。
本篇首先通過 react 腳手架建立專案,分析其目錄結構,接著編寫第一個元件、解決樣式覆蓋,最後配置代理 proxy 以及通過訊息釋出與訂閱解決兄弟元件之間的通訊問題。
Tip:我們要接手的 react 專案是:spug_web。
使用 react 腳手架建立專案 react-cli-demo
前面我們學習 vue 腳手架 vue-cli 建立一個專案是這樣:
> vue create vue-hello-world
在 react 中建立專案是這樣:
$ npx create-react-app react-cli-demo
Creating a new React app in exercise\react-cli-demo.
Installing packages. This might take a couple of minutes.
Installing react, react-dom, and react-scripts with cra-template...
added 1368 packages in 2m
169 packages are looking for funding
run `npm fund` for details
Initialized a git repository.
Installing template dependencies using npm...
npm WARN deprecated source-map-resolve@0.6.0: See https://github.com/lydell/source-map-resolve#deprecated
added 38 packages in 9s
169 packages are looking for funding
run `npm fund` for details
Removing template package using npm...
removed 1 package, and audited 1406 packages in 4s
169 packages are looking for funding
run `npm fund` for details
6 moderate severity vulnerabilities
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
Created git commit.
Success! Created react-cli-demo at exercise\react-cli-demo
Inside that directory, you can run several commands:
npm start
Starts the development server.
npm run build
Bundles the app into static files for production.
npm test
Starts the test runner.
npm run eject
Removes this tool and copies build dependencies, configuration files
and scripts into the app directory. If you do this, you can't go back!
We suggest that you begin by typing:
cd react-cli-demo
npm start
Happy hacking!
Create React App
是一個用於學習 React 的舒適環境,也是用 React 建立新的單頁應用
的最佳方式 —— 官網-Create React App
$ cd react-cli-demo/
本地啟動專案:
$ npm start
> react-cli-demo@0.1.0 start
> react-scripts start
(node:3880) [DEP_WEBPACK_DEV_SERVER_ON_AFTER_SETUP_MIDDLEWARE] DeprecationWarning: 'onAfterSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
(Use `node --trace-deprecation ...` to show where the warning was created)
(node:3880) [DEP_WEBPACK_DEV_SERVER_ON_BEFORE_SETUP_MIDDLEWARE] DeprecationWarning: 'onBeforeSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
Starting the development server...
Compiled successfully!
You can now view react-cli-demo in the browser.
Local: http://localhost:3000
On Your Network: http://192.168.85.1:3000
Note that the development build is not optimized.
To create a production build, use npm run build.
assets by path static/ 1.49 MiB
asset static/js/bundle.js 1.48 MiB [emitted] (name: main) 1 related asset
asset static/js/node_modules_web-vitals_dist_web-vitals_js.chunk.js 6.93 KiB [emitted] 1 related asset
asset static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg 2.57 KiB [emitted] (auxiliary name: main)
asset index.html 1.67 KiB [emitted]
asset asset-manifest.json 546 bytes [emitted]
cached modules 1.37 MiB (javascript) 31.3 KiB (runtime) [cached] 122 modules
webpack 5.69.1 compiled successfully in 1867 ms
自動開啟網頁:
react-cli-demo 目錄結構分析
exercise\react-cli-demo> dir
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2022/3/3 17:50 node_modules
d----- 2022/3/3 17:48 public
d----- 2022/3/18 19:22 src
-a---- 1985/10/26 16:15 310 .gitignore
-a---- 2022/3/3 17:49 1120931 package-lock.json
-a---- 2022/3/3 17:49 817 package.json
-a---- 1985/10/26 16:15 3359 README.md
一級目錄結構很簡單,我們主要分析一下 public
和 src
目錄
public 目錄
public> dir
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 1985/10/26 16:15 3870 favicon.ico
-a---- 2022/3/4 16:06 1966 index.html
-a---- 1985/10/26 16:15 5347 logo192.png
-a---- 1985/10/26 16:15 9664 logo512.png
-a---- 1985/10/26 16:15 492 manifest.json
-a---- 1985/10/26 16:15 67 robots.txt
從中我們猜測主要檔案應該是 index.html
。內容如下:
// 註釋已全部刪除
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
index.html
的核心就是 <div id="root"></div>
,即掛載的根元素
Tip:robots.txt
即robots協議,只是約定俗成的,所以並不能保證網站的隱私
src 目錄
src> dir
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2022/3/4 16:56 components
-a---- 1985/10/26 16:15 564 App.css
-a---- 2022/3/18 19:22 553 App.js
-a---- 1985/10/26 16:15 246 App.test.js
-a---- 1985/10/26 16:15 366 index.css
-a---- 1985/10/26 16:15 500 index.js
-a---- 1985/10/26 16:15 2632 logo.svg
-a---- 1985/10/26 16:15 362 reportWebVitals.js
-a---- 1985/10/26 16:15 241 setupTests.js
index.js 和 App.js
哪個是入口檔案?App.js
還是 index.js
?我們先看一下這兩個檔案的內容:
// index.js 已刪除註釋
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
// App.js
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
index.js
引用了 App.js
。
我們在回憶一下 vue-cli 生成的專案,也有 App.js
檔案,不過是被 main.js
引用。內容如下:
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
// App.js
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>
對比發現,都是將 App 元件掛載到 dom 上 —— react 是 #root
,vue 是 #app
至此,我們知道 index.js
是入口檔案,而 App 應該是根元件。
App.css
上面我們啟動 react-cli-demo 專案,網頁中有這麼一句話:
Edit src/App.js and save to reload.
編輯 src/App.js 並儲存以重新載入。
App.js
中有 import './App.css';
,筆者嘗試修改一下 App.css
:
.App-header {
- background-color: #282c34;
+ background-color: orange;
...
}
儲存後,發現頁面背景自動變成橙色
於是我們知道 App.css
應該是 App 元件的樣式。而 vue 中樣式、html和 js 都在一個 .vue
檔案中。
第一個元件 HelloWorld
元件建立有兩種方式:函式元件以及類元件。
這裡的 App.js
使用的是函式元件,我們也用函式的方式建立元件,然後讓 App 載入。請看示例:
// App.js
// webpack 中就可以省略 .js
import HelloWorld from './HelloWorld.js';
export default function App() {
return (
<div className="App">
< HelloWorld />
</div >
);
}
// HelloWorld.js
export default function HelloWorld() {
return <div>hello world!</div>
}
頁面顯示 hello world!
。
對應類元件的實現:
import { Component } from 'react'
export default class HelloWorld extends Component {
render() {
return <div>hello world!</div>
}
}
字尾js/jsx
我們寫的元件 HelloWorld 其實是 jsx 語法,所以可以將 HelloWorld.js
改為 HelloWorld.jsx
,引入的字尾名也同步一下即可。
import HelloWorld from './HelloWorld.jsx';
Tip:純邏輯的 js 可以用 .js
或小寫(例如 http.js
),元件用 Http.js
或 Http.jsx
。
Create React App
在內部使用 webpack,而 webpack 能夠使使用者在引入模組時不帶擴充套件:
import File from '../path/to/file';
經測試,無論是 HelloWorld.js
還是 HelloWorld.jsx
,都可以省略副檔名引入:
import HelloWorld from './HelloWorld';
components
src/App.js
作為元件的根元件(或元件殼子),現在我們的HelloWorld 元件和它是同一目錄,倘若以後元件變多了,豈不是不好管理,所以我們可以將元件放在 src/components
資料夾中。請看實現:
將 HelloWorld 元件程式碼移至 index.js
:
// src/components/HelloWorld/index.js
export default function HelloWorld() {
return <div>hello world!</div>
}
在 App.js
中修改引入元件的程式碼:
// 預設會去載入 HelloWorld 資料夾中的 index.js或 index.jsx
import HelloWorld from './components/HelloWorld';
...
樣式覆蓋
假如我在 App 中引入兩個元件,每個元件有自己的樣式,讓若發生衝突怎麼辦?請看示例:
// App.js
import HelloWorld from './components/HelloWorld'
import HelloWorld2 from './components/HelloWorld2'
export default function App() {
return (
<div className="App">
< HelloWorld />
< HelloWorld2 />
</div >
);
}
元件1 的文字是藍色
:
import './index.css'
export default function HelloWorld() {
return <div className="title">hello world!</div>
}
.title{color:blue}
元件2與元件1相同,唯一區別是文字顏色為紅色
:
.title{color:red}
最終,頁面中兩個元件的文字顏色都是紅色
。
頁面有如下程式碼:
<style>.title{color:blue}
</style>
<style>.title{color:red}
</style>
<div class="title">hello world!</div>
<div class="title">hello world!</div>
由此我們知道後者樣式將前者給覆蓋了。
樣式模組化
我們可以使用樣式模組化來修復樣式覆蓋的問題。
比如我要將 HelloWorld 元件樣式模組化,只需要兩步:
首先重新命名樣式檔案。在名字和 css 之間增加 module
:
component/HelloWorl/index.css
// 重新命名後
component/HelloWorl/index.module.css
然後使用樣式的方式也得調整。就像這樣:
import helloWorld from './index.module.css'
export default function HelloWorld() {
return <div className={helloWorld.title}>hello world!</div>
}
最終,頁面中兩個元件的文字顏色分別是藍色
和紅色
,與預期相符。
頁面有如下程式碼:
<style>.HelloWorld_title__kRYA7{color:blue}
</style>
<style>.title{color:red}
</style>
<div class="HelloWorld_title__kRYA7">hello world!</div>
<div class="title">hello world!</div>
Tip:效果其實和 vue 中 Scoped Css 類似
less
我們還可以使用 less 這類 css 預處理語言
來避免樣式衝突。就像這樣:
.HelloWorld {
.title{color: blue}
}
.HelloWorld2 {
.title{color: red}
}
代理 Proxy
在 vue-cli 中我們曾使用 proxy 做過一個需求:新建一個頁面,裡面有 2 個按鈕,點選按鈕能發出相應的請求,一個是非跨域請求,一個是跨域請求。
這裡我們也實現一下。無需按鈕,之間在元件中發請求即可。
在 React 開發中,你能使用任何你喜歡的 AJAX 庫,比如社群比較流行的 Axios,jQuery AJAX,或者是瀏覽器內建的 window.fetch —— 官網-如何在 React 中發起 AJAX 請求?
Tip:React 和 Vue 將注意力集中保持在核心庫,而將其他功能如ajax、路由和全域性狀態管理交給相關的庫。
axios
我們曾在 vue-loader 擴充套件 這裡將 axios 整合到專案中。這裡卻無需那麼複雜,只需能發出 ajax 請求。
根據 axios 官網 介紹,簡單使用只需兩步:
首先下載依賴包:
react-cli-demo> npm i axios
added 1 package, and audited 1407 packages in 7s
169 packages are looking for funding
run `npm fund` for details
6 moderate severity vulnerabilities
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
在 HelloWorld 元件中通過 axios 發起一個 ajax 請求:
// src/components/HelloWorld/index.jsx
const axios = require('axios');
export default function HelloWorld() {
axios.get('/index.html')
.then(function (response) {
console.log(response.data);
})
return <div>hello world!</div>
}
瀏覽器控制檯輸出:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<title>React App</title>
<script defer src="/static/js/bundle.js"></script></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
請求將 react-cli-demo/public/index.html
的內容返了回來,於是我們知道本地伺服器的根是 public
目錄。
setupProxy.js
spug_web 中有個叫 setupProxy.js
的檔案,內容如下:
// src/setupProxy.js
const proxy = require('http-proxy-middleware');
module.exports = function (app) {
app.use(proxy('/api/', {
target: 'http://127.0.0.1:8000',
changeOrigin: true,
ws: true,
headers: {'X-Real-IP': '1.1.1.1'},
pathRewrite: {
'^/api': ''
}
}))
};
Tip:http-proxy-middleware - http 代理中介軟體。
對比在 vue 中做 proxy 代理,程式碼非常相似:
// vue.config.js
module.exports = {
devServer: {
proxy: {
// 只有 /pengjiali 的請求會被代理
'/pengjiali': {
target: 'https://www.cnblogs.com/',
// changeOrigin: true
},
}
}
}
接下來我們就依葫蘆畫瓢:
新建 setupProxy.js
:
// src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
// 將原來的 proxy 改為 createProxyMiddleware
createProxyMiddleware(
'/pengjiali',
{
target: 'https://www.cnblogs.com/',
changeOrigin: true
}
)
)
}
// src/components/HelloWorld/index.jsx
const axios = require('axios');
export default function HelloWorld() {
axios.get('/pengjiali/p/14561119.html')
.then(function (response) {
// handle success
console.log(response.data);
}).catch(function (error) {
// handle error
console.log(error);
})
return <div>hello world2!</div>
}
重啟服務,控制檯輸出博文內容。
注:http-proxy-middleware
有兩點和 spug_web 不同:
- 筆者這版的react腳手架預設已有
http-proxy-middleware
,所以我們無需在下載。- vscode 中將滑鼠移至
http-proxy-middleware
就會顯示該檔案路徑。
- vscode 中將滑鼠移至
- 用法上從
proxy
改為createProxyMiddleware
。- 我們的版本是 2.0.4,也是此刻官網最新版,其用法使用的就是
createProxyMiddleware
- 我們的版本是 2.0.4,也是此刻官網最新版,其用法使用的就是
// 本地版本
react-cli-demo/node_modules/http-proxy-middleware (master)
$ cat package.json |head -n 5
{
"name": "http-proxy-middleware",
"version": "2.0.4",
"description": "The one-liner node.js proxy middleware for connect, express and browser-sync",
"main": "dist/index.js",
訊息訂閱與釋出
在 vue 中我們可以使用中央事件匯流排(或稱 bus)來解決兄弟元件之間的通訊。bus 相當於一箇中介,元件可以在其上訂閱訊息,當觸發時就會將訊息通知到訂閱者。其原理其實就是訊息訂閱與釋出。
react 可以通過 pubsub-js
包來實現元件之間通訊。
Tip:PubSubJS 是一個用 JavaScript 編寫的基於主題的釋出/訂閱庫 —— pubsub-js
下面我們定義兩個元件,元件1訂閱訊息,元件2釋出訊息。請看實現:
首先按照依賴包:
react-cli-demo> npm i pubsub-js
added 1 package, and audited 1408 packages in 5s
169 packages are looking for funding
run `npm fund` for details
6 moderate severity vulnerabilities
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
接著定義兩個元件:
// 元件1訂閱訊息
// src/components/HelloWorld/index.jsx
import PubSub from 'pubsub-js'
export default function HelloWorld() {
// 訂閱 message1
PubSub.subscribe('msg1', function (msg, data) {
console.log(msg, data);
});
return <div>hello world!</div>
}
// 元件2釋出訊息
// src/components/HelloWorld2/index.jsx
import PubSub from 'pubsub-js'
export default function HelloWorld() {
PubSub.publish('msg1', '旅遊去');
return <div>hello world2!</div>
}
頁面控制檯顯示:msg1 旅遊去
。
其他章節請看: