React/JSX 编码规范

目录

  1. 基本规范
  2. 创建模块
  3. 命名
  4. 声明模块
  5. 代码对齐
  6. 单引号还是双引号
  7. 空格
  8. 属性
  9. 引用
  10. 括号
  11. 标签
  12. 函数
  13. 模块生命周期
  14. [A11Y 无障碍规范](#A11Y 无障碍规范)
  15. Hook

基本规范

  1. 每个文件只写一个模块;多个无状态函数组件可以放在单个文件中。 eslint: react/no-multi-comp
  2. 推荐使用 JSX 语法。
  3. 不要使用 React.createElement,除非从非 JSX 文件初始化应用。

创建模块

Class /createClass/ 无状态组件选择

  1. 组件存在内部 state 或者 ref,推荐使用 class extends React.Component,不使用 React.createClass

eslint: react/prefer-es6-classreact/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>;
  }
}
  1. 组件无状态、无 ref,推荐使用普通 function 函数,不要用 class,不推荐依赖名称推断的箭头函数

    // bad
    class Listing extends React.Component {
    render() {
    return

    {this.props.hello}
    ;
    }
    }

    // bad (不推荐依赖函数名推断)
    const Listing = ({ hello }) => (

    {hello}
    );

    // good
    function Listing({ hello }) {
    return

    {hello}
    ;
    }


命名

eslint: react/jsx-pascal-case

  1. 文件名 :帕斯卡命名,例:ReservationCard.jsx

  2. 组件引用:React 组件名帕斯卡命名;组件实例使用小驼峰命名

    // bad
    import reservationCard from './ReservationCard';
    // good
    import ReservationCard from './ReservationCard';

    // bad
    const ReservationItem = ;
    // good
    const reservationItem = ;

  3. 模块命名 :组件名和文件名保持一致;文件夹作为组件入口时,使用index.js,直接引入文件夹名

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

  4. 高阶组件 HOC :生成组件的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;
    }

  5. 属性命名 :不要复用 DOM 原生属性名用于自定义含义(不要用style/className做业务标记)

    // bad

    // good


声明模块

  1. 不要手动写displayName,直接使用 class / 函数名称作为组件名称

    // bad
    export default React.createClass({
    displayName: 'ReservationCard',
    });

    // good
    export default class ReservationCard extends React.Component {
    }

  2. 禁止使用require引入组件 / 图片,统一使用 ES6 import语法

    // bad
    const qrCodeImg = require('@/assets/img/enter.png');

    // good
    import qrCodeImg from '@/assets/img/enter.png';


代码对齐

eslint: react/jsx-closing-bracket-locationreact/jsx-closing-tag-location

  1. 多行属性:每个属性单独一行,闭合标签另起一行

  2. 单行可容纳全部属性:直接写一行

  3. 子元素正常缩进

    // bad

    // good,多行属性,闭合标签另起一行

    // 单行容纳直接一行

    // 子元素缩进
    <Foo
    superLongParam="bar"
    anotherSuperLongParam="baz"


单引号还是双引号

eslint: jsx-quotes JSX 属性值使用双引号 " ;JS 普通字符串使用单引号 ',对齐 HTML 习惯。

复制代码
// bad
<Foo bar='bar' />
// good
<Foo bar="bar" />

空格

  1. 自闭合标签 / 前面保留一个空格

eslint: react/jsx-tag-spacing

复制代码
// bad
<Foo/>
// very bad
<Foo                 />
// bad
<Foo
/>
// good
<Foo />
  1. JSX {} 表达式内部不要加多余空格

eslint: react/jsx-curly-spacing

复制代码
// bad
<Foo bar={ baz } />
// good
<Foo bar={baz} />
  1. JSX 属性等号两侧禁止空格

eslint: react/jsx-equals-spacing

复制代码
// bad
<Hello name = {firstname} />;
<Hello name ={firstname} />;
<Hello name= {firstname} />;

// good
<Hello name={firstname} />;

属性

  1. JSX 属性名使用小驼峰 camelCase

    // bad

    // good

  2. 属性值为true时,直接省略值

eslint: react/jsx-boolean-value

复制代码
// bad
<Foo hidden={true} />
// good
<Foo hidden />
  1. 禁止使用未知 DOM 属性,class 写成 className

eslint: react/no-unknown-property

复制代码
// bad
<div class="hello">Hello World</div>
// good
<div className="hello">Hello World</div>
  1. <img>标签必须写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" />
  1. alt 文本不要包含 image /photo/picture 这类图片描述词

eslint: jsx-a11y/img-redundant-alt

复制代码
// bad
<img src="hello.jpg" alt="Picture of me waving hello" />
// good
<img src="hello.jpg" alt="Me waving hello" />
  1. 使用合法有效的 ARIA role,禁止抽象 role

eslint: jsx-a11y/aria-role

复制代码
// bad - 非法role
<div role="datepicker" />
// bad - 抽象role
<div role="range" />
// good
<div role="button" />
  1. 禁止使用accessKey属性

eslint: jsx-a11y/no-access-key

复制代码
// bad
<div accessKey="h" />
// good
<div />
  1. 数组渲染列表,key不要使用数组 index,优先使用业务唯一 id

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

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

  2. 非必传属性,显式定义defaultProps,作为组件文档

    // bad
    function SFC({ foo, bar, children }) {
    return

    {foo}{bar}{children}
    ;
    }
    SFC.propTypes = {
    foo: PropTypes.number.isRequired,
    bar: PropTypes.string,
    children: PropTypes.node,
    };

    // good
    function SFC({ foo, bar, children }) {
    return

    {foo}{bar}{children}
    ;
    }
    SFC.propTypes = {
    foo: PropTypes.number.isRequired,
    bar: PropTypes.string,
    children: PropTypes.node,
    };
    SFC.defaultProps = {
    bar: '',
    children: null,
    };

  3. 谨慎使用扩展运算符{...props},尽量解构剔除不需要的属性;高阶组件为特例允许透传

    // good 剔除无关属性再透传
    render() {
    const { irrelevantProp, ...relevantProps } = this.props;
    return <WrappedComponent {...relevantProps} />
    }

  4. JSX 单行最多 1 个 prop;超过 1 个就换行分行书写

eslint: react/jsx-max-props-per-line

复制代码
// bad
<Hello lastName="Smith" firstName="John" />;
// good
<Hello
  firstName="John"
  lastName="Smith"
/>;
  1. JSX 禁止重复属性

eslint: react/jsx-no-duplicate-props

复制代码
// bad
<Hello
  name="John"
  name="John"
/>;
// good
<Hello
  firstName="John"
  lastName="Smith"
/>;
  1. a 标签 target="_blank" 必须增加 rel="noopener noreferrer",防止安全漏洞

eslint: react/jsx-no-target-blank

复制代码
// bad
<a target='_blank' href="http://example.com"></a>
// good
<a target="_blank" rel="noopener noreferrer" href="http://example.com"></a>

引用

ref 优先使用回调 ref,禁止字符串 ref

eslint: react/no-string-refs

复制代码
// bad
<Foo ref="myRef" />
// good
<Foo ref={(ref) => { this.myRef = ref; }} />

括号

多行 JSX 返回必须包裹在()内;单行 JSX 不需要

eslint: react/jsx-wrap-multilines

复制代码
// 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>;
}

标签

  1. 无子女的标签必须自闭合

eslint: react/self-closing-comp

复制代码
// bad
<Foo className="stuff"></Foo>
// good
<Foo className="stuff" />
  1. 多行属性时,自闭合标签/>单独起一行

eslint: react/jsx-closing-bracket-location

复制代码
// bad
<Foo
  bar="bar"
  baz="baz" />

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

函数

  1. 列表渲染事件回调,使用箭头函数捕获局部变量

    function ItemList(props) {
    return (


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

    );
    }

  2. class 组件事件处理函数,推荐类属性箭头函数,避免 render 里 bind,减少每次渲染新建函数开销

eslint: react/jsx-no-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} />;
  }
}

// very good 类属性箭头函数(推荐)
class extends React.Component {
  onClickDiv = () => {
    // do stuff
  }
  render() {
    return <div onClick={this.onClickDiv} />
  }
}
  1. render必须显式return返回值

eslint: react/require-render-return

复制代码
// bad
render() {
  (<div />);
}

// good
render() {
  return (<div />);
}
  1. 禁止在componentDidUpdate中调用setState

eslint: react/no-did-update-set-state

复制代码
// bad
class Hello extends React.Component {
  componentDidUpdate() {
    this.setState({
      name: this.props.name.toUpperCase()
    });
  }
  render() {
    return <div>Hello {this.state.name}</div>;
  }
};

// good
class Hello extends React.Component {
  componentDidUpdate() {
    this.props.onUpdate();
  }
  render() {
    return <div>Hello {this.props.name}</div>;
  }
};
  1. 禁止在componentWillUpdate调用setState

eslint: react/no-will-update-set-state

  1. 不要使用ReactDOM.render返回的实例

eslint: react/no-render-return-value

复制代码
// bad
const inst = ReactDOM.render(<App />, document.body);
doSomethingWithInst(inst);

// good
ReactDOM.render(<App ref={doSomethingWithInst} />, document.body);
ReactDOM.render(<App />, document.body, doSomethingWithInst);

模块生命周期

React 16.3+ 生命周期规范

  1. 新增:static getDerivedStateFromPropsgetSnapshotBeforeUpdate
  2. 标记 UNSAFE,禁止使用:UNSAFE_componentWillMount / UNSAFE_componentWillUpdate / UNSAFE_componentWillReceiveProps
class 组件生命周期顺序
  1. constructor 构造函数
  2. static getDerivedStateFromProps 组件接收新数据
  3. render()
  4. componentDidMount 首次渲染完成
  5. shouldComponentUpdate 判断是否重渲染
  6. getSnapshotBeforeUpdate
  7. componentDidUpdate 更新渲染结束
  8. componentWillUnmount 组件销毁,清理资源
方法命名约定
  • 事件回调:onClickSubmit() / onChangeDescription()
  • render 内 getter:getSelectReason() / getFooterContent()
  • 分段渲染函数:renderNavigation() / renderProfilePicture()
propTypes /defaultProps 示例
复制代码
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;

A11Y 无障碍规范

全部规则来自 eslint-plugin-jsx-a11y

  1. ARIA role 必须合法、非抽象;自定义组件不检测

    // good

  2. aria-* 属性名称必须合法

    // bad

    // good

  3. ARIA 属性值类型合法(布尔属性只能 true/false)

    // Bad
    foo
    // Good

  4. img/area/input type=image/object 必须提供替代文本

    // Bad

    // Good
    Foo eating a sandwich.

  5. img alt 禁止冗余描述词 (image/photo/picture)

  6. label 必须关联表单控件,两种方式:内部包裹控件 /htmlFor + id 绑定

    // bad

    // good


  7. onMouseOver / onMouseOut 必须配套 onFocus / onBlur,支持键盘访问

    // good

    void 0} onFocus={()=>void 0} />
    void 0} onBlur={()=>void 0} />
  8. 交互式元素(带 onClick、role 交互角色)必须可聚焦

    // bad
    Submit
    // good
    Click me!

  9. role 如果有必填 aria 属性,必须补齐

    // bad

    // good

  10. 不要使用大于 0 的 tabIndex,不要手动干预 tab 顺序

    // Bad
    foo
    // Good
    foo
    bar

  11. 标题 h1~h6 必须包含可读内容,不能整体 aria-hidden

    // Bad

    // Good

    Heading Content!

  12. html 标签必须设置 lang 属性

    // Bad

    // Good
  13. 禁止使用<marquee><blink>干扰阅读元素

  14. scope属性只能 放在<th>标签

    // bad

    // good
  15. Emoji 表情增加role="img"aria-label,支持读屏

    // good
    🐼

  16. iframe 必须有非空 title

    // Bad