Aitter's Blog

ReactJS代码规范

一个项目大多都是由一个团队来完成,如果没有统一的代码规范,那么每个人的代码必定会风格迥异。且不说会存在多个人同时开发同一模块的情况,即使是分工十分明晰的,等到要整合代码的时候也有够头疼的了。大多数情况下,并非程序中有复杂的算法或是复杂的逻辑,而是去读别人的代码实在是一件痛苦的事情。统一的风格使得代码可读性大大提高了,人们看到任何一段代码都会觉得异常熟悉。显然的,规范的代码在团队的合作开发中是非常有益而且必要的。

简要

  • 要求说明
  • 基本概念
  • 通用规范
  • JSX规范
  • 组件规范
  • 组件分类
  • 组件结构
  • 组件通信

要求说明

  • 必须:表示绝对要求这样做。
  • 必须不:表示绝对不要求这样做。
  • 应该/建议:表示一般情况下应该这样做,但是在某些特定情况下可以忽视这个要求。
  • 应该不/不建议:表示一般情况下不应该这样做,但是在某些特定情况下可以忽视这个要求。
  • 可以:表示这个要求完全是可选的,你可以这样做,也可以不这样做。

基本概念

React生命周期

提供的一系列处理函数(钓子函数),这些函数会在组件生命周期的某个阶段调用。

  • 创建期:getDefaultProps
  • 创建期:getInitialState
    在组件挂载前(即:创建期)调用一次,其返回值将做为 this.state 的初始值

  • 创建期:componentWillMount
    在初始化渲染执行之前被调用。如果在这个方法内调用 setState() 方法,render() 方法将会收到更新后的state,也就是说这是做在组件渲染前最后一个修改state的机会。

  • 创建期:componentDidMount
    一般在这个方法中使用 this.getDOMNode() 方法访问原始DOM

  • 存在期:componentWillReceiveProps
    组件感知到props属性改变会,会调用此方法。render() 方法将会在其后调用

  • 存在期:shouldComponentUpdate
    在组件收到新的 propsstate。在这个方法中,我们可以访问组件的props和state属性,通过这两个属性可以确认组件是否需要更新,如果不需要更新,则返回false,则其后的方法将不会在执行

shouldComponentUpdate: function(nextProps, nextState) {
return nextProps.id !== this.props.id;
}
  • 存在期:componentWillUpdate
    在收到新的 propsstate 后调用,类似 componentWillMount()

  • 存在期:componentDidUpdate
    在组件重新渲染后立即被调用,当我们需要在组件重新渲染后操作DOM则需要使用这个方法

  • 销毁&清理期:componentWillUnmount
    是组件销毁&清理期唯一调用的方法,它会在组件从DOM中移除时被调用,这时我们可以清理一些状态或清理在 componentDidMount 中创建的DOM元素。

通用规范

  • 建议使用ES5原生方法操作数组
  • 建议能用三元表达式,就不用 if else,能用 &&,就不用三元表达式
// bad
if (res.chooseCard) {
TJ.G.set('setmeal.cardPrice', res.chooseCard.balance);
} else {
TJ.G.set('setmeal.cardPrice', 0);
}
// good
const chooseCard = res.chooseCard
TJ.G.set('setmeal.cardPrice', chooseCard ? chooseCard.balance : 0)
// bad
{this.state.savefail ?
<ErrorInfo className="note" errors='该项目为必选项, 不能取消'/>
:null
}
// good
{ this.state.savefail && <ErrorInfo className="note" errors='该项目为必选项, 不能取消'/> }
  • 建议 代码块使用多行注释,语句使用单行注释,参数说明使用代码后注释
/**
* desc: xxx组件
* author: lt
* useage: Toast.info('')
*/
import React from 'react'
const CONFIG = {
itemIds: TJ.getUrlParams('itemIds'), // 套餐默认项
orderId: TJ.getUrlParams('orderId'), // 订单id
}
exprot default React.createClass({
getInitialState(){}
// 选中当前项
selectCurrent(){},
render(){
return (
<div>
{/*头部*/}
<HeaderView />
</div>
)
}
})
  • 必须在DOM片段中使用双引号 ", js中使用 '
// bad
<Foo bar='bar' />
// good
<Foo bar="bar" />
// bad
<Foo style={{ left: "20px" }} />
// good
<Foo style={{ left: '20px' }} />

JSX规范

  • 建议遵守下面示例中的DOM片段对齐方式
// bad
<Foo superLongParam="bar"
anotherSuperLongParam="baz" />
// good
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
/>
// 如果 props 写在一行,那行结束标签也保持在一行
<Foo bar="bar" />
// 子组件保持一个缩进
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
>
<Quux />
</Foo>
  • 必须在自关闭标签前加一个空格
// bad
<Foo/>
// very bad
<Foo />
// bad
<Foo
/>
// good
<Foo />
  • 必须在DOM片段前后加一对括号(),当DOM片段在一行以上时
// bad
render() {
return <MyComponent className="long body" foo="bar">
<MyChild />
</MyComponent>;
}
// good
render() {
return (
<MyComponent className="long body" foo="bar">
<MyChild />
</MyComponent>
);
}
// good, when single line
render() {
const body = <div>hello</div>;
return <MyComponent>{body}</MyComponent>;
}
  • 建议将组件写成自关闭标签,当组件没有 children
// bad
<Foo className="stuff"></Foo>
// good
<Foo className="stuff" />

组件规范

  • 必须.jsx 结尾
  • 必须只包含一个 React Component
  • 必须只能使用 React.createClass() 来创建一个 React Component
  • 建议书写 propTypes,规定每个可接受属性的类型,并对 propTypes 加以说明,设置默认值,并申明在类外部
import React from 'react'
export default React.createClass({
displayName: 'Toast',
})
Toast.propTypes = {
// display tips
show: React.PropTypes.bool,
// type: `default`, `success`, `loading`
type: React.PropTypes.string,
};
Toast.defaultProps = {
show: false,
type: 'default'
};
  • 必须使用 camalCase 来命名 props
// bad
<Foo
UserName="hello"
phone_number={12345678}
/>
// good
<Foo
userName="hello"
phoneNumber={12345678}
/>
  • 建议当 props 的值为字面值 true时,省略 ={true}
// bad
<Foo
hidden={true}
/>
// good
<Foo
hidden
/>
  • 建议遵循如下的顺序排列JSX文件中的方法
    render() 始终放在最后
React.createClass({
displayName: '',
mixins: [],
statics: {},
propTypes: {},
getDefaultProps() {
// ...
},
getInitialState() {
// do something
},
componentWillMount() {
// do something
},
componentDidMount() {
// do something: add DOM event listener, etc.
},
componentWillReceiveProps() {
},
shouldComponentUpdate() {},
componentWillUpdate() {},
componentDidUpdate() {},
componentWillUnmount() {
// do something: remove DOM event listener. etc.
},
// clickHandlers or eventHandlers like onClickSubmit() or onChangeDescription()
handleClick() {
// ...
},
// getter methods for render like getSelectReason() or getFooterContent()
// Optional render methods like renderNavigation() or renderProfilePicture()
render() {
// ...
}
});
  • 必须在state中只存放组件运行期的状态,保持 state 的简洁性
  • 必须 仅在实例化生命周期中绑定window或body事件
  • 必须在销毁期生命周期中解绑window或body事件
componentDidMount: function() {
window.addEventListener('resize', this.handleResize);
},
componentWillUnmount: function() {
window.removeEventListener('resize', this.handleResize);
},
  • 建议使用延展操作符为子组件传递属性
// bad
<CartView
specialCompanyId={this.state.specialCompanyId}
data={this.state.data}
selectCurrent={this.state.selectCurrent}
isShowPrice={this.state.isShowCardPrice}
beforeModifyPrice={this.state.beforeModifyPrice}/>
// good
render() {
const props = {
specialCompanyId: this.state.specialCompanyId,
data: this.state.data,
selectCurrent: this.state.selectCurrent,
isShowCardPrice: this.state.isShowCardPrice,
beforeModifyPrice: this.state.beforeModifyPrice,
};
return <ProductPrice {...props} />
}
  • 建议复杂的render逻辑使用IIFE,在闭包中使用 if else

复杂的渲染逻辑中使用 if else

牺牲性能提升可读性

组件分类

约束好组件的职责,能让组件更好地解耦,知道什么功能是组件实现的,什么功能不需要实现

组件主要分为 通用组件(可复用组件)与 业务组件(一次性组件)

可复用组件实现通用的功能(不会因组件使用的位置、场景而变化)

  • 命名与业务无关( Dialog/Toast/Button/Cell…)
  • 只负责 UI展示、交互事件
  • 尽量少的依赖,最大程序保持独立性,以确保在任何业务中复用
  • 不依赖父组件,通过自定义事件与之交互
  • 数据扁平化,使用props检验接收的数据

业务组件实现偏业务化的功能

  • 获取数据
  • 数据处理逻辑
  • 引用可复用组件

通用组件:组件的最小单元,由业务组件引入使用,组装为业务组件,可分为布局组件、数据录入、数据展示、操作反馈等组件
业务组件:公共业务组件(多个业务复用) 与 页面业务组件(当前页面使用)

组件依赖

  • 能用组件更可能少的依赖
  • 组件的相关资源放置到同一个文件夹内(css、img)

组件结构

// ============== 依赖引用
import React from 'react';
import classNames from 'classnames';
import './toptips.less';
// ============== 定义类
var Toptips = React.createClass({
render(){
const {className, type, children, show, ...others} = this.props;
const cls = classNames({
'weui-toptips': true,
[`weui-toptips_${type}`]: true,
[className]: className
});
return(
<div className={cls} {...others} style={{display: show ? 'block' : 'none'}}>
{children}
</div>
)
}
})
// ============== 设置 propTypes 名称、类型、验证
Toptips.propTypes = {
/**
* display tips
*/
show: React.PropTypes.bool.isRequired,
/**
* type: `default`, `warn`, `info`, `primary`
*/
type: React.PropTypes.string,
};
// ============== 设置默认值
Toptips.defaultProps = {
show: false,
type: 'default'
};
// ============== 导出模块
export default Toptips

组件通信

组件通信的三种方法:

  • 使用props,构建通信链(嵌套深时,不适用)
  • 使用模块内部的局部变量,在父组件初始化后,赋值给全局变量,子组件使用该变量访问父组件的属性,完成通信(变量不利用维护)
  • 使用PubSub模式(利于解藕)

父子组件的通信

原则:props down, events up!

父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息,遵循单向数据流动的原则。

同级子组件的通信

使用 Pubsub 的事件发布订阅模式

在父组件中使用 Pubsub.subscribe() 订阅事件
在子组件中使用 Pubsub.publish() 触发事件,向父组件传递数据
componentWillUnmount 中使用 Pubsub.unsubscribe() 取消事件订阅