React/JSX 編碼規範

weixin_34194087發表於2017-11-29

我們在企業的專案開發中,制定一套完善有效的編碼規範是極為有必要的,也是必須的,因為好的編碼規範能極大的降低組員開發時的溝通成本,也更加便於codeReview,而且還能約束自己,養成正確的程式設計習慣。總之有了編碼規範,你會發現你慢慢的喜歡上自己寫的程式碼了。

目錄

  1. 基本規範
  2. 建立模組
  3. 命名
  4. 宣告模組
  5. 程式碼對齊
  6. 單引號還是雙引號
  7. 空格
  8. 屬性
  9. 引用
  10. 括號
  11. 標籤
  12. 函式/方法
  13. 模組生命週期

基本規範

  • 每個檔案只寫一個模組
    • 但是多個無狀態模組可以放在單個檔案中. eslint: react/no-multi-comp
  • 推薦使用JSX語法
  • 不要使用 React.createElement,除非從一個非JSX的檔案中初始化你的app

建立模組

  • 如果你的模組有內部狀態或者是refs, 推薦使用 class extends React.Component 而不是 React.createClass,也就是說推薦使用ES6語法建立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>;
  }
}
  • 如果你的模組沒有狀態或是沒有引用refs, 推薦使用普通函式(非箭頭函式)而不是類
// 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>;
}

命名

  • 副檔名: 使用.jsx作為React元件的副檔名
  • 檔名: 使用帕斯卡命名法命名檔案,譬如ReservationCard.jsx
  • 引用命名: 使用帕斯卡命名法命名元件和camelCase命名例項
// bad
const reservationCard = require('./ReservationCard');

// good
const ReservationCard = require('./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

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語法縮排/格式
// bad
<Foo superLongParam="bar"
     anotherSuperLongParam="baz" />

// good, 有多行屬性的話, 新建一行關閉標籤
<Foo
  superLongParam="bar"
  anotherSuperLongParam="baz"
/>

// 若能在一行中顯示, 直接寫成一行
<Foo bar="bar" />

// 子元素按照常規方式縮排
<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' }} />

空格

  • 總是在自動關閉的標籤前加一個空格,正常情況下也不需要換行
// bad
<Foo/>

// very bad
<Foo                 />

// bad
<Foo
 />

// good
<Foo />
  • 不要在JSX {} 引用括號裡兩邊加空格
// bad
<Foo bar={ baz } />

// good
<Foo bar={baz} />

屬性

  • JSX屬性名使用駱駝式風格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> 標籤總是新增 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"包括圖片含義這樣的詞, 中文也一樣

為什麼? 螢幕助讀器已經把 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
// bad - not an ARIA role
<div role="datepicker" />

// bad - abstract ARIA role
<div role="range" />

// good
<div role="button" />
  • 不要在標籤上使用 accessKey 屬性

為什麼? 螢幕助讀器在鍵盤快捷鍵與鍵盤命令時造成的不統一性會導致閱讀性更加複雜

// bad
<div accessKey="h" />

// good
<div />
  • 避免使用陣列的index來作為屬性key的值,推薦使用唯一ID
// 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 <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,
}
  • 儘可能少地使用擴充套件運算子

為什麼? 除非你很想傳遞一些不必要的屬性。對於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 (<div {...props} />);
}    
  • 特別提醒:儘可能地篩選出不必要的屬性。同時,使用prop-types-exact來預防問題出現
 //good
render() {
  const { irrelevantProp, ...relevantProps  } = this.props;
  return <WrappedComponent {...relevantProps} />
}

//bad
render() {
  const { irrelevantProp, ...relevantProps  } = this.props;
  return <WrappedComponent {...this.props} />
}    

引用

  • 總是在Refs裡使用回撥函式
// bad
<Foo
  ref="myRef"
/>

// good
<Foo
  ref={(ref) => { this.myRef = ref; }}
/>

括號

  • 將多行的JSX標籤寫在 ()裡
// 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>;
}

標籤

  • 對於沒有子元素的標籤來說總是自己關閉標籤
// bad
<Foo className="stuff"></Foo>

// good
<Foo className="stuff" />
  • 如果模組有多行的屬性, 關閉標籤時新建一行
// bad
<Foo
  bar="bar"
  baz="baz" />

// good
<Foo
  bar="bar"
  baz="baz"
/>

函式/方法

  • 使用箭頭函式來獲取本地變數
function ItemList(props) {
  return (
    <ul>
      {props.items.map((item, index) => (
        <Item
          key={item.key}
          onClick={() => doSomethingWith(item.name, index)}
        />
      ))}
    </ul>
  );
}
  • 當在 render() 裡使用事件處理方法時,提前在建構函式裡把 this 繫結上去

為什麼? 在每次 render 過程中, 再呼叫 bind 都會新建一個新的函式,浪費資源.

// 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模組中,不要給所謂的私有函式新增 _ 字首,本質上它並不是私有的

為什麼?_ 下劃線字首在某些語言中通常被用來表示私有變數或者函式。但是不像其他的一些語言,在JS中沒有原生支援所謂的私有變數,所有的變數函式都是共有的。儘管你的意圖是使它私有化,在之前加上下劃線並不會使這些變數私有化,並且所有的屬性(包括有下劃線字首及沒有字首的)都應該被視為是共有的

// 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 的生命週期函式
  1. 可選的 static 方法
  2. constructor 建構函式
  3. getChildContext 獲取子元素內容
  4. componentWillMount 模組渲染前
  5. componentDidMount 模組渲染後
  6. componentWillReceiveProps 模組將接受新的資料
  7. shouldComponentUpdate 判斷模組需不需要重新渲染
  8. componentWillUpdate 上面的方法返回 true, 模組將重新渲染
  9. componentDidUpdate 模組渲染結束
  10. componentWillUnmount 模組將從DOM中清除, 做一些清理任務
  11. 點選回撥或者事件處理器 如 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 的生命週期函式,與使用class稍有不同
  1. displayName 設定模組名稱
  2. propTypes 設定屬性的型別
  3. contextTypes 設定上下文型別
  4. childContextTypes 設定子元素上下文型別
  5. mixins 新增一些mixins
  6. statics
  7. defaultProps 設定預設的屬性值
  8. getDefaultProps 獲取預設屬性值
  9. getInitialState 或者初始狀態
  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

總結

有了好的編碼規範,這只是第一步,最重要的是,團隊要達成一致,遵守這套編碼,這樣才能發揮出規範強大的作用。

更多文章

  • 作者React Native開源專案OneM【500+ star】地址(按照企業開發標準搭建框架完成開發的):https://github.com/guangqiang-liu/OneM:歡迎小夥伴們 star
  • 作者簡書主頁:包含60多篇RN開發相關的技術文章http://www.jianshu.com/u/023338566ca5 歡迎小夥伴們:多多關注多多點贊
  • 作者React Native QQ技術交流群:620792950 歡迎小夥伴進群交流學習
  • 友情提示:在開發中有遇到RN相關的技術問題,歡迎小夥伴加入交流群(620792950),在群裡提問、互相交流學習。交流群也定期更新最新的RN學習資料給大家,謝謝大家支援!

小夥伴們掃下方二維碼加入RN技術交流QQ群

6342050-53baff819f7a76d4.jpg
QQ群二維碼,500+ RN工程師在等你加入哦

相關文章