从对Redux的使用看Angular2和React的设计差异

Redux中文文档中有这么一句话:

这里需要再强调一下:Redux 和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。

不得不说这句话极大的鼓舞了我,Redux作为一款前端状态管理工具本质上和特定框架是解耦的。如果业务有需求,我们当然可以也引入到其他框架,比如说我们项目用的Angular2。

当初怀着这样的心情查找了一下,果然Angular2也推出了对Redux的实现:ngrx。试着用了一下,觉得效果也挺好。

ngrx介绍

RxJS powered state management for Angular applications, inspired by Redux

@ngrx/store is a controlled state container designed to help write performant, consistent applications on top of Angular. Core tenets:

  • State is a single immutable data structure
  • Actions describe state changes
  • Pure functions called reducers take the previous state and the next action to compute the new state
  • State accessed with the Store, an observable of state and an observer of actions

上述前三点其实就是Redux的规则,而最后一条却是ngrx的特色。这个稍后展开。

Redux中文文档中用React作为示例框架,自然在用法上和ngrx有一些不同,而我认为这种不同不单单是使用上的,而且反应出两种框架不同的设计思想,这也是我想整理一下记录下来的原因。

先写几点目前水平能看到的,随着深入使用会不定期的补充。

Redux 如何接入框架

这个问题换一种问法就是Redux中的store是如何被React/Angular2拿到的?

React:

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import App from './containers/App';

const store = createStore(rootReducer, initialState);
render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Angular2:

import { Store } from '@ngrx/store';
import * as fromRoot from '../reducers';

@Component({
  selector: ...,
  template: ...,
  style: ...
})
export class AppComponent {
  // store 通过依赖注入
  constructor(private store: Store<fromRoot.State>) {
    this.showSidenav$ = this.store.select(fromRoot.getShowSidenav);
  }
}

从上面两段代码可以看出,React是在App根组件上面又包裹了一层Provider,通过Provider引入store,之后作为props的一部分传递给所有子组件。而Angular2相对简单一些,store直接注入到根组件中,就像注入一个普通的service一样。

从对引入store用法上的不同其实是可以看出Angular2和React设计理念的不同:React的设计初衷是作为MVC中的View层,至于其他部分,更多的是依赖生态系统的提供。所以React整合Redux更多的是从View层面着手,反映到使用方式上就是在根组件上再加一层。而Angular2的设计理念是提供一整套方案涉及到MVC的各方面,在引入http,route此类工具的时候就是通过依赖注入,在整合Redux时也没有例外。 依赖注入本来就是Angular2架构中非常关键的一环。

异步数据流

默认情况下,createStore() 所创建的 Redux store 没有使用 middleware,所以只支持 同步数据流。
React:
像 redux-thunk这样支持异步的 middleware 都包装了 store 的 dispatch() 方法,以此来让你 dispatch函数,在该函数中实现异步的逻辑。

Angular2:
在ngrx中你是找不到类似于ngrx-thunk的东西的。ngrx另辟蹊径,提出了一个叫做<@Effect的装饰器: 它来帮助我们检查 store 发出的 Action,将特定类型的 Action 过滤出来进行处理。当发现特定的 Action 发出之后,自动执行某些异步操作(比如执行异步请求回调),然后将处理的结果重新发送回 store 中。

Effects offer a way to isolate and easily test side-effects within your application.

Effect

可以看出,thunk的做法是扩展action,使其可以支持函数,然后在函数中操作异步逻辑,而在ngrx中,action始终还是那个简单的action,通过Effect装饰器类来监听action并作出相应的动作。这样的优点我觉得是实现了同步数据流和异步数据流解耦,代码层次更加清楚。

所以我觉得RxJs天生就和Redux完美匹配,Redux只关心在正确的时候dispatch正确的action,处理state的逻辑交给reducer,同步异步的管理交给RxJs.

Effect的技术核心是RxJs响应式编程思想。这一小节主要是从代码结构角度。下一小节将会从Observable的角度来谈。

sync/async

ngrx = Redux + RxJs

RxJs并不是Redux中的范畴,Angular2中有对RxJs的强大实现,ngrx很好的利用了这套支持,实现了一个响应式的Redux,使得Redux的功能在Angualr2中更加强大,使用更加简练。

注:Redux社区现在也提供了Redux-Observable中间件。

根据之前的代码可以看出,Effect中用actions$.ofType(specific-action)来监听特定的action dispatch的行为。而实现这一步的关键:make actions observable 是关键,只有将actions变成数据流(stream),我们才可以利用RxJs赋予的强大功能。

本小节的内容可以看作是对Effect的深入探究:make action observable:

actions.ts:

import { Action } from '@ngrx/store';
// 实现ngrx中的Action接口,将普通的action对象放入stream
export class SearchAction implements Action {
  readonly type = SEARCH;

  constructor(public payload: string) { }
}

effect.ts:
observable actions

search.ts

import { Store } from '@ngrx/store';
@Component({
  selector: 'bc-find-book-page',
  template: `
    <bc-book-search [query]="searchQuery$ | async" [searching]="loading$ | async" (search)="search($event)"></bc-book-search>
  `
})
export class FindBookPageComponent {
  constructor(private store: Store<fromRoot.State>) {
      //.....
  }

  search(query: string) {
    this.store.dispatch(new book.SearchAction(query));
  }
}

代码中可以看出store已经是observable了,当store dispatch一个action的时候,相当于向actions stream发出了一个执行指令。actions stream通过调用ofType这个operator将action导入到相应的处理逻辑。

猜你喜欢

转载自blog.csdn.net/Napoleonxxx/article/details/79327518