iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 25
0
Modern Web

ngrx/store 4 學習筆記系列 第 25

[ngrx/store-25] ngrx/store 設定篇及會員篇之 Action, Reducer

ngrx/store 設定篇及會員篇之 Action, Reducer

今天開始 今天完成

ngrx/store 設定

第一步:安裝相關套件

npm install @ngrx/core @ngrx/store @ngrx/effects @ngrx/router-store @ngrx/store-devtools reselect --save

第二步:建立目錄
在 src/app 底下

mkdir store
cd store
touch index.ts
mkdir actions
mkdir effects
mkdir reducers
mkdir selectors

並各自建立一個 index.ts,完成後如下

.
├── actions
│   └── index.ts
├── effects
│   └── index.ts
├── index.ts
├── reducers
│   └── index.ts
└── selectors
    └── index.ts

第三步:修改 src/app/store/index.ts
如下

export * from './actions';
export * from './reducers';
export * from './selectors';
export * from './effects';

先填一些東西讓 Compiler 不要抱怨
store/actions/index.ts

export const actions = 'Actions';

store/reducers/index.ts

export const reducers = 'Reducers';

store/effects/index.ts

export const effects = 'Effects';

store/selectors/index.ts

export const selectors = 'Selectors';

第四步:修改 src/app/app.module.ts

//... 省略
// for ngrx/store
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { StoreRouterConnectingModule, RouterStateSerializer } from '@ngrx/router-store';
import { environment } from '../environments/environment';
import * as fromStore from './store';
//... 省略

@NgModule({
    //...
        imports: [
        //...
        HttpClientModule,
        StoreModule.forRoot(fromStore.reducers),
        EffectsModule.forRoot(fromStore.effects),
        !environment.production ? StoreRouterConnectingModule : [],
        !environment.production ? StoreDevtoolsModule.instrument({ maxAge: 50 }) : [],
        //...
    ],
//...
  1. 我們用 index.ts 將所有 store 的東西包起來,在 StoreModule.forRoot()EffectsModule.forRoot() 傳進 fromStore.reducersfromStore.effects
  2. 加入 environment 環境變數,用來判別現在是 production mode 還是 development mode,我們只有在 development mode 才會打開 StoreDevtoolsModuleStoreRouterConnectingModule
  3. StoreDevtoolsModule 讓我們可以使用 Chrome Extensions Redux Devtools 來看 ngrx/store 的 Action 跟 State

第五步:安裝 Chrome Extensions Redux Devtools
到 Google Chrome 擴充功能,尋找 Redux
https://ithelp.ithome.com.tw/upload/images/20180110/201035740Th6cKpHjU.png
安裝後,點擊它會出現

https://ithelp.ithome.com.tw/upload/images/20180110/201035742Ds5GFnZyk.png

這是一個很強大的工具,官方文件,我們在開發時,會隨時用這個工具來除錯,可以看到現在我們的 store 跟 effects 已經初始化 (init),也可以看到 ROUTER_NAVIGATION

會員篇 - Actions

我們習慣上先定義動作(Actions),動作在 ngrx/store 裡其實是一個很簡單的物件,它的類別 (Class)如下
第一步: 在 store/actions/ 下建立 Class 檔案,並建立 spec 檔案 (測試用)

ng generate class user.actions --spec

第二步:修改 store/actions/user.actions.ts

import { Action } from '@ngrx/store';
import { User } from '../../models';

// define action types
export const LOGIN = '[user] LOGIN';
export const LOGIN_SUCCESS = '[user]  LOGIN_SUCCESS';
export const LOGIN_FAIL = '[user] LOGIN_FAIL';

export const LOGOUT = '[user] LOGOUT';
export const LOGOUT_SUCCESS = '[user] LOGOUT_SUCCESS';
export const LOGOUT_FAIL = '[user] LOGOUT_FAIL';

export const GETUSER = '[user] GETUSER';
export const GETUSER_SUCCESS = '[user] GETUSER_SUCCESS';
export const GETUSER_FAIL = '[user] GETUSER_FAIL';

// define Actions class
export class LoginAction implements Action {
    readonly type = LOGIN;
    constructor(public payload: User) { }
}
export class LoginSuccessAction implements Action {
    readonly type = LOGIN_SUCCESS;
    constructor(public payload: string) { }
}
export class LoginFailAction implements Action {
    readonly type = LOGIN_FAIL;
    constructor(public payload: any) { }
}
export class LogoutAction implements Action {
    readonly type = LOGOUT;
}
export class LogoutSuccessAction implements Action {
    readonly type = LOGOUT_SUCCESS;
}
export class LogoutFailAction implements Action {
    readonly type = LOGOUT_FAIL;
}
export class GetUserAction implements Action {
    readonly type = GETUSER;
}
export class GetUserSuccessAction implements Action {
    readonly type = GETUSER_SUCCESS;
    constructor(public payload: string) { }         // username
}
export class GetUserFailAction implements Action {
    readonly type = GETUSER_FAIL;
    constructor(public payload: any) { }
}
export type UserActions
    = LoginAction
    | LoginSuccessAction
    | LoginFailAction
    | LogoutAction
    | LogoutSuccessAction
    | LogoutFailAction
    | GetUserAction
    | GetUserSuccessAction
    | GetUserFailAction;
  1. 先從 @ngrx/store 導入 Action 介面
  2. 定義常數,我們為了怕會跟其他模組的字串重複,習慣上在字串前加模組名稱,如 [user] LOGIN
  3. 定義類別,有些動作帶 payload,有些不帶,帶 payload 的類別在這裡定義它的型態
  4. 最後 export UserActions 型態 (type)
  5. 因為用類別定義,我們將來在使用動作時,會用 new LoginAction() 的方式來產生物件

第三步:修改 app/store/actions/index.ts

//export const actions = 'Actions';  刪除
export * from './user.actions';

未來加新的動作檔案,只要在這裡做 export,這樣就定義好我們在會員模組用到的動作,基本上分為三類,Login, Logout 跟 GetUser

會員篇之 Reducer

第一步:建立檔案 src/app/store/reducers 下

ng generate class user.reducers --spec

第二步:修改 user.reducers.ts

import { ActionReducer, Action, ActionReducerMap } from '@ngrx/store';
import * as actions from '../actions';

export interface UsersState {
    isLogin: boolean;
    currentUser: string;
}

const initialState: UsersState = {
    isLogin: false,
    currentUser: ''
}

export function reducer(state: UsersState = initialState, action: actions.UserActions): UsersState {
    switch (action.type) {
        case actions.LOGOUT:
            return initialState;
        case actions.LOGIN_SUCCESS:
        case actions.GETUSER_SUCCESS:
            //return Object.assign({}, state, { currentUser: action.payload, isLogin: true });
            return { ...state, currentUser: action.payload, isLogin: true };
        default:
            return state
    }
}
// for selector
export const getIsLogin = (state: UsersState) => state.isLogin;
export const getCurrentUser = (state: UsersState) => state.currentUser;
  1. 先將剛剛定義的動作導入,記得 Reducer 的作用是將目前的狀態,加入新的動作,產生新的狀態,而新的狀態產生都要 Immutable,可以回顧一下之前的 [ngrx/store-12] Store 加完整的 Reducer[ngrx/store-7] 純函數 (Pure Function) 以及 [ngrx/store-8] Javascript Mutable 跟 Immutable 資料型態
  2. 定義狀態,這裡的狀態包含了登入狀態(布林值)及使用者名稱 (字串)
  3. 定義初始狀態,記得 Store 其實是一個 BehaviorSubject ([ngrx/store-11] Store 架構加入最簡單的 Reducer),我們需要給一個初始值
  4. 定義 reducer 函數,這個函數是純函數,在登入成功以及從後端得到使用者名稱成功後,產生新的狀態,可以用 Object.assign({}, state, {currentUser: action.payload, isLogin: true}); 或者擴展語法 {...state, currentUser: action.payload, isLogin: true};
  5. 最後 export 對應的兩個狀態,都是函數物件(function objects),這個會傳給上層的 selector,來讓使用者(元件或服務)訂閱最新的狀態。

第三步:修改 app/store/reducers/indext.ts

import { ActionReducerMap } from '@ngrx/store';
import * as user from './user.reducers';

export interface State {
    user: user.UsersState
}
export const reducers: ActionReducerMap<State> = {
    user: user.reducer
}
  1. 定義最上層的狀態樹,以後加新的狀態,只要在這裡加上去即可
  2. 定義 root reducer,同樣,要加新的 Reducer,只要加入這裡,就會在 app.module.ts 中註冊
  3. 我們用這種 index.ts 的方式讓將來要擴充 store 的時候比較方便,後面會陸續看到加入的 狀態跟reducer。

下次我們來完成會員篇的 Effects 跟 Selector,以及更新元件


上一篇
[ngrx/store-24] Angular 網站實例 - 會員報告篇
下一篇
[ngrx/store-26] ngrx/store 之會員篇 Effects, Selector
系列文
ngrx/store 4 學習筆記30

尚未有邦友留言

立即登入留言