今天要來學習在設計元件時有哪些要點,以及如何避免寫出「不乾淨」的元件。
製作元件時的大重點:保持元件的純淨 (Keeping Components Pure)
具體來說是什麼呢?你可以想像元件就像是食譜,只要照著食譜指示的原料以及步驟,就可以製作成固定、特定口味的料理。所以我們可以理解元件的產出必須要有穩定性,而不會因為每一次的製作有出乎意料的結果。
理解了元件的特質,我們可以遵循一些原則來讓元件是個好食譜。
讓元件專注於自己該做的事
假如有一個元件,它被用來計算一個數字的平方。按照「專注於自己的事情」的原則,這個元件只應該專注於計算平方,而不應該在計算平方的過程中修改或影響其他已存在的變數或物件。
元件的渲染結果應該具備可預測性:也就是相同的input則應有相同的產出
元件應該只負責回傳 JSX 元素,不應該在渲染之前修改任何之前存在的物件或變數。元件應該只依賴於傳入的屬性(props),並將所需的資料作為 props 進行處理和渲染。
實際來看看一些React官方文件的challeges 練習來理解吧。
這是一個時鐘,它應該在特定的時段切換白天與夜晚,但時鐘無法正常顯示,為什麼?
export default function Clock({ time }) {
let hours = time.getHours();
if (hours >= 0 && hours <= 6) {
document.getElementById('time').className = 'night';
} else {
document.getElementById('time').className = 'day';
}
return (
<h1 id="time">
{time.toLocaleTimeString()}
</h1>
);
}
時鐘應該做什麼?時鐘應該只做取得時間並依據時間切換class
真正要動的東西是什麼? 時間以及<h1>
的class → 也就是在return的JSX挖二個JavaScript花括號讓我們所需要的資料能夠動態地出現。
<h1 className={className}>
{time.toLocaleTimeString()}
</h1>
邏輯寫什麼?洞挖好了再來把邏輯補上,維持原先的if statement ,也回憶起JSX的class寫法了。done。
解決方法:
export default function Clock({ time }) {
let hours = time.getHours();
let className;
if (hours >= 0 && hours <= 6) {
className = 'night';
} else {
className = 'day';
}
return (
<h1 className={className}>
{time.toLocaleTimeString()}
</h1>
);
}
這是一個Profile元件,它已經壞掉了。當我們點擊 Collapse
收合再打開,它會呈現同一人物。
上程式碼:
import Panel from './Panel.js';
import { getImageUrl } from './utils.js';
let currentPerson;
export default function Profile({ person }) {
currentPerson = person;
return (
<Panel>
<Header />
<Avatar />
</Panel>
)
}
function Header() {
return <h1>{currentPerson.name}</h1>;
}
function Avatar() {
return (
<img
className="avatar"
src={getImageUrl(currentPerson)}
alt={currentPerson.name}
width={50}
height={50}
/>
);
}
點擊前:
點擊後:
問題點:the current person在渲染的過程中改變了,因為存在著預先存在的變數(preexisting variable)也可以視為全域變數,而Profile 元件寫入了該變數。原本**currentPerson
** 被放在全域範圍中,並在 Profile
元件以及 Header
和 Avatar
元件中共用,這導致結果無法預測。
思考:
我們先看看 問題的程式碼的資料結構關係:
Profile
元件將 currentPerson
設定為 person
。Header
和 Avatar
函數直接訪問全域變數 currentPerson
。那三個元件彼此應該要是什麼關係?Profile
要包含 Header
和 Avatar
,而這在問題的程式碼中,則沒有因爲依據這樣的關係來傳遞資料。
明白了這階層關係,我們可以運用這樣的關係來設計資料的傳遞,也透過最大的元件Profile
來設定prop 並將資料傳遞給子元件。
解決方法:
import Panel from './Panel.js';
import { getImageUrl } from './utils.js';
export default function Profile({ person }) {
return (
<Panel>
<Header person={person} />
<Avatar person={person} />
</Panel>
)
}
function Header({ person }) {
return <h1>{person.name}</h1>;
}
function Avatar({ person }) {
return (
<img
className="avatar"
src={getImageUrl(person)}
alt={person.name}
width={50}
height={50}
/>
);
}
Profile
元件將 person
傳遞給 Header
和 Avatar
元件作為屬性。
從上面二個練習可以看出若元件沒有保持純粹,則有可能沒辦法正常運作,也可能產生非預期的效果。