React組件開發的十條最佳實踐,你認同幾條?


React組件開發的十條最佳實踐,你認同幾條?

React.js


Web前端開發者創建被許多人使用的React組件是困難的。尤其當組件的屬性作為公開API的一部分,你不得不仔細考慮哪些屬性應該接受。

這篇文章會快速介紹一些在API設計時的通用最佳實踐,並且給出10條明確的最佳實踐指導你創建讓同事開發者喜歡使用的組件。

React組件開發的十條最佳實踐,你認同幾條?

API是什麼?

API是什麼?

API或者應用程序接口(Application Programming Interface),主要是指兩部分代碼相遇的地方。這是你的代碼和其他世界接觸的地方。我們稱這個接觸表面為接口。這些是可交互的動作集合或者數據點。

介於前端和後端之間的接口就是一個API。你可以通過這個API訪問一系列特定的數據和功能。

介於一個類和調用代碼之間接口也是一個API。你可以在類上調用方法,來獲取數據或者觸發封裝在其中的功能。

沿著這個思路,你的組件接受的屬性也是其API。這是你的用戶和組件交互的方式,因此當你決定暴露哪些屬性時有很多類似的規則和考慮可以應用。

API設計的部分最佳實踐

那麼在設計一個API時有哪些規則和考慮可以應用?在這方面我們做了一些調查,並找到了許多極好的資源。我們挑選了兩篇文章,Josh Tauberer的“What Makes a Good API?”和Ron Kurir的同名文章,從中提取了4條最佳實踐來遵循。

穩定的版本管理

當你在創建一個API時要考慮的最重要的一點是儘可能保持穩定。這意味著破壞性變更的次數很少。如果你有破壞性的變更,確保寫一份詳細的升級指南,並且如果可能,提供一個重構件(code-mod)來給用戶自動化處理這些變更。

如果你發佈了API,確保遵守語義化版本。這讓用戶更容易決定使用什麼版本。

描述性的錯誤信息

任何調用API出錯的時候,你都應該儘可能的解釋什麼地方出錯,並告知如何修復。返回一個錯誤使用且沒有任何其他提示的響應會讓使用者感到羞愧,這不是一種好的用戶體驗。

相反,提供描述性的錯誤來幫助用戶修復他們調用API的方式。

少讓開發者感到驚訝

開發者是脆弱的人類,因此當他們使用API時不應該讓他們感到驚訝。換句話說,儘可能讓API更直觀。這可以通過遵守最佳實踐和命名規範來達到。

另外需要放在心上是保持你的代碼一致性。如果你在一處地方給布爾型屬性名稱前面添加了is或has,而在另外一處忽略了,這會讓其他人感到困惑。

最小化API接口

當我們談到最小化的時候-同樣要最小化你的API。更多的特性當然是好的,但是API接口暴露得越少,使用者學習的成本也更低。從而讓用戶認為這是一個易用的API。

有很多方式可以控制API的規模,其中一個就是從舊的中重構一個新的來。

組件開發的十條最佳實踐

React組件開發的十條最佳實踐,你認同幾條?

這4條黃金法則在REST API和Pascal語言程序中使用得很好,那麼如何將他們拿到React的現代世界中來呢?

像我們前面提到的,組件也有自己的API。我們稱做props,這是給組件傳遞數據、回調和其他功能的方式。我們如何組織props對象才能不破壞上面的規則?我們如何開發組件才能讓其他開發者在使用組件時更便捷?

我們創建了開發組件時的10條不錯的規則清單,希望對你有幫助。

1. 組件使用文檔

如果組件沒有提供如何使用的文檔,那麼顯然組件是無用的。好吧,大多時候,使用者總是可以通過查看實現來了解如何使用,但那很少是最好的用戶體驗。

有很多方式可以給組件編寫文檔,從我們的角度想推薦3個選項:

  • Storybook
  • Styleguidist
  • Docz

前兩個在你開發組件時可以提供一個playground用來試驗,第3個提供了MDX來更自由的書寫文檔。(注:最新版本三者都已支持Markdown語法)

不管選擇哪一個,確保提供API文檔,及組件如何、何時使用相關的文檔。後者在共享組件庫中更為重要,那樣人們才能在合適的位置使用正確的按鈕或者佈局。

2. 允許上下文的語義

譯者注: 標題的原文是Allow for contextual semantics,中文翻譯不好把握供參考

HTML是一門以語義化方式組織信息的語言。但是大多數的組件是由

標籤組成的。這在某種程度上是講得通的,因為通用組件不能假設是否應該是 <article>、 或者一個 <aside>,但是這並不是理想。

相反,我們建議允許組件接受一個as屬性,用以覆蓋被渲染的DOM元素。下面是一個如何實現的例子:

<code>function Grid({ as: Element, ...props }) {    
return <eelement>
}

Grid.defaultProps = { as: 'div',};/<code>

我們將as屬性重命名為一個本地的變量Element,並在JSX中使用。我們提供了一個通用的默認值,在你明確不需要傳遞更具語義化HTML標籤的情況下使用。

當我們使用 <grid>組件時,你僅需要傳遞正確的標籤:

<code>function App() {    
return (
<grid>
\t<morecontent>
/<grid>
);
}/<code>

注意這在使用React組件時同樣適用。一個很好的例子是,當你有一個 <button>組件想要渲染成React Router的 <link>組件時:

<code><button>    
Go to Profile
/<button>/<code>
3. 避免布爾型屬性

布爾型屬性聽起來是個極好的主意。你可以不需要傳值的情況下使用,這看起來很優雅:

<code><button>BUY NOW!/<button>/<code>

但是即使他們看起來很不錯,單布爾屬性只能允許兩種可能性。開和關,顯示和隱藏,1和0。

任何時候當你開始引入像尺寸、變形、顏色或其他任何像下面所列可能有兩個之外的值,你就會有麻煩了。

<code><button>    
WHAT AM I??
/<button>/<code>

換句話說,布爾屬性通常無法適應需求變更。因此,嘗試使用字符串類型的枚舉來作為屬性,這樣就有機會使用任何值而不是僅有兩個值的選擇。

<code><button>    
I am primarily a large button
/<button>/<code>

這並不代表布爾值屬性沒有一點用武之地。當然是有的。上面列的disabled屬性就應該是布爾型,因為在啟用和禁用之間沒有中間狀態。保留他們作為真正的兩個選項使用。

4. 使用props.children

React有一些與其他屬性略有不同的特殊屬性。一個是key,在列表項中被用來跟蹤順序,另一個是children。

任何放置在組件開和閉標籤間的內容都會被放在props.children屬性中。因此,你應該儘可能經常使用。

原因是這樣做比通過添加一個content屬性或者其他專門類似文本簡單值屬性的方式要更加容易使用。

<code><tablecell>
// 對比
<tablecell>Some text/<tablecell>/<code>

使用props.children有很多積極的意義。第一點,這有點類似平常HTML的工作方式。第二,你可以自由地傳遞任何你想要傳的內容。不用添加leftIcon和rightIcon屬性到你的組件中,僅僅只要轉到props.children屬性中即可。

<code><tablecell>    
<importanticon> Some text<

/TableCell>/<tablecell>/<code>

你可能會爭辯組件應該只允許接收並渲染普通文本,這在某些情況下可能是對的。至少現在,通過使用props.children,能實現一個適應未來需求變更的組件。

5. 讓父組件接入內部邏輯

有時我們會創建有很多內部邏輯和狀態的組件,例如自動補全的下拉組件或者交互式圖表。

這些類型的組件通常都會涉及比較繁瑣的接口,其中一個原因就是要支持後續的一系列覆蓋和特殊使用場景。

如何能做到僅僅提供一個簡單、標準化屬性來讓用戶控制、響應或者覆蓋默認組件行為呢?

Kent C.Dodds寫了一篇關於“state reducers”概念的好文章,關於概念本身,和另外一篇如何用React Hooks實現。

快速總結一下,這個模式通過傳遞一個“state reducer”函數給組件,從而讓父組件可以訪問任何在你的組件中派發的action。你可以改變狀態,或者觸發邊界效應等。這是一個極好的實現高級定製的方式,而不用藉助其他屬性。

代碼如下:

<code>function MyCustomDropdown(props) {    
const stateReducer = (state, action) => {
if (action.type === Dropdown.actions.CLOSE) {
buttonRef.current.focus();
}
};
return (
<>
\t<dropdown>
\t\t\t<button>Open/<button>
\t\t>
\t)
}/<code>

順便說一下,你當然也可以創建更簡單的方式來響應事件。上面的例子中提供一個onClose屬性可能會更好的用戶體驗。保留好state reducer模式以備不時之需。

6. 展開剩餘屬性

任何時候當你創建一個新的組件時,確保展開剩餘的屬性到有意義的元素上。

你不需要持續添加那些原本在父組件或父元素上並會傳遞到組件上的屬性到你的組件中。這會讓你的API更穩定,並且不會因其他開發者需要一個新的事件監聽或者arial標籤而發佈許多小的版本。

可以像下面這樣做:

<code>function ToolTip({ isVisivle, ...rest }) {    
return isVisible ? : null;
}/<code>

任何時候你的組件傳遞一個屬性到你的實現中,像一個類名或者一個onClick處理函數,確保外部的使用者同樣可以做同樣的事情。在類的情況,使用好用的classnames軟件包來追加類名(或者使用字符串拼接)。

<code>import classNames from 'classnames';

function ToolTip(props) {
return (
\t{...prpos}
\t\t\tclassName={classNames('tooltip', props.className)}
/>
)
}/<code>

對於點擊事件處理函數或者其他回調,通過一個輔助函數來組合一個單獨的函數,這裡有一種實現方式:

<code>function combine(...functions) {    
return (...args) =>
\tfunctions
\t.filter(func => typeof func === 'function')
\t.forEach(func => func(...args));
}/<code>

這裡我們創建一個函數來接受一系列函數並組合,返回一個新的依次使用對應參數調用他們的回調函數。

你可以像這樣使用:

<code>function ToolTip(props) {    
const [isVisible, setVisible] = React.useState(false);

return (
\t{...props}
\t\t\tclassName={classNames('tooltip', props.className)}
onMouseIn={combile(() => setVisible(true), props.onMouseIn)}
onMouseOut={combile(() => setVisible(false), props.onMouseOut)}
/>
\t);
}/<code>
7. 設置足夠的默認值

無論何時如果可以的話,確保給你的屬性提供足夠的默認值。這樣做的話,可以最小化必須要傳遞的屬性數量,並且這樣能非常簡化你的實現。

舉一個onClick處理函數的例子。如果在你的代碼中不強制要求,那麼可以提供一個空函數作為默認屬性。這樣的話,你可以在代碼中跟一直有傳遞對應屬性一樣調用。

另一個例子例如定製的輸入。除非用戶提供,否則的話假設輸入的字符串是一個空字符串。這可以確保總是在處理一個字符串對象,而不是undefined或null。

8. 不要重命名HTML屬性

HTML作為一門語言同樣有其自己的屬性,而這就是HTML元素自身的API。為什麼不繼續保持使用這個API呢?

就如我們之前提到的,最小化API接口暴露和保持某種程度的直觀是兩種改善組件API的極好方式。因此比起創建自己的screenReaderLabel屬性,為何不直接使用HTML已經提供給你的aria-label呢?

因此不要為了自己的方便使用而去重命名既有的HTML屬性。這樣並不是用一個新的API來替換既存的API,而是在頂層添加一個自己的。人們通常還可以繼續和你的screenReaderLabel屬性一起傳遞aria-label,那麼最終哪個才是該使用的呢?

再說一點,確保永遠不要在組件中覆蓋HTML屬性。一個比較好的例子就是 <button>元素的type屬性,具有submit(默認)、button或者reset。然而,許多開發者傾向於把這個屬性用於表示按鈕的可視化類型(如primary, cta等等)。

屬性他用之後,你必須要添加其他新的屬性來覆蓋表示type屬性,這樣會導致困惑、疑慮和讓用戶憤怒。

相信我,我一次又一次的犯了這個錯誤,我承認這真是一個狼狽的決定。

9. 屬性類型定義

沒有文檔能比在代碼中的文檔更好的。React通過prop-types軟件包提供了極好的方式來聲明組件API。現在就開始使用吧。

你可以指定任意類型和形式的必選和可選屬性,並可以通過JSDoc註釋來改善。

如果你忽略了一個必選的屬性,或者傳遞一個無效、非期望的值,那麼你會在控制檯中得到運行時警告。這對開發來講是極好的,並可以在生產打包時被刪掉。

如果你是通過TypeScript或Flow來開發React應用,那麼你可以通過語言特性獲得這種API文檔。這可以獲得更好的工具支持,和更好的用戶體驗。

如果你自己沒有使用具有類型的JavaScript,那麼你應該始終考慮給你的用戶提供類型定義。這樣,他們在使用你的組件時會更加容易。

10. 為開發者設計

最後,最重要的一個規則。確保你的API和組件體驗是為那些將會使用的人優化的–你的同事開發者。

一種改善開發者體驗的方式是為不合理使用提供足夠的錯誤信息,並在有更好的方式使用組件的情況下提供僅在開發環境的警告。

當在提供錯誤和警告時,儘量通過鏈接引用你的文檔或者提供簡單的代碼示例。讓用戶越快找到錯誤並修復,這會讓用戶感到你的組件越好用。

事實證明,這些冗長的錯誤和警告並不會影響最終打包的大小。感謝無用代碼精簡的幫助,在構建生產包的時候這些文本和錯誤的代碼都會被移除。

在這方面做得非常好的一個庫就是React本身。任何時候當你忘記在列表項中指定一個key時,或者拼錯一個生命週期函數,忘記繼承正確的基類或者用不正確的方式調用hook時,你會在控制檯中得到大量的錯誤信息。為什麼你的組件使用人員要期望的更少呢?

因此為你的未來用戶設計,為5周後的你自己設計,為當你離開後必須要接手維護你代碼的可憐傢伙設計!為開發者設計。

總結

從經典的API設計中我們可以學到許多很好的建議。通過遵循文中的建議、技巧、規則和最佳實踐,你應該可以創建簡單易用,容易維護,直觀並在需要的時候非常靈活的組件。

那麼你在創建一個出色的組件時有哪些最喜歡的建議?


關於本文


分享到:


相關文章: