React最佳实践

这篇文章主要讲的是在React组件开发中的一些规范,我也将其实践于项目中,结合我在项目中的实践进行了一些批注,意欲解释其为什么要这么做,希望能给新开发react的小伙伴有一些帮助。

渣翻见谅…

React最佳实践

目录

  1. 概述
  2. 组件的组织结构
    1. 基础组件结构
    2. 规范props格式
  3. 推荐模式
    1. Props数据的处理
    2. State数据的处理
    3. 在render方法中使用三元表达式
    4. View层组件
    5. 容器组件
  4. 反模式
    1. 条件判断语句
    2. 不要在 render 方法中缓存数据
    3. 存在性检验
    4. 根据props来设置state
  5. 实例
    1. 合理命名事件处理函数
    2. 对event合理命名
    3. 使用propType
    4. 使用实体字符
    1. 表格
  6. 工具库
    1. classNames库
  7. 其他
    1. JSX
    2. ES2015
    3. react-rails
    4. rails-assets
    5. flux

概述

本文讨论我们如何正确的在Rails框架中编写 React.js 。我们一直在努力用最优雅的方式去编写React组件,在多次的失败尝试之后,我们得出了以下这些所推荐的方式;如果有的地方看起来不太适合,那么他可能真的不合适。希望你能让我们也知道哪些地方不合适。

所有的例子基于ES2015语法编写,使用 babel 转译以及采用 react-rails gem 框架。

组件的组织结构

基础组件结构

  • class的定义
    • constructor
    • 事件绑定
    • 组件生命周期事件
    • get/set 数据处理函数
    • render 方法
  • defaultProps
  • proptypes
class Personextends React.Component {
  constructor (props) {
    super(props);
 
    this.state = { smiling: false };
 
    this.handleClick = () => {
      this.setState({smiling: !this.state.smiling});
    };
  }
 
  componentWillMount () {
    // 事件监听 (flux/reduce 的Store、WebSocket、document等) add event listeners (Flux Store, WebSocket, document, etc.)
  }
 
  componentDidMount () {
    // React.getDOMNode() DOM操作等相关逻辑
  }
 
  componentWillUnmount () {
    // 移除事件监听 remove event listeners (Flux Store, WebSocket, document, etc.)
  }
 
  getsmilingMessage () {
    return (this.state.smiling) ? "is smiling" : "";
  }
 
  render () {
    return (
      <divonClick={this.handleClick}>
        {this.props.name} {this.smilingMessage}
      </div>
    );
  }
}
 
Person.defaultProps = {
  name: 'Guest'
};
 
Person.propTypes = {
  name: React.PropTypes.string
};

译注:

在willMount 生命周期中virtualDom已经完成,所以可以进行事件绑定

在DidMount 中组件已经被渲染,此时可以用this.refs了,写canvas的同学要注意这点。

规范Props格式

两个及以上props换行书写。

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

推荐模式

Props数据的处理

使用 Get/Set访问器属性 来做数据处理。

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

参阅:4.2 反模式-不要在 render 方法中缓存数据

State数据的处理

数据处理函数的命名应该在开头处添加一个动词,以增加可读性。

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

这里的对象方法 必须 返回一个 布尔值 。

译注:

这里想表达的是,state做为管理组件状态的数据,数据结构应该尽量简单,清晰的表示组件状态,复杂的业务逻辑应该放在父组件通过props传递的方式来获得,所以作者推荐state的处理必须返回布尔值。

参阅: 4.1 反模式-条件判断语句

在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>
  );
}

View层组件

以view层为基本最小单元来组织组件。不要创建只能一次性使用的巨无霸组件以及域组件 。

// bad
class PeopleWrappedInBSRowextends React.Component {
  render () {
    return (
      <divclassName="row">
        <Peoplepeople={this.state.people} />
      </div>
    );
  }
}
// good
class BSRowextends React.Component {
  render () {
    return <divclassName="row">{this.props.children}</div>;
  }
}
 
class SomeViewextends React.Component {
  render () {
    return (
      <BSRow>
        <Peoplepeople={this.state.people} />
      </BSRow>
    );
  }
}

容器组件

使用一个父级容器来进行数据请求,然后用子级组件来进行对应的渲染 — Jason Bonta

Bad

// CommentList.js
 
class CommentListextends 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 CommentListextends React.Component {
  render() {
    return (
      <ul>
        {this.props.comments.map(({body, author}) => {
          return <li>{body}—{author}</li>;
        })}
      </ul>
    );
  }
}
// CommentListContainer.js
 
class CommentListContainerextends React.Component {
  getInitialState () {
    return { comments: [] }
  }
 
  componentDidMount () {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments});
      }.bind(this)
    });
  }
 
  render () {
    return <CommentListcomments={this.state.comments} />;
  }
}

了解更多

相关视频

反模式

条件判断语句

不要在  render 方法中写判断语句,多采用三元或短路。

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

这个情况最好的方法就是使用 父组件容器 来管理state,新的state数据作为props传给子组件。

参阅: 2.2推荐模式-State数据的处理

不要在render方法中缓存数据

不要在  render 方法中进行数据处理、state/props的保存。

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

我认为这样的风格是为了性能考量。

参阅:2.1推荐模式-props数据的处理

存在性检验

不要在根组件检查存在性。

使用函数式编写组件时,返回的数据类型应该恒定。

// bad
const Person = props => {
  if (this.props.firstName)
    return <div>{this.props.firstName}</div>
  else
    return null
}

所有组件都 必须 被渲染。

所以可以增加一个自己觉得合适的默认值—— defaultProps 。

译注:

所有组件都进行渲染主要是为了方便进行控制和管理。

即不需要呈现的组件,渲染为一个空标签即可。

// better
const Person = props =>
  <div>{this.props.firstName}</div>
 
Person.defaultProps = {
  firstName: "Guest"
}

如果一个组件通过条件判断来决定渲染与否,那么可以将存在性检验写在其私有的组件语句中。

// best
const TheOwnerComponent = props =>
  <div>
    {props.person && <Person {...props.person} />}
  </div>

上述的存在性检验主要用于对象或者数组。

组件所需要的props中,嵌套的数据类型使用 PropTypes来进行检测。

根据props来设置state

根据props设置state,并且给予props明确的含义。

// 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 <divonClick={this.punchABadger} />;
}
// good
handleClick () { /*...*/ },
 
render () {
  return <divonClick={this.handleClick} />;
}

处理函数的命名应该:

– 以 handle 作为开始

– 以其对应的事件作为名字的结束 (例如, Click , Change )

– 使用一般现在时

如果你需要一些消除歧义的名称,你可以在 handle 和事件名中间增加一些补充信息,

比如说你想要区分 onChange 事件,就可以是: handleNameChange 和 handleAgeChange。不过若真如此,你可能需要想一下是不是需要创造一个新的组件。

对event合理命名

对于所有者自身的事件使用自定义事件来命名。

class Ownerextends React.Component {
  handleDelete () {
    // handle Ownee's onDelete event
  }
 
  render () {
    return <OwneeonDelete={this.handleDelete} />;
  }
}
 
class Owneeextends React.Component {
  render () {
    return <divonChange={this.props.onDelete} />;
  }
}
 
Ownee.propTypes = {
  onDelete: React.PropTypes.func.isRequired
};

使用propType

proptype表示组件所期望的数据类型,以及用于产生有意义的警告。

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

如果接受到的 name 字段而不是 string , MyValidatedComponent 会打出一个警告。

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

这个组件要求 所传入的 props 必须有 name 字段。

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

组件会对要求字段进行验证。

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

了解更多: Prop Validation

使用实体字符

使用React原生的 String.fromCharCode() 来处理特殊字符。

// bad
<div>PiCO · Mascot</div>
 
// nope
<div>PiCO · Mascot</div>
 
// good
<div>{'PiCO ' + String.fromCharCode(183) + ' Mascot'}</div>
 
// better
<div>{`PiCO ${String.fromCharCode(183)} Mascot`}</div>

了解更多: JSX Gotchas

表格

在 table 标签中要记得使用 tbody 。

如果你忘记了 tbody 标签,虽然React不会像浏览器那样觉得你很ZZ然后帮你加一个,但是React会继续渲染JSX即给 table 里面直接插入 tr ,最后的结果可能不可控,让你一脸懵逼,所以记得使用 tbody 。

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

工具库

classNames库

使用 classNames 这个库来做类名判断与管理。

// bad
getclasses () {
  letclasses = ['MyComponent'];
 
  if (this.state.active) {
    classes.push('MyComponent--active');
  }
 
  return classes.join(' ');
}
 
render () {
  return <divclassName={this.classes} />;
}
// good
render () {
  letclasses = {
    'MyComponent': true,
    'MyComponent--active': this.state.active
  };
 
  return <divclassName={classnames(classes)} />;
}

了解更多: Class Name Manipulation

其他

JSX

我在的项目组中曾经有一些 CoffeeScript的拥趸,不幸的事情是在写CoffeeScript数据模板的时候,因为JSX进行了抽象导致模板钩子的实现改变了。

所以我们不推荐使用CoffeeScript来编写render方法。

当必须使用CoffeeScript时,你可以看一下我们怎么使用CoffeeScript: CoffeeScript and JSX

ES2015

react-rails 现在已经集成了 babel ,所有babel能做的事情,在Rails中也可以,你可以参阅文档来了解其相关配置。

react-rails

react-rails 可以用于所有使用React开发的Rails Apps。它使得Rails与React之间可以完美结合。

rails-assets

rails-assets 是一个资源管理框架,用于管理项目中的js/css等静态资源。我这里最流行的React库是 Bower 它其可以非常方便的添加依赖与react资源。

警告:rails-assets 需要 Sprockets来进行 bower项目的访问。这是rails使用js传统的方法的胜利,这种方法不需要借助js模块化管理工具。

flux

使用 Alt 来进行flux开发。flux 文档提到建议配合Alt开发。

原文地址: react-patterns

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值