美文网首页
Airbnb JavaScript React 风格指南

Airbnb JavaScript React 风格指南

作者: 南方帅 | 来源:发表于2018-12-24 13:28 被阅读0次

    基本法


    • 一个文件只能有一个组件
    • 用JSX语法糖
    • 不要用 React.createElement, 除非你从一个非 JSX 文件中初始化 app。class vs component

    Class vs React.createClass vs stateless


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


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

    命名


    • 扩展名: 用 .jsx 作为组件扩展名。

    • 文件名: 用大驼峰作为文件名,如:ReservationCard.jsx

    • 参数命名: React 组件用大驼峰,组件的实例用小驼峰。 eslint: react/jsx-pascal-case
      instances

    // 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 可以用于开发者工具或者错误信息中,同时还有一个值可以清晰的表达这种组件关系,这可以帮助人们理解到底发生了什么

    // 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 组件的属性名表达不同的意义

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

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

    声明


    • 不要通过 displayName 命名组件。最好通过引用命名组件。
    // bad
    export default React.createClass({
      displayName: 'ReservationCard',
      // stuff goes here
    });
    
    // good
    export default class ReservationCard extends React.Component {
    }
    

    对齐


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

    引号


    • 在 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' }} />
    

    空格


    // bad
    <Foo/>
    
    // very bad
    <Foo                 />
    
    // bad
    <Foo
     />
    
    // good
    <Foo />
    
    // bad
    <Foo bar={ baz } />
    
    // good
    <Foo bar={baz} />
    
    // 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 />
    
    • <img> 标签通常会设置 alt 属性。如果图片是表现型的, alt可以是空字符串或者 <img> 必须有 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" />
    
    • 不要在 <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" />
    
    // bad - 不是一个 ARIA role
    <div role="datepicker" />
    
    // bad - 抽象的 ARIA role
    <div role="range" />
    
    // good
    <div role="button" />
    
    • 不要在元素上用 accessKey。 eslint: jsx-a11y/no-access-key 使用屏幕阅读器和键盘的人使用的键盘快捷键和键盘命令之间的不一致使得可访问性变得复杂。
    // bad
    <div accessKey="h" />
    
    // good
    <div />
    
    • 避免用数组下标作为 key 属性,推荐用稳定的 ID

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

    // bad
    {todos.map((todo, index) =>
      <Todo
        {...todo}
        key={index}
      />
    )}
    
    // good
    {todos.map(todo => (
      <Todo
        {...todo}
        key={todo.id}
      />
    ))}
    
    • 对于所有非必须属性,定义一个明确的默认值。

    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} 除非你更喜欢把不需要的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 函数做单测时尤其有用。
    // bad
    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


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

    括号


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

    标签


    // bad
    <Foo variant="stuff"></Foo>
    
    // good
    <Foo variant="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>
      );
    }
    

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

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

    // bad
    React.createClass({
      _onClickSubmit() {
        // do stuff
      },
    
      // other stuff
    });
    
    // good
    class extends React.Component {
      onClickSubmit() {
        // do stuff
      }
    
      // other stuff
    }
    
    *   ```source-js-jsx
        // 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`
    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` 等...
    
        ```source-js-jsx
        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`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md)
    
    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`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-is-mounted.md)
    
    > Why? [`isMounted` 是反模式][anti-pattern], 这个在 ES6 class 里不允许的,而且即将被官方废弃。
    

    相关文章

      网友评论

          本文标题:Airbnb JavaScript React 风格指南

          本文链接:https://www.haomeiwen.com/subject/wrhbkqtx.html