New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React + Redux 最佳实践 #1

Open
sorrycc opened this Issue Feb 29, 2016 · 60 comments

Comments

Projects
None yet
@sorrycc
Owner

sorrycc commented Feb 29, 2016

更新:我们基于此最佳实践做了一个封装方案:dva,可以简化使用 redux 和 redux-saga 时很多繁杂的操作。

前端变化虽快,但其实一直都围绕这几个概念在转:

  • URL - 访问什么页面
  • Data - 显示什么信息
  • View - 页面长成什么样
  • Action - 对页面做了什么操作
  • API Server - Data 数据的来源

在 redux 的生态圈内,每个环节有多种方案,比如 Data 可以是 immutable 或者 plain object,在你选了 immutable 之后,用 immutable.js 还是 seamless-immutable,以及是否用 redux-immutable 来辅助数据修改,都需要选择。

本文总结目前 react + redux 的最佳实践,解释原因,并提供可选方案。

心急的朋友可以直接看代码:https://github.com/sorrycc/github-stars

一、URL > Data

需求

routing

选择

react-router + react-router-redux: 前者是业界标准,后者可以同步 route 信息到 state,这样你可以在 view 根据 route 信息调整展现,以及通过 action 来修改 route 。

可选

二、Data

需求

为 redux 提供数据源,修改容易。

方案

plain object: 配合 combineReducer 已经可以满足需求。

同时在组织 Store 的时候,层次不要太深,尽量保持在 2 - 3 层。如果层次深,可以考虑用 updeep 来辅助修改数据。

可选

immutable.js: 通过自定义的 api 来操作数据,需要额外的学习成本。不熟悉 immutable.js 的可以先尝试用 seamless-immutable,JavaScript 原生接口,无学习门槛。

另外,不推荐用 redux-immutable 以及 redux-immutablejs,一是没啥必要,具体看他们的实现就知道了,都比较简单;更重要的是他们都改写了 combineReducer,会带来潜在的一些兼容问题。

三、Data > View

需求

数据的过滤和筛选。

方案

reselect: store 的 select 方案,用于提取数据的筛选逻辑,让 Component 保持简单。选 reselct 看重的是 可组合特性缓存机制

可选

四、View 之 CSS 方案

需求

合理的 CSS 方案,考虑团队协作。

方案

css-modules: 配合 webpack 的 css-loader 进行打包,会为所有的 class name 和 animation name 加 local scope,避免潜在冲突。

直接看代码:

Header.jsx

import style from './Header.less';
export default () => <div className={style.normal} />;

Header.less

.normal { color: red; }

编译后,文件中的 style.normal.normal 在会被重命名为类似 Header__normal___VI1de

可选

bem, rscss ,这两个都是基于约定的方案。但基于约定会带来额外的学习成本和不遍,比如 rscss 要求所有的 Component 都是两个词的连接,比如 Header 就必须换成类似 HeaderBox 这样。

radium,inline css 方案,没研究。

五、Action <> Store,业务逻辑处理

需求

统一处理业务逻辑,尤其是异步的处理。

方案

redux-saga: 用于管理 action,处理异步逻辑。可测试、可 mock、声明式的指令。

可选

redux-loop: 适用于相对简单点的场景,可以组合异步和同步的 action 。但他有个问题是改写了 combineReducer,会导致一些意想不到的兼容问题,比如我在特定场景下用不了 redux-devtool 。

redux-thunk, redux-promise 等: 相对原始的异步方案,适用于更简单的场景。在 action 需要组合、取消等操作时,会不好处理。

saga 入门

在 saga 之前,你可能会在 action creator 里处理业务逻辑,虽然能跑通,但是难以测试。比如:

// action creator with thunking
function createRequest () {
  return (dispatch, getState) => {
    dispatch({ type: 'REQUEST_STUFF' });
    someApiCall(function(response) {
      // some processing
      dispatch({ type: 'RECEIVE_STUFF' });
    });
  };
}

然后组件里可能这样:

function onHandlePress () {
  this.props.dispatch({ type: 'SHOW_WAITING_MODAL' });
  this.props.dispatch(createRequest());
}

这样通过 redux state 和 reducer 把所有的事情串联到起来。

但问题是:

Code is everywhere.

通过 saga,你只需要触发一个 action 。

function onHandlePress () {
  // createRequest 触发 action `BEGIN_REQUEST`
  this.props.dispatch(createRequest());
}

然后所有后续的操作都通过 saga 来管理。

function *hello() {
  // 等待 action `BEGIN_REQUEST`
  yield take('BEGIN_REQUEST');
  // dispatch action `SHOW_WAITING_MODAL`
  yield put({ type: 'SHOW_WAITING_MODAL' });
  // 发布异步请求
  const response = yield call(myApiFunctionThatWrapsFetch);
  // dispatch action `PRELOAD_IMAGES`, 附上 response 信息
  yield put({ type: 'PRELOAD_IMAGES', response.images });
  // dispatch action `HIDE_WAITING_MODAL`
  yield put({ type: 'HIDE_WAITING_MODAL' });
}

可以看出,调整之后的代码有几个优点:

  • 所有业务代码都存于 saga 中,不再散落在各处
  • 全同步执行,就算逻辑再复杂,看起来也不会乱

六、Data <> API Server

需求

异步请求。

方案

isomorphic-fetch: 便于在同构应用中使用,另外同时要写 node 和 web 的同学可以用一个库,学一套 api 。

然后通过 async + await 组织代码。

示例代码:

import fetch from 'isomorphic-fetch';
export async function fetchUser(uid) {
  return await fetch(`/users/${uid}`).then(res => res.json());
};

可选

reqwest

最终

(完)

@yiminghe

This comment has been minimized.

yiminghe commented Feb 29, 2016

updeep 也说了,对于大数据量效率没有 immutable.js 高效,不如推荐 immutable.js

@xujihui1985

This comment has been minimized.

xujihui1985 commented Feb 29, 2016

saga这个词是cqrs来的,是用来监听多个事件,同步事件的,比如创建订单会锁定库存,创建订单对象,当锁定库存和创建订单对象都成功时会处理的方法,所有的action都放里面感觉不是很合适

@unclay

This comment has been minimized.

unclay commented Feb 29, 2016

虽然没用过react,但看看前边说围绕着几个概念在转比较赞同,开发思维感觉又清晰了点

@AllenFang

This comment has been minimized.

AllenFang commented Mar 2, 2016

Nice introduction!

@ziluo

This comment has been minimized.

ziluo commented Mar 2, 2016

赞,急需

@sskyy

This comment has been minimized.

sskyy commented Mar 4, 2016

saga 的用途在这个例子里面没有讲清楚。

function createRequest () {
  return (dispatch, getState) => {
    dispatch({ type: 'REQUEST_STUFF' });
    someApiCall(function(response) {
      // some processing
      dispatch({ type: 'RECEIVE_STUFF' });
    });
  };
}

这段代码和下面 saga 的代码区别只是 dispatch 变成了 put。someApiCall 变成了 generator 。

@sskyy

This comment has been minimized.

sskyy commented Mar 4, 2016

saga 的作用最主要还是解决复杂的异步交互情况,特别是竞争状态。参见 http://stackoverflow.com/questions/34930735/pros-cons-of-using-redux-saga-with-es6-generators-vs-redux-thunk-with-es7-async/34933395 saga 作者自己的回答。不过感觉对我们目前的业务来说 overkill 了。

@sorrycc

This comment has been minimized.

Owner

sorrycc commented Mar 4, 2016

saga 是通用方案,不管是简单还是复杂,有些业务看起来简单,但说不定有一个点的异步逻辑比较复杂呢。竞争状态是其中的一个场景,我觉得他最重要的点是可以统一管理业务代码,并且只需要接收一个 action 来触发。

@superRaytin

This comment has been minimized.

superRaytin commented Mar 4, 2016

👍

@fengmk2

This comment has been minimized.

fengmk2 commented Mar 5, 2016

看着 saga 就觉得好熟悉。

@shepherdwind

This comment has been minimized.

shepherdwind commented Mar 6, 2016

上面例子,saga 在前端用,使用 generator 似乎区别只是异步改为同步写法而已。

generator 最大的问题,如果是高级浏览器还好,要兼容低版本的浏览器,需要一堆转换代码,感觉不是很好。在我们的业务中,异步请求是很小的一部分操作,如果后台是自己控制,页面中的数据,基本上一次请求就都拿过来了。同样,可以在前端操作页面,最终完成后,进行一次提交,完成所有的修改。这种情况下,异步操作,用最简单的 thunk 就够了。

@pigcan

This comment has been minimized.

pigcan commented Mar 7, 2016

看成了 Soga 😄

@Justin-lu

This comment has been minimized.

Justin-lu commented Jun 9, 2016

赞,正在学习~

@cyy0523xc

This comment has been minimized.

cyy0523xc commented Jul 4, 2016

赞!

@mengxingshike2012

This comment has been minimized.

mengxingshike2012 commented Jul 6, 2016

saga, 一开始还以为是日文, 这前台的概念真是越来越多了...

@jerexyz

This comment has been minimized.

jerexyz commented Jul 11, 2016

👍,正在学习

@rendongsc

This comment has been minimized.

rendongsc commented Jul 22, 2016

好文!

@sabertazimi sabertazimi referenced this issue Jul 31, 2016

Closed

TODO : React Learning #11

0 of 10 tasks complete
@oConnerCooper

This comment has been minimized.

oConnerCooper commented Aug 2, 2016

前人栽树后人乘凉,很棒,公司内部项目准备就这么玩

@sorrycc

This comment has been minimized.

Owner

sorrycc commented Aug 2, 2016

@oConnerCooper 推荐用 dva 搭建 react 项目,是基于这套最佳实践的封装。 #8

@oConnerCooper

This comment has been minimized.

oConnerCooper commented Aug 3, 2016

@sorrycc 可以可以,先看看内部实现,这样用dva相对思路更清晰些,直接用框架,有点黑盒的感觉。喜欢看源码=。=

@carlos121493

This comment has been minimized.

carlos121493 commented Aug 21, 2016

dva中saga的takeLatest可以在哪里设置?

@clarkhan

This comment has been minimized.

clarkhan commented Aug 31, 2016

多个应用整合的场景不知道大家有没有考虑过,有没有一些好的实践方式?

比如,后台管理类系统,非常庞大,是由N多个业务领域相对独立的管理系统组成的。
虽然应用开发与部署相对独立,但是我们肯定希望对于用户来讲,能提供较为统一的体验:将各个系统整合起来,提供统一的导航、菜单、页面布局等等。

以往传统的开发形式,可能有 iframe、后端渲染 import 等等方法。
但在 React 这种 SPA 的应用中,不知道有啥好的处理方式。尤其是公共的部分,是可能会包含业务逻辑,不是纯粹的“展示组件”。
即使,在不考虑与老系统的兼容情况下,只是多个React 的 SPA 整合,路由的处理之类的也还没想到很顺畅的方法。

我说的这种场景,有点类似于阿里云的管理控制台(从使用上来看,觉得相似)。阿里的管理控制台应该是 angularjs 实现。其细节不太清楚。

@sorrycc

This comment has been minimized.

Owner

sorrycc commented Aug 31, 2016

@clarkhan 我们是把公共部分提取成 npm 包。

@clarkhan

This comment has been minimized.

clarkhan commented Aug 31, 2016

@sorrycc 包含业务逻辑么。比如单应用中可能用 action -> reducer 处理的部分,甚至是 ajax 等会封装到 有“业务状态和处理逻辑”的组件中?

@kpaxqin

This comment has been minimized.

kpaxqin commented Aug 31, 2016

@clarkhan
这个问题可以参考Elm的架构,如果每个公共组件都分别提供reducer/action/view/model,使用者将这些碎片自由组合的话就没什么问题,elm中ajax是在Reducer中触发的,所以ajax也可以复用。这种程度的复用即使是redux-saga也做不到(因为依赖顶层的middleware)

但是,个人认为这种针对副作用的复用是非常非常极端的情况,比如ajax,即使组件拆成了对全局无依赖的碎片,ajax本身通常也会依赖到全局的token

@xiemeilong

This comment has been minimized.

xiemeilong commented Mar 23, 2017

请问考虑过mobxjs吗

@zheng520

This comment has been minimized.

zheng520 commented Mar 26, 2017

搞c++的路过,原来我不会看代码。。。。

@djyde

This comment has been minimized.

djyde commented Apr 6, 2017

@xiemeilong 我用 MobX 实现了一套 https://github.com/djyde/cans

@xiyuanyuan

This comment has been minimized.

xiyuanyuan commented Apr 7, 2017

大神,借宝地一用。
想请教一下:在项目中使用了redux,知道智能组件和展示组件的区别。可不可以在智能组件中使用react原始的this.state来控制自身内容展示和隐藏。即操作this.setState()的方法。

@Pines-Cheng

This comment has been minimized.

Pines-Cheng commented Apr 24, 2017

@clarkhan
随着业务越来越多,也遇到了同样的问题。感觉还是得在原有的基础上再拆一层。

@kpaxqin

This comment has been minimized.

kpaxqin commented Apr 27, 2017

@sunOpar @xufei

我觉得按route拆model(modal一般指模态窗吧?)是对的,elm的状态树本身就是随着组件树组合的,根结点自然就是route。redux里需要预定义state tree才引申出了怎么拆的问题。

按这个思路平级state是【不必要】存在的,每个页面都单独初始化store就行了,单独拥有自己的root reducer,这也更接近redux的模仿对象——elm的做法

至于复用,创建reducer所需要的函数本来就是可复用的,创建的过程也可以进行抽象,所以页面间逻辑复用不会有问题。

跨页面之间数据共享的需求,不应该走redux store,而应该由localstorage/api等手段来解决,因为store是存在内存中的,一刷新就没了。严格区分页面的external resource我认为是更好的实践

@lixuecn lixuecn referenced this issue Jul 23, 2017

Closed

123123 #1

@wedaren wedaren referenced this issue Aug 2, 2017

Open

本质 #22

@zpp-gp

This comment has been minimized.

zpp-gp commented Aug 21, 2017

请问dva 与mobx 区别有什么?

@JackGit

This comment has been minimized.

JackGit commented Oct 19, 2017

thumbup

@ptspzy ptspzy referenced this issue Oct 27, 2017

Open

React #86

@tcstory

This comment has been minimized.

tcstory commented Nov 7, 2017

为啥ajax库不选择superagent?

@slogeor

This comment has been minimized.

slogeor commented Dec 17, 2017

各位大佬:有几个疑问。

  1. state 和 props 都能触发 render,这两者如何选择?
  2. 接口请求,封装到 action 还是单独页面,单独请求?
  3. props 传递的层级一般控制在几层比较合适
@sunyongjian

This comment has been minimized.

sunyongjian commented Jan 10, 2018

@slogeor

  1. 看你的 state 需不需要共享了,如果要共享,状态提升肯定涉及到 props 传递,子组件就是根据 props 触发 re-render。选择肯定是看场景啊,简单的 ui 组件,自己有个 state 就够了。业务中大部分都是 smart + dumb 组件,props 多一些,尤其是引入 antd 等 ui 库。
  2. 如果这个页面所在的项目,有 redux 这样的状态管理,那数据流交给 redux 处理比较好一些,项目内一般有封装好的 fetch,saga 等方法,把 effects -> reducer -> store 。不过这个数据可能不是共享的,放 redux 唯一的 store 不太好。用 mobx 的话就容易解决这个问题了,起码比 state 好,我最不喜欢用 state 去处理 effects ,保存接口数据了。
  3. 两层。毕竟现在都是 smart + dumb,只要 store,组件拆分的好。

奥,忘了说 dva 也可以很好的解决你的问题 😆

@HuangHongRui

This comment has been minimized.

HuangHongRui commented Jan 18, 2018

项目要使用Dva...前来学习... 🐤

@suxu

This comment has been minimized.

suxu commented Mar 29, 2018

😢 dva generate is disabled since we don't have enough time currently.

@xuqinggang

This comment has been minimized.

xuqinggang commented May 26, 2018

各位大神们,看看下面两张图给点意见呗~~

  1. 参照MVC,设计的。
    1
    2.一个详细一点的数据流动图
    2
    下面这篇文章是有关react项目重构的思考
@sabrinaluo

This comment has been minimized.

sabrinaluo commented Jun 7, 2018

请教一下data部分如果使用了normalizr,应该如何于immutable配合呢?谢谢

@sorrycc

This comment has been minimized.

Owner

sorrycc commented Jun 7, 2018

不要用 normalizr 了,用 dva + dva-immer 就好。

@DiamondYuan

This comment has been minimized.

DiamondYuan commented Nov 5, 2018

根据上面的流程图,view 是 通过 action 改变 data。然后data再渲染 view。
那么页面的 未加载/加载中/加载完成/请求失败 等状态放在哪里呢?
如果是使用 saga ,页面调用 action 后,除非外部传入,是不知道请求状态的。

@sabertazimi sabertazimi referenced this issue Nov 10, 2018

Open

TODO: Web Learning #105

0 of 31 tasks complete
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment