前言:对很多 react 新手来说,网上能找到的资源大都是些简单的 tutorial ,它们能教会你如何使用 react ,但并不会告诉你怎么在实际项目中优雅的组织和编写 react 代码。用谷歌搜中文“ React 最佳实践”发现前两页几乎全都是同一篇国外文章的译文...所以我总结了下自己过去那个项目使用 React 踩过的一些坑,也整理了一些别人的观点,希望对部分 react 使用者有帮助。

React 与 AJAX

React只负责处理View这一层,它本身不涉及网络请求/AJAX,所以这里我们需求考虑两个问题:

  • 第一,用什么技术从服务端获取数据;

  • 第二,获取到的数据应该放在react组件的什么位置。

React官方提供了一种解决方案:Load Initial Data via AJAX

使用jQuery的Ajax方法,在一个组件的componentDidMount()中发ajax请求,拿到的数据存在组件自己的state中,并调用setState方法去更新UI。如果是异步获取数据,则在componentWillUnmount中取消发送请求。

如果只是为了使用jQuery的Ajax方法就引入整个jQuery库,既是一种浪费又加大了整个应用的体积。那我们还有什么其他的选择吗?事实上是有很多的:fetch()fetch polyfillaxios...其中最需要我们关注的是window.fetch(),它是一个简洁、标准化的javascript的Ajax API。在Chrome和Firefox中已经可以使用,如果需要兼容其他浏览器,可以使用fetch polyfill。

React官方文档只告诉了我们在一个单一组件中如何通过ajax从服务器端获取数据,但并没有告诉我们在一个完整的实际项目中到底应该把数据存在哪些组件中,这部分如果缺乏规范的话,会导致整个项目变得混乱、难以维护。下面给出三种比较好的实践:

1. 所有的数据请求和管理都存放在唯一的一个根组件

让父组件/根组件集中发送所有的ajax请求,把从服务端获取的数据统一存放在这个组件的state中,再通过props把数据传给子组件。这种方法主要是针对组件树不是很复杂的小型应用。缺点就是当组件树的层级变多了以后,需要把数据一层一层地传给子组件,写起来麻烦,性能也不好。

2. 设置多个容器组件专门处理数据请求和管理

其实跟第一种方法类似,只不过设置多个容器组件来负责数据请求和状态管理。这里我们需要区分两种不同类型的组件,一种是展示性组件(presentational component),另一种是容器性组件(container component)。展示性组件本身不拥有任何状态,所有的数据都从容器组件中获得,在容器组件中发送ajax请求。两者更详细的描述,可以阅读下这篇文章:Presentational and Container Components

一个具体的例子:

假设我们需要展示用户的姓名和头像,首先创建一个展示性组件<UserProfile />,它接受两个Props:nameprofileImage。这个组件内部没有任何关于Ajax的代码。

然后创建一个容器组件<UserProfileContainer />,它接受一个userId的参数,发送Ajax请求从服务器获取数据存在state中,再通过props传给<UserProfile />组件。

3. 使用Redux或Relay的情况

Redux管理状态和数据,Ajax从服务器端获取数据,所以很显然当我们使用了Redux时,应该把所有的网络请求都交给redux来解决。具体来说,应该是放在Async Actions。如果用其他类Flux库的话,解决方式都差不多,都是在actions中发送网络请求。

Relay是Facebook官方推出的一个库。如果用它的话,我们只需要通过GraphQL来声明组件需要的数据,Relay会自动地把下载数据并通过props往下传递。不过想要用Relay,你得先有一个GraphQL的服务器...

一个标准组件的组织结构

1 class definition
1.1 constructor
1.1.1 event handlers
1.2 'component' lifecycle events
1.3 getters
1.4 render
2 defaultProps
3 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
};

以上示例代码的来源:https://github.com/planningcenter/react-patterns#component-organization

使用 PropTypes 和 getDefaultProps()

  1. 一定要写PropTypes,切莫为了省事而不写

  2. 如果一个Props不是requied,一定在getDefaultProps中设置它

    React.PropTypes主要用来验证组件接收到的props是否为正确的数据类型,如果不正确,console中就会出现对应的warning。出于性能方面的考虑,这个API只在开发环境下使用。

基本使用方法:

propTypes: {
myArray: React.PropTypes.array,
myBool: React.PropTypes.bool,
myFunc: React.PropTypes.func,
myNumber: React.PropTypes.number,
myString: React.PropTypes.string, // You can chain any of the above with `isRequired` to make sure a warning
// is shown if the prop isn't provided.
requiredFunc: React.PropTypes.func.isRequired
}

假如我们props不是以上类型,而是拥有复杂结构的对象怎么办?比如下面这个:

{
text: 'hello world',
numbers: [5, 2, 7, 9],
}

当然,我们可以直接用React.PropTypes.object,但是对象内部的数据我们却无法验证。

propTypes: {
myObject: React.PropTypes.object,
}

进阶使用方法:shape() 和 arrayOf()

propTypes: {
myObject: React.PropTypes.shape({
text: React.PropTypes.string,
numbers: React.PropTypes.arrayOf(React.PropTypes.number),
})
}

下面是一个更复杂的Props:

[
{
name: 'Zachary He',
age: 13,
married: true,
},
{
name: 'Alice Yo',
name: 17,
},
{
name: 'Jonyu Me',
age: 20,
married: false,
}
]

综合上面,写起来应该就不难了:

propTypes: {
myArray: React.PropTypes.arrayOf(
React.propTypes.shape({
name: React.propTypes.string.isRequired,
age: React.propTypes.number.isRequired,
married: React.propTypes.bool
})
)
}

把计算和条件判断都交给 render() 方法吧

1. 组件的state中不能出现props
 // BAD:
constructor (props) {
this.state = {
fullName: `${props.firstName} ${props.lastName}`
};
} render () {
var fullName = this.state.fullName;
return (
<div>
<h2>{fullName}</h2>
</div>
);
}
// GOOD:
render () {
var fullName = `${this.props.firstName} ${this.props.lastName}`;
}

当然,复杂的display logic也应该避免全堆放在render()中,因为那样可能导致整个render()方法变得臃肿,不优雅。我们可以把一些复杂的逻辑通过helper function移出去。

// GOOD: helper function
renderFullName () {
return `${this.props.firstName} ${this.props.lastName}`;
} render () {
var fullName = this.renderFullName();
}
2. 保持state的简洁,不要出现计算得来的state
// WRONG:
constructor (props) {
this.state = {
listItems: [1, 2, 3, 4, 5, 6],
itemsNum: this.state.listItems.length
};
}
render() {
return (
<div>
<span>{this.state.itemsNum}</span>
</div>
)
}
// Right:
render () {
var itemsNum = this.state.listItems.length;
}
3. 能用三元判断符,就不用If,直接放在render()里
// BAD:
renderSmilingStatement () {
if (this.state.isSmiling) {
return <span>is smiling</span>;
}else {
return '';
}
}, 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>
);
}
4. 布尔值都不能搞定的,交给IIFE吧

Immediately-invoked function expression

return (
<section>
<h1>Color</h1>
<h3>Name</h3>
<p>{this.state.color || "white"}</p>
<h3>Hex</h3>
<p>
{(() => {
switch (this.state.color) {
case "red": return "#FF0000";
case "green": return "#00FF00";
case "blue": return "#0000FF";
default: return "#FFFFFF";
}
})()}
</p>
</section>
);
5. 不要把display logic写在componentWillReceivePropscomponentWillMount中,把它们都移到render()中去。

如何动态处理 classNames

1. 使用布尔值
// BAD:
constructor () {
this.state = {
classes: []
};
} handleClick () {
var classes = this.state.classes;
var index = classes.indexOf('active'); if (index != -1) {
classes.splice(index, 1);
} else {
classes.push('active');
} this.setState({ classes: classes });
}
// GOOD:
constructor () {
this.state = {
isActive: false
};
} handleClick () {
this.setState({ isActive: !this.state.isActive });
}
2. 使用classnames这个小工具来拼接classNames:
// BEFORE:
var Button = React.createClass({
render () {
var btnClass = 'btn';
if (this.state.isPressed) btnClass += ' btn-pressed';
else if (this.state.isHovered) btnClass += ' btn-over';
return <button className={btnClass}>{this.props.label}</button>;
}
});
// AFTER:
var classNames = require('classnames'); var Button = React.createClass({
render () {
var btnClass = classNames({
'btn': true,
'btn-pressed': this.state.isPressed,
'btn-over': !this.state.isPressed && this.state.isHovered
});
return <button className={btnClass}>{this.props.label}</button>;
}
});

未完待续...

[转] React 最佳实践——那些 React 没告诉你但很重要的事的更多相关文章

  1. React最佳实践(1)

    React最佳实践不敢妄谈,但最差实践非知乎莫属. 旧版知乎看起来土了点,但体验流畅,起码用起来舒服. 新版知乎看起来UI现代化,技术实现上采用了React,但是可能因为知乎缺钱,请不起高水平的前端工 ...

  2. iShow UI for React 最佳实践

    React 与 AJAX React只负责处理View这一层,它本身不涉及网络请求/AJAX,所以这里我们需求考虑两个问题: 第一,用什么技术从服务端获取数据: 第二,获取到的数据应该放在react组 ...

  3. 我的 React 最佳实践

    There are a thousand Hamlets in a thousand people's eyes. ----- 威廉·莎士比亚 免责声明:以下充满个人观点,辩证学习 React 目前开 ...

  4. web前端开发最佳实践笔记

    一.文章开篇 由于最近也比较忙,一方面是忙着公司的事情,另外一方面也是忙着看书和学习,所以没有时间来和大家一起分享知识,现在好了,终于回归博客园的大家庭了,今天我打算来分享一下关于<web前端开 ...

  5. React服务器渲染最佳实践

    源码地址:https://github.com/skyFi/dva-starter React服务器渲染最佳实践 dva-starter 完美使用 dva react react-router,最好用 ...

  6. 我们编写 React 组件的最佳实践

    刚接触 React 的时候,在一个又一个的教程上面看到很多种编写组件的方法,尽管那时候 React 框架已经相当成熟,但是并没有一个固定的规则去规范我们去写代码. 在过去的一年里,我们在不断的完善我们 ...

  7. 腾讯优测优分享 | 探索react native首屏渲染最佳实践

    腾讯优测是专业的移动云测试平台,旗下的优分享不定时提供大量移动研发及测试相关的干货~ 此文主要与以下内容相关,希望对大家有帮助. react native给了我们使用javascript开发原生app ...

  8. 探索react native首屏渲染最佳实践

    文 / 腾讯 龚麒 0.前言 react native给了我们使用javascript开发原生app的能力,在使用react native完成兴趣部落安卓端发现tab改造后,我们开始对由react n ...

  9. 总结 React 组件的三种写法 及最佳实践 [涨经验]

    React 专注于 view 层,组件化则是 React 的基础,也是其核心理念之一,一个完整的应用将由一个个独立的组件拼装而成. 截至目前 React 已经更新到 v15.4.2,由于 ES6 的普 ...

随机推荐

  1. Strongly connected HDU - 4635(判断强连通图 缩点)

    找出强联通块,计算每个连通块内的点数.将点数最少的那个连通块单独拿出来,其余的连通块合并成一个连通分量. 那么假设第一个连通块的 点数是 x  第二个连通块的点数是 y 一个强连通图(每两个点之间,至 ...

  2. Eclipse构建项目时"An internal error occurred during: "Building workspace". Java heap space"

    出现这个错误,eclipse 会卡死,以及自动退出 解决方案 工程根目录 找到项目中.project文件 删除这两处   第一处:  <buildCommand>          < ...

  3. 2019.3.16 noiac的原题模拟赛

    RT,这太谔谔了,我不承认这是模拟赛 但是虽然是搬了三道题,题目本身也还能看,就这么着吧 (怎么机房里就我一道原题都没做过啊 T1 CF24D Broken Robot 比较简单地列出式子之后,我们发 ...

  4. 8.30 牛客OI赛制测试赛1 F题 子序列

    题目描述 给出一个长度为n的序列,你需要计算出所有长度为k的子序列中,除最大最小数之外所有数的乘积相乘的结果 输入描述: 第一行一个整数T,表示数据组数.对于每组数据,第一行两个整数N,k,含义如题所 ...

  5. DynamicSegmentTree

    最近尝试了一下动态开点线段树,英文直译就是Dynamic Open Point Segment Tree,太SB了. 就跟之前的主席树写法差不多. if(!x || x == y) { x = ++t ...

  6. CentOS6.7定制化制作ISO

    CentOS6.7定制化制作ISO 以CentOS 6.7-minimal为例. 欢迎大家转载,并保留原文出处.内容若有错误或补充,请联系:szyzln@126.com 本文主要讲解如何在已有官方Ce ...

  7. java基础知识代码-------枚举类型

    package com.mon10.day22; /** * 类说明 :枚举类型,案例二 * * @author 作者 : chenyanlong * @version 创建时间:2017年10月22 ...

  8. 把iPad上的视频推送到大麦盒子去

    把iPad上的视频推送到大麦盒子去   最近因为升级家里的宽带,服务商送了一个大麦盒子给我.   大麦盒子,就是一个网络机顶盒,用它可以通过互联网收看电视剧.电影.电视节目.音乐等等.除了它自身带的一 ...

  9. decimal, float 和double

    阿里的 Java 手册里写着: 6. [强制] 小数类型为 decimal,禁止使用 float 和 double. 说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的 ...

  10. jQuery下实现等待指定元素加载完毕

    先声明下这个方法的使用场合,以免误导大家..比如在博客园,我们没法修改他的源代码,那么只能想办法监视元素的出现了.所以下面方法是在修改不了源码的情况下使用,而非写自己的项目.. 今天在改博客几个样式的 ...