其他章節請看:
尾篇
前面我們依次學習了 react 基礎知識
、react 腳手架建立專案
、react 路由
,已經花費了不少時間,但距離接手 spug_web 專案還有一些困難。
package.json
作為專案的核心,從中我們能大概知曉此專案到底用了哪些技術,所以筆者決定從其入手。
spug_web/package.json
為什麼沒有看見 redux
,mobx
是什麼?antd
是什麼,react-app-rewired
、@babel/plugin-proposal-decorators
又有什麼作用?—— 本篇都能找到答案
// spug_web/package.json
{
"name": "spug_web",
"version": "3.0.0",
"private": true,
"dependencies": {
"@ant-design/icons": "^4.3.0",
"ace-builds": "^1.4.13",
"antd": "^4.10.3",
"axios": "^0.21.0",
"bizcharts": "^3.5.9",
"history": "^4.10.1",
"lodash": "^4.17.19",
"mobx": "^5.15.6",
"mobx-react": "^6.3.0",
"moment": "^2.24.0",
"react": "^16.13.1",
"react-ace": "^9.5.0",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.3",
"xterm": "^4.6.0",
"xterm-addon-fit": "^0.5.0"
},
"scripts": {
"start": "react-app-rewired start",
"build": "GENERATE_SOURCEMAP=false react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@babel/plugin-proposal-decorators": "^7.10.5",
"customize-cra": "^1.0.0",
"less": "^3.12.2",
"less-loader": "^7.1.0",
"react-app-rewired": "^2.1.6",
"http-proxy-middleware": "0.19.2"
}
}
antd
antd 是基於 Ant Design 設計體系的 React UI 元件庫,主要用於研發企業級中後臺產品。
Tip:類似的 ui 庫有:element-ui、material-ui(國外比較流行)、vant-ui(移動端)
安裝
react-cli-demo> npm i antd
added 58 packages, and audited 1479 packages in 27s
171 packages are looking for funding
run `npm fund` for details
8 vulnerabilities (6 moderate, 2 high)
To address issues that do not require attention, run:
npm audit fix
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
使用
ui 庫使用起來通常比較簡單,找到需要的示例,比如按button、日期、彈框等,複製貼上即可。
進入官網的 元件 頁面:
button
以 button 為例,比如我需要使用第一個按鈕(”Primary Button“):
找到對應的程式碼片段即可:
import { Button } from 'antd';
+ import 'antd/dist/antd.css'
export default function App() {
return (
<div className="App">
<Button type="primary">Primary Button</Button>
</div >
);
}
注:需要引入 antd 的樣式檔案 antd.css
。
倘若需要 button 更為詳細的介紹,可以看底部的 api。就像這樣:
icon
語義化的向量圖形。使用圖示元件,你需要安裝 @ant-design/icons
圖示元件包 —— 官網-Icon圖示
react-cli-demo> npm i @ant-design/icons
up to date, audited 1479 packages in 6s
171 packages are looking for funding
run `npm fund` for details
8 vulnerabilities (6 moderate, 2 high)
To address issues that do not require attention, run:
npm audit fix
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
Tip:以前 icon 是整合在 antd 中的。有些人可能無需使用圖示,所以後來就獨立了出來。
按需引入 css
我們或許只使用 button 元件,卻將 antd 的所有元件的樣式都引入進來(gzipped 後一共大約 60kb
):
import { Button } from 'antd';
+ import 'antd/dist/antd.css'
下面我們要做的就是是:按需引入 css。
Tip:類似這種需求,由於高度整合,所以怎麼做通常不能自己去像,需要看對應的文件。
注:目前 antd 文件是 4.x,我們可以看 3.x 的文件,因為舊的內容或許更詳細、亦或不同。
安裝依賴包用於對 create-react-app
的預設配置進行自定義:
- 由於需要對
create-react-app
的預設配置進行自定義,這裡我們使用react-app-rewired
(一個對 create-react-app 進行自定義配置的社群解決方案) - 由於新的
react-app-rewired@2.x
版本的關係,你還需要安裝customize-cra
react-cli-demo> npm i -D react-app-rewired customize-cra
added 4 packages, and audited 1483 packages in 15s
171 packages are looking for funding
run `npm fund` for details
8 vulnerabilities (6 moderate, 2 high)
To address issues that do not require attention, run:
npm audit fix
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
安裝依賴包用於按需載入元件樣式:
babel-plugin-import
是一個用於按需載入元件程式碼和樣式的 babel 外掛
react-cli-demo> npm i babel-plugin-import
added 1 package, and audited 1484 packages in 4s
171 packages are looking for funding
run `npm fund` for details
8 vulnerabilities (6 moderate, 2 high)
To address issues that do not require attention, run:
npm audit fix
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
修改 scripts:
- 通過
react-app-rewired
來啟動、打包和測試
/* package.json */
"scripts": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test",
+ "test": "react-app-rewired test",
}
專案根目錄建立一個 config-overrides.js
用於修改預設配置:
// config-overrides.js
const { override, fixBabelImports } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
);
移出手動引入的 antd 樣式:
- import 'antd/dist/antd.css'
重啟後發現按鈕樣式正常。
Tip:更詳細介紹請看 高階配置
主題色
antd 預設主題色是支付寶的藍色。
按照配置主題的要求,自定義主題需要用到 less 變數覆蓋功能:
react-cli-demo> npm i -D less less-loader
added 15 packages, and audited 1499 packages in 1m
173 packages are looking for funding
run `npm fund` for details
8 vulnerabilities (6 moderate, 2 high)
To address issues that do not require attention, run:
npm audit fix
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
修改 config-overrides.js
檔案如下:
- const { override, fixBabelImports } = require('customize-cra');
+ const { override, fixBabelImports, addLessLoader } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
- style: 'css',
+ style: true,
}),
+ addLessLoader({
+ javascriptEnabled: true,
+ modifyVars: { '@primary-color': '#1DA57A' },
+ }),
);
報錯如下:
Compiled with problems:X
ERROR in ./node_modules/antd/es/button/style/index.less (./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[1].oneOf[9].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].oneOf[9].use[2]!./node_modules/resolve-url-loader/index.js??ruleSet[1].rules[1].oneOf[9].use[3]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[1].oneOf[9].use[4]!./node_modules/antd/es/button/style/index.less)
Module build failed (from ./node_modules/less-loader/dist/cjs.js):
ValidationError: Invalid options object. Less Loader has been initialized using an options object that does not match the API schema.
// options 具有未知屬性“modifyVars”。 這些屬性有效
- options has an unknown property 'modifyVars'. These properties are valid:
object { lessOptions?, additionalData?, sourceMap?, webpackImporter?, implementation? }
at validate (react-cli-demo\node_modules\schema-utils\dist\validate.js:105:11)
at Object.getOptions (react-cli-demo\node_modules\webpack\lib\NormalModule.js:580:19)
at Object.lessLoader (react-cli-demo\node_modules\less
根據網上資料嘗試修改如下:
const { override, fixBabelImports, addLessLoader } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
}),
addLessLoader({
lessOptions: {
javascriptEnabled: true,
modifyVars: { '@primary-color': '#1DA57A' },
}
}),
);
報錯資訊變成:
Compiled with problems:X
ERROR in ./node_modules/antd/es/button/style/index.less (./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[1].oneOf[9].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].oneOf[9].use[2]!./node_modules/resolve-url-loader/index.js??ruleSet[1].rules[1].oneOf[9].use[3]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[1].oneOf[9].use[4]!./node_modules/antd/es/button/style/index.less)
Module build failed (from ./node_modules/postcss-loader/dist/cjs.js):
ValidationError: Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'plugins'. These properties are valid:
object { postcssOptions?, execute?, sourceMap?, implementation? }
at validate (react-cli-demo\node_modules\schema-utils\dist\validate.js:105:11)
at Object.getOptions (react-cli-demo\node_modules\webpack\lib\NormalModule.js:580:19)
at Object.loader (react-cli-demo\node_modules\postcss-loader\dist\index.js:40:24)
Tip:時間有限且不緊急,故筆者決定暫放。
mobx
簡單、可擴充套件的狀態管理。—— npm-mobx
Tip:文件可參考 印象中文-mobx、github-mobx-api
spug_web 狀態管理沒有使用 redux 而是使用 mobx。以下擷取自 alarm 路由元件:
// spug_web/src/pages/alarm/alarm/index.js
/**
* Copyright (c) OpenSpug Organization. https://github.com/openspug/spug
* Copyright (c) <spug.dev@gmail.com>
* Released under the AGPL-3.0 License.
*/
import React from 'react';
import { observer } from 'mobx-react';
import { SyncOutlined } from '@ant-design/icons';
import { Input, Button } from 'antd';
import { SearchForm, AuthDiv, Breadcrumb } from 'components';
import ComTable from './Table';
import store from './store';
export default observer(function () {
return (
<AuthDiv auth="alarm.alarm.view">
<Breadcrumb>
<Breadcrumb.Item>首頁</Breadcrumb.Item>
<Breadcrumb.Item>報警中心</Breadcrumb.Item>
<Breadcrumb.Item>報警歷史</Breadcrumb.Item>
</Breadcrumb>
<SearchForm>
<SearchForm.Item span={8} title="任務名稱">
<Input allowClear value={store.f_name} onChange={e => store.f_name = e.target.value} placeholder="請輸入"/>
</SearchForm.Item>
<SearchForm.Item span={8}>
<Button type="primary" icon={<SyncOutlined/>} onClick={store.fetchRecords}>重新整理</Button>
</SearchForm.Item>
</SearchForm>
<ComTable/>
</AuthDiv>
)
})
// spug_web/src/pages/alarm/alarm/store.js
/**
* Copyright (c) OpenSpug Organization. https://github.com/openspug/spug
* Copyright (c) <spug.dev@gmail.com>
* Released under the AGPL-3.0 License.
*/
import { observable, computed } from 'mobx';
import http from 'libs/http';
class Store {
@observable records = [];
@observable isFetching = false;
@observable f_name;
@observable f_status = '';
@computed get dataSource() {
let records = this.records;
if (this.f_name) records = records.filter(x => x.name.toLowerCase().includes(this.f_name.toLowerCase()));
if (this.f_status) records = records.filter(x => x.status === this.f_status);
return records
}
fetchRecords = () => {
this.isFetching = true;
http.get('/api/alarm/alarm/')
.then(res => this.records = res)
.finally(() => this.isFetching = false)
};
}
export default new Store()
主要用到 mobx-react
和 mobx
兩個包。
具體用法請看下文。
準備環境
注:本小節有點麻煩,主要是用於讓專案支援裝飾器語法。但是 mobx 不使用裝飾器語法照常能工作。
spug_web 中有個依賴包 @babel/plugin-proposal-decorators,用於支援 es6 中裝飾器的語法。就像這樣:
// spug_web/src/pages/alarm/alarm/store.js
class Store {
@observable records = [];
@observable isFetching = false;
@observable f_name;
@observable f_status = '';
...
首先安裝 mobx
、mobx-react
依賴包:
react-cli-demo> npm i mobx mobx-react
added 3 packages, and audited 1502 packages in 7s
176 packages are looking for funding
run `npm fund` for details
8 vulnerabilities (6 moderate, 2 high)
To address issues that do not require attention, run:
npm audit fix
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
"mobx": "^6.5.0",
"mobx-react": "^7.3.0",
注:最初打算安裝和 spug_web 相同的版本,但提示安裝失敗:
"mobx": "^5.15.6",
"mobx-react": "^6.3.0",
倘若將 src/index.js
替換成下面程式碼,頁面顯示 App 10
則說明環境準備就緒。
先替換內容啟動:
// react-cli-demo/src/index.js (完整程式碼)
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, } from "react-router-dom";
import { observable } from 'mobx';
class Store {
@observable count = 10
}
class App extends React.Component {
render() {
const { store } = this.props
return (
<div>
App {store.count}
</div>
)
}
}
ReactDOM.render(
<Router>
<App store={new Store()} />
</Router>,
document.getElementById('root')
);
Tip:為方便演示,未將 App 元件抽離出單獨檔案。
Compiled with problems:X
ERROR in ./src/index.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: react-cli-demo\src\index.js: Support for the experimental syntax 'decorators-legacy' isn't currently enabled (8:3):
6 |
7 | class Store {
> 8 | @observable count = 10
| ^
9 | }
10 |
11 | class App extends React.Component {
安裝 @babel/plugin-proposal-decorators:
react-cli-demo> npm i -D @babel/plugin-proposal-decorators@7
changed 1 package, and audited 1502 packages in 6s
176 packages are looking for funding
run `npm fund` for details
8 vulnerabilities (6 moderate, 2 high)
To address issues that do not require attention, run:
npm audit fix
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
參考 spug_web 配置這個包:
// config-overrides.js
const { override, addDecoratorsLegacy, fixBabelImports } = require('customize-cra');
module.exports = override(
+ addDecoratorsLegacy(),
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
);
重啟後發現頁面能顯示App 10
,但頁面蒙版上報錯如下:
Compiled with problems:X
ERROR
src\index.js
Line 8:2: Parsing error: This experimental syntax requires enabling one of the following parser plugin(s): "decorators-legacy", "decorators". (8:2)
第 8:2 行:解析錯誤:此實驗性語法需要啟用以下解析器外掛之一:“decorators-legacy”、“decorators”。 (8:2)
vscode 中有兩個錯誤提示,其中之一就有上述錯誤:
新建 .babelrc
檔案
// react-cli-demo\.babelrc
{
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
}
Tip:參考 @babel/plugin-proposal-decorators
接著對 vscode 進行設定:
- 設定 -> 搜尋
experimentalDecorators
-> 打上勾
重啟 vscode,再次重啟服務。如果幸運的話,一切都應該就正常了。
裝飾器
裝飾器是處理類的函式。
Tip:參考 @babel/plugin-proposal-decorators
簡單的類裝飾器
請看示例:
// src/decorators-test.js
@fn
class Dog {}
function fn(target) {
target.aName = 'pjl'
}
// pjl
console.log(Dog.aName)
Dog 類被 fn 裝飾後,增加了一個類的屬性 aName
。
Tip:src/index.js
中引入此檔案。就像這樣:import './decorators-test'
類裝飾器
裝飾器函式可以通過函式返回;裝飾器也可以多個一起使用。就像這樣:
@fn
@fn2(18)
class Dog {}
function fn(target) {
target.aName = 'pjl'
}
// 返回一個裝飾器
function fn2(value) {
return function (target) {
target.age = value
}
}
console.log(Dog.aName) // pjl
console.log(Dog.age) // 18
給例項新增屬性
裝飾器可以給例項新增屬性。就像這樣:
@fn3
class Dog {}
function fn3(target) {
target.prototype.c = 'cc'
}
console.log(new Dog().c) // cc
Class 成員裝飾器
下面定義了一個只讀裝飾器:
class Dog {
@readOnly aName = 'pjl'
}
// target 目標類的 prototype
// key 類成員的名字
function readOnly(target, key, descriptor) {
descriptor.writable = false
}
let dog = new Dog()
console.log(dog.aName) // pjl
// Uncaught TypeError: Cannot assign to read only property 'aName' of object '#<Dog>'
dog.aName = 'li'
類似的有“Class function decorator”,就像這樣:
class C {
@enumerable(false)
method() {}
}
function enumerable(value) {
return function(target, key, descriptor) {
descriptor.enumerable = value;
return descriptor;
};
}
第一個示例-自增
在”準備環境“章節中我們從 mobx 中讀取了數字 10,現在我們要增加一個按鈕,每點選一次,數字能自增1。請看實現:
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, } from "react-router-dom";
import { observable, action, makeObservable, computed } from 'mobx';
import { observer } from 'mobx-react';
class Store {
constructor() {
makeObservable(this, {
// 將 count 轉為可觀察的
count: observable,
// action,動作,用於更改狀態(count)
increment: action,
// 將 total 標記為計算屬性
total: computed
})
}
count = 10
increment() {
console.log('increment')
this.count++
}
get total() {
console.log('total')
return this.count * 10
}
}
// react 與 mobox 的橋樑
// 倘若註釋掉 @observer,點選”自增1“,頁面內容總是顯示 `App 10`,儘管 increment 方法也被執行。
@observer
class App extends React.Component {
render() {
const { store } = this.props
return (
<div>
App {store.count}
<button onClick={() => store.increment()}>自增1</button>
<p>total: {store.total}</p>
<p>total: {store.total}</p>
</div>
)
}
}
ReactDOM.render(
<Router>
<App store={new Store()} />
</Router>,
document.getElementById('root')
);
頁面顯示:
App 10 自增1
total: 100
total: 100
控制檯輸出一條:total
。
observable 可觀察的
observable
定義了一個儲存狀態的可跟蹤欄位。
倘若將上述例子中的 count: observable
註釋,也就是說 count 不再是一個可跟蹤的欄位,接下來無論怎麼點選”自增1“按鈕,頁面內容也只會顯示”App 10“。
action
action
將方法標記為將修改狀態的操作。
倘若將上述例子中的 increment: action
註釋,再次點選”自增1“按鈕,頁面內容會變成”App 11“,但控制檯有警告:
[MobX] Since strict-mode is enabled, changing (observed) observable values without using an action is not allowed. Tried to modify: Store@1.count
[MobX] 由於啟用了嚴格模式,因此不允許在不使用 action 的情況下更改(觀察到的)可觀察值。 嘗試修改:Store@1.count
提醒我們要使用 action
來更改可觀察值。
configure
我們可以通過 configure
來關閉提示 action
中的警告,但通常不推薦:
import { configure } from 'mobx'
configure({
enforceActions: 'never'
})
computed 計算屬性
computed 標記了一個 getter,它將從狀態中獲取新的事實並快取其輸出。
Tip:類似 vue 中的計算屬性,也有快取。
上述例子初始化頁面只輸出一次 total
,儘管我們讀取了 2 次。倘若我們註釋掉 total: computed
這行,也就是取消 total 為計算屬性,頁面在初始化時就會輸出 2 次 total
。
this 為 undefined
上述例子將替換 button 這行程式碼,然後點選”自增1“按鈕,控制檯報錯如下:
increment() {
console.log('increment')
this.count++
}
- <button onClick={() => store.increment()}>自增1</button>
+ <button onClick={store.increment}>自增1</button>
Uncaught TypeError: Cannot read properties of undefined (reading 'count')
這是因為 increment()
中 this 是 undefined
。可以通過以下方式解決:
- 使用
action.bound
:
makeObservable(this, {
count: observable,
- increment: action,
+ increment: action.bound,
total: computed
})
- 藉助箭頭函式:
increment() {
console.log('increment')
this.count++
}
改為
increment = () => {
console.log('increment')
this.count++
}
makeAutoObservable
與使用 makeObservable
相比,makeAutoObservable
函式可以更緊湊且更易於維護,因為不必明確提及新成員。
所以上述例子的 constructor
部分可以改為:
constructor() {
// 引數1,讓哪個物件變成可觀察的
// 引數2,排除變成可觀察的屬性或方法
// 引數3,這裡是自動繫結 this
+ makeAutoObservable(this, {}, { autoBind: true })
- makeObservable(this, {
- count: observable,
- increment: action,
- total: computed
- })
}
Tip:更多介紹請參考 makeautoobservable
監聽屬性
autorun
autorun
接收一個函式,該函式總是立即被觸發一次。並自動訂閱函式中可觀察屬性,一旦某屬性變化,則該函式會再次被執行。
請看示例:
...
import { autorun } from 'mobx'
class Store {
constructor() {
makeObservable(this, {
count: observable,
age: observable,
})
setTimeout(() => {
this.count++
this.age++
}, 1000)
autorun(() => {
console.log(this.count, this.age);
})
}
count = 10
age = 1
}
控制檯輸出:
// 總會執行一次
10 1
// 過一秒
11 1
11 2
預設會執行一次。由於訂閱了兩個可觀察屬性,所以只要可觀察屬性被修改,函式就會被執行。
倘若註釋 age: observable
,輸出:
10 1
11 1
reaction
reaction
是 autorun
的變種,對於如何追蹤 observable 賦予了更細粒度的控制
- 與 autorun 不同,初始化時不會執行
- 可以接收兩個函式作為引數
- 引數1的返回值作為第二個引數的輸入
重寫上述例子,將 autorun 替換成 reaction:
import { reaction } from 'mobx'
class Store {
constructor() {
makeObservable(this, {
count: observable,
age: observable,
})
setTimeout(() => {
this.count++
this.age++
}, 1000)
reaction(
() => ({
count: this.count,
age: this.age
}),
// oldVal 是上一次的值
(val, oldVal) => {
console.log(val, oldVal)
}
)
}
count = 10
age = 1
}
// 一秒後
{count: 11, age: 1} {count: 10, age: 1}
{count: 11, age: 2} {count: 11, age: 1}
倘若註釋 age: observable
,輸出:
{count: 11, age: 1} {count: 10, age: 1}
非同步處理
比如點選”自增1“按鈕,一秒後修改狀態,我們假如這樣寫:
increment() {
setTimeout(() => {
this.count++
}, 1000)
}
控制檯會報警告:
[MobX] Since strict-mode is enabled, changing (observed) observable values without using an action is not allowed. Tried to modify: Store@1.count
一種方式是將 imcrement()
函式拆分成兩個方法,就像這樣:
increment() {
this.count++
}
asyncIncrement() {
setTimeout(() => {
this.increment()
}, 1000)
}
<button onClick={() => store.asyncIncrement()}>自增1</button>
更好的方式是使用 runInAction
:
import { runInAction } from 'mobx'
increment() {
setTimeout(() => {
runInAction(() => {
this.count++
})
}, 1000)
}
顧名思義,函式在 action 中執行。
新的開始
以下是剩餘一些包的作用:
-
ace-builds,Ace 是一個用 JavaScript 編寫的程式碼編輯器。
-
bizcharts,阿里通用圖表元件庫,致力於打造企業中後臺高效、專業、便捷的資料視覺化解決方案,基於 G2與G2Plot封裝的React圖表庫,已經歷阿里複雜業務場景長達三年的洗禮,在靈活性、易用性、豐富度上滿足常規圖表和高度自定義圖表的業務實現
-
lodash,提供模組化、效能和附加功能的現代 JavaScript 實用程式庫。
-
moment,用於解析、驗證、操作和格式化日期的 JavaScript 日期庫。
-
react-ace,Ace 的一組 react 元件
import Editor from 'react-ace';
- xterm,Xterm.js 是一個用 TypeScript 編寫的前端元件,它允許應用程式在瀏覽器中將功能齊全的終端帶給使用者。它被 VS Code、Hyper 和 Theia 等流行專案使用。
至此,我們基本具備接手 spug_web 這個 react 前端專案的能力,但是,react 的旅途才剛剛開始。
其他章節請看: