我們在企業(yè)的項(xiàng)目開發(fā)中,制定一套完善有效的編碼規(guī)范是極為有必要的,也是必須的,因?yàn)楹玫木幋a規(guī)范能極大的降低組員開發(fā)時的溝通成本,也更加便于codeReview,而且還能約束自己,養(yǎng)成正確的編程習(xí)慣??傊辛司幋a規(guī)范,你會發(fā)現(xiàn)你慢慢的喜歡上自己寫的代碼了。
目錄
基本規(guī)范
- 每個文件只寫一個模塊
- 但是多個無狀態(tài)模塊可以放在單個文件中. eslint: react/no-multi-comp
- 推薦使用JSX語法
- 不要使用
React.createElement,除非從一個非JSX的文件中初始化你的app
創(chuàng)建模塊
- 如果你的模塊有內(nèi)部狀態(tài)或者是refs, 推薦使用 class extends React.Component 而不是 React.createClass,也就是說推薦使用ES6語法創(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>;
}
}
- 如果你的模塊沒有狀態(tà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的模塊. 但是,如果整個文件夾是一個模塊,使用 index.js作為入口文件,然后直接使用 index.js 或者文件夾名作為模塊的名稱
// bad
import Footer from './Footer/Footer';
// bad
import Footer from './Footer/index';
// good
import Footer from './Footer';
- 高階模塊命名: 對于生成一個新的模塊,其中的模塊名 displayName 應(yīng)該為高階模塊名和傳入模塊名的組合. 例如, 高階模塊 withFoo(), 當(dāng)傳入一個 Bar 模塊的時候, 生成的模塊名 displayName 應(yīng)該為 withFoo(Bar)
為什么?一個模塊的 displayName 可能會在開發(fā)者工具或者錯誤信息中使用到,因此有一個能清楚的表達(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)的屬性來用作其他的用途
為什么?對于style 和 className這樣的屬性名,我們都會默認(rèn)它們代表一些特殊的含義,如元素的樣式,CSS class的名稱。在你的應(yīng)用中使用這些屬性來表示其他的含義會使你的代碼更難閱讀,更難維護(hù),并且可能會引起bug
jsx
// bad
<MyComponent style="fancy" />
// good
<MyComponent variant="fancy" />
聲明模塊
- 不要使用 displayName 來命名React模塊,而是使用引用來命名模塊, 如 class 名稱
// bad
export default React.createClass({
displayName: 'ReservationCard',
// stuff goes here
});
// good
export default class ReservationCard extends React.Component {
}
代碼對齊
- 遵循以下的JSX語法縮進(jìn)/格式
// bad
<Foo superLongParam="bar"
anotherSuperLongParam="baz" />
// good, 有多行屬性的話, 新建一行關(guān)閉標(biāo)簽
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
/>
// 若能在一行中顯示, 直接寫成一行
<Foo bar="bar" />
// 子元素按照常規(guī)方式縮進(jìn)
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
>
<Quux />
</Foo>
單引號還是雙引號
- 對于JSX屬性值總是使用雙引號("), 其他均使用單引號(')
為什么? HTML屬性也是用雙引號, 因此JSX的屬性也遵循此約定
// bad
<Foo bar='bar' />
// good
<Foo bar="bar" />
// bad
<Foo style={{ left: "20px" }} />
// good
<Foo style={{ left: '20px' }} />
空格
- 總是在自動關(guān)閉的標(biāo)簽前加一個空格,正常情況下也不需要換行
// bad
<Foo/>
// very bad
<Foo />
// bad
<Foo
/>
// good
<Foo />
- 不要在JSX {} 引用括號里兩邊加空格
// 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(感覺是以類似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)注為圖片了, 所以沒有必要再在 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
// bad - not an ARIA role
<div role="datepicker" />
// bad - abstract ARIA role
<div role="range" />
// good
<div role="button" />
- 不要在標(biāo)簽上使用 accessKey 屬性
為什么? 屏幕助讀器在鍵盤快捷鍵與鍵盤命令時造成的不統(tǒng)一性會導(dǎo)致閱讀性更加復(fù)雜
// bad
<div accessKey="h" />
// good
<div />
- 避免使用數(shù)組的index來作為屬性key的值,推薦使用唯一ID
// bad
{todos.map((todo, index) =>
<Todo
{...todo}
key={index}
/>
)}
// good
{todos.map(todo => (
<Todo
{...todo}
key={todo.id}
/>
))}
- 對于所有非必須的屬性,總是手動去定義defaultProps屬性
為什么? propTypes 可以作為模塊的文檔說明, 并且聲明 defaultProps 的話意味著閱讀代碼的人不需要去假設(shè)一些默認(rèn)值。更重要的是, 顯示的聲明默認(rèn)屬性可以讓你的模塊跳過屬性類型的檢查
// 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)算符
為什么? 除非你很想傳遞一些不必要的屬性。對于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} />
}
}
}
- 只有在清楚明白擴(kuò)展對象時才使用擴(kuò)展運(yùn)算符。這非常有用尤其是在使用Mocha測試組件的時候
export default function Foo {
const props = {
text: '',
isPublished: false
}
return (<div {...props} />);
}
- 特別提醒:盡可能地篩選出不必要的屬性。同時,使用prop-types-exact來預(yù)防問題出現(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; }}
/>
括號
- 將多行的JSX標(biāo)簽寫在 ()里
// 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)簽
- 對于沒有子元素的標(biāo)簽來說總是自己關(guān)閉標(biāo)簽
// bad
<Foo className="stuff"></Foo>
// good
<Foo className="stuff" />
- 如果模塊有多行的屬性, 關(guān)閉標(biāo)簽時新建一行
// bad
<Foo
bar="bar"
baz="baz" />
// good
<Foo
bar="bar"
baz="baz"
/>
函數(shù)/方法
- 使用箭頭函數(shù)來獲取本地變量
function ItemList(props) {
return (
<ul>
{props.items.map((item, index) => (
<Item
key={item.key}
onClick={() => doSomethingWith(item.name, index)}
/>
))}
</ul>
);
}
- 當(dāng)在 render() 里使用事件處理方法時,提前在構(gòu)造函數(shù)里把 this 綁定上去
為什么? 在每次 render 過程中, 再調(diào)用 bind 都會新建一個新的函數(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ì)上它并不是私有的
為什么?_ 下劃線前綴在某些語言中通常被用來表示私有變量或者函數(shù)。但是不像其他的一些語言,在JS中沒有原生支持所謂的私有變量,所有的變量函數(shù)都是共有的。盡管你的意圖是使它私有化,在之前加上下劃線并不會使這些變量私有化,并且所有的屬性(包括有下劃線前綴及沒有前綴的)都應(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開源項(xiàng)目OneM【500+ star】地址(按照企業(yè)開發(fā)標(biāo)準(zhǔn)搭建框架完成開發(fā)的):https://github.com/guangqiang-liu/OneM:歡迎小伙伴們 star
- 作者簡書主頁:包含60多篇RN開發(fā)相關(guān)的技術(shù)文章http://www.itdecent.cn/u/023338566ca5 歡迎小伙伴們:多多關(guān)注,多多點(diǎn)贊