这篇文章主要讲的是在React组件开发中的一些规范,我也将其实践于项目中,结合我在项目中的实践进行了一些批注,意欲解释其为什么要这么做,希望能给新开发react的小伙伴有一些帮助。
渣翻见谅…
React最佳实践
目录
- 概述
- 组件的组织结构
- 基础组件结构
- 规范props格式
- 推荐模式
- Props数据的处理
- State数据的处理
- 在render方法中使用三元表达式
- View层组件
- 容器组件
- 反模式
- 条件判断语句
- 不要在 render 方法中缓存数据
- 存在性检验
- 根据props来设置state
- 实例
- 合理命名事件处理函数
- 对event合理命名
- 使用propType
- 使用实体字符
- 坑
- 表格
- 工具库
- classNames库
- 其他
- JSX
- ES2015
- react-rails
- rails-assets
- 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