減少維護環境配置 React Create Script、Axios API、Redux toolkit

me
林彥成
2019-06-04 | 5 min.
文章目錄
  1. 1. React Create Script
    1. 1.1. 支援 SCSS
    2. 1.2. 支援環境變數
    3. 1.3. Bundle analysis
  2. 2. Axios API
  3. 3. Redux toolkit

什麼是 Boilerplate? Boilerplate 是樣板,當我們常常重複地做一件事情,作為攻城獅好像就應該把這些東西抽出來,有了樣板之後,卻發現自己還是一直在複製貼上?該如何減少重複的樣版撰寫?

以下分三個常見我認為可以有改進空間的部分:

  • create-app-script: 目標是盡可能減少維護的成本,目標擺在商業邏輯,而非專案配置
  • Axios: 希望可以統一管理資料處理邏輯
  • Redux toolkit: 減少每次都一直寫幾乎一樣的 action、reducer

React Create Script

主要設計給入門者以及不想維護環境配置檔的開發者使用,如果沒有較偏門的需求,強烈建議採用。

寫前端最常遇到的問題就是專案配置,人力少的情況下根本沒必要每次重開一個新專案又自己重新配置一次或因為什麼需求微調、還要注意升級某某套件時的相容性,舉例來說像是如果要處理 SCSS、PWA、伺服器渲染(SSR)、多國語系(i18n)的狀況:

  1. 如果在專案進行到一半才在規劃,很大的可能會遇到各種未知的小問題,像是有些多國語系的資料有些可從前端但有些要從後端來,要怎麼在伺服器渲染時進行處理?
  2. 事先裝相關的套件,做好相關配置,但過了兩個月之後發現,React 跳大版本了,有些相關套件必須升級不可,不然不能用?
  3. 不熟 webpack

如果這時候,在大部分東西都不改的情況下,直接移轉到一些已經做好的配置樣板中,或是一開始就使用有團隊維護的配置呢?或許問題就會減少很多?

常用工具

  • create-app-script: 相關設定都封裝在 react-scrips 中,主要設計給入門者以及不想維護環境配置檔的開發者使用,如果沒有較偏門的需求,強烈建議採用
  • react-boilerplate: 人工維護版的專案配置,複雜又被解開過的 react-scripts,適合想自力更生又想客製一點小東西的情況
  • next.js: 封裝 next 裡,是一套框架,包含伺服器渲染、多國語系、PWA、路由

支援 SCSS

無痛支援必須推,現在就只需要 npm install sass --save-dev 後就可以直接 import './main.scss' 了。

支援環境變數

環境變數的用途是需要依環境不同給不同的設定,因為

  • 測試網站跟正式網站打的後端 API 會不一樣
  • 測試網站需要在網頁的 console 中也有 log,正式則不需要
  • 正式環境不需要 .map 檔來 debug

這就需要透過不同的配置來做,主要有透過設定 .env 環境檔,如果需要自訂變數,可以將變數都以 REACT_APP_ 開頭命名,這樣就能在程式碼中使 process.env.REACT_APP_XXXX 來存取

舉例來說如果不需要 .map 檔,可以透過在專案的根目錄新增一個環境變數檔 GENERATE_SOURCEMAP = true

Bundle analysis

react create script 裡頭有這樣的一個配置,可以讓我們分析 Bundle 的 Chunk 大小,當無腦開發到一段時間以後,會發現我們的 bundle 越來越大包,當然初步還是可以使用 react 16.6.0 之後推出的功能來把 bundle 拆成小的 chunk,如果小的 chunk 大小還是太大,就需要使用分析的工具了,這樣的工具可以告訴我們到底哪個函式庫占了比較大的空間。

如果發現用了一個肥大函式庫,但只用了不到 1% 的功能,那就是時候找替代方案或是考慮自己實做了。

https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size#docsNav

Axios API

打 API 的時候,可能常常不只一個後端,不同後端也會需要有兩個不同的 token,放置的位置可能也不一樣,成功回覆的狀態碼也不同,失敗的情況也不大相同。所以如果當多個地方都需要同時打 A、B 兩個不同的 API 時,也代表多個地方都要進行類似的預/後處理。

axios 透過產生 instance 的方式,幫我們統一處理了 request 發出前,接收 response 後,統一的預處理、後處理的問題。這樣只要在不同的地方使用 A、B instance 去進行 API 的串接即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const A = axios.create();

// Add a request interceptor
A.interceptors.request.use(
function (config) {
// Do something before request is sent
return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);

// Add a response interceptor
A.interceptors.response.use(
function (response) {
// Do something with response data
return response;
},
function (error) {
// Do something with response error
return Promise.reject(error);
}
);

A.get("/api/path");

Redux toolkit

為什麼需要 Redux? 使用與否的差異在哪?

直接開始一個情境,我們想像一個頁面中有三個元件:

  1. 元件一: 登入按鈕區塊,登入後顯示 hello, XXX (XXX 為學生名稱)
  2. 元件二: 顯示登入後撈回的各科成績資料
  3. 元件三: 更改學生姓名區塊

那麼有或沒有 Redux 的情況下,要怎麼實作這樣的頁面呢?關鍵需要解決的問題就是元件之間溝通的問題:

  • 沒有 Redux 時,我們可以使用一個 Container 元件來管理狀態,然後把以上三個元件都當作子元件放在容器裡,並在容器中寫幾個 callback function 當作 props 傳進子元件中,讓子元件可以在改變狀態時,把改變的狀態及時回傳到容器裡,這就是官方文件中寫的 lift state up

  • 有 Redux 最大的改變就是,lift state up to store,把狀態統一管理,這裡有個重要的原則就是 Single source of truth,所有的來源都是來自於一個可被預測的地方,也因為 Redux 這樣的特性,所以 debug tool 可以輕鬆地幫我們做到時空旅行,當操作狀態的函式皆固定時,我們就可以透過操作函式讓狀態停在任意想要的時間點,酷吧 XDDD

在使用 Redux 的時候,我們通常要同時維護 reducer 以及 action,如果還要搭配 API 進行,又會需要多維護一個 middleware,可是在簡單的專案中,絕大多數時候我們進行的工作都是類似的,redux-api 在這種時候就幫我們少寫很多 action + reducer,也協助我們處理的非同步的部分,簡單來說,本來要維護三個地方,現在變成只要維護一份配置檔就好了。官方也有相關的建議,甚至後來還推出了 toolkit 來協助寫法簡化。

https://redux.js.org/recipes/reducing-boilerplate

redux-act 則是另外一套函式庫,可以協助我們更快的產生 action 跟 reducer,官方的範例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Import functions
import { createStore } from "redux";
import { createAction, createReducer } from "redux-act";

// Create an action creator (description is optional)
const increment = createAction("increment the state");
const decrement = createAction("decrement the state");

// Create a reducer
// (ES6 syntax, see Advanced usage below for an alternative for ES5)
const counterReducer = createReducer(
{
[increment]: (state) => state + 1,
[decrement]: (state) => state - 1,
},
0 // <-- This is the default state
);

// Create the store
const counterStore = createStore(counterReducer);

// Dispatch actions
counterStore.dispatch(increment()); // counterStore.getState() === 1
counterStore.dispatch(decrement()); // counterStore.getState() === 0

使用 ReduxToolKits 後按照功能搭配分類,覺得較適合大型專案,依照功能就會分成

  • typeOne
  • typeTwo
  • typeThree

每個功能所需要的 action、components、containers、reducers 都會放在一起,所以在開發時,每個工程師都可以在獨立的資料夾中完成該次的任務。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
src
└── features
├── typeOne
│ ├── TypeOne.js
│ ├── TypeOne.styles.scss
│ └── typeOneSlice.js
├── typeTwo
│ ├── TypeTwo.js
│ ├── TypeTwo.styles.scss
│ └── typeTwoSlice.js
└── typeThree
├── TypeThree.js
├── TypeThree.styles.scss
└── typeThreeSlice.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { createSlice } from "@reduxjs/toolkit";

const initialState = { value: 0 };

const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment(state) {
state.value++;
},
decrement(state) {
state.value--;
},
incrementByAmount(state, action) {
state.value += action.payload;
},
},
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

喜歡這篇文章,請幫忙拍拍手喔 🤣


share