減少 React 專案的 Boilerplate 從專案配置、API、Redux 來看優化的可能

me
林彥成
2019-06-04 | 5 min.
文章目錄
  1. 1. Boilerplate 簡介
  2. 2. 專案配置
  3. 3. API
  4. 4. Redux
  5. 5. redux-api 簡介
  6. 6. redux-api 優缺點比較

Boilerplate 是樣板,該如何減少重複的樣版撰寫? 像是 create-app-script 減少重複的專案配置,axios 統一了資料的進出管理,redux-api 幫我們少寫很多 action + reducer。

Boilerplate 簡介

Boilerplate 翻譯是樣板,當我們常常重複地做一件事情,作為攻城獅好像就應該把這些東西抽出來,如果不想自己抽,那當然就是找人家寫好的,也少了一道自己維護跟 Debug 的程序。但有了樣板之後,卻發現自己還是一直在複製貼上樣板?這時候突然覺得有點傻眼對吧?能再做的就是再抽一次了。以下分三個常見我認為可以有改進空間的部分:

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

專案配置

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

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

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

  • create-app-script: 相關設定都封裝在 react-scrips 中
    SCSS、網站發佈、環境變數、Bundle 分析、PWA

  • next.js: 封裝 next 裡,是一套框架
    伺服器渲染、多國語系、PWA、路由

  • react-boilerplate: 人工維護版的專案配置,複雜又被解開過的 react-scripts,適合想自力更生又想客製一點小東西的情況

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

為什麼需要 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

redux-api 簡介

在剛接觸 redux 時,會發現被規範的流程,步驟清楚也容易了解,但,卻稍嫌繁複。

  1. 狀態都抽到 Store,當需要狀態就需要跟 Store 有連結
  2. 控制 Store 只能透過 Action
  3. 相關存放邏輯放在 Reducer

為什麼說繁複,當每需要一個共用狀態時,就要寫一個 Action 和一個 Reducer,剛開始還覺得,還拆的蠻簡單清楚的,找東西也很方便,照這樣的理念開工後,只覺得一直在做機械式複製貼上改名稱,開始想想?只是處理個狀態有必要嗎? Orz

網路上後來有大大,可能?也發現了這樣的問題,於是把這樣的過程封裝起來,實作中也考慮到很多 API 在叫用後端資料時,會需要使用到簡易的 token 認證,於是,簡單的外殼?就這樣出來,現在只需要配置檔跟連接就好了,配置我們的狀態名稱搭配需要打的後端 API,流程大概會是:

  1. 叫用 API (可以設定 before 的動作像是 token 檢查等)
  2. 成功則用收到的資料呼叫一個 action
  3. 到元件中取得 Store 中剛剛 dispatch 的資料

以下是官方範例,將 action, fetch, reducer 結合在一個配置檔中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import "isomorphic-fetch";
import reduxApi, { transformers } from "redux-api";
import adapterFetch from "redux-api/lib/adapters/fetch";
export default reduxApi({
// simple endpoint description
entry: `/api/v1/entry/:id`,
// complex endpoint description
regions: {
url: `/api/v1/regions`,
// reimplement default `transformers.object`
transformer: transformers.array,
// base endpoint options `fetch(url, options)`
options: {
headers: {
Accept: "application/json",
},
},
},
}).use("fetch", adapterFetch(fetch));

redux-api 優缺點比較

可能遇到的問題:
都封裝起來了,錯誤處理怎麼辦?可以用接收 response 的 callback 處理。

  • 好處: 當 api 命名不符合習慣的 restful 命名原則,或是 .net 大小寫規則跟大家不一樣,可以映射成順眼的命名,如果是按照 restful 的方式設計,在實作上會非常快速且方便,因為這個外殼已經包含了一個 CRUD 的公版。

  • 壞處: 如果遇到更複雜的處理,像是需要套 RX 或是 Saga 時,不太確定要怎麼整合在一起?

參考連結:
https://github.com/agraboso/redux-api-middleware
https://github.com/lexich/redux-api


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

share