「譯」10個React組件的戒律

原文鏈接:https://dev.to/selbekk/the-10-component-commandments-2a7f

創建被很多人用的組件是很難的。如果那些`props`作為公共API,你需要非常謹慎的考慮哪些`props`是可以接收的。

這篇文章將給你快速介紹API的最佳實踐方法,同時有十條實踐戒律清單,你可以在創建組件的時候使用,你的開發者將會很樂意使用它們。

「譯」10個React組件的戒律

什麼是API?

一個API或者說一個應用程序接口,基本上就是兩段代碼相遇的地方。它連接*你的代碼*和剩下的世界。我們稱這種連接為接口。它是你可以與之交互的一組定義的actions或者data actions。

後端與前端之間的接口稱為API。你可以通過與此API交互訪問給定的一組數據和功能。

類和調用該類的代碼之間的接口也是API,你可以調用類裡面的方法去取數據或者觸發封裝在其中的功能。

按照同樣的思路,**你組件接收props同樣也是API**。這是你的用戶和你的組件的交互方式,當你決定暴露哪些公共API,會有很多同樣的規則和注意事項。

API設計中的一些最佳實踐

所以在設計API的時候,有哪些適用的規則和考慮因素?我們為此做了一些研究,結果發現那裡有很多很棒的資源。我們選擇了兩篇文章——Josh Tauberer的What Makes a Good API ?和Ron Kurir's article with the same title——我們提出了4個最佳實踐。

穩定的版本控制

當你考慮創建一個API,其中一個最重要的事情就是儘可能保證API的穩定。這意味著要最大限度減少break change。如果你必須引入一個break change,要確保寫明確的升級指南,如果可能的話,提供一個代碼模塊,為使用者自動化升級。

如果你發佈一個API,確保遵循語義版本控制。這讓使用者決定使用哪個版本變得很容易。

描述性錯誤信息

每當當調用你的API發生錯誤,你應該儘可能解釋是什麼發生了錯誤,如何修正該錯誤。沒有任何其他上下文的情況下使用“錯誤使用”響應不是一個好的用戶體驗。

相反,寫描述性錯誤,幫助用戶修正他們調用API時候的錯誤。

減少開發人員的意外

開發人員是脆弱的生物,你不想在他們使用你的API時候驚嚇他們。換句話說,讓你的API儘可能的保持直觀。你可以根據最佳實踐和命名規範實現它們。

要記住的另一件事情是你記得跟你的代碼保持一致。如果你在一個地方的boolean值之前加了`is`或`has`,但是在下一個地方沒有加,很容讓人產生困惑。

最小化你的API

雖說我們說的是最小化東西——儘量減少你的API。有很多功能是非常好的,但是你的API越少,你的用戶學習起來就越容易。反過來,這被認為是一個易於使用的API。

總有辦法控制API的大小——其中一個方法就是從舊的API重構一個新的API。

十個組件的告誡

「譯」10個React組件的戒律

這裡有4個黃金法則適用於Pascal中的REST API和舊程序,但是如何轉化到現代世界的React?

就像我們前面提到的,組件有它們自己的API。我們稱它們為`props` , 這就是我們如何用數據,回調函數和其他功能來提供組件。我們如何構建不違反上述規則的`props`對象?我們如何寫方便其他開發人員測試的組件?

我們創建了當你創建你的組件的時候**10個需要遵循的規則**清單,希望它們對你有用。

1.編寫文檔

如果沒有好的文檔說明你的組件如何使用,那麼根據定義,這個組件是無用的,雖然幾乎所有的用戶都可以查看組件的實現方式,但是這樣做用戶體驗會很糟糕。

有幾種方式來記錄組件,但是這裡的三種選擇是我們所推薦的:

  • Storybook
  • Styleguidist
  • Docz

前兩個為你提供組件開發的場所,而第三個讓你用MDX編寫更自由的文檔。

不管你選擇哪個,要保證記錄API以及如何,何時使用組件。最後一部分在共享組件庫中非常重要——所以人們在給定的上下文中使用爭取的按鈕或者佈局網格。

2.允許上下文語意

HTML是一種用語義方式構建信息的語言。然而,我們大多數組件使用`div`標籤。這個在某種程度上說是有道理的——因為通用組建無法真正的假設它應該是一個``或者`

`或者一個``——但這並不理想。

我們建議你允許你的組件接受一個`as`作為props,這將始終允許你覆蓋正在呈現的DOM元素。這裡是如何實現的一個demo:

function Grid({ as: Element, ...props }) {
 return 
}
Grid.defaultProps = {
 as: 'div',
};

我們重命名`as`為局部變量`Element`在JSX中使用,同時我們給`as`一個默認值,在不傳遞語義標籤的時候使用。

當使用這個``組件時,你只需要傳遞正確的標籤即可:

function App() {
 return (
 
 
 
 );
}

注意,這對React組件同樣有效。如果你想有一個``組件,渲染為React Router的``:

3.避免使用布爾類型pops

布爾類型的props聽起來很不錯,你可以不實用value指定它們,所以它們看起來很優雅:

但是即使它們看起來很優雅,布爾屬性只允許使用兩個屬性。on或off,顯示或隱藏,1或0。

每當你開始為像大小,顏色或者任何可能不是二元選擇的屬性使用布爾屬性,你可能會陷入麻煩。

換句話說,布爾屬性無法隨著需求的變化而變化。使用類似字符串的枚舉值作為屬性可能是一個更好的選擇:

這並不是說布爾屬性沒有使用的地方。`disabled`這樣的屬性還是應該使用布爾值——因為這裡不會有介於啟用和禁用的屬性之間。

4.使用`props.children`

React有一些特殊的屬性,它們的處理方式與其他屬性不同。其中一個是`key`,它用於跟蹤列表項順序,另一個就是`children`。

任何放在打開和閉合的組件標籤之間的東西都可以用`props.children`讀取到。你應該儘可能使用它。

這樣做的原因是,它比使用`content`props或者通常只接受簡單值(比如文本)要簡單的多。

// vs
Some text

這裡有幾個使用`props.children`的好處。首先它類似於常規的HTML的工作方式。其次,你可以自由的傳遞任何你想傳遞的!而不是給你的組件添加`leftIcon`和`rightIcon` props——只需要將它們作為`props.children`的一部分。

  Some text

你總是爭辯說你的組件只允許傳純文本,在一些情況下,這可能是真實的。但是至少現在,使用`props.children`可以為不斷變化的需求檢驗你的API。

5.讓parent hook進入內部邏輯

有時候我們創建有大量內在邏輯和狀態的組件,比如自動完成下拉菜單或者交互式圖標。

這些組件通常遭遇冗餘的API,其中一個原因是隨著時間的推移你通常需要支持覆蓋量和一些特殊的用法。

如果我們只是提供一個單一,標準化的props,可以被用戶控制,作出反應或者覆蓋默認行為的組件呢?

Kent C. Dodds 寫過關於這個概念的非常讚的文章,叫做“state reducers”。

快速總結一下,這種傳遞“state reducers”函數到你組件的模式可以讓組件使用者訪問組件內部的所有actions。你可以改變state甚至觸發副作用。這是一種非常好方式,允許高級定製,而不是所有的props。

function MyCustomDropdown(props) {
 const stateReducer = (state, action) => {
 if (action.type === Dropdown.actions.CLOSE) {
 buttonRef.current.focus();
 }
 };
 return (
 <>
 
 
 >
}

順便說一句,你當然可以創建更簡單的方式對事件作出反應。提供一個`onClose` prop 在前面的例子中可能帶來更好的用戶體驗。保存state reducer模式,以備不時之需。

6.傳遞剩餘的props

每當你創建新組建——要確保剩餘的props被用於任何有意義的元素。

你不必繼續在你的組件上添加props,只需要將它們傳遞到底層組件或者元素上就行。這樣做將讓你的API更加穩定,無論何時,下一個開發者需要新的監聽器或者aria-tag,都無需大量的小版本的顛簸。

你可以像這樣做:

function ToolTip({ isVisible, ...rest }) {
 return isVisible ? 

: null; }

每當你的組件在實現中傳遞props,例如一個類名或者`onClick`回調,要確保外部使用者會做同樣的事情。在使用calss的時候,你可以簡單使用npm 包 `classnames`或者簡單的字符串連接附加props。

import classNames from 'classnames';
function ToolTip(props) {
 return (
 

}

對於點擊處理和其他回調,你可以使用工具將它們組合到一個函數,就像這樣:

function combine(...functions) {
 return (...args) =>
 functions
 .filter(func => typeof func === 'function')
 .forEach(func => func(...args));
}

這裡,我們創建了一個接受你的函數list並將它們組合的函數,它返回一個使用相同參數順序調用函數list的回調。

你可以這樣使用它們:

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

setVisible(true), props.onMouseIn)} onMouseOut={combine(() => setVisible(false), props.onMouseOut)} /> ); }

7.提供足夠的默認值

只要你可以,請確保為你的props提供足夠的默認值。通過這種方式,你可以使你必須傳遞props的數量最少——這樣可以大大簡化你的實現。

拿`onClick`處理函數舉例。如果你的代碼中沒有要求,提供一個空函數作為默認值。通過這種方式,你可以調用該函數,就好像它總是被提供。

另一個例子是自定義輸入。除非明確提供,否則假設輸入的字符串是一個空字符串。這將讓你確保你始終處理的是字符串對象,而不是`undefined`或者`null`。

8.不要重命名HTML的屬性

HTML有著自己的props或者說屬性,它本身就是HTML元素的API。為什麼不繼續使用這個API?

正如我們前面提到的,將API最小化和使其變得直觀是優化你的組件API的很好的方式。所以與其在你的組件中使用自己創建的`screenReaderLabel`props,為什麼不直接使用已經給你提供好的`aria-label`API?

所以,為了自己的“易用性”**不要重命名任何現有的HTML屬性**。你甚至不應該用新的API替換現有的API,而是應該在上層添加你自己的。用戶可以將`aria-label`和你定義的`screenReaderLabel`一起傳遞,那麼最終的value應該是哪個呢?

另外,你的組件中要確保永遠不覆蓋HTML的屬性。一個好的例子是`

通過改變這個props的用途,你必須添加其他屬性去覆蓋真實的`type`屬性,這隻會給用戶帶來混亂,困惑和痛苦。

相信我——我一次又一次的犯這個錯誤——這真是一個讓人難以接受的決定。

9.給props添加types

沒有文檔會比內嵌在代碼中的文檔更好(譯者注:言外之意是再好的文檔也比不上規範的代碼,畢竟一目瞭然)。React提供`prop-types`包去申明你的組件API。現在,開始使用它。

你可以指定你所需和可選擇的props的任何類型和形式,你甚至可以使用[JSDoc comments](https://devhints.io/jsdoc)去改善它。

如果你省略所需的pros,或者傳遞一個無效的或不是期望得到的值,你將在控制檯看到一個運行時的warning。這對開發時非常友好,在構建生產環境時可以去除。

如果你使用Typescript或者Flow寫React,你可以將這種API文檔作為語言特性。這樣可以提供更好的工具支持和出色的用戶體驗。

如果你沒有使用類型化JavaScript,你仍然應該考慮為那些使用者提供type定義。通過這種方式,用戶在使用你的組件時將變得更容易。

10.為開發者設計

最後一個最重要的規則。確保你的API和“組件體驗”針對將使用它的人進行優化——你的開發者。

其中一種優化開發者體驗的方式是對無效的使用提供足夠的錯誤信息,以及當有更好的方式來使用你的組件的時候的開發警告。

當寫你的錯誤和警告,確保用鏈接引用你的文檔或者提供簡單的代碼示例。使用者越快弄明白哪裡出了錯並且如何修復它,就,你的組件使用起來就感覺越好。

事實證明,擁有這些冗長的錯誤和警告並不會影響你包的最終大小。感覺死碼消除的奇蹟,所有的這些文字和錯誤碼,在打包到生產環境時都會被移除。

React本身就是一個非常出色的包。每當你忘記給你的list item指定key,或者拼錯生命週期,或者忘記正確的類繼承或者使用不確定的方式調用hooks,你將在控制檯看到錯誤信息。為什麼使用你組件的用戶應該期望的比這些少?

所以為你未來的用戶設計。5周內為自己設計。為那些可憐的笨蛋設計,他們必須在你離開後維護你的代碼!為開發人員設計。

總結

我們可以從經典的API設計中學到很多東西。通過本文提到的這些提示,技巧,規則和戒律,你應該創建 使用容易,維護簡單並且在必要時非常靈活的組件。

在創建組件的這些tips中,你最喜歡哪一個呢?

本文首發於博客/微信公眾號,更多內容可關注微信公眾號:tranceCoder


分享到:


相關文章: