React 中的接口隔離原則
本文爲翻譯
本文譯者爲 360 奇舞團前端開發工程師
原文標題:Interface Segregation Principle in React
原文作者:Alex Kondovt 原文地址:https://alexkondov.com/interface-segregation-principle-in-react/
React 中的接口隔離原則
SOLID 原則是我學習的第一個軟件設計概念,時至今日,它們仍然是對我的職業生涯影響最大的知識。如果沒有它們,也許我永遠不會開始關注代碼的質量和項目的結構。
儘管它們最適合面向對象的開發,但無論我在什麼環境和模式下工作,我都會將它們牢記在心。
說有一條 SOLID 原則我能夠應用到任何地方,那就是關於接口隔離的原則。
接口隔離原則
該原則是指我們應避免創建包含許多方法或值的大型接口。相反,我們應該創建更小的接口,以滿足使用這些接口的函數或類的需要。
如果你想了解一下歷史,據我所知,該原則是施樂公司提出的,當時他們的軟件只有一個 “工作” 類,負責你可以執行的所有任務。隨着時間的推移,這個類的可維護性成了問題。
interface Job {
fax(): void;
scan(): void;
print(): void;
}
function print(job: Job) {
// We're using only a single method from the interface
// But we expect the entire interface to be implemented
job.print();
}
於是,他們將其拆分成更小的類和接口,這些類和接口只負責特定的任務,這樣代碼就變得更簡單了。
interface PrintJob {
print(): void;
}
function print(job: PrintJob) {
// We only expect the interface to implement the print method
job.print();
}
這只是一個簡單的例子,旨在說明問題:使用較小的接口不僅更易於實現和維護,而且也更易於測試。
然而,當我學習這些原則時,我發現很難將它們轉化爲前端開發。我交互的唯一接口是 prop 定義。而且它們總是特定於組件,重用大型接口並不是真正的問題。
但幾年後,我發現 “不依賴於不需要的值” 這一抽象原則實際上在 React 中也有用武之地。
依賴龐大的組件
想象一下,我們有如下組件,它希望將用戶對象作爲 prop 傳遞給它:
interface Props {
user: User;
}
function UserGreeting({ user }: Props) {
return <h1>Hey, {user.name}!</h1>;
}
不要關注引用相等或重新渲染的問題,它們不是現在的重點。
我們的組件需要一個用戶對象,這是一個必須的 prop,所以我們提供了它。但仔細觀察組件的實現後,我們發現它實際上只使用了一個值 --name。
這違反了接口隔離原則。
最後一句話聽起來很隱晦,但不用太在意。這個組件只是有點欺騙性。它向使用它的開發人員表明,它需要一個對象,儘管它只使用了對象的一小部分。但在編程中,就像在生活中一樣,誠實是有幫助的。
如果我們依賴於整個對象,可能會增加組件的使用難度。如果我們能明確自己需要的值,那就更好了。
interface Props {
name: string;
}
function UserGreeting({ name }: Props) {
return <h1>Hey, {name}!</h1>;
}
我們可以用代理指標來衡量我們是否改進了設計,那就是評估使用此 API 進行測試是否更容易。
使用以前的道具,我們在測試組件時必須模擬整個用戶對象。對用戶對象的任何改動,如添加額外的字段,都必須反映在組件的測試中,即使這些改動是不需要的。
但有了這種實現方式,我們只需更改組件所期望的原始值,而不會受到對象未來變化的影響。
這無疑更簡單。
Prop Drilling
違反這一原則的另一種常見方式是 Prop Drilling。這是所有前端框架中常見的一種反模式。當我們將一個值傳遞到多個不需要該值的組件時,就會發生這種情況。
function Dashboard({ user }) {
return (
<section>
<Header />
...
</section>
)
}
function Header({ user }) {
return (
<header>
<Navigation user={user} />
</header>
)
}
function Navigation({ user }) {
return (
<nav>
<UserGreeting name={user.name} />
...
</nav>
)
}
function UserGreeting({ name }) {
return <h1>Hey, {name}!</h1>;
}
這是一個問題,因爲我們又一次欺騙了代碼的讀者。通過觀察我們組件的道具,他們會認爲他們需要用戶對象以便從中渲染一些內容,但事實上,他們只是將用戶對象傳遞給他們的子組件。他們實際上並沒有使用它。
在這種情況下,我們需要尋找一種不同的解決方案。
在 React 中,最常見的做法是使用上下文或使用狀態管理庫,讓我們的組件直接讀取值。
function UserGreeting() {
const user = useUser();
return <h1>Hey, {user.name}!</h1>;
}
但我們經常忘記的另一個選擇是組件合成。
function Dashboard({ user }) {
return (
<section>
<Header>
<Navigation>
<UserGreeting name={user.name} />
</Navigation>
</Header>
</section>
)
}
這樣我們就不會將值傳遞給不需要它們的組件。
接口隔離化繁爲簡
這個想法的美妙之處在於它可以被描述得如此簡單 -- 代碼不應該依賴於它不使用的值和方法。如果你不需要某些東西,就不要要求它。就是這麼簡單。
上述示例中唯一的接口就是道具定義。我們沒有做任何比平時更復雜的事情。重要的是,我們遵循的是主要原則,而不是任意實現。
我們不希望我們的代碼依賴於它不需要的東西,這就是接口隔離的意義所在。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/TFWd4LcnmYOksfh1nb0Vwg