Skip to content

Latest commit

 

History

History
856 lines (654 loc) · 21.3 KB

React.js.md

File metadata and controls

856 lines (654 loc) · 21.3 KB

React/JSX 编码规范

基本规范

  • 每个文件只包含的一个 React 组件:
    • 联系紧密的组件可以使用「命名空间」的形式;
    • 每个文件中可包含多个纯函数组件。
  • 始终使用 JSX 语法,不要使用 React.createElement 创建 ReactElement,以提高编写速度、可读性、可维护性(没有 JSX 转换的特殊场景例外,如在 console 中测试组件)。

组件创建方式

React 中可以通过三种方式创建组件:ES6 classcreateReactClass函数式组件

  • 如果组件有内部状态,或者使用了生命周期方法,优先使用 class extends React.Component

    // bad
    var createReactClass = require('create-react-class');
    var Greeting = createReactClass({
      /// ...
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    });
    
    // good
    class Greeting extends React.Component {
      // ...
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }

    如果组件内部未使用状态或生命周期方法,优先使用普通函数(非箭头函数)创建组件:

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

Why? Mixins introduce implicit dependencies, cause name clashes, and cause snowballing complexity. Most use cases for mixins can be accomplished in better ways via components, higher-order components, or utility modules.

命名规范

  • 扩展名:使用 .jsx 作为 React 组件的扩展名;

  • 文件名:使用大驼峰命名法(PascalCase),如 MyComponent.jsx

  • 组件命名:组件名称和文件名一致,如 MyComponent.jsx 里的组件名应该是 MyComponent;一个目录的根组件使用 index.jsx 命名,以目录名称作为组件名称;

    // Use the filename as the component name
    
    // file contents
    class CheckBox extends React.Component {
      // ...
    }
    
    export default CheckBox;
    
    // in some other file
    // bad
    import CheckBox from './checkBox';
    
    // bad
    import CheckBox from './check_box';
    
    // good
    import CheckBox from './CheckBox';
    
    
    // for root components of a directory,
    // use index.jsx as the filename and use the directory name as the component name
      
    // bad
    import Footer from './Footer/Footer.jsx';
        
    // bad
    import Footer from './Footer/index.jsx';
        
    // good
    import Footer from './Footer';
  • 引用命名:React 组件使用大驼峰命名法(PascalCase),组件实例使用小驼峰命名法(camelCase)

        // bad
    import reservationCard from './ReservationCard';
    
    // good
    import ReservationCard from './ReservationCard';
    
    // bad
    const ReservationItem = <ReservationCard />;
    
    // good
    const reservationItem = <ReservationCard />;
    
    // HTML tag
    const myDivElement = <div className="foo" />;
    ReactDOM.render(myDivElement, mountNode);
  • 高阶组件命名: Use a composite of the higher-order component's name and the passed-in component's name as the displayName on the generated component. For example, the higher-order component withFoo(), when passed a component Bar should produce a component with a displayName of withFoo(Bar).

    Why? A component's displayName may be used by developer tools or in error messages, and having a value that clearly expresses this relationship helps people understand what is happening.

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

带命名空间的组件

  • 如果一个组件有许多关联子组件,可以以该组件作为命名空间编写、调用子组件。

    class Form extends React.Component {  
      // ...
    }
    
    class Row extends React.Component {}
    class Label extends React.Component {}
    class Input extends React.Component {}
    
    Form.Row = Row;
    Form.Label = Label;
    Form.Input = Input;
    
    export default Form;
    
    // refence Form component
    import Form from './Form';
    
    const App = (
      <Form>
        <Form.Row>
          <Form.Label />
          <Form.Input />
        </Form.Row>
      </Form>
    );

组件声明

  • 不要使用 displayName 来命名组件,通过引用来命名。

     // bad
     export default React.createClass({
       displayName: 'ReservationCard',
       // stuff goes here
     });
    
     // good
    class ReservationCard extends React.Component {
    }
    
     export default ReservationCard;

属性

属性命名

  • React 组件的属性使用小驼峰命名法

  • 使用 className 代替 class 属性;

  • 使用 htmlFor 代替 for 属性;

  • 不要把 DOM 组件的属性用作其他用途。

    Why? People expect props like style and className to mean one specific thing. Varying this API for a subset of your app makes the code less readable and less maintainable, and may cause bugs.

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

传递给 HTML 的属性:

  • 传递给 HTML 元素的自定义属性,需要添加 data- 前缀,React 不会渲染非标准属性;
  • 无障碍属性 aria- 可以正常使用。

属性设置

  • 在组件行内设置属性(以便 propTypes 校验),不要在外部改变属性的值;

  • 属性较多使用 {...this.props} 语法;

    const component = <Component />;
    component.props.foo = x; // bad
    component.props.bar = y; // also bad
    
    // good
    const component = <Component foo={x} bar={y} />;
    
    // good
    const props = {};
    props.foo = x;
    props.bar = y;
    const component = <Component {...props} />;
  • 属性值明确为 true 时,省略值。

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

属性对齐方式

  • 属性较少时可以行内排列;
  • 属性较多时每行一个属性,闭合标签单独成行。
// bad - too long
<input type="text" value={this.state.newDinosaurName} onChange={this.inputHandler.bind(this, 'newDinosaurName')} />  

// bad - aligning attributes after the tag
<input type="text"  
       value={this.state.newDinosaurName}
       onChange={this.inputHandler.bind(this, 'newDinosaurName')} /> 

// good
<input  
  type="text"
  value={this.state.newDinosaurName}
  onChange={this.inputHandler.bind(this, 'newDinosaurName')}
 />
 
 // if props fit in one line then keep it on the same line
<Foo bar="bar" />

// children get indented normally
<Foo
  superLongParam="bar"
  anotherSuperLongParam="baz"
>
  <Spazz />
</Foo>


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

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

属性空格

  • 属性 = 前后不要添加空格;
  • JSX 中的花括号前后不要添加空格。
// bad
<Foo bar={ baz } foo = "bar" />

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

// good { left: '20px' } 为一个对象
<Foo style={{ left: '20px' }} />

key

  • 避免使用数组的索引作为 key 值,优先使用唯一 ID 作为 key 值。 (参考文章
// bad
{todos.map((todo, index) =>
  <Todo
    {...todo}
    key={index}
  />
)}

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

同时满足以下条件时可以使用数组索引作为 key 值:

  • 数组是静态的:不经过计算,也不会改变;
  • 数组项没有 id;
  • 数组不做排序或者过滤操作。

propTypes 及默认值

  • 组件属性都应该在 propTypes 中声明类型;
  • 始终明确指定非必选属性的默认值。

Why? propTypes are a form of documentation, and providing defaultProps means the reader of your code doesn’t have to assume as much. In addition, it can mean that your code can omit certain type checks.

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

A11Y

  • Always include an alt prop on <img> tags. If the image is presentational, alt can be an empty string or the <img> must have 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" />
  • Do not use words like "image", "photo", or "picture" in <img> alt props. eslint: jsx-a11y/img-redundant-alt

    Why? Screenreaders already announce img elements as images, so there is no need to include this information in the alt text.

    // bad
    <img src="hello.jpg" alt="Picture of me waving hello" />
    
    // good
    <img src="hello.jpg" alt="Me waving hello" />
  • Use only valid, non-abstract ARIA roles. eslint: jsx-a11y/aria-role

    // bad - not an ARIA role
    <div role="datepicker" />
    
    // bad - abstract ARIA role
    <div role="range" />
    
    // good
    <div role="button" />
  • Do not use accessKey on elements. eslint: jsx-a11y/no-access-key

Why? Inconsistencies between keyboard shortcuts and keyboard commands used by people using screenreaders and keyboards complicate accessibility.

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

// good
<div />

引号

  • JSX 属性使用双引号 "
  • JS 使用单引号 '
// bad
<Foo bar='bar' />

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

// bad
<Foo style={{ left: "20px" }} />

// good
<Foo style={{ left: '20px' }} />

// JavaScript Expression
const person = <Person name={window.isLoggedIn ? window.name : ''} />;

// HTML/JSX
const myDivElement = <div className="foo" />;
const app = <Nav color="blue" />;

const content = (
  <Container>
    {window.isLoggedIn ? <Nav /> : <Login />}
  </Container>
);

条件 JSX

  • 简短的输出在行内直接三元运算符;

    {this.state.show && 'This is Shown'}
    {this.state.on ? 'On' : 'Off'}
  • 较复杂的结构可以在 .render() 方法内定义一个以 Html 结尾的变量(命名方式仅供参考);

    let dinosaurHtml = '';
    
    if (this.state.showDinosaurs) {  
      dinosaurHtml = (
        <section>
          <DinosaurTable />
          <DinosaurPager />
        </section>
      );
    }
    
    return (  
      <div>
        ...
        {dinosaurHtml}
        ...
      </div>
    );
  • 运算逻辑简单的直接使用行内迭代。

    return (
      <div>
        {this.props.data.map((data) => {
          return (<Component data={data} key={data.id} />);
        })}
      </div>
    );

() 使用

  • 多行的 JSX 使用 () 包裹,有组件嵌套时使用多行模式;

     // bad
     return (<div><ComponentOne /><ComponentTwo /></div>);
     
     // good
     var multilineJsx = (  
       <header>
         <Logo />
         <Nav />
       </header>
     );
     
     // good
     return (
     <div>
       <ComponentOne />
       <ComponentTwo />
     </div>
    );
  • 单行 JSX 省略 ()

     var singleLineJsx = <h1>Simple JSX</h1>;  
     
     // good, when single line
     render() {
       const body = <div>hello</div>;
    
       return <MyComponent>{body}</MyComponent>;
     }

自闭合标签

  • 自闭合所有没有子组件的标签;
  • 自闭合标签 / 前留一个空格
// bad
<Logo></Logo>
<Logo/>

// very bad
<Foo                 />

// bad
<Foo
 />

// good
<Logo />

Ref

  • 始终使用 ref 回调。 eslint: react/no-string-refs

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

方法

  • 使用箭头函数遮蔽本地变量。

    function ItemList(props) {
      return (
        <ul>
          {props.items.map((item, index) => (
            <Item
              key={item.key}
              onClick={() => doSomethingWith(item.name, index)}
            />
          ))}
        </ul>
      );
    }
  • constructor 中绑定 this,而不是引用的时候绑定。eslint: react/jsx-no-bind

    Why? A bind call in the render path creates a brand new function on every single 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? Underscore prefixes are sometimes used as a convention in other languages to denote privacy. But, unlike those languages, there is no native support for privacy in JavaScript, everything is public. Regardless of your intentions, adding underscore prefixes to your properties does not actually make them private, and any property (underscore-prefixed or not) should be treated as being public. See issues #1024, and #490 for a more in-depth discussion.

    // 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 />);
    }
  • 事件处理方法以 handle 或者 on 开头,如 handleClick() {}

  • 慎用 Class Properties 语法(最终规范可能会跟提案有差异)。

    class SayHello extends React.Component {
      constructor(props) {
        super(props);
        this.state = {message: 'Hello!'};
      }
      // WARNING: this syntax is experimental!
      // Using an arrow here binds the method:
      handleClick = () => {
        alert(this.state.message);
      }
    
      render() {
        return (
          <button onClick={this.handleClick}>
            Say hello
          </button>
        );
      }
    }

组件代码组织

  • 按照生命周期顺序组织组件的属性、方法;

  • 方法(属性)之间空一行;

  • render() 方法始终放在最后;

  • 自定义方法 React API 方法之后、render() 之前;

  • class extends React.Component 顺序:

    1. static 属性
    2. static 方法
    3. constructor
    4. getChildContext
    5. componentWillMount
    6. componentDidMount
    7. componentWillReceiveProps
    8. shouldComponentUpdate
    9. componentWillUpdate
    10. componentDidUpdate
    11. componentWillUnmount
    12. 点击处理函数或者其他事件处理函数,如 onClickSubmit()onChangeDescription()
    13. render 的 getter 方法,如 getSelectReason()getFooterContent()
    14. 可选的 render 方法,如 renderNavigation()renderProfilePicture()
    15. render
  • 定义 propTypes, defaultProps, contextTypes

    import React from 'react';
    import PropTypes from 'prop-types';
    
    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 = {
      id: PropTypes.number.isRequired,
      url: PropTypes.string.isRequired,
      text: PropTypes.string,
    };
    
    Link.defaultProps = {
      text: 'Hello World',
    };
    
    export default Link;
    
    
    // static
    import React from 'react';
    import PropTypes from 'prop-types';
    
    class Link extends React.Component {
      static propTypes = {
        id: PropTypes.number.isRequired,
        url: PropTypes.string.isRequired,
        text: PropTypes.string,
      };
    
      static defaultProps = {
        text: 'Hello World',
      };
    
      static methodsAreOk() {
        return true;
      }
    
      render() {
        return (
          <a href={this.props.url} data-id={this.props.id}>
            {this.props.text}
          </a>
        );
      }
    }
    
    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. 点击处理函数或者其他事件处理函数,如 onClickSubmit()onChangeDescription()
    19. render 的 getter 方法,如 getSelectReason()getFooterContent()
    20. 可选的 render 方法,如 renderNavigation()renderProfilePicture()
    21. render

注释

  • 组件之间的注释需要用 {} 包裹。

    var content = (
      <Nav>
        {/* child comment, put {} around */}
        <Person
          /* multi
             line
             comment */
          name={window.isLoggedIn ? window.name : ''} // end of line comment
        />
      </Nav>
    );

isMounted

Why? isMounted is an anti-pattern, is not available when using ES6 classes, and is on its way to being officially deprecated.

代码校验工具

参考资源