我的React最佳实践

作者: 灯草 | 来源:发表于2017-03-04 18:35 被阅读0次

一些问题


react为前端开发带来了很多的便利,配合一些前端组件库,我们能够十分迅速的开发出一个完整的前端项目。但是在使用react开发的过程中同样会遇到很多问题,在个人的react开发过程中,认为主要集中在这几个部分:

  1. 组件状态管理
  2. 组件代码和业务代码强耦合
  3. 组件的粒度
组件状态管理

组件状态管理主要包括两个部分:一个是组件的状态存在哪里,另一个是组件的状态该如何存储。
对于第一个问题,其实比较好解答,对于一个复杂的react前端项目,依赖于类似redux的flux流,将组件状态存储在一个全局的store中是一个很好的选择。在复杂前端项目的开发中,除了一些基础公共组件,我会尽可能的避免将组件状态存储在组件state中。主要原因有两点,一个是项目复杂后,组件间的状态传递会变得异常恶心,需要依靠大量props属性传递函数,并且强依赖组件生命周期函数。第二个从设计角度出发,react组件其实可以理解为一个纯函数,它根据传递进去的属性做相应的渲染,如果组件自己保持了state,那么它可能会导致意料外的渲染结果。
第二个问题比较麻烦,对于一个组件,它的状态可能包括两个部分,一种可能是data,它们通常来自于server,比如一个列表list。另一种则是state,它表示的是页面状态,比如一个table 的loading状态。这两种数据有时候甚至会有交集,因此如何管理它们也是一个问题。

组件代码和业务代码强耦合

react是一个负责view层的前端库,所以组件本身是不包含业务逻辑的。抽象来说,组件其实就是一个空的盒子,你给它什么,它就渲染出什么状态,因此具体的业务逻辑不应该在盒子里面做,而应该抽象成具体给这个盒子什么样的数据。因此在组件实现过程中,应该避免参杂各种业务逻辑代码,但这个其实不太好实现,对于非redux项目,你可能在代码中能够看到各种setState方法调用,而在redux项目中,则是各种dispatch。

组件的粒度

在一个项目中,我们会写很多组件,有时候为了考虑组件的复用性,我们会把一个复杂的组件拆分成很多个基础组件。基础组件的实现其实要比业务组件复杂很多,因为服用的问题需要考虑更多的分支条件和边界条件,对于开发速度会造成很大影响。

解决思路


其实对于上面的问题,并没有银弹,也就是很难找到一个包治百病的方法。更多的也是根据不同的业务场景做具体的实现,因此虽然这篇文章说的是最佳实践,其实也只是对应一些场景下的解决思路,但是应该能够解决大部分应用场景下的痛点。


整体框架

项目的整体框架如下,其实相对于其它react前端项目来说,它最大的不同可能就是抽象出了一个业务层,下面我们具体介绍各个部分。

数据层

依赖于redux,我们将所有的状态存储在一个全局的store中,所有对于store的修改都是通过action进行操作。在数据层中我们依赖了三个库,分别是reduxredux-actions以及react-redux。redux应该是目前使用最广的flux库,依赖redux我们可以很好的管理store中存储的状态。而依赖redux-actions,我们能够快速的创建actions,并且在reducer中监听它们,react-redux可以通过connect方法将store中的数据映射到组件的props中。
在store的设计上,我将它分成了两部分,一块是用来存储来自server的数据data,另一部分则是组件本身的状态state。这样带来的好处是可以解决某些状态回退需求,比如我们有一个修改配置的操作,如果只有state存储配置数据,那么这次修改保存失败后,我们无法退回到修改前的状态。
配置的数据通常来自于远端,因此原始数据存储在data中,而这个弹窗表单的状态则存储在state中,这里存在一个问题,就是我们需要讲data中的数据拷贝一份到state中,同时保存成功后,也需要对data中的数据做一下更新。对于数据的保存,我们依赖immutable,关于immutable有许多文章介绍,这里就不做详细介绍。

#actions
import { createAction } from 'redux-actions';
export const setCount = createAction('SETCOUNT');

#reducer
import { handleActions } from 'redux-actions';
import Immutable from 'immutable';
export const test = handleActions({
  SETCOUNT: (state, action) => state.setIn(['data', 'count'], action.payload),
}, Immutable.fromJS({data:{count: 0}}));

#View
import React from 'react';
import { connect } from 'react-redux';

const mapStateToProps = state => ({ count: state.getIn(['data', 'count'])});
const Test = (props) => {
  return (<div>{props.count}</div>)
}
export default connect(mapStateToProps)(Test);

#这样调用dispatch(setCount(10))就可以完成修改count的操作
业务层

在过去的项目中,具体的业务逻辑通常在View层中完成,因此我们能够在view层的代码中看到许许多多的dispatch方法,异步fetch方法,以及数据组装方法。这种情况其实是讲业务逻辑和前端组件进行了强耦合,造成了组件的复用性变差,同时如果组件设计不合理,debug的时候问题的定位会变得异常困难,你往往分不清是组件出了问题,还是业务代码出了问题。
为了解决这个问题,在实践过程中我们单独的抽离了一层业务逻辑层,业务层中一个比较难处理的问题就是如何处理异步操作,这里我们主要依赖的是redux-thunk,这个组件其实很简单,代码也很少,感兴趣的可以看看。它的主要作用是可以让我们dispatch异步方法,同时依赖react-redux中的bindActionCreators方法将业务层中的方法绑定到组件的props中。
我们也将全局store也注入到了业务层代码中,通过store.getState()方法和依赖reselect获取状态树中的数据。而不依靠view组件中定义的方法传递的参数。
通过上面的操作,前端组件和业务逻辑进行了充分的解耦。

#actions
import { createAction } from 'redux-actions';
export const setCount = createAction('SETCOUNT');
export const setServerData = createAction('SETSERVERDATA');

#reducer
import { handleActions } from 'redux-actions';
import Immutable from 'immutable';
export const test = handleActions({
  SETCOUNT: (state, action) =>
      state.setIn(['data', 'count'], action.payload),
  SETSERVERDATA: (state, action) =>
      state.setIn(['data', 'serverCount'], action.payload),
}, Immutable.fromJS({data:{count: 0, serverCount: 0}}));

#业务层actionCreators
import {setCount} from 'actions';
import store from 'reducer/store';
import { getCount } from 'selectors'; 
export const addOne() {
  const count = getCount(store.getState());
  dispatch(setCount(count + 1));
}
export const fetchDataFromServer() {
     fetch('url')
    .then(res => {
         dispatch(setServerData(res)) 
    })
   .catch(e => { // error handle})
}

#业务层selectors
import { createSelector } from 'reselect';
export const data = state => state.test.get('data');
export const getCount = createSelector(
  data,
  data => data.get('count')
)

#View 变得简单了
import React from 'react';
import { connect, } from 'react-redux';
import { bindActionCreators } from 'redux';
import { getCount } from 'selectors';
 //将actionAreators中的方法绑定到view的props中
import * as actions from 'actionAreators'

const mapStateToProps = state => ({ count: getCount(state) });
const mapDispatchToProps =
  dispatch => bindActionCreators({ ... actions }, dispatch);

const Test = (props) => {

  addOne() {
    this.props.addOne();
  }
  return (<div onClick={() => this.addOne}>{props.count}</div>)
}
export default connect(mapStateToProps,mapDispatchToProps)(Test);

#这样调用dispatch(setCount(10))就可以完成修改count的操作
View层

对于前端组件我们将它分为三种,一种是基础组件component,一种是业务组件widget,以及顶层组件View。
对于基础组件component,一般是粒度很细,跟业务没有太大关系的组件。它们一般是公司自己的前端组件库,或者一些开源组件库,以及项目中通用的组件。基础前端组件的开发相对来说比较困难,因为需要考虑很多边界条件和分支条件,同时还需要提供比较完善的接口,特别是针对于一些有输入属性的组件更加需要注意。
前端基础组件的设计因为需要考虑到组件的复用性,会在组件中保留state,以及使用setState方法,而不是像其他类型组件一样完全依赖于传入的属性。这里推荐一下蚂蚁的antd库;
业务组件widget一般是由多个基础组件组成的组件,它们一般不保存自己的state,同时也不像View组件一样从store中获取数据,而是完全通过传入的props来进行渲染,同时它的事件handle函数也完全依赖于顶层View组件传入。
View组件式顶层组件,它通过connect链接store中的数据和业务层中的方法,并将它们传入到子组件的props中,这样View组件就相当于整个页面的入口,因此当出现问题的时候,我们可以很好的定位,从View层一层层捋就行,而不用担心是不是某个widget组件出了问题。

#Widget
import React from 'react';
export const ChildTest = (props) => {
  click() {
    this.props.clickHandle();
  }
  return (<div onClick={() => this.click}>{props.count}</div>)
}

#View
import React from 'react';
import { connect, } from 'react-redux';
import { bindActionCreators } from 'redux';
import { getCount } from 'selectors';
 //将actionAreators中的方法绑定到view的props中
import * as actions from 'actionAreators'
import ChildTest from './widgets/ChildTest'

const mapStateToProps = state => ({ count: getCount(state) });
const mapDispatchToProps =
  dispatch => bindActionCreators({ ... actions }, dispatch);

const Test = (props) => {
  addOne() {
    this.props.addOne();
  }
  return (<div><ChildTest clickHandle={() => this.addOne} /></div>)
}
export default connect(mapStateToProps,mapDispatchToProps)(Test);

这种设计思路下可能会造成的问题就是可能组件结构会很深,不过这种情况可以在设计widget的时候解决,遇到很深的情况下我们最好将深层组件抽离出来,然后作为组件的props.children传入。

其它

整个项目的结构一般如下:

.
├── business
│   ├── actionCreators
│   └── selectors
├── common
│   ├── Constants.js
│   ├── Utils.js
│   └── apis
├── components
│   └── OptComponent.js
├── main.js
├── public
│   └── index.html
├── redux
│   ├── actions
│   ├── index.js
│   └── reducers
├── router.js
└── views
    ├── Index
    └── Layout

存在的问题主要在于异步的处理,后面发现逻辑复杂后,redux-thunk有一些力不从心。可能能使用其它换方案,比如redux-saga

写在最后

这个框架实践有一段时间了,大部分场景都能比较好的处理,但是对于一些复杂的场景也存在一些问题,比如大量的异步请求,异步竞态出现的场景,这套框架可能就不太适合。
在整个实现过程中,其实很多思想都是借鉴了后端的,其实业务层其实也就是后端的biz层。数据层毋庸置疑对应后端的数据层,只不过不是像mysql那样的关系型数据库,而是mongodb或者redis这样的非关系型数据库,而action则是对应的dao操作。
文章还有很多不足的地方,望斧正,共勉~~

相关文章

  • 我的React最佳实践

    一些问题 react为前端开发带来了很多的便利,配合一些前端组件库,我们能够十分迅速的开发出一个完整的前端项目。但...

  • React最佳实践

    tags:开发随笔 代码风格 用ES6,遵循Airbnb的React编码规范和javascript 编码规范。两个...

  • react最佳实践

    react最佳实践(基础篇) 以下仅仅是个人意见或者是react实践中的技巧,只针对代码不针对个人 无论如何,请考...

  • react最佳实践

    看到石墨的react文档。提到http://andrewhfarmer.com/react-ajax-best-p...

  • React开发相关资料

    React-Router 中文简明教程 总结 React 组件的三种写法 及最佳实践 [涨经验] React-UI组件

  • React 组件性能优化

    React 组件性能优化最佳实践 React 组件性能优化的核心是减少渲染真实 DOM 节点的频率,减少 Virt...

  • react学习文档

    通过实例,学习编写 React 组件的“最佳实践”https://zhuanlan.zhihu.com/p/278...

  • React+Redux工程目录结构,最佳实践

    参考 Redux进阶系列1: React+Redux项目结构最佳实践 《深入浅出React和Redux》一书的第四...

  • React Ajax最佳实践

    当你开始询问关于React和AJAX的一些东西时,专家们首先就会告诉你React只是一个View层的库,它并没有网...

  • React Native最佳实践

    感觉自己有点狂,居然敢在自己的文章中说这是最佳实践。不过有人不是说了吗,牛皮还是要吹的,万一实现了呢? 接下来公司...

网友评论

    本文标题:我的React最佳实践

    本文链接:https://www.haomeiwen.com/subject/srkjgttx.html