2

原文:react-patterns

代码结构

  • class definition

    • constructor

      • event handlers

    • 'component' lifecycle

    • getters

    • render

  • defaultProps

  • proptypes

class Person extends React.Component {
  constructor (props) {
    super(props);

    this.state = { smiling: false };

    this.handleClick = () => {
      this.setState({smiling: !this.state.smiling});
    };
  }

  componentWillMount () {
    // add event listeners (Flux Store, WebSocket, document, etc.)
  },

  componentDidMount () {
    // React.getDOMNode()
  },

  componentWillUnmount () {
    // remove event listeners (Flux Store, WebSocket, document, etc.)
  },

  get smilingMessage () {
    return (this.state.smiling) ? "is smiling" : "";
  }

  render () {
    return (
      <div onClick={this.handleClick}>
        {this.props.name} {this.smilingMessage}
      </div>
    );
  },
}

Person.defaultProps = {
  name: 'Guest'
};

Person.propTypes = {
  name: React.PropTypes.string
};

格式化属性

当有2个以上属性时,就换行显示

// bad
<Person
 firstName="Michael" />

// good
<Person firstName="Michael" />
// bad
<Person firstName="Michael" lastName="Chan" occupation="Designer" favoriteFood="Drunken Noodles" />

// good
<Person
 firstName="Michael"
 lastName="Chan"
 occupation="Designer"
 favoriteFood="Drunken Noodles" />

依赖属性

使用getters方法代替定义依赖属性

// bad
  firstAndLastName () {
    return `${this.props.firstName} ${this.props.lastname}`;
  }

  // good
  get fullName () {
    return `${this.props.firstName} ${this.props.lastname}`;
  }

依赖状态

使用getters方法代替定义依赖状态,注意为了提高可读性需要在命名时加上一个动词前缀。

// bad
happyAndKnowsIt () {
  return this.state.happy && this.state.knowsIt;
}
// good
get isHappyAndKnowsIt () {
  return this.state.happy && this.state.knowsIt;
}

这些方法必须返回boolean类型

用三元运算代替Sub-render方法

保证渲染逻辑都写在render方法里

// bad
renderSmilingStatement () {
  return <strong>{(this.state.isSmiling) ? " is smiling." : ""}</strong>;
},

render () {
  return <div>{this.props.name}{this.renderSmilingStatement()}</div>;
}
// good
render () {
  return (
    <div>
      {this.props.name}
      {(this.state.smiling)
        ? <span>is smiling</span>
        : null
      }
    </div>
  );
}

视图组件

用定义的组件组成视图。不要创建混杂着布局和功能的一次性组件

// bad
class PeopleWrappedInBSRow extends React.Component {
  render () {
    return (
      <div className="row">
        <People people={this.state.people} />
      </div>
    );
  }
}
// good
class BSRow extends React.Component {
  render () {
    return <div className="row">{this.props.children}</div>;
  }
}

class SomeView extends React.Component {
  render () {
    return (
      <BSRow>
        <People people={this.state.people} />
      </BSRow>
    );
  }
}

容器组件(有状态组件)

容器组件负责获取数据并交付给相应的子组件渲染,仅此而已。— Jason Bonta

// bad
// CommentList.js

class CommentList extends React.Component {
  getInitialState () {
    return { comments: [] };
  }

  componentDidMount () {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments});
      }.bind(this)
    });
  }

  render () {
    return (
      <ul>
        {this.state.comments.map(({body, author}) => {
          return <li>{body}—{author}</li>;
        })}
      </ul>
    );
  }
}
//good
// CommentList.js

class CommentList extends React.Component {
  render() {
    return (
      <ul>
        {this.props.comments.map(({body, author}) => {
          return <li>{body}—{author}</li>;
        })}
      </ul>
    );
  }
}
// CommentListContainer.js

class CommentListContainer extends React.Component {
  getInitialState () {
    return { comments: [] }
  }

  componentDidMount () {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments});
      }.bind(this)
    });
  }

  render () {
    return <CommentList comments={this.state.comments} />;
  }
}

相关链接:
Container Components
React.js Conf 2015 - Making your app fast with high-performance components

render中缓存状态

不要在render中缓存状态

// bad
render () {
  let name = `Mrs. ${this.props.name}`;

  return <div>{name}</div>;
}

// good
render () {
  return <div>{`Mrs. ${this.props.name}`}</div>;
}
// best
get fancyName () {
  return `Mrs. ${this.props.name}`;
}

render () {
  return <div>{this.fancyName}</div>;
}

这里多半是出于代码风格的考虑,不过还是保持比较好,我怀疑也可能跟性能有关。

复合条件

不要把复合条件判断放在render里。

// bad
render () {
  return <div>{if (this.state.happy && this.state.knowsIt) { return "Clapping hands" }</div>;
}
// better
get isTotesHappy() {
  return this.state.happy && this.state.knowsIt;
},

render() {
  return <div>{(this.isTotesHappy) && "Clapping hands"}</div>;
}

这里最好的解决方案是使用容器组件来管理你的状态然后在通过属性(props)往下层传递。

检查空值

不要去检查是否存在某个prop值,快使用defaultProps

// bad
render () {
  if (this.props.person) {
    return <div>{this.props.person.firstName}</div>;
  } else {
    return null;
  }
}
// good
class MyComponent extends React.Component {
  render() {
    return <div>{this.props.person.firstName}</div>;
  }
}

MyComponent.defaultProps = {
  person: {
    firstName: 'Guest'
  }
};

当你的值是对象或者数组时,使用 PropTypes.shape声明嵌套数据的预期类型。

通过Props设置State

不要通过props值去设置state,除非明显是个初始值。

// bad
getInitialState () {
  return {
    items: this.props.items
  };
}
// good
getInitialState () {
  return {
    items: this.props.initialItems
  };
}

详细请阅读官网的Props in getInitialState Is an Anti-Pattern

命名事件响应方法

// bad
punchABadger () { /*...*/ },

render () {
  return <div onClick={this.punchABadger} />;
}
// good
handleClick () { /*...*/ },

render () {
  return <div onClick={this.handleClick} />;
}

处理方法的命名必须:

  • 第一个单词为handle

  • 最后一个单词为要响应的事件(比如Click,Change)

  • 现在时态
    如果为了避免命名冲突,你可以在handle和事件名中间加入其他信息。比如,你可以定义handleNameChange 和handleAgeChange来区分onChange的不同响应处理。不过当你这样做的时候,你要问问自己是否需要一个新的组件了。

命名事件

可以使用自定义事件替代预设的事件名。

class Owner extends React.Component {
  handleDelete () {
    // handle Ownee's onDelete event
  }

  render () {
    return <Ownee onDelete={this.handleDelete} />;
  }
}

class Ownee extends React.Component {
  render () {
    return <div onChange={this.props.onDelete} />;
  }
}

Ownee.propTypes = {
  onDelete: React.PropTypes.func.isRequired
};

使用PropTypes

使用PropTypes可以预先定义属性的类型,可以在之后获得一些有意义的警告信息。

MyValidatedComponent.propTypes = {
  name: React.PropTypes.string
};

MyValidatedComponentname属性值如果不是string类型的话, 会输出警告。

<Person name=1337 />
// Warning: Invalid prop `name` of type `number` supplied to `MyValidatedComponent`, expected `string`.

在这里也可以设置属性是否是必须存在的。

MyValidatedComponent.propTypes = {
  name: React.PropTypes.string.isRequired
}

这个组件会验证是否存在name属性。

<Person />
// Warning: Required prop `name` was not specified in `Person`

相关链接:Prop Validation

使用特殊符号

要在使用React中使用特殊符号,请使用String.fromCharCode()

// bad
<div>PiCO · Mascot</div>

// nope
<div>PiCO &middot; Mascot</div>

// good
<div>{'PiCO ' + String.fromCharCode(183) + ' Mascot'}</div>

// better
<div>{`PiCO ${String.fromCharCode(183)} Mascot`}</div>

相关链接:HTML Entities

Tables

浏览器认为你是愚蠢的,但是React不这么人为。请始终为你的table组件添加tbody。

// bad
render () {
  return (
    <table>
      <tr>...</tr>
    </table>
  );
}

// good
render () {
  return (
    <table>
      <tbody>
        <tr>...</tr>
      </tbody>
    </table>
  );
}

浏览器会自动插入tbody当你忘了写它。React则不会,这样会给你的代码带来混乱,请记住使用tbody。

classnames

使用classNames管理你的classes逻辑。

// bad
get classes () {
  let classes = ['MyComponent'];

  if (this.state.active) {
    classes.push('MyComponent--active');
  }

  return classes.join(' ');
}

render () {
  return <div className={this.classes} />;
}
// good
render () {
  let classes = {
    'MyComponent': true,
    'MyComponent--active': this.state.active
  };

  return <div className={classnames(classes)} />;
}

相关链接:Class Name Manipulation


華山D小黑
188 声望6 粉丝

人若不贱,我就不贱,人若犯贱,我必更贱