React 编码规约

React 编码规约

1 编码风格

1.1 缩进

  • 1.1.1【强制】JSX 语法使用 2 个空格缩进。eslint: react/jsx-indent react/jsx-indent-props react/jsx-closing-tag-location

    对于 JSX 语法,遵循与 JS 规约和 HTML 规约一致的 2 个空格缩进,不要使用 4 空格或 tab 缩进:

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

1.2 空格

  • 1.2.1【强制】自闭合标签的斜线前有且仅有一个空格。eslint: no-multi-spaces react/jsx-tag-spacing

    // bad
    <Foo/>
    
    // very bad
    <Foo                 />
    
    // bad
    <Foo
     />
    
    // good
    <Foo />
    
  • 1.2.1【强制】JSX 行内属性之间仅有一个空格。eslint: react/jsx-props-no-multi-spaces

    同一行中标签和属性之间、属性之间只有一个空格。

    // bad
    <App  spacy />
    <App too  spacy />
    
    // good
    <App cozy />
    <App very cozy />
    
  • 1.2.2【强制】JSX 属性的大括号内部两侧无空格。eslint: react/jsx-curly-spacing

    // bad
    <Foo bar={ baz } />
    
    // good
    <Foo bar={baz} />
    
  • 1.2.3【强制】不要在 JSX 属性的等号两边加空格。eslint: jsx-equals-spacing

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

1.3 小括号

  • 1.3.1【强制】多行的 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 - 单行的 jsx 无需加圆括号
    render() {
      const body = <div>hello</div>;
      return <MyComponent>{body}</MyComponent>;
    }
    

1.4 标签

  • 1.4.1【强制】无子元素的标签需写成自闭合标签。eslint: react/self-closing-comp

    // bad
    <Foo variant="stuff"></Foo>
    
    // good
    <Foo variant="stuff" />
    
  • 1.4.2【强制】标签属性的换行。eslint: react/jsx-max-props-per-line react/jsx-first-prop-new-line

    对 JSX 标签属性的换行,遵循以下规则:

    • 标签名和它的属性可以写在一行,前提是不超过单行最大 100 字符数的限制
    • 如果标签有多个属性,且存在换行,则每个属性都需要换行独占一行
    // bad - 属性应全部换行,或全部跟组件名写在一行
    <Foo superLongParam="bar"
         anotherSuperLongParam="baz" />
    
    // good
    <Foo
      superLongParam="bar"
      anotherSuperLongParam="baz"
    />
    
    // good - 组件名和属性可以写在一行,前提是不超过单行最大字符限制
    <Foo bar="bar" />
    
    // bad
    <Hello foo={{
        }}
        bar />
    
    // good
    <Hello foo={{
    }} />
    
    <Hello
        foo={{
        }}
        bar
    />
    
  • 1.4.3【强制】标签的属性有多行时,结束标签需另起一行。eslint: react/jsx-closing-bracket-location

    // bad
    <Foo
      bar="bar"
      baz="baz" />
    
    // good
    <Foo
      bar="bar"
      baz="baz"
    />
    
  • 1.4.4【强制】禁止在有子节点的组件或 DOM 元素中使用 dangerouslySetInnerHTML 属性。eslint: react/no-danger-with-children

    // bad
    <div dangerouslySetInnerHTML={{ __html: "HTML" }}>
      Children
    </div>
    
    <Hello dangerouslySetInnerHTML={{ __html: "HTML" }}>
      Children
    </Hello>
    
    // good
    <div dangerouslySetInnerHTML={{ __html: "HTML" }} />
    
    <Hello dangerouslySetInnerHTML={{ __html: "HTML" }} />
    
    <div>
      Children
    </div>
    
    <Hello>
      Children
    </Hello>
    
  • 1.4.5【强制】HTML 自闭标签不能有子节点。eslint: react/void-dom-elements-no-children

    HTML 自闭标签,比如 img,br,hr,被统称为空 DOM 元素,不能给他们定义子节点。

    // bad
    <br>Children</br>
    <br dangerouslySetInnerHTML={{ __html: 'HTML' }} />
    
    // good
    <div>Children</div>
    <div dangerouslySetInnerHTML={{ __html: 'HTML' }} />
    
  • 1.4.6【推荐】不要使用危险属性。eslint: react/no-danger

    React中的危险属性是指那些已知会引起应用程序漏洞的属性。这些属性命名为 dangerouslyXyz 已经清楚地表明它们是危险的,应该尽量避免使用。详细文档

    // bad
    <div dangerouslySetInnerHTML={{ __html: "Hello World" }}></div>;
    
    // good
    <div>Hello World</div>;
    
  • 1.4.7【强制】JSX 语句的文本节点中不要使用注释字符串(例如,以//或/ *开头)。eslint: react/jsx-no-comment-textnodes

    // bad
    class Hello extends React.Component {
      render() {
        return (
          <div>// empty div</div>
        );
      }
    };
    
    class Hello extends React.Component {
      render() {
        return (
          <div>
            /* empty div */
          </div>
        );
      }
    };
    
    // good
    class Hello extends React.Component {
      render() {
        return <div>{/* empty div */}</div>;
      }
    };
    
    class Hello extends React.Component {
      render() {
        return <div /* empty div */></div>;
      }
    };
    
    class Hello extends React.Component {
      render() {
        return <div className={'foo' /* temp class */}</div>;
      }
    };
    
  • 1.4.8【强制】标签中禁止出现无意义字符,比如 > " } '。eslint: react/no-unescaped-entities

> 可用 &gt; 替代

" 可用 &quot;&ldquo;&#34; 或者 &rdquo; 替代

' 可用 &apos;&lsquo;&#39; 或者 &rsquo; 替代

} 可用 &#125; 替代

或者写在表达式里,比如 <div>{'>'}</div>

// bad
<MyComponent
  a="b">  {/* oops! */}
  c="d"
  Intended body text
</MyComponent>

// good
<div> &gt; </div>
<div> {'>'} </div>

2 语言特性

2.1 基本

  • 2.1.1【参考】使用 JSX 语法时,防止 React 变量被标记为未使用,可以使用 @jsx 标注来指定 React 之外的变量。eslint: react/jsx-uses-react

    // bad
    var React = require('react');
    // nothing to do with React
    
    /** @jsx Foo */
    var React = require('react');
    var Hello = <div>Hello {this.props.name}</div>;
    
    // good
    var React = require('react');
    var Hello = <div>Hello {this.props.name}</div>;
    
    /** @jsx Foo */
    var Foo = require('foo');
    var Hello = <div>Hello {this.props.name}</div>;
    
  • 2.1.2【强制】不要使用未声明的组件。eslint: react/jsx-no-undef react/jsx-uses-vars

    不允许没有引用组件就直接使用,也可能是组件名拼写错误。

    // bad
    <Hello name="John" />;
    
    // good
    import Hello from './Hello';
    
    <Hello name="John" />;
    
  • 2.1.3【强制】每个文件只包含一个 React 组件。eslint: react/no-multi-comp

    但是可以包含多个函数组件

  • 2.1.4【强制】不要在函数组件中使用 this。eslint: react/no-this-in-sfc

    // bad
    function Foo(props, context) {
      return (
        <div>
          {this.context.foo ? this.props.bar : ''}
        </div>
      );
    }
    
    // good
    function Foo(props, context) {
      return (
        <div>
          {context.foo ? props.bar : ''}
        </div>
      );
    }
    
  • 2.1.5【强制】使用 ES6 class 创建组件 ,而不是 createReactClass 。eslint: react/prefer-es6-class

    // bad
    const Listing = createReactClass({
     // ...
     render() {
       return <div>{this.state.hello}</div>;
     }
    });
    
    // good
    class Listing extends React.Component {
     // ...
     render() {
       return <div>{this.state.hello}</div>;
     }
    }
    
  • 2.1.6【参考】如果组件没有内部状态或 refs ,应使用函数组件,而不是类组件。eslint: react/prefer-stateless-function

    // bad
    class Listing extends React.Component {  
      render() {
        return <div>{this.props.hello}</div>;
      }
    }
    
    // bad
    const Listing = ({ hello }) => (
      <div>{hello}</div>
    );
    
    // good
    function Listing({ hello }) {
      return <div>{hello}</div>;
    }
    
  • 2.1.7【强制】不要使用 React.createElement,除非你不是用 JSX 文件初始化应用程序。

2.2 方法

  • 2.2.1【推荐】不要在 JSX 属性中使用 .bind()。eslint: react/jsx-no-bind

    这不利于组件性能,每次 render 都会创建一个新的函数。

    有 2 种替代方案:

    // bad
    class extends React.Component {
      onClickDiv() {
        // ...
      }
    
      render() {
        return <div onClick={this.onClickDiv.bind(this)} />;
      }
    }
    
    // good - 在 constructor 中绑定事件处理函数
    class extends React.Component {
      constructor(props) {
        super(props);
    
        this.onClickDiv = this.onClickDiv.bind(this);
      }
    
      onClickDiv() {
        // ...
      }
    
      render() {
        return <div onClick={this.onClickDiv} />;
      }
    }
    
    // good - 使用 react 的 property initializers 特性
    class extends React.Component {
      constructor(props) {
        super(props);
      }
    
      onClickDiv = () => {
        // ...
      }
    
      render() {
        return <div onClick={this.onClickDiv} />;
      }
    }
    
  • 2.2.2【强制】render 方法必须要有返回值。eslint: react/require-render-return

    // bad
    render() {
      (<div />);
    }
    
    // good
    render() {
      return (<div />);
    }
    
  • 2.2.3【强制】禁止使用 ReactDOM.render 的返回值。eslint: react/no-render-return-value

    render()返回 ReactComponent 实例的引用。然而,应该避免使用这个返回值,因为在某些情况下,React 的未来版本中 render 方法可能会异步执行。如果需要引用 ReactComponent 实例,根元素需要增加 ref 回调。

    // bad
    const inst = ReactDOM.render(<App />, document.body);
    doSomethingWithInst(inst);
    
    // good
    ReactDOM.render(<App ref={doSomethingWithInst} />, document.body);
    
    ReactDOM.render(<App />, document.body, doSomethingWithInst);
    
  • 2.2.4【强制】在扩展 React.PureComponent 时禁止使用 shouldComponentUpdate。eslint: react/no-redundant-should-component-update

    定义 React.PureComponent 扩展组件时使用 shouldComponentUpdate 虽然有效,但是扩展 PureComponent 变得毫无意义。

    // bad
    class Foo extends React.PureComponent {
      shouldComponentUpdate() {
        // do check
      }
    
      render() {
        return <div>Radical!</div>
      }
    }
    
    function Bar() {
      return class Baz extends React.PureComponent {
        shouldComponentUpdate() {
          // do check
        }
    
        render() {
          return <div>Groovy!</div>
        }
      }
    }
    
    // good
    class Foo extends React.Component {
      shouldComponentUpdate() {
        // do check
      }
    
      render() {
        return <div>Radical!</div>
      }
    }
    
    function Bar() {
      return class Baz extends React.Component {
        shouldComponentUpdate() {
          // do check
        }
    
        render() {
          return <div>Groovy!</div>
        }
      }
    }
    
    class Qux extends React.PureComponent {
      render() {
        return <div>Tubular!</div>
      }
    }
    
  • 2.2.5【强制】禁止使用已经废弃的方法。eslint: react/no-deprecated

    随着React版本升级,有些方法逐渐被弃用。

    // bad
    React.render(<MyComponent />, root);
    
    React.unmountComponentAtNode(root);
    
    React.findDOMNode(this.refs.foo);
    
    React.renderToString(<MyComponent />);
    
    React.renderToStaticMarkup(<MyComponent />);
    
    React.createClass({ /* Class object */ });
    
    const propTypes = {
      foo: PropTypes.bar,
    };
    
    //Any factories under React.DOM
    React.DOM.div();
    
    import React, { PropTypes } from 'react';
    
    class Foo extends React.Component {
      componentWillMount() { }
      componentWillReceiveProps() { }
      componentWillUpdate() { }
      // ...
    }
    
    class Foo extends React.PureComponent {
      componentWillMount() { }
      componentWillReceiveProps() { }
      componentWillUpdate() { }
      // ...
    }
    
    var Foo = createReactClass({
      componentWillMount: function() {},
      componentWillReceiveProps: function() {},
      componentWillUpdate: function() {},
      // ...
    })
    
    // good
    ReactDOM.render(<MyComponent />, root);
    
    // When [1, {"react": "0.13.0"}]
    ReactDOM.findDOMNode(this.refs.foo);
    
    import { PropTypes } from 'prop-types';
    
    class Foo {
      componentWillMount() { }
      componentWillReceiveProps() { }
      componentWillUpdate() { }
    }
    
  • 2.2.6【强制】不要使用 findDOMNode。eslint: react/no-find-dom-node

    严格模式下已经弃用 findDOMNode

    // bad
    class MyComponent extends Component {
      componentDidMount() {
        findDOMNode(this).scrollIntoView();
      }
    
      render() {
        return <div />
      }
    }
    
    // good
    class MyComponent extends Component {
      componentDidMount() {
        this.node.scrollIntoView();
      }
    
      render() {
        return <div ref={node => this.node = node} />
      }
    }
    
  • 2.2.7【强制】不要使用 componentWillMount、componentWillReceiveProps、componentWillUpdate。

    不要再使用 componentWillMountcomponentWillReceivePropscomponentWillUpdate。使用这些生命周期方法通常会导致错误和不一致,因此React 计划在17版本删掉这些方法。

    • componentWillMount() 可以用 constructor() 或 componentDidMount() 替代;
    • componentWillReceiveProps() 可以用 componentDidUpdate() 或其他方式替换;
    • componentWillUpdate() 可以用 componentDidUpdate() 替换或者把逻辑写在 getSnapshotBeforeUpdate() 中。

    使用rename-unsafe-lifecycles codemod自动为不推荐使用的生命周期钩子添加“UNSAFE_”前缀。转化为

    • UNSAFE_componentWillMount()
    • UNSAFE_componentWillReceiveProps()
    • UNSAFE_componentWillUpdate()
  • 2.2.8【强制】不要在 componentWillUpdate 内改变 state 值。eslint: react/no-will-update-set-state

    首先,不要再使用 componentWillUpdate,React 未来在17版本计划删掉 componentWillUpdate。通常可以用 componentDidUpdate() 替代。使用rename-unsafe-lifecycles codemod自动更新组件。

    不要在 componentWillUpdate 调用 this.setState()。若你需要更新状态响应属性的变更,使用 getDerivedStateFromProps() 代替。在 componentWillUpdate 中改变 state 的值可能会引起组件的不确定状态。

    // bad
    class Hello extends React.Component {
      componentWillUpdate() {
        this.setState({
          name: this.props.name.toUpperCase()
        });
      }
    
      render() {
        return <div>Hello {this.state.name}</div>;
      }
    };
    
    // good
    class Hello extends React.Component {
      componentWillUpdate() {
        this.props.prepareHandler();
      }
    
      render() {
        return <div>Hello {this.props.name}</div>;
      }
    };
    
    class Hello extends React.Component {
      componentWillUpdate() {
        this.prepareHandler(function callback(newName) {
          this.setState({
            name: newName
          });
        });
      }
    
      render() {
        return <div>Hello {this.props.name}</div>;
      }
    };
    

2.3 Props

  • 2.3.1【强制】采用小驼峰风格命名 prop 。eslint: react/no-unknown-property

    // bad
    <Foo
      UserName="hello"
      phone_number={12345678}
    />
    
    // good
    <Foo
      userName="hello"
      phoneNumber={12345678}
    />
    
  • 2.3.2【强制】声明的 prop 必须被使用。eslint: react/no-unused-prop-types

    声明而未使用的 prop 可能带来潜在的问题,也会给维护者造成困扰,应将它们删除。

    // bad
    var Hello = createReactClass({
      propTypes: {
        name: PropTypes.string
      },
      render: function() {
        return <div>Hello Bob</div>;
      }
    });
    
    var Hello = createReactClass({
      propTypes: {
        firstname: PropTypes.string.isRequired,
        middlename: PropTypes.string.isRequired, // middlename is never used below
        lastname: PropTypes.string.isRequired
      },
      render: function() {
        return <div>Hello {this.props.firstname} {this.props.lastname}</div>;
      }
    });
    
    // good
    var Hello = createReactClass({
      propTypes: {
        name: PropTypes.string
      },
      render: function() {
        return <div>Hello {this.props.name}</div>;
      }
    });
    
  • 2.3.3【参考】 props,state 优先使用解构赋值。eslint: react/destructuring-assignment

    // bad
    const MyComponent = (props) => {
      return (<div id={props.id} />)
    };
    
    // good
    const MyComponent = ({id}) => {
      return (<div id={id} />)
    };
    
    const MyComponent = (props, context) => {
      const { id } = props;
      return (<div id={id} />)
    };
    
  • 2.3.4【强制】prop 值为 true 时,可以省略它的值。eslint: react/jsx-boolean-value

    // bad
    <Foo
      hidden={true}
    />
    
    // good
    <Foo
      hidden
    />
    
  • 2.3.5【推荐】prop 需要 propTypes 验证。eslint: react/prop-types

    PropTypes 验证接收到的数据从而提高组件的可重用性。如果其他开发传入了不正确数据类型,可以及时警告。

    // bad
    class Greeting extends React.Component {
      render() {
        return (
          <h1>Hello, {this.props.name}</h1>
        );
      }
    }
    
    // good
    class Greeting extends React.Component {
      render() {
        return (
          <h1>Hello, {this.props.name}</h1>
        );
      }
    }
    
    Greeting.propTypes = {
      name: PropTypes.string
    };
    
  • 2.3.6【推荐】不要使用模糊的类型检查器。eslint: react/forbid-prop-types

    不要使用模糊的类型验证,比如 any, array, object。它们可以用其他明确的类型代替。any可以替换为任意类型,array 和 object 可以分别替换为 arrayOf 和 shape。

    // bad
    class MyComponent extends React.Component {
      ...
    }
    
    MyComponent.propTypes = {
      // 任意类型的数据
      optionalAny: PropTypes.any,
      // 一个未指定元素类型的数组
      optionalArray: PropTypes.array,
      // 一个未指定属性类型的对象
      optionalObject: PropTypes.object
    };
    
    // good
    class MyComponent extends React.Component {
      ...
    }
    
    MyComponent.propTypes = {
      // 指明待验证数据的特性类型,确保接收的数据是有效的
      optionalAny: PropTypes.string,
      requiredAny: PropTypes.any.isRequired,
      // 一个指定了元素类型的数组
      optionalArray: PropTypes.arrayOf(PropTypes.number),
      // 一个指定了属性类型的对象
      optionalObject: PropTypes.shape({
        color: PropTypes.string,
        fontSize: PropTypes.number
      }),
    };
    
  • 2.3.7【参考】属性需要指定 defaultProps,除了 isRequired 的属性。eslint: react/require-default-props

    // bad
    function MyStatelessComponent({ foo, bar }) {
      return <div>{foo}{bar}</div>;
    }
    MyStatelessComponent.propTypes = {
      foo: PropTypes.number.isRequired,
      bar: PropTypes.string,
    };
    
    // good
    function MyStatelessComponent({ foo, bar }) {
      return <div>{foo}{bar}</div>;
    }
    MyStatelessComponent.propTypes = {
      foo: PropTypes.number.isRequired,
      bar: PropTypes.string,
    };
    MyStatelessComponent.defaultProps = {
      bar: '',
    };
    
  • 2.3.8【推荐】不要用数组的索引值作为 map 生成元素的 key。eslint: react/no-array-index-key

    为什么?React 使用 key 来标识哪些项已更改,已添加或已删除, key 应该始终稳定。使用不稳定的 ID 是一种反模式,因为它不能唯一标识元素。如果数组重新排序或将元素添加到数组的开头,可能会更改索引导致不必要的渲染,对性能产生负面影响。

    如果数组的顺序可能发生变化,我们不建议使用索引值作为 key。

    // bad
    {todos.map((todo, index) =>
      <Todo
        {...todo}
        key={index}
      />
    )}
    
    // good
    {todos.map(todo => (
      <Todo
        {...todo}
        key={todo.id}
      />
    ))}
    
  • 2.3.9【强制】禁止将 children 作为属性名。eslint: react/no-children-prop

    使用 JSX 时,children 应嵌套在开始和结束标签之间。不使用JSX时,应将 children 作为附加参数传递给 React.createElement

    // bad
    <div children='Children' />
    
    <MyComponent children={<AnotherComponent />} />
    <MyComponent children={['Child 1', 'Child 2']} />
    
    React.createElement("div", { children: 'Children' })
    
    // good
    <div>Children</div>
    
    <MyComponent>Children</MyComponent>
    
    <MyComponent>
      <span>Child 1</span>
      <span>Child 2</span>
    </MyComponent>
    
    React.createElement("div", {}, 'Children')
    React.createElement("div", 'Child 1', 'Child 2')
    
  • 2.3.10【强制】不要声明重复的属性名。eslint: react/jsx-no-duplicate-props

    // bad
    <Hello name="John" name="John" />;
    
    // good
    <Hello firstname="John" lastname="Doe" />;
    
  • 2.3.12【强制】style 的属性值必须是一个对象。eslint: react/style-prop-object

    // bad
    <div style="color: 'red'" />
    <div style={true} />
    <Hello style={true} />
    
    const styles = true;
    <div style={styles} />
    
    React.createElement("div", { style: "color: 'red'" });
    React.createElement("div", { style: true });
    React.createElement("Hello", { style: true });
    
    const styles = true;
    React.createElement("div", { style: styles });
    
    // good
    <div style={{ color: "red" }} />
    <Hello style={{ color: "red" }} />
    
    const styles = { color: "red" };
    <div style={styles} />
    
    React.createElement("div", { style: { color: 'red' }});
    React.createElement("Hello", { style: { color: 'red' }});
    
    const styles = { height: '100px' };
    React.createElement("div", { style: styles });
    
  • 2.3.11【推荐】不要单独使用 target='_blank'。eslint: react/jsx-no-target-blank

    target='_blank' 常用于在新标签页打开。使用这个属性可能造成严重的安全问题。建议和 rel='noreferrer noopener' 一起使用。详见

    // bad
    const Hello = <a target='_blank' href="http://example.com/"></a>
    const Hello = <a target='_blank' href={ dynamicLink }></a>
    
    // good
    const Hello = <p target='_blank'></p>
    const Hello = <a target='_blank' rel='noopener noreferrer' href="http://example.com"></a>
    const Hello = <a target='_blank' href="relative/path/in/the/host"></a>
    const Hello = <a target='_blank' href="/absolute/path/in/the/host"></a>
    const Hello = <a></a>
    

2.4 State

  • 2.4.1【强制】不要在 setState 中使用 this.state。eslint: react/no-access-state-in-setstate

    在 setState 中使用 this.state 可能导致错误,当两个 state 在同一个批处理中时,引用的是旧状态而不是新状态。
    为避免这种情况,请在回调中使用 preState 作为第一个参数。

    // bad
    function increment() {
      this.setState({ value: this.state.value + 1 });
    }
    
    // good
    function increment() {
      this.setState(prevState => ({ value: prevState.value + 1 }));
    }
    

    bad case 中假设 value 为1,有两个 setState 操作在同一个批处理中执行,实际执行的是:

    setState({ value: 1 + 1 })
    setState({ value: 1 + 1 })
    

    good case 中 react 会以正确的更新后的状态调用参数。实际执行的是:

    setState({ value: 1 + 1 })
    setState({ value: 2 + 1 })
    
  • 2.4.2【强制】声明的 state 必须被使用。eslint: react/no-unused-state

    声明而未使用的 state 可能带来潜在的问题,也会给维护者造成困扰,应将它们删除。

    // bad
    class MyComponent extends React.Component {
      state = { foo: 0 };
    
      render() {
        return <SomeComponent />;
      }
    }
    
    var UnusedGetInitialStateTest = createReactClass({
      getInitialState: function() {
        return { foo: 0 };
      },
      render: function() {
        return <SomeComponent />;
      }
    })
    
    // good
    class MyComponent extends React.Component {
      state = { foo: 0 };
    
      render() {
        return <SomeComponent foo={this.state.foo} />;
      }
    }
    
    var UnusedGetInitialStateTest = createReactClass({
      getInitialState: function() {
        return { foo: 0 };
      },
      render: function() {
        return <SomeComponent foo={this.state.foo} />;
      }
    })
    

2.5 Refs

  • 2.5.1【强制】使用 ref 回调函数或 React.createRef(),不要使用字符串。eslint: react/no-string-refs

    // bad - 使用字符串
    class MyComponent extends React.Component {
      componentDidMount() {
        this.refs.inputRef.focus();
      }
    
      render() {
        return <input type="text" ref="inputRef" />;
      }
    }
    
    // good - 使用回调函数
    class MyComponent extends React.Component {
      componentDidMount() {
        this.inputRef.focus();
      }
    
      render() {
        return <input type="text" ref={(ele) => { this.inputRef = ele; }} />;
      }
    }
    
    // good - 使用 React.createRef(),React V16 后版本支持
    class MyComponent extends React.Component {
      constructor(props) {
        super(props);
    
        this.inputRef = React.createRef();
      }
    
      componentDidMount() {
        this.inputRef.current.focus();
      }
    
      render() {
        return <input type="text" ref={this.inputRef} />;
      }
    }
    

2.6 顺序

  • 2.6.1【参考】组件方法的排序规则。eslint: react/sort-comp

    React 组件内有声明周期方法、事件处理方法、render 方法等几类方法,指定这些方法按固定的顺序排序可以增强代码的一致性,方便查找和阅读。

    我们推荐的方法排序如下:

    • 可选的 static 方法
    • constructor
    • getChildContext
    • componentWillMount
    • componentDidMount
    • componentWillReceiveProps
    • shouldComponentUpdate
    • componentWillUpdate
    • componentDidUpdate
    • componentWillUnmount
    • clickHandlers 或 eventHandlers 比如 onClickSubmit()onChangeDescription()
    • render 的 getter 方法 比如 getSelectReason()getFooterContent()
    • 可选的 render 方法 比如 renderNavigation()renderProfilePicture()
    • render

3 命名

  • 3.1【强制】文件扩展名: 使用 .jsx、.tsx、.js 或 .ts 作为 React 组件的文件扩展名。eslint: react/jsx-filename-extension

  • 3.2【强制】引用名:使用大驼峰风格命名引用的组件,使用小驼峰风格命名引用组件的实例。eslint: react/jsx-pascal-case

    // bad
    import reservationCard from './reservation-card';
    
    // good
    import ReservationCard from './reservation-card';
    
    // bad
    const ReservationItem = <ReservationCard />;
    
    // good
    const reservationItem = <ReservationCard />;
    
  • 3.3【推荐】高阶组件命名:将高阶组件名和传入组件名组合作为 displayName。

    例如,高阶组件 withFoo() ,当传入组件 Bar 时,应该产生一个组件,应使用 withFoo(Bar) 作为生成组件的 displayName。

    组件的 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;
    }
    

4 Hooks

  • 4.1【强制】只在最顶层调用 Hooks,不要在循环、条件和嵌套函数中调用 Hooks。eslint: rules of Hooks - only call Hooks at the top level

    // bad - call Hooks inside conditions
    function ComponentWithConditionalHook() {
      if (cond) {
        useConditionalHook();
      }
    }
    
    // bad - call Hooks inside loops
    function ComponentWithHookInsideLoop() {
      while (cond) {
        useHookInsideLoop();
      }
    }
    
    // bad - call Hooks inside callback
    function ComponentWithHookInsideCallback() {
      useEffect(() => {
        useHookInsideCallback();
      });
    }
    
    // good
    function ComponentWithHook() {
      useHook();
    }
    
  • 4.2【强制】Hooks 命名必须以 use 开头,小驼峰形式

    // bad
    const customHook = () => {}
    
    // good
    const useCustomHook = () => {}
    
  • 4.3【强制】只在 React 函数组件和自定义 Hooks 中调用 Hooks,不能在普通的 JavaScript 函数中调用 Hooks。eslint: rules of Hooks - only call Hooks from React functions

    // bad - call Hooks inside class componennt
    class ClassComponentWithHook extends React.Component {
      render() {
        React.useState();
      }
    }
    
    // bad - call Hooks inside normal function
    function normalFunctionWithHook() {
      useHookInsideNormalFunction();
    }
    
    // good - call Hooks inside function component
    function ComponentWithHook() {
      useHook();
    }
    
    // good - call Hooks inside custom Hooks
    function useHookWithHook() {
      useHook();
    }
    
  • 4.4【推荐】useEffect类似 Hooks 需要声明所有依赖。eslint: exhaustive-deps

    此规则在某些场景下可能过于严格,并且 ESLint autofix 可能会造成一些问题,因此需注意:

    • 升级 eslint-plugin-react-hooks 到 2.4.0 版本及以上,因为 2.4.0 版本后该规则的 autofix 被默认禁用
    • 如果某些场景下此规则确实不适用,可以通过 ESLint 行注释手动禁用此规则,在行尾添加: // eslint-disable-line react-hooks/exhaustive-deps
    // bad
    function MyComponent() {
      const local = {};
      useEffect(() => {
        console.log(local);
      }, []);
    }
    
    // good
    function MyComponent() {
      const local = {};
      useEffect(() => {
        console.log(local);
      }, [local]);
    }
    

5 无障碍

  • 5.1【推荐】img 标签应包含 alt 属性。eslint: jsx-a11y/alt-text

    如果图片无需被无障碍阅读器识别(如作为 button 的 icon 使用),你可以将 alt 属性写为空字符串

    // bad
    <img src="hello.jpg" />
    
    // good
    <img src="hello.jpg" alt="Me waving hello" />
    
    // good - 图片无需被无障碍阅读器识别时
    <button>
      <img src="icon.png" alt="" />
      Save
    </button>
    
  • 5.2【推荐】img 标签的 alt 属性不要使用 "image","photo","picture" 之类的关键词。eslint: jsx-a11y/img-redundant-alt

    屏幕阅读器已会将 img 元素识别成图片,再在 alt 中包含这类关键词没有意义。

    // bad
    <img src="hello.jpg" alt="Picture of me waving hello" />
    
    // good
    <img src="hello.jpg" alt="Me waving hello" />
    
  • 5.3【推荐】<iframe> 元素必须有一个唯一的 title 属性,表示其内容。

    // bad
    <iframe />
    <iframe {...props} />
    <iframe title="" />
    <iframe title={undefined} />
    <iframe title={false} />
    <iframe title={true} />
    <iframe title={42} />
    
    // good
    <iframe title="This is a unique title" />
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容