前端React框架和jsx语法的编码规范

发布时间 2023-03-31 15:44:05作者: 席超

基本规则(Basic Rules)

每个文件只包含一个 React 组件

  • 然而,在一个文件里包含多个没有 state 或纯组件是允许的。eslint: react/no-multi-comp.
  • 经常用 JSX 语法。
  • 不要用 React.createElement, 除非你从一个非 JSX 文件中初始化 app。

Class vs React.createClass vs stateless

如果你要用 state refs, 最好用 class extends React.Component 而不是 React.createClass. eslint: react/prefer-es6-class react/prefer-stateless-function

// 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>;
  }
}

如果你没有使用 state、 refs ,最好用正常函数(不是箭头函数)也不是 class:

// 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>;
}

混合(Mixins)

不要用 mixins.

Why? mixins 会引入一些隐含依赖,导致命名冲突,会导致滚雪球式的复杂度。大多数情况下,mixins 都可以通过组件,高阶组件 HOC或者工具模块更好的实现。Why? mixins 会引入一些隐含依赖,导致命名冲突,会导致滚雪球式的复杂度。大多数情况下,mixins 都可以通过组件,高阶组件 HOC或者工具模块更好的实现。

命名(Naming)

扩展名: 用 .jsx 作为组件扩展名。
文件名: 用大驼峰作为文件名,如: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.jsx 作为文件名,同时用文件夹名作为组件名

// bad
import Footer from './Footer/Footer';
// bad
import Footer from './Footer/index';

// good
import Footer from './Footer';

高阶组件HOC命名: 用高阶组件名和传入的组件名组合作为生成的组件的 displayName。举个例子,一个高阶组件 withFoo(), 当传入一个组件 Bar 应该生成一个新的组件,他的 displayName 属性是 withFoo(Bar)。

Why? 组件的 displayName 可以用于开发者工具或者错误信息中,同时还有一个值可以清晰的表达这种组件关系,这可以帮助人们理解到底发生了什么 Why? 组件的 displayName 可以用于开发者工具或者错误信息中,同时还有一个值可以清晰的表达这种组件关系,这可以帮助人们理解到底发生了什么

// 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;
}

Props 命名: 避免用 DOM 组件的属性名表达不同的意义

Why? 人们期望 style、 className 这种属性代表一个明确的意义。为应用程序的一个子集改变此API会使代码的可读性降低,维护性降低,并可能导致错误。 Why? 人们期望 style、 className 这种属性代表一个明确的意义。为应用程序的一个子集改变此API会使代码的可读性降低,维护性降低,并可能导致错误。

// bad
<MyComponent style="fancy" />
// bad
<MyComponent className="fancy" />

// good
<MyComponent variant="fancy" />

声明(Declaration)

不要通过 displayName 命名组件。最好通过引用命名组件。

// 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-location react/jsx-closing-tag-location

// bad
<Foo superLongParam="bar"
anotherSuperLongParam="baz" />

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

// 如果能放在一行,也可以用单行表示
<Foo bar="bar" />

// Foo 里面的标签正常缩进
<Foo
 superLongParam="bar"
 anotherSuperLongParam="baz"
>
 <Quux />
</Foo>

// bad
{showButton &&
 <Button />
}
// bad
{
 showButton &&
 <Button />
}

// good
{showButton && (
 <Button />
)}
// good
{showButton && <Button />}

引号(Quotes)

在 JSX 属性中用双引号("),但是在js里用单引号(')。eslint: jsx-quotes

Why? 正常的 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)

props 用小驼峰

// bad
<Foo
  UserName="hello"
  phone_number={12345678}
/>

// good
<Foo
  userName="hello"
  phoneNumber={12345678}
/>

如果 prop 的值是 true 可以忽略这个值,直接写 prop 名就可以。eslint: react/jsx-boolean-value

// bad
<Foo
  hidden={true}
/>
// good
<Foo
  hidden
/>

// good
<Foo hidden />

标签通常会设置 alt 属性。如果图片是表现型的, alt可以是空字符串或者必须有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", "picture" 这些单词。eslint: jsx-a11y/img-redundant-alt

Why? 因为屏幕阅读器已经将 img 发音为图片了,所以这个信息就不需要出现在 alt 文本里了。

// bad
<img src="hello.jpg" alt="Picture of me waving hello" />

// good
<img src="hello.jpg" alt="Me waving hello" />

只用可用的,不抽象的 ARIA roles. eslint: jsx-a11y/aria-role

// bad - 不是一个 ARIA role
<div role="datepicker" />

// bad - 抽象的 ARIA role
<div role="range" />

// good
<div role="button" />

不要在元素上用 accessKey。eslint: jsx-a11y/no-access-key

Why? 使用屏幕阅读器和键盘的人使用的键盘快捷键和键盘命令之间的不一致使得可访问性变得复杂。

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

// good
<div />

避免用数组下标作为 key 属性,推荐用稳定的 ID

Why? 不使用稳定杆的 ID is an anti-pattern 会对组件性能产生消极影响,并且组件状态容易出现问题。如果数组元素可能会发生变化,我们不推荐使用下标作为key。

// bad
{todos.map((todo, index) =>
  <Todo
    {...todo}
    key={index}
  />
)}

// good
{todos.map(todo => (
  <Todo
    {...todo}
    key={todo.id}
  />
))}

对于所有非必须属性,定义一个明确的默认值。

Why? propTypes 是一个文档形式,同时提供默认属性意味着使用者不需要假定那么多值。另外,这也意味着你的代码可以忽略类型检查。

// 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,
};

少用props扩展运算符,既 {...props}

Why? 除非你更喜欢把不需要的props属性传入组件。而且对于 v15.6.1 及更早以前的 React, 你只能给DOM元素传非HTML属性的props。

例外:
HOC 是代理 props 并且提成了propTypes

function HOC(WrappedComponent) {
  return class Proxy extends React.Component {
    Proxy.propTypes = {
      text: PropTypes.string,
      isLoading: PropTypes.bool
    };

    render() {
      return <WrappedComponent {...this.props} />
    }
  }
}

扩展一个已知的,有明确属性的对象也是可以的。这个对用 Mocha 的 beforeEach 函数做单测时尤其有用。

export default function Foo {
 const props = {
   text: '',
   isPublished: false
 }
 return (<div {...props} />);
}

使用说明:尽可能过滤出不需要的属性。同时用prop-type-exact去帮助避免bug。

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

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

引用(Refs)

推荐用 ref callback 函数。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 variant="long body" foo="bar">
            <MyChild />
          </MyComponent>;
}

// good
render() {

return (
  <MyComponent variant="long body" foo="bar">
    <MyChild />
  </MyComponent>
);
}

// good, 单行可以直接写
render() {
  const body = <div>hello</div>;
  return <MyComponent>{body}</MyComponent>;
} 

标签(Tags)

当没有子元素时,最好用自闭合标签。eslint: react/self-closing-comp

// bad
<Foo variant="stuff"></Foo>

// good
<Foo variant="stuff" />

如果你的组件有多行属性,用他的闭合标签单独作为结束行。eslint: react/jsx-closing-bracket-location

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

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

方法(Methods)

用箭头函数关闭局部变量。

function ItemList(props) {
  return (
    <ul>
      {props.items.map((item, index) => (
        <Item
          key={item.key}
          onClick={() => doSomethingWith(item.name, index)}
        />
      ))}
    </ul>
  );
}

在构造函数里绑定事件处理函数。eslint: react/jsx-no-bind

Why? render 函数中的绑定调用在每次 render 的时候都会创建一个新的函数。

// 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 组件里使用下划线作为内部方法名前缀。

Why? 下划线前缀有时候在其他语言里被用于表示私有。但是 JavaScript 原生并不支持私有,所有东西都是公有的。尽管在你的意图里,对你的属性添加下划线前缀不是真的是他变成私有属性,而且任何属性(不论是不是下划线前缀)都被认为是公有的。

// bad
React.createClass({
  _onClickSubmit() {
    // do stuff
  },
  // other stuff
});

// good
class extends React.Component {
  onClickSubmit() {
    // do stuff
  }
  // other stuff
}

确保你的 render 函数有返回值。eslint: react/require-render-return

// bad
render() {
 (<div />);
}

// good
render() {
 return (<div />);
}

排序(Ordering)

class extends React.Component 内部属性的顺序:

  1. 可选的 static 方法
  2. constructor
  3. getChildContext
  4. componentWillMount
  5. componentDidMount
  6. componentWillReceiveProps
  7. shouldComponentUpdate
  8. componentWillUpdate
  9. componentDidUpdate
  10. componentWillUnmount
  11. clickHandlers or eventHandlers 如: onClickSubmit()、 onChangeDescription()
  12. getter methods for render 如: getSelectReason()、 getFooterContent()
  13. optional render methods 如: renderNavigation()、 renderProfilePicture()
  14. 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 内部属性排序:eslint: react/sort-comp

  1. displayName
  2. propTypes
  3. contextTypes
  4. childContextTypes
  5. 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 如: onClickSubmit()、 onChangeDescription()
  19. getter methods for render 如: getSelectReason()、 getFooterContent()
  20. optional render methods 如: renderNavigation()、 renderProfilePicture
  21. render

isMounted

不要用 isMounted。eslint: react/no-is-mounted

Why? [isMounted 是反模式][anti-pattern], 这个在 ES6 class 里不允许的,而且即将被官方废弃。