3-5 React 元件的拆分

本單元對應的專案分支為:split-components


先前,我們在整個頁面中都只使用了一個 React 元件,並把所有的 HTML 結構包在這個元件中

在這個元件中包含所有的 HTML 結構,但是當專案一大起來之後,一個頁面中若包含所有的 HTML 結構、邏輯判斷等等,將會變得難以管理,你可以想像一隻檔案打開來後有好幾萬行,要如何找到想要修改的元素呢?

因此在 React 中,元件除了能方便開發者重複使用外,還有一點是讓開發者去管理各個「功能獨立」的元素,並且把每個元件都拆分成獨立的 JS 檔案後,管理上就會方便許多了。

在這個單元中最後呈現的畫面不會有太大的變化,但元件和檔案的拆分則會有明顯的不同。

讀者們可以先留意一下,這裡我們會先把 <div className="unit-control">...</div><div className="card-footer">...</div> 的部分先進行元件的拆分;對於中間的 <div className="converter">...</div> 區塊,因為會需要更多資料傳遞的觀念,為了避免一次吸收太多的資訊而難以消化,因此會在後面的單元再來拆分這個部分。

拆出 UnitControl 元件#

React 元件的拆分非常簡單,其實你早就會了。舉例來說,現在想要把 <div className="unit-control">...</div> 的這個區塊拆成一個獨立的元件:

Imgur

只需要透過函式定義一個新的 React 元件,名稱就取為 UnitControl,要留意的是 React 元件的命名是使用大寫駝峰,因此首字需要大寫,同時因為這個元件單純只是要回傳 JSX 而沒有要做其他處理,所以可以在箭頭函式的 => 後直接回傳 JSX 即可,像這樣:

const UnitControl = () => (
<div className="unit-control">
<div className="unit">Mbps</div>
<span className="exchange-icon fa-fw fa-stack">
<i className="far fa-circle fa-stack-2x" />
<i className="fas fa-exchange-alt fa-stack-1x" />
</span>
<div className="unit">MB/s</div>
</div>
);

這樣就完成了一個名為 <UnitControl /> 的 React 元件,只需要在你想要使用它的地方帶入即可,像是下圖這樣:

Imgur

建立 CardFooter 元件#

除了 <UnitControl /> 可以拆分成獨立的元件外,我們也進一步把 <div class="card-footer">...</div> 的部分拆成一個獨立的元件,像這是這樣:

const CardFooter = () => <div className="card-footer">FAST</div>;

接著同樣在原本的位置把這個元件套用進去:

function App() {
// ...
return (
<div className="container">
<div className="card-header">Network Speed Converter</div>
<div className="card-body">
<UnitControl />
<UnitConverter />
</div>
<CardFooter />
</div>
);
}

💡 小提醒:這裡讀者可能會有些困惑, CardFooter 才一行而已為什麼要拆成一個元件?這主要是因為在後面的單元中將會對它做更多的變化,因此這裡實作時先把它進行拆分。

檔案拆分#

現在你會發現在同一支 app.js 中就包含了三個 React 元件,目前因為程式碼還不多,所以不會感到太過複雜,但未來如果內容變多或需要在元件中進行邏輯運算時,在一隻檔案中包含多個元件就不會是太好的做法。好在透過 ES Module 系統,可以把元件拆分成不同的檔案。

新增 components 資料夾#

現在我們先在 src 中新增 components 資料夾,並分別在裡面新增 UnitControl.jsCardFooter.js 這兩隻檔案,檔案目錄會長像這樣:

Imgur

建立 UnitControl 檔案#

接著在 src/components/UnitControl.js 中,先匯入 React 套件,把原本寫在 App.js 中的 UnitControl 元件剪下放進來,然後用 export default 加以匯出:

// ./src/components/UnitControl.js
import React from 'react';
const UnitControl = () => <div className="unit-control">{/* ... */}</div>;
export default UnitControl;
caution

小提醒:只要有用到 JSX 的檔案,都記得要在最上方匯入 React 套件,檔案才能被正確解析。

建立 CardFooter#

同樣的,把原本寫在 App.js 中的 CardFooter 元件剪下放進來,然後用 export default 加以匯出:

// ./src/components/CardFooter.js
import React from 'react';
const CardFooter = () => <div className="card-footer">FAST</div>;
export default CardFooter;

在 App.js 中匯入 Component#

最後,回到 App.js 中,只需要把剛剛 export 的元件匯入(import)即可直接使用,程式碼幾乎不用動:

// .src/App.js
import React, { useState } from 'react';
import UnitControl from './components/UnitControl';
import CardFooter from './components/CardFooter';
import './App.css';
function App() {
// ...
}
export default App;

換你了:試著拆分 React 元件吧#

現在要請你試著把 UnitControlCardFooter 這兩個元件拆分出來,分別放在 components 資料夾中的 UnitControl.jsCardFooter.js。拆分完之後,一樣記得透過 npm start 來把專案啟動起來,這時候的畫面應該一樣可以正常顯示,並且沒有任何不同:

  • ./src/components/UnitControl.js 中建立 UnitControl 元件
  • ./src/components/CardFooter.js 中建立 CardFooter 元件
  • ./App.js 中匯入 UnitControl 和 CardFooter
  • npm start 確認專案能正確啟動

在這個單元中,對於中間的 <div className="converter">...</div> 區塊還沒有進行元件的拆份,這是因為這個區塊有綁定事件在內,會需要更多資料傳遞的觀念,為了避免一次吸收太多的資訊而難以消化,因此會在後面的單元再來拆分這個部分。

在下一個單元中我們要先讓 CardFooter 的文字和顏色能根據網速而有不同的呈現方式。本單元相關之網頁連結、完整程式碼與程式碼變更部分可於 split-components 分支檢視:https://github.com/pjchender/learn-react-from-hooks-internet-speed-converter/tree/split-components

Imgur