React/JSX 編碼規(guī)范

我們?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ě)的代碼了。

目錄

  1. 基本規(guī)范
  2. 創(chuàng)建模塊
  3. 命名
  4. 聲明模塊
  5. 代碼對(duì)齊
  6. 單引號(hào)還是雙引號(hào)
  7. 空格
  8. 屬性
  9. 引用
  10. 括號(hào)
  11. 標(biāo)簽
  12. 函數(shù)/方法
  13. 模塊生命周期

基本規(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ù)
  1. 可選的 static 方法
  2. constructor 構(gòu)造函數(shù)
  3. getChildContext 獲取子元素內(nèi)容
  4. componentWillMount 模塊渲染前
  5. componentDidMount 模塊渲染后
  6. componentWillReceiveProps 模塊將接受新的數(shù)據(jù)
  7. shouldComponentUpdate 判斷模塊需不需要重新渲染
  8. componentWillUpdate 上面的方法返回 true, 模塊將重新渲染
  9. componentDidUpdate 模塊渲染結(jié)束
  10. componentWillUnmount 模塊將從DOM中清除, 做一些清理任務(wù)
  11. 點(diǎn)擊回調(diào)或者事件處理器 如 onClickSubmit() 或 onChangeDescription()
  12. render 里的 getter 方法 如 getSelectReason() 或 getFooterContent()
  13. 可選的 render 方法 如 renderNavigation() 或 renderProfilePicture()
  14. 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稍有不同
  1. displayName 設(shè)定模塊名稱
  2. propTypes 設(shè)置屬性的類型
  3. contextTypes 設(shè)置上下文類型
  4. childContextTypes 設(shè)置子元素上下文類型
  5. mixins 添加一些mixins
  6. statics
  7. defaultProps 設(shè)置默認(rèn)的屬性值
  8. getDefaultProps 獲取默認(rèn)屬性值
  9. getInitialState 或者初始狀態(tài)
  10. getChildContext
  11. componentWillMount
  12. componentDidMount
  13. componentWillReceiveProps
  14. shouldComponentUpdate
  15. componentWillUpdate
  16. componentDidUpdate
  17. componentWillUnmount
  18. clickHandlers or eventHandlers like onClickSubmit() or onChangeDescription()
  19. getter methods for render like getSelectReason() or getFooterContent()
  20. Optional render methods like renderNavigation() or renderProfilePicture()
  21. 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)贊
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容