Airbnb - 你非常值得學習的 React-JSX 編碼風格指南
作者:jiahao-c 譯
https://github.com/jiahao-c/javascript/tree/master/react
Airbnb React/JSX 風格指南
此風格指南主要基於目前流行的 JavaScript 標準。對於項目中是否應允許使用一些慣例(如 async/await,靜態 class 屬性等)的問題,應視具體情況而定。目前,本指南不包含並且不推薦使用任何第三階段 * 前的功能。
譯者注:第三階段指 TC39 流程中的 Stage 3 - Candidate。每個新的 ECMAScript 提案從起草到完成共分五個階段。
內容目錄
-
基本規範
-
Class、React.createClass 與 stateless
-
Mixins(混入)
-
命名
-
聲明模塊
-
代碼對齊
-
單引號還是雙引號
-
空格
-
屬性
-
Refs 引用
-
括號
-
標籤
-
函數 / 方法
-
模塊生命週期
-
isMounted
Basic Rules 基本規範
-
每個文件只寫一個模塊.
-
但多個無狀態模塊可以放在單個文件中. eslint:
react/no-multi-comp. -
推薦使用 JSX 語法.
-
不要使用
React.createElement,除非在從一個非 JSX 的文件中初始化 app. -
只有在使用
arrayOf,objectOf, 或shape明確指出arrays和objects所包含的內容時,react/forbid-prop-types纔會允許這個屬性 (props).
創建模塊
Class vs React.createClass vs stateless
-
如果你的模塊有內部狀態或者是
refs, 推薦使用class extends React.Component而不是React.createClass.
eslint:react/prefer-es6-classreact/prefer-stateless-function// bad const Listing = React.createClass({ // ... render() { return <section>{this.state.hello}</section>; } }); // good class Listing extends React.Component { // ... render() { return <section>{this.state.hello}</section>; } }如果你的模塊沒有狀態或是沒有引用
refs, 推薦使用普通函數(非箭頭函數)而不是類:// bad class Listing extends React.Component { render() { return <section>{this.props.hello}</section>; } } // bad (relying on function name inference is discouraged) const Listing = ({ hello }) => ( <section>{hello}</section> ); // good function Listing({ hello }) { return <section>{hello}</section>; }
Mixins
- 不要使用 mixins.
爲什麼? Mixins 會增加隱式的依賴,導致命名衝突,並且會以雪球式增加複雜度。在大多數情況下 Mixins 可以被更好的方法替代,如:組件化,高階組件,工具模塊等。
Naming 命名
-
擴展名: React 模塊使用
.jsx擴展名.eslint:
react/jsx-filename-extension -
文件名: 文件名使用帕斯卡命名. 如,
ReservationCard.jsx. -
引用命名: React 模塊名使用帕斯卡命名,實例使用駱駝式命名. eslint:
react/jsx-pascal-case// bad import reservationCard from './ReservationCard'; // good import ReservationCard from './ReservationCard'; // bad const ReservationItem = <ReservationCard />; // good const reservationItem = <ReservationCard />; -
模塊命名: 模塊使用當前文件名一樣的名稱. 比如
ReservationCard.jsx應該包含名爲ReservationCard的模塊. 但是,如果整個文件夾是一個模塊,使用index.js作爲入口文件,然後直接使用index.js或者文件夾名作爲模塊的名稱:// bad import Footer from './Footer/Footer'; // bad import Footer from './Footer/index'; // good import Footer from './Footer'; -
高階模塊命名: 對於生成一個新的模塊,其中的模塊名
displayName應該爲高階模塊名和傳入模塊名的組合. 例如, 高階模塊withFoo(), 當傳入一個Bar模塊的時候, 生成的模塊名displayName應爲withFoo(Bar).爲什麼?一個模塊的
displayName可能會在開發者工具或者錯誤信息中使用到,因此有一個能清楚的表達這層關係的值能幫助我們更好地理解模塊發生了什麼,更好地進行 Debug。// bad export default function withFoo(WrappedComponent) { return function WithFoo(props) { return <WrappedComponent {...props} foo />; } } // good export default function withFoo(WrappedComponent) { function WithFoo(props) { return <WrappedComponent {...props} foo />; } const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component'; WithFoo.displayName = `withFoo(${wrappedComponentName})`; return WithFoo; } -
屬性命名: 避免使用 DOM 相關的屬性來用作其他的用途。
爲什麼?對於
style和className這樣的屬性名,我們都會默認它們代表一些特殊的含義,如元素的樣式,CSS class 的名稱。在你的應用中使用這些屬性來表示其他的含義會使你的代碼更難閱讀,更難維護,並且可能會引起 bug。// bad <MyComponent style="fancy" /> // good <MyComponent variant="fancy" />
Declaration 聲明模塊
-
不要使用
displayName來命名 React 模塊,而是使用引用來命名模塊, 如 class 名稱.// bad export default React.createClass({ displayName: 'ReservationCard', // stuff goes here }); // good export default class ReservationCard extends React.Component { }
Alignment 代碼對齊
-
遵循以下的 JSX 語法縮進 / 格式. eslint:
react/jsx-closing-bracket-locationreact/jsx-closing-tag-location// bad <Foo superLongParam="bar" anotherSuperLongParam="baz" /> // good <Foo superLongParam="bar" anotherSuperLongParam="baz" /> // if props fit in one line then keep it on the same line <Foo bar="bar" /> // children get indented normally <Foo superLongParam="bar" anotherSuperLongParam="baz" > <Quux /> </Foo> // bad {showButton && <Button /> } // bad { showButton && <Button /> } // good {showButton && ( <Button /> )} // good {showButton && <Button />} // good {someReallyLongConditional && anotherLongConditional && ( <Foo superLongParam="bar" anotherSuperLongParam="baz" /> ) } // good {someConditional ? ( <Foo /> ) : ( <Foo superLongParam="bar" anotherSuperLongParam="baz" /> )}
Quotes 單引號還是雙引號
-
對於 JSX 屬性值總是使用雙引號 (
"), 其他均使用單引號 ('). eslint:jsx-quotes爲什麼? HTML 屬性也是用雙引號, 因此 JSX 的屬性也遵循此約定.
// bad <Foo bar='bar' /> // good <Foo bar="bar" /> // bad <Foo style={{ left: "20px" }} /> // good <Foo style={{ left: '20px' }} />
Spacing 空格
-
總是在自動關閉的標籤前加一個空格,正常情況下也不需要換行. eslint:
no-multi-spaces,react/jsx-tag-spacing// bad <Foo/> // very bad <Foo /> // bad <Foo /> // good <Foo /> -
不要在 JSX
{}引用括號裏兩邊加空格. eslint:react/jsx-curly-spacing// bad <Foo bar={ baz } /> // good <Foo bar={baz} />
Props 屬性
-
JSX 屬性名使用小駱駝拼寫法
camelCase,如果屬性名是一個 React 組件名,則使用大駱駝拼寫法PascalCase// bad <Foo User phone_number={12345678} /> // good <Foo user phoneNumber={12345678} Component={SomeComponent} /> -
如果屬性值爲
true, 可以直接省略. eslint:react/jsx-boolean-value// bad <Foo hidden={true} /> // good <Foo hidden /> // good <Foo hidden /> -
<img>標籤總是添加alt屬性. 如果圖片以 presentation(感覺是以類似 PPT 方式顯示?) 方式顯示,alt可爲空, 或者<img>要包含role="presentation". eslint:jsx-a11y/alt-text// bad <img src="hello.jpg" /> // good <img src="hello.jpg" alt="Me waving hello" /> // good <img src="hello.jpg" alt="" /> // good <img src="hello.jpg" role="presentation" /> -
不要在
alt值裏使用如 "image", "photo", or "picture" 包括圖片含義這樣的詞, 中文也一樣. eslint:jsx-a11y/img-redundant-alt爲什麼? 屏幕助讀器已經把
img標籤標註爲圖片了, 所以沒有必要再在alt裏說明了.// bad <img src="hello.jpg" alt="Picture of me waving hello" /> // good <img src="hello.jpg" alt="Me waving hello" /> -
使用有效正確的 aria
role屬性值 ARIA roles. eslint:jsx-a11y/aria-role// bad - not an ARIA role <section role="datepicker" /> // bad - abstract ARIA role <section role="range" /> // good <section role="button" /> -
不要在標籤上使用
accessKey屬性. eslint:jsx-a11y/no-access-key
爲什麼? 屏幕助讀器在鍵盤快捷鍵與鍵盤命令時造成的不統一性會導致閱讀性更加複雜.
// bad
<section accessKey="h" />
// good
<section />
-
避免使用數組的 index 來作爲
key屬性的值。譯者注:key 屬性指名稱爲
key的屬性,即props.key應當使用穩定不變的 ID。(使用不穩定的 ID 是一個反面模式,會降低性能、造成組件狀態出錯) 。特別是當元素的順序可能改變的情況下,不應使用數組的 index 作爲
key.譯者注:反面模式 (Anti-Pattern),指低效或是有待優化的軟件設計模式。
// bad
{todos.map((todo, index) =>
<Todo
{...todo}
key={index}
/>
)}
// good
{todos.map(todo => (
<Todo
{...todo}
key={todo.id}
/>
))}
- 對於所有非必須的屬性,總是手動去定義
defaultProps屬性.
爲什麼? propTypes 可以作爲模塊的文檔說明, 並且聲明 defaultProps 的話意味着閱讀代碼的人不需要去假設一些默認值。更重要的是, 顯示的聲明默認屬性可以讓你的模塊跳過屬性類型的檢查.
// bad
function SFC({ foo, bar, children }) {
return <section>{foo}{bar}{children}</section>;
}
SFC.propTypes = {
foo: PropTypes.number.isRequired,
bar: PropTypes.string,
children: PropTypes.node,
};
// good
function SFC({ foo, bar, children }) {
return <section>{foo}{bar}{children}</section>;
}
SFC.propTypes = {
foo: PropTypes.number.isRequired,
bar: PropTypes.string,
children: PropTypes.node,
};
SFC.defaultProps = {
bar: '',
children: null,
};
- 儘可能少地使用擴展運算符
爲什麼? 除非你很想傳遞一些不必要的屬性。對於 React v15.6.1 和更早的版本,你可以給 DOM 傳遞一些無效的 HTML 屬性
例外情況:
- 使用了變量提升的高階組件
function HOC(WrappedComponent) {
return class Proxy extends React.Component {
Proxy.propTypes = {
text: PropTypes.string,
isLoading: PropTypes.bool
};
render() {
return <WrappedComponent {...this.props} />
}
}
}
- 只有在清楚明白擴展對象時才使用擴展運算符。這非常有用尤其是在使用 Mocha 測試組件的時候。
export default function Foo {
const props = {
text: '',
isPublished: false
}
return (<section {...props} />);
}
特別提醒:儘可能地篩選出不必要的屬性。同時,使用 prop-types-exact 來預防問題出現。
// bad
render() {
const { irrelevantProp, ...relevantProps } = this.props;
return <WrappedComponent {...this.props} />
}
// good
render() {
const { irrelevantProp, ...relevantProps } = this.props;
return <WrappedComponent {...relevantProps} />
}
Refs
-
總是在 Refs 裏使用回調函數. eslint:
react/no-string-refs// bad <Foo ref="myRef" /> // good <Foo ref={(ref) => { this.myRef = ref; }} />
Parentheses 括號
-
將多行的 JSX 標籤寫在
()裏. eslint:react/jsx-wrap-multilines// bad render() { return <MyComponent class> <MyChild /> </MyComponent>; } // good render() { return ( <MyComponent class> <MyChild /> </MyComponent> ); } // good, 單行可以不需要 render() { const body = <section>hello</section>; return <MyComponent>{body}</MyComponent>; }
Tags 標籤
-
對於沒有子元素的標籤來說總是自己關閉標籤. eslint:
react/self-closing-comp// bad <Foo class></Foo> // good <Foo class /> -
如果模塊有多行的屬性, 關閉標籤時新建一行. eslint:
react/jsx-closing-bracket-location// bad <Foo bar="bar" baz="baz" /> // good <Foo bar="bar" baz="baz" />
Methods 函數
-
使用箭頭函數來獲取本地變量。這使得傳遞數據給事件處理器 (event handler) 很方便。不過,尤其是當傳遞給自定義的純組件 (PureComponent) 時,要確保這些箭頭函數對性能影響不是太大,因爲它們會每次都會觸發可能無意義的重新渲染。
function ItemList(props) { return ( <ul> {props.items.map((item, index) => ( <Item key={item.key} onClick={(event) => { doSomethingWith(event, item.name, index); }} /> ))} </ul> ); } -
當在
render()裏使用事件處理方法時,提前在構造函數里把this綁定上去. eslint:react/jsx-no-bind爲什麼? 在每次
render過程中, 再調用bind都會新建一個新的函數,浪費資源。在類成員變量 (class fields) 裏不要使用箭頭函數,因爲箭頭函數會造成它難以測試和調試,並會降低性能。從概念上講,類成員變量存的應該是數據,而不是邏輯或方法。// bad class extends React.Component { onClickDiv() { // do stuff } render() { return <section onClick={this.onClickDiv.bind(this)} />; } } // very bad class extends React.Component { onClickDiv = () => { // do stuff } render() { return <section onClick={this.onClickDiv} /> } } // good class extends React.Component { constructor(props) { super(props); this.onClickDiv = this.onClickDiv.bind(this); } onClickDiv() { // do stuff } render() { return <section onClick={this.onClickDiv} />; } } -
在 React 模塊中,不要給所謂的私有函數添加
_前綴,本質上它並不是私有的.爲什麼?
_下劃線前綴在某些語言中通常被用來表示私有變量或者函數。但是不像其他的一些語言,在 JS 中沒有原生支持所謂的私有變量,所有的變量函數都是共有的。儘管你的意圖是使它私有化,在之前加上下劃線並不會使這些變量私有化,並且所有的屬性(包括有下劃線前綴及沒有前綴的)都應該被視爲是共有的。瞭解更多詳情請查看 Issue #1024, 和 #490 。// bad React.createClass({ _onClickSubmit() { // do stuff }, // other stuff }); // good class extends React.Component { onClickSubmit() { // do stuff } // other stuff } -
在
render方法中總是確保return返回值. eslint:react/require-render-return// bad render() { (<section />); } // good render() { return (<section />); }
Ordering React 模塊生命週期
class extends React.Component的生命週期函數:
-
可選的
static方法 -
constructor構造函數 -
getChildContext獲取子元素內容 -
componentWillMount模塊渲染前 -
componentDidMount模塊渲染後 -
componentWillReceiveProps模塊將接受新的數據 -
shouldComponentUpdate判斷模塊需不需要重新渲染 -
componentWillUpdate上面的方法返回true, 模塊將重新渲染 -
componentDidUpdate模塊渲染結束 -
componentWillUnmount模塊將從 DOM 中清除, 做一些清理任務 -
點擊回調或者事件處理器 如
onClickSubmit()或onChangeDescription() -
render裏的 getter 方法 如getSelectReason()或getFooterContent() -
可選的 render 方法 如
renderNavigation()或renderProfilePicture() -
renderrender() 方法
-
如何定義
propTypes,defaultProps,contextTypes, 等等其他屬性...import React from 'react'; import PropTypes from 'prop-types'; const propTypes = { id: PropTypes.number.isRequired, url: PropTypes.string.isRequired, text: PropTypes.string, }; const defaultProps = { text: 'Hello World', }; class Link extends React.Component { static methodsAreOk() { return true; } render() { return <a href={this.props.url} data-id={this.props.id}>{this.props.text}</a>; } } Link.propTypes = propTypes; Link.defaultProps = defaultProps; export default Link; -
React.createClass的生命週期函數,與使用 class 稍有不同: eslint:react/sort-comp
-
displayName設定模塊名稱 -
propTypes設置屬性的類型 -
contextTypes設置上下文類型 -
childContextTypes設置子元素上下文類型 -
mixins添加一些 mixins -
statics -
defaultProps設置默認的屬性值 -
getDefaultProps獲取默認屬性值 -
getInitialState或者初始狀態 -
getChildContext -
componentWillMount -
componentDidMount -
componentWillReceiveProps -
shouldComponentUpdate -
componentWillUpdate -
componentDidUpdate -
componentWillUnmount -
clickHandlers or eventHandlers like
onClickSubmit()oronChangeDescription() -
getter methods for
renderlikegetSelectReason()orgetFooterContent() -
Optional render methods like
renderNavigation()orrenderProfilePicture() -
render
isMounted
- 不要再使用
isMounted. eslint:react/no-is-mounted
爲什麼?
isMounted反人類設計模式:(), 在 ES6 classes 中無法使用, 官方將在未來的版本里刪除此方法.
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/e7YUdTS73rM6QN9WrpjUkg