角度のアプリケーションアーキテクチャの設計-3:Ngrxストア

これは、アプリケーションの設計および開発プロセスの経験をまとめ、このシリーズは、私はこの角度、過去2年間の経験、イオン性、さらにはVuejsや他のフレームワークを結合しますする一連の記事の角度アプリケーションアーキテクチャの設計についてです問題と教訓のようなコンポーネントの設計、組み立て、使用のNgrxストア間のデータ交換やコミュニケーションなど、に関連する問題のいくつかは、Rxjsは使用角度アプリケーションアーキテクチャの設計と応答性のプログラミングのアイデアについて言えば、学びました。これらのデザインのアイデアや方法は、角度のために、またVuejsにも適用され、他のフロントエンドフレームワークを反応しないだけ。
もちろん、アプリケーションアーキテクチャ設計なしフリーサイズ標準、彼は唯一の特定の条件に基づくことができます。あなたがより良いアイデアを持っている場合は、共有してください。

プレゼンテーションの一部  データサービスモードを使用して一方向のデータフロー、イベントフローに。これは、反応して、実際Reduxのモードで再来とフラックスがあり、角度で、Ngrxがあります。コンポーネントとその機能のNgrxを見て、のは、イベントの前の一方向のデータフローの組み合わせを見てみましょう:
画像のキャプション

Ngrx、ストア内Ngrx上のすべてのデータによって使用後のselect方法の使用、selectデータのうち、サブスクリプションでObservableのすべての変更がデータに、配信を介して、データオブジェクトをactionすることによりreducer、このイベント、イベントに応答ストア内のデータを更新するためのプロセスの結果は、それがであるcommitデータを更新、更新されたデータを更新するために、加入者に通知します。

角度ストア関係、およびコンポーネントを格納し、どのデータおよびイベントの相互作用は、図2に示されているように。我々はNgrx十分に活用することができる方法を見てください。

モジュラー、状態ツリー

ストアに格納されたデータでNgrxデータは、セーブが呼び出されstate、この状態は木であってもよい、我々は、モジュールとして、ツリー構造の第一段階であることができ、各モジュールは、にしたがってデータ内のデータをターゲットにしようとしツリーで編成関係自体、。

簡単な例を見てみましょう、ユーザーセンターのページは、ページのデザインは次のとおりです
画像のキャプション
ので、上のいくつかのページで、ユーザー情報、ユーザーの財布が所有バランス、クーポン残高及びその他の情報、およびクーポンのリストとが含まれています。

従って、我々は以下のおおよそのように、構造体の内部設計データを格納します。
画像のキャプション
この構造では、我々は全体アプリが等いくつかのモジュール、ユーザ情報、注文、ショッピングカート、商品に分け、次いで、ユーザ・モジュール、含まれている状態であろうデータユーザ情報、ユーザ情報、ユーザアドレス、ユーザーのクーポン、財布など。

この例では、ユーザのクーポン情報、財布および方法を用いて、これらのコンポーネント内のユーザ情報に他の情報を入れて、次のように、これらのデータの関係は、次のとおり
画像のキャプション

状態は、そのユーザーに設計されています。

export interface UserAccount {
  username: string
  other_fields: string
  vouchers: Array<any>
  wallet: any
}
export interface UserState {
  authenticated: boolean
  account: UserAccount
  messages: Array<any>
  addresses: Array<any>
}

const initialState: UserState = {
  authenticated: false,
  account: null,
  messages: [],
  addresses: []
}

私たちは、これを選択します。

export const account = (state: State) => state.user.account
export const userVouchers = (state: State) => state.user.account.vouchers
export const userWallet = (state: State) => state.user.account.wallet

この我々が選択見ることができますから、最初からある店舗全体のルートをすべて選択し、それがAppStateです。ダウンツリーによると、ユーザ情報があるようなレベルを選択しますstate.user.accountデータが店内に変更されたとき、私たちはこのように変更されます。

export function reducer(state = initialState, action: user.Actions): UserState {
  switch (action.type) {
    case user_account.GET_WALLET_SUCCESS: {
      const wallet = action.wallet // 从action中得到更新的数据
      return Object.assign({}, state, {
        wallet: wallet
      })
    }
    ...
  }
}

我々は、状態内のすべてのオブジェクトへの参照を更新するために、オブジェクトの更新に元の状態(状態のユーザ・モジュール)で、時間データNgrx更新ストア、このから、この方法の減速を見ることができます新しいオブジェクトへの参照をコピーします。このアップデートの方法によって、私たちは次のことができます。

  1. ユーザ状態の基準値を更新します。
  2. そのデータを保証するために新しい状態への参照(変更されることに加えて)すべての元のデータが変更されていない基準値が変更されません。
  3. 変更されたデータは、その基準が変更されます。

通过这样的修改方式,再加上我们从store里select的数据是Observable类型的,所以,只有被修改的数据的订阅会被触发,那么我们就可以通过合理的设计我们的state的数据结构和与相应的组件之间的数据关系,来更合理的处理我们的数据的交互和处理。

在我们上面的用户信息的组件中,用户state的每个数据被修改,整个用户的state的引用值就会被更新,但是,它里面没有被修改的那部分数据的引用值也不会被修改,从而它们的订阅器也不会被触发。

在这个实例中,我们将用户的优惠券、钱包数据放在了用户基本信息的对象里。实际上只是为了演示这种树状的数据结构,并不是说在这个例子中有什么特别的用处。

一个数据的多个响应

有时候,我们需要在一个数据被修改的时候,更新页面上两个地方。比如说很多应用中都会有"我的消息"页面,用列表的方式显示消息,在页面的右上角也有一个用户的未读消息数。用户可以点一个消息,然后这个消息直接在页面上展开阅读,再点一下就收缩这条消息。当一个消息被阅读的时候,右上角的消息数会减少1。

这个例子中,用户的state中有一个messages:

export interface UserState {
  account: Account
  messages: Array<any>
  ...
}

const initialState: UserState = {
  account: null,
  messages: [],
  ...
}

在我们的reducer中,阅读消息的时候,可以更改这一条消息的是否已读状态,把所有的消息放到新的列表里(因为到更新消息的引用值),或者直接从服务器重新获得消息列表。但是无论如何,消息列表的引用值会被修改。我们为了在页面中2个地方更新消息数据,可以使用2种方式:

  1. 可以使用2个select,分别用于获取消息列表,和统计消息列表中的未读数。
  2. 使用1个获取消息列表,然后在组件中订阅的地方统计未读消息数。

我推荐是第一种方式,因为这样我们的组件就可以尽量的简单,把有关数据和对数据的查询操作放在select里。所以这两个select可以这样:

export const messages = (state: State) => state.user.messages
export const messageCount = (state: State) => {
  // 过滤未读的消息并统计数量
  return _.filter(state.user.messages, msg => !msg.read).count()
}

通过这个实例,我们可以将Ngrx的select看作是从数据模型到页面组件里数据模型的映射。所以这个select不是简单的将store里面的数据简单的暴露给组件,而是应该承担数据映射的功能。

数据模型和视图模型

在上面的例子中,我们从数据模型messages中,通过select得到了一个新数据,也就是新消息数量,绑定到某个页面的显示组件中。这个state的messages数据是我们的数据模型,而这个显示在右上角的新消息数,就是一个视图模型,也就是在显示组件(也可能是功能组件)中显示的数据。下面我们就讨论一下这个数据模型和视图模型。

数据模型和视图模型之间的关系,其实就很像我们的数据库,其中数据模型就是数据库中的一个个表,而视图模型就是针对这个数据模型做的查询操作。查询可能是把几个表关联到一起展示,也可能是针对一个表根据一些条件做查询,也可能再针对这个结果做一个统计等。

例如在一个表中,保存的是消息,里面存的发信人、收信人都是存的用户的id,但是我们需要的数据是用户的昵称。那我们就可以关联消息表和用户表,根据用户的id关联,在返回的结果中包含消息和收信人、发信人的昵称。

而在Ngrx中的select就可以当做是数据库的SQL查询语句,它根据store里面的数据,根据一些条件查询,或做某一些统计,结果就是一个包含结果的Observable对象。每当state里面的数据更新的时候,最新的数据也会通过这些select查询被更新,并绑定到显示组件上。

所以,我们的数据从服务上获取,到最终显示到页面上经历几个状态:

  1. 从服务器获取的数据。
  2. 保存到store里面的数据,也就是数据模型。
  3. select以后要显示到页面上的数据,也就是视图模型。

然后,会有两个对数据的操作:

  1. 从服务器获取的数据,可能会经过一些简单的修改、合并、转换,保存到store中,保存的时候,要从业务和数据的角度出发,更好的设计数据结构,能够将这个数据更好的与最终的显示组件结合。
  2. 我们使用select,通过对数据做一些查询、合并、统计,得到一个最终用于展示到显示组件的数据。

通过这种方式,我们就能让我们的模型,和我们的展示的视图之间更好的解耦,把对数据的查询和转换留在store的select里面,让显示组件无需为了显示而处理数据。

视图模型的注意点

有一点有关视图模型需要特别说明的是,每当数据模型里面的数据修改时,所有跟这个数据有关的视图模型的订阅也会被触发。
举个例子,还是上面的用户消息的例子。假设在我们的消息数据中有一个属性是“是否回复”,也就是用户回复了一条消息后,标记为true。那么,如果用户打开一条之前已经读过的消息,然后进行回复。这时,用户的messages数据发生修改,那么上面的2个select的订阅器都会被触发。但是,这时候,有关未读消息数的这个数据其实是没有改变的,但还是被重新计算了一次。如果我们select的结果是一个对象,这时候对象的引用值发生改变,那么在页面上的相应组件也会被刷新。

所以,在使用视图模型的时候一定要注意,你的select使用的数据一定要经过仔细设计,不能为了页面显示方便,就一股脑的从根的state获取好多数据并生成一个对象返回。这样会严重影响性能。

模型state和UI state

我们保存在store中的数据,除了业务数据,其实我们也可以把页面状态的数据保存到store中,也就是UI state。比如说一个典型的场景就是一个比较复杂的买票页,我可能需要输入购买数量,选择购买票的座位,有一些演唱会或项目还要求按照购买数量输入购买人的身份证号。如果我们把这些数据也作为一个UI state模块,保存在store中,那么当用户由于一些原因跳到了其他页面,然后再回来这个购买页的时候,之前输入的信息都还在。这样对用户的交互体验可能会更好,特别是在手机上。

使用UI state还有一个好处就是,我们的store里面的数据完全能够确定页面的状态,不管是用户买票输入的内容,还是支付的时候选择的支付方式等,都保存在store中。然后当我们使用Ngrx的开发工具(chrome的DevTool插件)的时候,我们可以选择任何一个历史的store的状态,这样页面就会按照这个时候的state来展示。这样,当我们进行了一些操作以后,通过选择某一个时间点的state,就能重现当时那个时间的页面状态,这就是Ngrx里面所说的 Time Travel。

那么,哪些数据需要保存在store中?可以使用下面两个简单的标准:

  1. 需要保存页面的状态。例如用户输入一些内容后,跳到其他页面,再回到之前页面,需要显示之前输入的内容。
  2. 需要频繁

进一步解耦组件跟数据模型

刚才我们把数据的展示过程中对数据的处理,和组件直接做了解耦,也就是不在组件中转换数据,而是在select中转换好。但是,即便这样,我们的store和我们的组件直接的关联还是太紧密了,我们看一个例子:

export class UserComponent {
    users$ = this.store.select(state => state.users); foo$ = this.store.select(state => state.foo); bar$ = this.store.select(state => state.bar); constructor(private store: Store<ApplicationState>){} addUser(user: User): void { this.store.dispatch({type: ADD_USER, payload: {user}} } removeUser(userId: string): void { this.store.dispatch({type: REMOVE_USER, payload: {userId}} } } 

根据我们上面的说法,这样用似乎没什么问题,数据从store中select得来,绑定到模板中,数据的更新发送到store中处理。但是,这个组件和store的关联还是太紧密,我们的组件需要知道store中保存的数据的结构,store里面能够处理的action,以及它需要的参数是什么样的。

而我们在设计应用架构的时候,一直都在说解耦解耦,显然这样的关联是违背了我们的解耦原则。一般我们说解耦的时候,大多数情况是要把展示逻辑和业务逻辑解耦,也就是页面上触发一个事件的时候不需要知道业务处理模块里面的具体情况。在Ngrx中,就是尽量把dispatch action的部分封装到一个Service当中,不要让显示组件直接去使用store内部的action。而对于数据获取,我们还是需要知道store里面的数据结构,才能在页面显示。

所以,对于上面的代码,我们可以创建一个如下的Service类:

export class UserService {
    // 只将state里面的用户模块暴露出来,组件就从该服务中通过这个user$来访问内部数据 users$ = this.store.select(state => state.users); constructor(private store: Store<ApplicationState>, private http: Http){ } addUser(user: User): void { this.store.dispatch({type: ADD_USER, payload: {user}} } removeUser(userId: string): void { this.store.dispatch({type: REMOVE_USER, payload: {userId}} } fetchUsers(): void{ this.store.dispatch({type: GET_USER, payload: null} } } 

我々だからUserService店と直接ブリッジ構成要素として、非表示にするには、店舗のアクション、コンポーネントのみに非常にフレンドリーなイベントメソッドを公開。

おすすめ

転載: www.cnblogs.com/reaf/p/11274067.html