重構元件撰寫方式的三個目標
這篇文章會分享三個加速 React.js 開發的目標,主要內容皆從底下這篇文章翻譯而來
- 更少的打字時間
- 更少的除錯與測試時間
- 更少的閱讀時間
Double your react coding speed with this simple trick
更少的打字時間
要讓打字時間縮短,大致上分為兩個方向:
DRY 增加共用的程式碼: 大元件拆分成小且可重用的 hooks 或元件
- 資料處理邏輯獨立
- 元件只處理顯示
- 簡化輸入參數 (props)
善用工具
- Snippets: 編輯器外掛協助自動完成
- Auto Import: 編輯器外掛協助路徑輸入
- prettier: 編輯器自動格式化
更少的除錯與測試時間
通常找到錯誤的流程要先把 App 用開發模式跑起來後,操作元件重現錯誤並查看相關錯誤訊息,修復後重試。在開發階段如果要避免低階錯誤,可分成兩個方向
- 撰寫測試並導入 CI/CD,在流程中就靠機器提早幫我們發現問題
- 善用工具,在開發階段透過 eslint、props type check、TypeScript 來避免低階問題
更少的閱讀時間
開發者最常花時間在看懂程式碼而非撰寫,通常好的程式都有容易被修改、容易發現問題的優點,那在撰寫上列出幾個我覺得需要改掉的壞習慣:
- 程式碼太多暗號
- 類似功能卻散在各處
- 太長的函式,需要花時間才能了解
- 使用上需要滿滿的參數卻又未分類
- 太多布林值狀態
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| value[0][index] = sum;
<Grid data={gridData} pagination={false} autoSize={true} enableSort={true} sortOrder="desc" disableSelection={true} infiniteScroll={true} ... />
const options = { pagination: false, autoSize: true, enableSort: true, sortOrder: 'desc', disableSelection: true, infiniteScroll: true, ... }
<Grid data={gridData} options={options} />
const [isLoading, setIsLoading] = useState(false) const [isFinished, setIsFinished] = useState(false) const [hasError, setHasError] = useState(false)
const fetchSomething = () => { setIsLoading(true)
fetch(url) .then(() => { setIsLoading(false) setIsFinished(true) }) .catch(() => { setHasError(true) }) }
const [state, setState] = useState('idle')
const fetchSomething = () => { setState('loading')
fetch(url) .then(() => { setState('finished') }) .catch(() => { setState('error') }) }
|
重構元件範例
元件優化的方向與過程,最主要還是拆解成可重複使用的小單位,像是運用 render props 或是 HOC 甚至是寫成 Custom hook,對岸維護的 ahook 功能就非常多樣。常見可以優化的部分我覺得分以下三個部分:
- 打 API 的寫法
- 初階: 在元件裡面抓資料
- 進階: 透過 redux 這類工具統一資料處理邏輯
- 進階: 寫一個 hook 或是使用 react-query
- 錯誤處理
- 初階: 直接在打 API 的 function 中寫判斷
- 進階: ErrorBoundary 處理或是 axios interceptor
- 樣式檔的優化
- 初階: CSS
- 進階: Sass or SCSS
- 進階: CSS-in-JS
- 進階: Atomic-css
最開始的元件撰寫通常會長成下面這樣,在同樣一個元件裡面放了狀態,打 API 抓資料的相關程式,還有顯示的部分,好處當然就是直觀也不會說不好維護,但當這樣的元件有 50 個的時候,打 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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| import React, { useEffect, useState } from "react";
import AddModal from "../components/AddModal"; import LoadingIndicator from "../components/LoadingIndicator"; import BrowserItem from "../components/BrowserItem";
import colors from "../config/colors";
function Browsers() { const URL = "https://google.com/myData.json";
const [loading, setLoading] = useState(true); const [browsers, setBrowsers] = useState([]);
const [modalVisible, setModalVisible] = useState(false); const [description, setDescription] = useState("");
const changeDescription = (description) => { setDescription(description); setModalVisible(!modalVisible); };
const changeOpacity = () => { setModalVisible(!modalVisible); };
useEffect(() => { fetch(URL) .then((response) => response.json()) .then((responseJson) => { return responseJson.Browsers; }) .then((browsers) => { setBrowsers(browsers); setLoading(false); }) .catch((error) => { console.log(error); }) .finally(() => setLoading(false)); }, []);
return ( <> {loading ? ( <LoadingIndicator /> ) : ( <> <AddModal modalVisible={modalVisible} changeOpacity={() => changeOpacity()} description={description} /> <List data={browsers} keyExtractor={(browser) => browser.fullname} renderItem={({ item }) => ( <BrowserItem {...item} changeDescription={() => changeDescription(item.description)} /> )} /> </> )} </> ); }
export default Browsers;
|
將資料邏輯切割成更小可以重複使用的 hooks,當這些資料存取寫法被共用時就達到
- 加速開發: 因為重複使用
- 更快速找到問題: 遇到問題可以減少閱讀和測試這些部分
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 28 29 30 31 32
| import { useEffect, useState } from "react";
function useFetch(url) { const [loading, setLoading] = useState(false); const [data, setData] = useState(undefined);
useEffect(() => { setLoading(true); fetch(url) .then((response) => response.json()) .then(setData) .finally(() => setLoading(false)) .catch((error) => Alert.alert("Fetch error", error)); }, [url]);
return { loading, data, }; }
function useBrowsers(url) { const { loading, data } = useFetch(url); const [selectedBrowser, setSelectedBrowser] = useState(undefined);
return { loading, browsers: data?.Browsers, selectedBrowser, setSelectedBrowser, }; }
|
切割成更小可重用的元件且拿掉大部分的資料邏輯,僅留下 conditional render 的相關實作,元件行數下降找問題的速度肯定又更上層樓。
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 28 29 30 31 32 33 34 35 36 37 38 39 40
| function UIFriendlyList(props) { if (props.loading) { return <LoadingIndicator />; }
if (props?.data && props.data.length === 0) { return <Text>This list is empty (</Text>; }
return <List {...props} />; }
function BrowsersList(props) { const { loading, selectedBrowser, setSelectedBrowser, browsers } = props; return ( <View style={styles.container}> <AddModal modalVisible={Boolean(selectedBrowser)} onClose={() => setSelectedBrowser(undefined)} description={selectedBrowser?.description} /> <UIFriendlyList loading={loading} data={browsers} renderItem={({ item }) => ( <BrowserItem key={item.fullname} browser={item} onPress={() => setSelectedBrowser(item)} /> )} /> </View> ); }
function Browsers() { return <BrowsersList {...useBrowsers("https://google.com/myData.json")} />; }
|
喜歡這篇文章,請幫忙拍拍手喔 🤣