前端組件設(shè)計原則
譯者:@沒有好名字了
譯文:https://github.com/lightningminers/article/issues/36,https://juejin.im/post/5c49cff56fb9a049bd42a90f
作者:@Andrew Dinihan
原文:https://engineering.carsguide.com.au/front-end-component-design-principles-55c5963998c9
前言
我在最近的工作中開始使用 Vue 進行開發(fā),但是我在上一家公司積累了三年以上 React 開發(fā)經(jīng)驗。雖然在兩種不同的前端框架之間進行切換確實需要學習很多,但是二者之間在很多基礎(chǔ)概念、設(shè)計思路上是相通的。其中之一就是組件設(shè)計,包括組件層次結(jié)構(gòu)設(shè)計以及組件各自的職責劃分。
組件是大多數(shù)現(xiàn)代前端框架的基本概念之一,在 React 和 Vue 以及 Ember 和 Mithril 等框架中均有所體現(xiàn)。組件通常是由標記語言、邏輯和樣式組成的集合。它們被創(chuàng)建的目的就是作為可復用的模塊去構(gòu)建我們的應用程序。
類似于傳統(tǒng) OOP 語言中 class 的設(shè)計,在設(shè)計組件的時候需要考慮到很多方面,以便它們可以很好的復用,組合,分離和低耦合,但是功能可以比較穩(wěn)定的實現(xiàn),即使是在超出實際測試用例范圍的情況下。這樣的設(shè)計說起來容易做起來卻很難,因為現(xiàn)實中我們往往沒有足夠的時間按照最優(yōu)的方式去做。
方法
在本文中,我想介紹一些組件相關(guān)的設(shè)計概念,在進行前端開發(fā)時應該考慮這些概念。我認為最好的方法是給每個概念一個簡潔精煉的名字,然后逐一解釋每個概念是什么以及為什么重要,對于比較抽象概念的會舉一些例子來幫助理解。
以下這個列表并不是不全面也不完整,但我注意到的只有 8 件事情值得一提,對于那些已經(jīng)可以編寫基本組件但想要提高他們的技術(shù)設(shè)計技能的人來說。所以這是列表:
以下列舉的這個列表僅僅是是我注意到的 8 個方面,當然組件設(shè)計還有其他一些方面。在此我只是列舉出來我認為值得一提的。
對于已經(jīng)掌握基本的組件設(shè)計并且想要提高自身的組件設(shè)計能力的開發(fā)者,我認為以下 8 項是我認為值得去注意的,當然這并不是組件設(shè)計的全部。
層次結(jié)構(gòu)和 UML 類圖
扁平化、面向數(shù)據(jù)的 state/props
更加純粹的 State 變化
低耦合
輔助代碼分離
提煉精華
及時模塊化
集中/統(tǒng)一的狀態(tài)管理
請注意,代碼示例可能有一些小問題或有點人為設(shè)計。但是它們并不復雜,只是想通過這些例子來幫助更好的理解概念。
層次結(jié)構(gòu)和類圖
應用內(nèi)的組件共同形成組件樹, 而在設(shè)計過程中將組件樹可視化展示可以幫助你全面了解應用程序的布局。一個比較好的展示這些的辦法就是組件圖。
UML 中有一個在 OOP 類設(shè)計中經(jīng)常使用的類型,稱為 UML 類圖。類圖中顯示了類屬性、方法、訪問修飾符、類與其他類的關(guān)系等。雖然 OOP 類設(shè)計和前端組件設(shè)計差異很大,但是通過圖解輔助設(shè)計的方法值得參考。對于前端組件,該圖表可以顯示:
State
Props
Methods
與其他組件的關(guān)系( Relationship to other components )
因此,讓我們看一下下面這個基礎(chǔ)表組件的組件層次圖,該組件的渲染對象是一個數(shù)組。該組件的功能包括顯示總行數(shù)、標題行和一些數(shù)據(jù)行,以及在單擊其單元格標題格時對該列進行排序。在它的 props 中,它將傳遞列列表(具有屬性名稱和該屬性的人類可讀版本),然后傳遞數(shù)據(jù)數(shù)組。我們可以添加一個可選的’on row click’功能來進行測試。

雖然這樣的事情可能看起來有點多,但是它具有許多優(yōu)點,并且在大型應用程序開發(fā)設(shè)計中所需要的。這樣會帶來的一個比較重要的問題是它會需要你在開始 codeing 之前就需要考慮到具體細節(jié)的實現(xiàn),例如每個組件需要什么類型的數(shù)據(jù),需要實現(xiàn)哪些方法,所需的狀態(tài)屬性等等。
一旦你對如何構(gòu)建一個組件(或一組組件)的整體有大概的思路,就會很容易認為當自己真正開始編碼實現(xiàn)時,它會如自己所期望的按部就班的完成,但事實上往往會出現(xiàn)一些預料之外的事情, 當然你肯定不希望因此去重構(gòu)之前的某些部分,或者忍受初始設(shè)想中的缺點并因此擾亂你的代碼思路。而這些類圖的以下優(yōu)點可以幫助你有效的規(guī)避以上問題,優(yōu)點如下:
一個易于理解的組件組成和關(guān)聯(lián)視圖
一個易于理解的應用程序 UI 層次結(jié)構(gòu)的概述
一個結(jié)構(gòu)數(shù)據(jù)層次及其流動方式的視圖
一個組件功能職責的快照
便于使用圖表軟件創(chuàng)建
順帶一提,上圖并不是基于某些官方標準,比如 UML 類圖,它是我基本上創(chuàng)建的一套表達規(guī)則。例如,在 props 、方法的參數(shù)和返回值的數(shù)據(jù)類型定義聲明都是基于 Typescript 語法。我還沒有找到書寫前端組件類圖的官方標準,可能是由于前端 Javascript 開發(fā)的相對較新且生態(tài)系統(tǒng)不夠完善所致,但如果有人知道主流標準,請在回復中告訴我!
扁平的,面向數(shù)據(jù)的 state/props
在 state 和 props 頻繁被 watch 和 update 的情況下,如果你有使用嵌套數(shù)據(jù),那么你的性能可能會受到影響,尤其是在以下場景中,例如一些因為淺對于而觸發(fā)的重新渲染;在涉及 immutability 的庫中,比如 React,你必須創(chuàng)建狀態(tài)的副本而不是像在 Vue 中那樣直接更改它們,并且使用嵌套數(shù)據(jù)這樣做可能會創(chuàng)建笨拙,丑陋的代碼。
//Flat,?data-oriented?state/props
const?state?=?{
??clients:?{
????allClients,
????firstClient,
????lastClient:?{
??????name:?'John',
??????phone:?'Doe',
??????address:?{
????????number:?5,
????????street:?'Estin',
????????suburb:?'Parrama',
????????city:?'Sydney'
??????}
????}
??}
}
//?倘若我們需要去修改 address number時需要怎么辦?
const?test?=?{
??clients:?{
????...state.clients,
????lastClient:?{
??????...state.clients.lastClient,
??????address:?{
????????...state.clients.lastClient.address,
????????number:?10
??????}
????}
??}
}即使使用展開運算符,這種寫法也并不夠優(yōu)雅。扁平 props 也可以很好地清除組件正在使用的數(shù)據(jù)值。如果你傳給組件一個對象但是你并不能清楚的知道對象內(nèi)部的屬性值,所以找出實際需要的數(shù)據(jù)值是來自組件具體的屬性值則是額外的工作。但如果 props 足夠扁平化,那么起碼會方便使用和維護。
//?我們無法得知?customer?這個對象里面擁有什么屬性
//?這個組件需要使用這個對象所有的屬性值或者只是需要其中的一部分?
//?如果我想要將這個組件在別處使用,我應該傳入什么樣的對象
<listItem?customer={customer}/>
//?下面的這個組件接收的屬性就一目了然
<listItem?phone={customer.phone}?name={customer.name}?iNumber={customer.iNumber}??/>state / props 還應該只包含組件渲染所需的數(shù)據(jù)。You shouldn’t store entire components in the state/props and render straight from there.
(此外,對于數(shù)據(jù)繁重的應用程序,數(shù)據(jù)規(guī)范化可以帶來巨大的好處,除了扁平化之外,你可能還需要考慮一些別的優(yōu)化方法)。
更加純粹的 State 變化
對 state 的更改通常應該響應某種事件,例如用戶單擊按鈕或 API 的響應。此外它們不應該因為別的 state 的變化而做出響應,因為 state 之間這種關(guān)聯(lián)可能會導致難以理解和維護的組件行為。state 變化應該沒有副作用。
如果你濫用watch而不是有限考慮以上原則,那么在 Vue 的使用中就可能由此引發(fā)的問題。我們來看一個基本的 Vue 示例。我正在研究一個從 API 獲取一些數(shù)據(jù)并將其呈現(xiàn)給表的組件,其中排序,過濾等功能都是后端完成的,因此前端需要做的就是 watch 所有搜索參數(shù),并在其變化時觸發(fā) API 調(diào)用。其中一個需要 watch 的值是“zone”,這是一個過濾器。當更改時,我們想要使用過濾后的值重新獲取服務(wù)端數(shù)據(jù)。watcher 如下:
//State?change?purity
zone:{
??handler()?{
????//?重置頁碼
????if(this.pagination.page?>?1){
????????this.pagination.page?=?1
????????return;
????}
????this.getDataFromApi()
??}
}你會發(fā)現(xiàn)一些奇怪的東西。如果他們超出了結(jié)果的第一頁,我們重置頁碼然后結(jié)束?這似乎不對,如果它們不在第一頁上,我們應該重置分頁并觸發(fā) API 調(diào)用,對吧?為什么我們只在第 1 頁上重新獲取數(shù)據(jù)?實際上原因是這樣,讓我們來看下完整的 watch:
watch:?{
??pagination()?{
????this.getDataFromApi()
??}
},
zone:?{
??handler()?{
????//?重置頁碼
????if(this.pagination.page?>?1)?{
????????this.pagination.page?=?1
????????return;
????}
????this.getDataFromApi()
??}
}當分頁改變時,應用首先會通過 pagination 的處理函數(shù)重新獲取數(shù)據(jù)。因此,如果我們改變了分頁,我們并不需要去關(guān)注數(shù)據(jù)更新這段邏輯。
讓我們一下來考慮以下流程:如果當前頁面超出了第 1 頁并且更改了 zone,而這個變化會觸發(fā)另一個狀態(tài)(pagination)發(fā)生變化,進而觸發(fā) pagination 的觀察者重新請求數(shù)據(jù)。這樣并不是預料之中的行為,而且產(chǎn)生的代碼也不夠直觀。
解決方案是改變頁碼這個行為的事件處理函數(shù)(不是觀察者,用戶更改頁面的實際處理函數(shù))應該更改頁面值并觸發(fā) API 調(diào)用請求數(shù)據(jù)。這也將消除對觀察者的需求。通過這樣的設(shè)置,直接從其他地方改變分頁狀態(tài)也不會導致重新獲取數(shù)據(jù)的副作用。
雖然這個例子非常簡單,但不難看出將更復雜的狀態(tài)更改關(guān)聯(lián)在一起會產(chǎn)生令人難以理解的代碼,這些代碼不僅不可擴展并且是調(diào)試的噩夢。
松耦合
組件的核心思想是它們是可復用的,為此要求它們必須具有功能性和完整性。“耦合”是指實體彼此依賴的術(shù)語。松散耦合的實體應該能夠獨立運行,而不依賴于其他模塊。就前端組件而言,耦合的主要部分是組件的功能依賴于其父級及其傳遞的 props 的多少,以及內(nèi)部使用的子組件(當然還有引用的部分,如第三方模塊或用戶腳本)。
緊密耦合的組件往往更不容易被復用,當它們作為特定父組件的子項時,就很難正常工作,當父組件的一個子組件或一系列子組件只能在該父組件才能夠正常發(fā)揮作用時,就會使得代碼寫的很冗余。因為父子組件別過度的關(guān)聯(lián)在一起了。
在設(shè)計組件時,你應該考慮到更加通用的使用場景,而不僅僅只是為了滿足最開始某個特定場景的需求。雖然一般來說組件最初都是出于特定目的進行設(shè)計,但沒關(guān)系,如果在設(shè)計它們站在更高的角度去看待,那么很多組件將具有更好的適用性。
讓我們看一個簡單的 React 示例,你想在寫出一個帶有一個 logo 的鏈接列表,通過連接可以訪問特定的網(wǎng)站。最開始的設(shè)計可能是并沒有跟內(nèi)容合理的進行解耦。下面是最初的版本:
const?Links?=?()=>(
??<div?className="links-container">
????<div?class="links-list">
??????<a?href="/">
????????Home
??????a>
??????<a?href="/shop">
????????Products
??????a>
??????<a?href="/help">
????????Help
??????a>
????div>
????<div?className="links-logo">
??????<img?src="/default/logo.png"/>
????div>
??div>
)雖然這這樣會滿足預期的使用場景,但卻很難被復用。如果你想要更改鏈接地址該怎么辦?你必須重新復制一份相同代碼,并且手動去替換鏈接地址。而且, 如果你要去實現(xiàn)一個用戶可以更改連接的功能,那么意味著不可能將代碼寫“死”,也不能期望用戶去手動修改代碼,那么讓我們來看一下復用性更高的組件應該如何設(shè)計:
const?DEFAULT_LINKS?=?[
??{route:?"/",?text:?"Home"},
??{route:?"/shop",?text:?"Products"},
??{route:?"/help",?text:?"Help"}
]
const?DEFAULT_LOGO?=?"/default/logo.png"
const?Links?=?({links?=?DEFAULT_LINKS,logoPath?=?DEFAULT_LOGO?})?=>?(
??<div?className="links-container">
????<div?class="links-list">
???????//?將數(shù)組依次渲染為超鏈接
???????links.map((link)?=>?<a?href={link.route}>?{link.text}a>)
????div>
????<div?className="links-logo">
??????<img?src={logoPath}/>
????div>
??div>
)在這里我們可以看到,雖然它的原始鏈接和 logo 具有默認值,但我們可以通過 props 傳入的值去覆蓋掉默認值。讓我們來看一下它在實際中的使用:
const?adminLinks?=?{
??links:?[
????{route:?"/",?text:?"Home"},
????{route:?"/metrics",?text:?"Site?metrics"},
????{route:?"/admin",?text:?"Admin?panel"}
??],
??logoPath:?"/admin/logo.png"
}
并不需要重新編寫新的組件!如果我們解決上文中用戶可以自定義鏈接的使用場景,可以考慮動態(tài)構(gòu)建鏈接數(shù)組。此外,雖然在這個具體的例子中沒有解決,但我們?nèi)匀豢梢宰⒁獾竭@個組件沒有與任何特定的父/子組件建立密切關(guān)聯(lián)。它可以在任何需要的地方呈現(xiàn)。改進后的組件明顯比最初版本具有更好的復用性。
如果不是要設(shè)計需要服務(wù)于特定的一次性場景的組件,那么設(shè)計組件的最終目標是讓它與父組件松散耦合,呈現(xiàn)更好的復用性,而不是受限于特定的上下文環(huán)境。
輔助代碼分離
這個可能不那么的偏理論,但我仍然認為這很重要。與你的代碼庫打交道是軟件工程的一部分,有時一些基本的組織原則可以使事情變得更加順暢。在長時間與代碼相處的過程中,即使改變一個很小的習慣也可以產(chǎn)生很大的不同。其中一個有效的原則就是將輔助代碼分離出來放在特定的地方,這樣你在處理組件時就不必考慮這些。以下列舉一些方面:
配置代碼
假數(shù)據(jù)
大量非技術(shù)說明文檔
因為在嘗試處理組件的核心代碼時,你不希望看到與技術(shù)無關(guān)的一些說明(因為會多滾動幾下鼠標滾輪甚至打斷思路)。在處理組件時,你希望它們盡可能通用且可重用。查看與組件當前上下文相關(guān)的特定信息可能會使得設(shè)計出來的組件不易與具體業(yè)務(wù)解耦。
提煉精華
雖然這樣做起來可能具有挑戰(zhàn)性,但開發(fā)組件的一個好方法是使它們包含渲染它們所需的最小 Javascript。一些無關(guān)緊要的東西,比如數(shù)據(jù)獲取,數(shù)據(jù)整理或事件處理邏輯,理想情況下應該將通用的部分移入外部 js 或或者放在共同的祖先中。
單獨從組件分的“視圖”部分來看,即你看到的內(nèi)容(html 和 樣式)。其中的 Javascript 僅用于幫助渲染視圖,可能還有一些針對特定組件的邏輯(例如在其他地方使用時)。除此之外的任何事情,例如 API 調(diào)用,數(shù)值的格式化(例如貨幣或時間)或跨組件復用的數(shù)據(jù),都可以移動外部的 js 文件中。讓我們看一下 Vue 中的一個簡單示例,使用嵌套列表組件。我們可以先看下下面這個有問題的版本。
這是第一個層級:
//?組件父級
????<div>
????????<ul?v-for="(topLevelItem,i)?in?fomattedItems"?:key="i">
????????????<img?:src="topLevelItem.imagePath">
????????ul>
????????<nested-list?:items="topLevelItem.nestedItems"?/>
????div>
template>
<script>
import?{?mapState?}?from?'vuex'
import?NestedList?from?'~/components/NestedList?'
import?data?from?'~/data/items.json'
import?removeFalsyItems?from?'~/scripts/removeFalsyItems'
export?default?{
????data()?{
????????return?{}
????},
????components:?{
????????NestedList
????},
????computed:?{
????????fomattedItems()?{
????????????return?removeFalsyItems(data)
????????}
????}
}
script>這是嵌套列表組件:
//?nestedList?組件
<template>
??<div>
????<ul>
??????<li?v-for="(secondLevelItem,?i)?in?items"?:key="i"?@click="updateText(secondLevelItem.text)">{{secondLevelItem.text}}li>
????ul>
??div>
template>
<script?>
import?axios?from?'axios'
export?default?{
??props:?{
????items:?{
??????type:?Array,
??????defafult:?()?=>?[]
????}
??},
??methods:?{
????updataText(text)?{
??????this.$store.commit('updateText',?text)
??????axios.post('/endpoint',?{?text?}).then(res?=>?{
????????console.log(res)
??????})
????}
??}
}
script>在這里我們可以看到此列表的兩個層級都具有外部依賴關(guān)系,最上層導引入外部 js 文件中的函數(shù)和 JSON 文件的數(shù)據(jù),嵌套組件連接到 Vuex 存儲并使用 axios 發(fā)送請求。它們還具有僅適用于當前場景的嵌入功能(最上層中源數(shù)據(jù)處理和嵌套列表的中度 click 時間的特定響應功能)。
雖然這里采用了一些很好的通用設(shè)計技術(shù),例如將通用的 數(shù)據(jù)處理方法移動到外部腳本而不是直接將函數(shù)寫死,但這樣仍然不具備很高的復用性。如果我們是從 API 的響應中獲取數(shù)據(jù),但是這個數(shù)據(jù)跟我們期望的數(shù)據(jù)結(jié)構(gòu)或者類型不同的時候要怎么辦?或者我們期望單擊嵌套項時有不同的行為?在遇到這些需求的場景下,這個組件無法被別的組件直接引用并根據(jù)實際需求改變自身的特性。
讓我們看看我們是否可以通過提升數(shù)據(jù)并將事件處理作為 props 傳遞來解決這個問題,這樣組件就可以簡單地呈現(xiàn)數(shù)據(jù)而不會封裝任何其他邏輯。
這是改進后的第一級別:
//?組件父級
??<div>
????<ul?v-for="(topLevelItem,i)?in?fomattedItems"?:key="i">
??????<img?:src="topLevelItem.imagePath">
????ul>
????<nested-list?:items="topLevelItem.nestedItems"?v-bind="{onNestedItemClick}"?/>
??div>
template>
<script>
import?NestedList?from?'~/components/NestedList?'
export?default?{
??components:?{
????NestedList
??},
??props:?{
??????items:{
??????????type:?Array,
??????????default:?null
??????},
??????onNestedItemClick:{
??????????type:?Function,
??????????default:?null
??????}
??}
}
script>而新的第二級:
//?nestedList?組件
<template>
??<div>
????<ul>
??????<li?v-for="(secondLevelItem,?i)?in?items"?:key="i"?@click="onNestedItemClick(secondLevelItem.text)">{{secondLevelItem.text}}li>
????ul>
??div>
template>
<script?>
import?axios?from?'axios'
export?default?{
??props:?{
????items:?{
??????type:?Array,
??????defafult:?null
????},
????onNestedItemClick:?{
??????type:?Function,
??????defafult:?null
????}
??}
}
script>使用這個新列表,我們可以獲得想要的數(shù)據(jù),并定義了嵌套列表的 onClick 處理函數(shù),以便在父級中傳入任何我們想要的操作,然后將它們作為 props 傳遞給頂級組件。這樣,我們可以將導入和邏輯留給單個根組件,所以不需要為了能夠在新的場景下使用去重新再實現(xiàn)一個類似組件。
有關(guān)此主題的簡短文章可以在這里找到。它由 Redux 的作者 Dan Abramov 編寫,雖然是用 React 舉例說明。但是組件設(shè)計的思想是通用的。
及時模塊化
我們在實際進行組件抽離工作的時候,需要考慮到不要過度的組件化,誠然將大塊代碼變成松散耦合且可用的部分是很好的實踐,但是并不是所有的頁面結(jié)構(gòu)(HTML 部分)都需要被抽離成組件,也不是所有的邏輯部分都需要被抽出到組件外部。
在決定是否將代碼分開時,無論是 Javascript 邏輯還是抽離為新的組件,都需要考慮以下幾點。同樣,這個列表并不完整,只是為了讓你了解需要考慮的各種事項。(記住,僅僅因為它不滿足一個條件并不意味著它不會滿足其他條件,所以在做出決定之前要考慮所有條件):
是否有足夠的頁面結(jié)構(gòu)/邏輯來保證它?
如果它只是幾行代碼,那么最終可能會創(chuàng)建更多的代碼來分隔它,而不僅僅是將代碼放入其中。
代碼重復(或可能重復)?
如果某些東西只使用一次,并且服務(wù)于一個不太可能在其他地方使用的特定用例,那么將它嵌入其中可能會更好。如果需要,你可以隨時將其分開(但不要在需要做這些工作的時候?qū)⒋俗鳛橥祽械慕杩冢?/span>
它會減少需要書寫的模板嗎?
例如,假設(shè)你想要一個帶有特定樣式的 div 屬性結(jié)構(gòu)和一些靜態(tài)內(nèi)容/功能的組件,其中一些可變內(nèi)容嵌套在內(nèi)部。通過創(chuàng)建可重用的包裝器(與 React 的 HOC 或 Vue 的 slot 一樣),你可以在創(chuàng)建這些組件的多個實例時減少模板代碼,因為你不需要重新再寫外部的包裝代碼。
性能會收到影響嗎?
更改 state/props 會導致重新渲染,當發(fā)生這種情況時,你需要的是 只是重新去渲染經(jīng)過 diff 之后得到的相關(guān)元素節(jié)點。在較大的、關(guān)聯(lián)很緊密的組件中,你可能會發(fā)現(xiàn)狀態(tài)更改會導致在不需要它的許多地方重新呈現(xiàn),這時應用的性能就可能會開始受到影響。
你是否會在測試代碼的所有部分時遇到問題?
我們總是希望能夠進行充分的測試,比如對于一個組件,我們會期望它的正常工作不依賴特定的用例(上下文),并且所有 Javascript 邏輯都按預期工作。當元素具有某個特定假設(shè)的上下文或者分別將一大堆邏輯嵌入到單個函數(shù)中時,這樣將會很難滿足我們的期望。如果測試的組件是具有比較大模板和樣式的單個巨型組件,那么組件的渲染測試也會很難進行。
你是否有一個明確的理由?
在分割代碼時,你應該考慮它究竟實現(xiàn)了什么。這是否允許更松散的耦合?我是否打破了一個邏輯上有意義的獨立實體?這個代碼是否真的可能在其他地方被重復使用?如果你不能清楚地回答這個問題,那最好先不要進行組件抽離。因為這樣可能導致一些問題(比如拆解掉原本某些潛在的耦合關(guān)系)。
這些好處是否超過了成本?
分離代碼不可避免地需要時間和精力,其數(shù)量根據(jù)具體情況而變化,并且在最終做出此決定時會有許多因素(例如此列表中列舉出來的一些)。一般來說,進行一些對抽象的成本和收益研究可以幫助更快更準確去做出是否需要組件化的決策。最后,我提到了這一點,因為如果我們過分關(guān)注優(yōu)勢,就很容易忘記達成目標所需要做的努力,所以在做出決定以前需要權(quán)衡這兩個方面。
集中/統(tǒng)一的狀態(tài)管理
許多大型應用程序使用 Redux 或 Vuex 等狀態(tài)管理工具(或者具有類似 React 中的 Context API 狀態(tài)共享設(shè)置)。這意味著他們從 store 獲得 props 而不是通過父級傳遞。在考慮組件的可重用性時,你不僅要考慮直接的父級中傳遞而來的 props,還要考慮 從 store 中獲取到的 props。如果你在另一個項目中使用該組件,則需要在 store 中使用這些值。或許其他項目根本不使用集中存儲工具,你必須將其轉(zhuǎn)換為從父級中進行 props 傳遞 的形式。
由于將組件掛接到 store(或上下文)很容易并且無論組件的層次結(jié)構(gòu)位置如何都可以完成,因此很容易在 store 和 web 應用的組件之間快速創(chuàng)建大量緊密耦合(不關(guān)心組件所處的層級)。通常將組件與 store 進行關(guān)聯(lián)只需簡單幾行代碼。但是請注意一點,雖然這種連接(耦合)更方便,但它的含義并沒有什么不同,你也需要考慮盡量符合如同在使用父級傳遞方式時的要點。
最后
我想提醒大家的是:應該更注重以上這些組件設(shè)計的原則和你已知的一些最佳實踐在實際中的應用。雖然你應該盡力維護良好的設(shè)計,但是不要為了包裝 JIRA ticket 或一個取消請求而有損代碼完整性,同時總是把理論置于現(xiàn)實世界結(jié)果之上的人也往往會讓他們的工作受到影響。大型軟件項目有許多活動部分,軟件工程的許多方面與編碼沒有特別的關(guān)系,但仍然是不可或缺的,例如遵守最后期限和處理非技術(shù)期望。
雖然充分的準備很重要,應該成為任何專業(yè)軟件設(shè)計的一部分,但在現(xiàn)實世界中,切實的結(jié)果才是最為重要的。當你被雇用來實際創(chuàng)造一些東西時,如果在最后期限到來之前,你有的只是一個如何構(gòu)建完美產(chǎn)品的驚人計劃,但卻沒有實際的成果,你的雇主可能不會太高興吧?此外,軟件工程中的東西很少完全按計劃進行,因此過度具體的計劃往往會在時間使用方面得到適得其反的效果。
此外,組件規(guī)劃和設(shè)計的概念也適用于組件重構(gòu)。雖然用了 50 年的時間來計劃一切令人難以忍受的細節(jié),然后從一開始就完美地編寫它就會很好,回到現(xiàn)實世界,我們往往會遇到這種情況,即為了趕進度而不能使代碼達到完美的預期。然而,一旦我們有了空閑時間,那么一個推薦的做法就是回過頭來重構(gòu)早期不夠理想的的代碼,這樣它就可以作為我們向前發(fā)展的堅實基礎(chǔ)。
在一天結(jié)束時,雖然你的直接責任可能是“編寫代碼”,但你不應忽視你的最終目標,即建立一些東西。創(chuàng)建產(chǎn)品。為了產(chǎn)生一些你可以引以為豪的東西并幫助別人,即使它在技術(shù)上并不完美,永遠記得找到一個平衡點。不幸的是,在一周內(nèi)每天 8 小時盯著眼前的代碼會使得眼界和角度變得更為“狹窄”,這個時候你需要的你是退后一步,確保你不要為了一顆樹而失去整個森林。
