我們?cè)谄髽I(yè)的項(xiàng)目開(kāi)發(fā)中,制定一套完善有效的編碼規(guī)范是極為有必要的,也是必須的,因?yàn)楹玫木幋a規(guī)范能極大的降低組員開(kāi)發(fā)時(shí)的溝通成本,也更加便于codeReview,而且還能約束自己,養(yǎng)成正確的編程習(xí)慣??傊辛司幋a規(guī)范,你會(huì)發(fā)現(xiàn)你慢慢的喜歡上自己寫(xiě)的代碼了。
目錄
- 基本規(guī)范
- 創(chuàng)建模塊
- 命名
- 聲明模塊
- 代碼對(duì)齊
- 單引號(hào)還是雙引號(hào)
- 空格
- 屬性
- 引用
- 括號(hào)
- 標(biāo)簽
- 函數(shù)/方法
- 模塊生命周期
基本規(guī)范
- 每個(gè)文件只寫(xiě)一個(gè)模塊
- 但是多個(gè)無(wú)狀態(tài)模塊可以放在單個(gè)文件中. eslint: react/no-multi-comp
- 推薦使用JSX語(yǔ)法
- 不要使用
React.createElement,除非從一個(gè)非JSX的文件中初始化你的app
創(chuàng)建模塊
- 如果你的模塊有內(nèi)部狀態(tài)或者是refs, 推薦使用 class extends React.Component 而不是 React.createClass,也就是說(shuō)推薦使用ES6語(yǔ)法創(chuàng)建component
// bad
const Listing = React.createClass({
// ...
render() {
return <div>{this.state.hello}</div>;
}
});
// good
class Listing extends React.Component {
// ...
render() {
return <div>{this.state.hello}</div>;
}
}
- 如果你的模塊沒(méi)有狀態(tài)或是沒(méi)有引用refs, 推薦使用普通函數(shù)(非箭頭函數(shù))而不是類
// bad
class Listing extends React.Component {
render() {
return <div>{this.props.hello}</div>;
}
}
// bad (relying on function name inference is discouraged)
const Listing = ({ hello }) => (
<div>{hello}</div>
);
// good
function Listing({ hello }) {
return <div>{hello}</div>;
}
命名
- 擴(kuò)展名: 使用.jsx作為React組件的擴(kuò)展名
- 文件名: 使用帕斯卡命名法命名文件,譬如ReservationCard.jsx
- 引用命名: 使用帕斯卡命名法命名組件和camelCase命名實(shí)例
// bad
const reservationCard = require('./ReservationCard');
// good
const ReservationCard = require('./ReservationCard');
// bad
const ReservationItem = <ReservationCard />;
// good
const reservationItem = <ReservationCard />;
- 模塊命名: 模塊使用當(dāng)前文件名一樣的名稱. 比如 ReservationCard.jsx 應(yīng)該包含名為 ReservationCard的模塊. 但是,如果整個(gè)文件夾是一個(gè)模塊,使用 index.js作為入口文件,然后直接使用 index.js 或者文件夾名作為模塊的名稱
// bad
import Footer from './Footer/Footer';
// bad
import Footer from './Footer/index';
// good
import Footer from './Footer';
- 高階模塊命名: 對(duì)于生成一個(gè)新的模塊,其中的模塊名 displayName 應(yīng)該為高階模塊名和傳入模塊名的組合. 例如, 高階模塊 withFoo(), 當(dāng)傳入一個(gè) Bar 模塊的時(shí)候, 生成的模塊名 displayName 應(yīng)該為 withFoo(Bar)
為什么?一個(gè)模塊的 displayName 可能會(huì)在開(kāi)發(fā)者工具或者錯(cuò)誤信息中使用到,因此有一個(gè)能清楚的表達(dá)這層關(guān)系的值能幫助我們更好的理解模塊發(fā)生了什么,更好的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相關(guān)的屬性來(lái)用作其他的用途
為什么?對(duì)于style 和 className這樣的屬性名,我們都會(huì)默認(rèn)它們代表一些特殊的含義,如元素的樣式,CSS class的名稱。在你的應(yīng)用中使用這些屬性來(lái)表示其他的含義會(huì)使你的代碼更難閱讀,更難維護(hù),并且可能會(huì)引起bug
jsx
// bad
<MyComponent style="fancy" />
// good
<MyComponent variant="fancy" />
聲明模塊
- 不要使用 displayName 來(lái)命名React模塊,而是使用引用來(lái)命名模塊, 如 class 名稱
// bad
export default React.createClass({
displayName: 'ReservationCard',
// stuff goes here
});
// good
export default class ReservationCard extends React.Component {
}
代碼對(duì)齊
- 遵循以下的JSX語(yǔ)法縮進(jìn)/格式
// bad
<Foo superLongParam="bar"
anotherSuperLongParam="baz" />
// good, 有多行屬性的話, 新建一行關(guān)閉標(biāo)簽
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
/>
// 若能在一行中顯示, 直接寫(xiě)成一行
<Foo bar="bar" />
// 子元素按照常規(guī)方式縮進(jìn)
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
>
<Quux />
</Foo>
單引號(hào)還是雙引號(hào)
- 對(duì)于JSX屬性值總是使用雙引號(hào)("), 其他均使用單引號(hào)(')
為什么? HTML屬性也是用雙引號(hào), 因此JSX的屬性也遵循此約定
// bad
<Foo bar='bar' />
// good
<Foo bar="bar" />
// bad
<Foo style={{ left: "20px" }} />
// good
<Foo style={{ left: '20px' }} />
空格
- 總是在自動(dòng)關(guān)閉的標(biāo)簽前加一個(gè)空格,正常情況下也不需要換行
// bad
<Foo/>
// very bad
<Foo />
// bad
<Foo
/>
// good
<Foo />
- 不要在JSX {} 引用括號(hào)里兩邊加空格
// bad
<Foo bar={ baz } />
// good
<Foo bar={baz} />
屬性
- JSX屬性名使用駱駝式風(fēng)格camelCase
// bad
<Foo
UserName="hello"
phone_number={12345678}
/>
// good
<Foo
userName="hello"
phoneNumber={12345678}
/>
- 如果屬性值為 true, 可以直接省略
// bad
<Foo
hidden={true}
/>
// good
<Foo
hidden
/>
// good
<Foo hidden />
- <img> 標(biāo)簽總是添加 alt 屬性. 如果圖片以presentation(感覺(jué)是以類似PPT方式顯示?)方式顯示,alt 可為空, 或者<img> 要包含role="presentation"
// 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"包括圖片含義這樣的詞, 中文也一樣
為什么? 屏幕助讀器已經(jīng)把 img 標(biāo)簽標(biāo)注為圖片了, 所以沒(méi)有必要再在 alt 里說(shuō)明了
// bad
<img src="hello.jpg" alt="Picture of me waving hello" />
// good
<img src="hello.jpg" alt="Me waving hello" />
- 使用有效正確的 aria role屬性值 ARIA roles
// bad - not an ARIA role
<div role="datepicker" />
// bad - abstract ARIA role
<div role="range" />
// good
<div role="button" />
- 不要在標(biāo)簽上使用 accessKey 屬性
為什么? 屏幕助讀器在鍵盤(pán)快捷鍵與鍵盤(pán)命令時(shí)造成的不統(tǒng)一性會(huì)導(dǎo)致閱讀性更加復(fù)雜
// bad
<div accessKey="h" />
// good
<div />
- 避免使用數(shù)組的index來(lái)作為屬性key的值,推薦使用唯一ID
// bad
{todos.map((todo, index) =>
<Todo
{...todo}
key={index}
/>
)}
// good
{todos.map(todo => (
<Todo
{...todo}
key={todo.id}
/>
))}
- 對(duì)于所有非必須的屬性,總是手動(dòng)去定義defaultProps屬性
為什么? propTypes 可以作為模塊的文檔說(shuō)明, 并且聲明 defaultProps 的話意味著閱讀代碼的人不需要去假設(shè)一些默認(rèn)值。更重要的是, 顯示的聲明默認(rèn)屬性可以讓你的模塊跳過(guò)屬性類型的檢查
// bad
function SFC({ foo, bar, children }) {
return <div>{foo}{bar}{children}</div>;
}
SFC.propTypes = {
foo: PropTypes.number.isRequired,
bar: PropTypes.string,
children: PropTypes.node,
};
// good
function SFC({ foo, bar, children }) {
return <div>{foo}{bar}{children}</div>;
}
SFC.propTypes = {
foo: PropTypes.number.isRequired,
bar: PropTypes.string,
children: PropTypes.node,
};
SFC.defaultProps = {
bar: '',
children: null,
}
- 盡可能少地使用擴(kuò)展運(yùn)算符
為什么? 除非你很想傳遞一些不必要的屬性。對(duì)于React v15.6.1和更早的版本,你可以給DOM傳遞一些無(wú)效的HTML屬性
例外情況:
- 使用了變量提升的高階組件
function HOC(WrappedComponent) {
return class Proxy extends React.Component {
Proxy.propTypes = {
text: PropTypes.string,
isLoading: PropTypes.bool
};
render() {
return <WrappedComponent {...this.props} />
}
}
}
- 只有在清楚明白擴(kuò)展對(duì)象時(shí)才使用擴(kuò)展運(yùn)算符。這非常有用尤其是在使用Mocha測(cè)試組件的時(shí)候
export default function Foo {
const props = {
text: '',
isPublished: false
}
return (<div {...props} />);
}
- 特別提醒:盡可能地篩選出不必要的屬性。同時(shí),使用prop-types-exact來(lái)預(yù)防問(wèn)題出現(xiàn)
//good
render() {
const { irrelevantProp, ...relevantProps } = this.props;
return <WrappedComponent {...relevantProps} />
}
//bad
render() {
const { irrelevantProp, ...relevantProps } = this.props;
return <WrappedComponent {...this.props} />
}
引用
- 總是在Refs里使用回調(diào)函數(shù)
// bad
<Foo
ref="myRef"
/>
// good
<Foo
ref={(ref) => { this.myRef = ref; }}
/>
括號(hào)
- 將多行的JSX標(biāo)簽寫(xiě)在 ()里
// bad
render() {
return <MyComponent className="long body" foo="bar">
<MyChild />
</MyComponent>;
}
// good
render() {
return (
<MyComponent className="long body" foo="bar">
<MyChild />
</MyComponent>
);
}
// good, 單行可以不需要
render() {
const body = <div>hello</div>;
return <MyComponent>{body}</MyComponent>;
}
標(biāo)簽
- 對(duì)于沒(méi)有子元素的標(biāo)簽來(lái)說(shuō)總是自己關(guān)閉標(biāo)簽
// bad
<Foo className="stuff"></Foo>
// good
<Foo className="stuff" />
- 如果模塊有多行的屬性, 關(guān)閉標(biāo)簽時(shí)新建一行
// bad
<Foo
bar="bar"
baz="baz" />
// good
<Foo
bar="bar"
baz="baz"
/>
函數(shù)/方法
- 使用箭頭函數(shù)來(lái)獲取本地變量
function ItemList(props) {
return (
<ul>
{props.items.map((item, index) => (
<Item
key={item.key}
onClick={() => doSomethingWith(item.name, index)}
/>
))}
</ul>
);
}
- 當(dāng)在 render() 里使用事件處理方法時(shí),提前在構(gòu)造函數(shù)里把 this 綁定上去
為什么? 在每次 render 過(guò)程中, 再調(diào)用 bind 都會(huì)新建一個(gè)新的函數(shù),浪費(fèi)資源.
// bad
class extends React.Component {
onClickDiv() {
// do stuff
}
render() {
return <div onClick={this.onClickDiv.bind(this)} />;
}
}
// good
class extends React.Component {
constructor(props) {
super(props);
this.onClickDiv = this.onClickDiv.bind(this);
}
onClickDiv() {
// do stuff
}
render() {
return <div onClick={this.onClickDiv} />;
}
}
- 在React模塊中,不要給所謂的私有函數(shù)添加 _ 前綴,本質(zhì)上它并不是私有的
為什么?_ 下劃線前綴在某些語(yǔ)言中通常被用來(lái)表示私有變量或者函數(shù)。但是不像其他的一些語(yǔ)言,在JS中沒(méi)有原生支持所謂的私有變量,所有的變量函數(shù)都是共有的。盡管你的意圖是使它私有化,在之前加上下劃線并不會(huì)使這些變量私有化,并且所有的屬性(包括有下劃線前綴及沒(méi)有前綴的)都應(yīng)該被視為是共有的
// bad
React.createClass({
_onClickSubmit() {
// do stuff
},
// other stuff
});
// good
class extends React.Component {
onClickSubmit() {
// do stuff
}
// other stuff
}
- 在 render 方法中總是確保 return 返回值
// bad
render() {
(<div />);
}
// good
render() {
return (<div />);
}
模塊生命周期
- class extends React.Component 的生命周期函數(shù)
- 可選的 static 方法
- constructor 構(gòu)造函數(shù)
- getChildContext 獲取子元素內(nèi)容
- componentWillMount 模塊渲染前
- componentDidMount 模塊渲染后
- componentWillReceiveProps 模塊將接受新的數(shù)據(jù)
- shouldComponentUpdate 判斷模塊需不需要重新渲染
- componentWillUpdate 上面的方法返回 true, 模塊將重新渲染
- componentDidUpdate 模塊渲染結(jié)束
- componentWillUnmount 模塊將從DOM中清除, 做一些清理任務(wù)
- 點(diǎn)擊回調(diào)或者事件處理器 如 onClickSubmit() 或 onChangeDescription()
- render 里的 getter 方法 如 getSelectReason() 或 getFooterContent()
- 可選的 render 方法 如 renderNavigation() 或 renderProfilePicture()
- render render() 方法
- 如何定義 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 的生命周期函數(shù),與使用class稍有不同
- displayName 設(shè)定模塊名稱
- propTypes 設(shè)置屬性的類型
- contextTypes 設(shè)置上下文類型
- childContextTypes 設(shè)置子元素上下文類型
- mixins 添加一些mixins
- statics
- defaultProps 設(shè)置默認(rèn)的屬性值
- getDefaultProps 獲取默認(rèn)屬性值
- getInitialState 或者初始狀態(tài)
- getChildContext
- componentWillMount
- componentDidMount
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
- componentDidUpdate
- componentWillUnmount
- clickHandlers or eventHandlers like onClickSubmit() or onChangeDescription()
- getter methods for render like getSelectReason() or getFooterContent()
- Optional render methods like renderNavigation() or renderProfilePicture()
- render
總結(jié)
有了好的編碼規(guī)范,這只是第一步,最重要的是,團(tuán)隊(duì)要達(dá)成一致,遵守這套編碼,這樣才能發(fā)揮出規(guī)范強(qiáng)大的作用。
更多文章
- 作者React Native開(kāi)源項(xiàng)目OneM【500+ star】地址(按照企業(yè)開(kāi)發(fā)標(biāo)準(zhǔn)搭建框架完成開(kāi)發(fā)的):https://github.com/guangqiang-liu/OneM:歡迎小伙伴們 star
- 作者簡(jiǎn)書(shū)主頁(yè):包含60多篇RN開(kāi)發(fā)相關(guān)的技術(shù)文章http://www.itdecent.cn/u/023338566ca5 歡迎小伙伴們:多多關(guān)注,多多點(diǎn)贊