完成示例 Loginlogout 使用者
先決條件
這個主題不是關於終極版和/或 NGRX:
- 你需要對 Redux 感到滿意
- 至少要了解 RxJs 和 Observable 模式的基礎知識
首先,讓我們從一開始就定義一個示例並使用一些程式碼:
作為開發人員,我想:
- 有一個
IUser
介面,用於定義User
的屬性 - 宣告我們稍後將用於操作
Store
中的User
的動作 - 定義
UserReducer
的初始狀態 - 建立減速器
UserReducer
- 將我們的
UserReducer
匯入我們的主模組以構建Store
- 使用
Store
中的資料在我們的檢視中顯示資訊
劇透警報 :如果你想在我們開始之前立即嘗試演示或閱讀程式碼,這裡是一個 Plunkr( 嵌入檢視或執行檢視 )。
1)定義 IUser
介面
我喜歡將介面拆分為兩部分:
- 我們將從伺服器獲取的屬性
- 我們僅為 UI 定義的屬性(例如,按鈕應該旋轉)
這是我們將使用的介面 IUser
:
user.interface.ts
export interface IUser {
// from server
username: string;
email: string;
// for UI
isConnecting: boolean;
isConnected: boolean;
};
2)宣告操作 User
的動作
現在我們必須考慮減速器應該採取什麼樣的行動。
我們在這裡說:
user.actions.ts
export const UserActions = {
// when the user clicks on login button, before we launch the HTTP request
// this will allow us to disable the login button during the request
USR_IS_CONNECTING: 'USR_IS_CONNECTING',
// this allows us to save the username and email of the user
// we assume those data were fetched in the previous request
USR_IS_CONNECTED: 'USR_IS_CONNECTED',
// same pattern for disconnecting the user
USR_IS_DISCONNECTING: 'USR_IS_DISCONNECTING',
USR_IS_DISCONNECTED: 'USR_IS_DISCONNECTED'
};
但在我們使用這些操作之前,讓我解釋為什麼我們需要一個服務來為我們傳送一些這些操作:
假設我們想要連線使用者。所以我們將點選一個登入按鈕,這就是將要發生的事情:
- 單擊按鈕
- 該元件捕獲事件並呼叫
userService.login
userService.login
方法dispatch
更新我們的商店屬性的事件:user.isConnecting
- 觸發 HTTP 呼叫(我們將在演示中使用
setTimeout
來模擬非同步行為 ) - 一旦
HTTP
呼叫完成,我們將發出另一個動作來警告我們的商店使用者被記錄
user.service.ts
@Injectable()
export class UserService {
constructor(public store$: Store<AppState>) { }
login(username: string) {
// first, dispatch an action saying that the user's tyring to connect
// so we can lock the button until the HTTP request finish
this.store$.dispatch({ type: UserActions.USR_IS_CONNECTING });
// simulate some delay like we would have with an HTTP request
// by using a timeout
setTimeout(() => {
// some email (or data) that you'd have get as HTTP response
let email = `${username}@email.com`;
this.store$.dispatch({ type: UserActions.USR_IS_CONNECTED, payload: { username, email } });
}, 2000);
}
logout() {
// first, dispatch an action saying that the user's tyring to connect
// so we can lock the button until the HTTP request finish
this.store$.dispatch({ type: UserActions.USR_IS_DISCONNECTING });
// simulate some delay like we would have with an HTTP request
// by using a timeout
setTimeout(() => {
this.store$.dispatch({ type: UserActions.USR_IS_DISCONNECTED });
}, 2000);
}
}
3)定義 UserReducer
的初始狀態
user.state.ts
export const UserFactory: IUser = () => {
return {
// from server
username: null,
email: null,
// for UI
isConnecting: false,
isConnected: false,
isDisconnecting: false
};
};
4)建立減速器 UserReducer
reducer 有兩個引數:
- 目前的狀態
Action<{type: string, payload: any}>
的Action
提醒: 需要在某個時刻初始化 reducer
當我們在第 3 部分中定義了 reducer 的預設狀態時,我們將能夠像這樣使用它:
user.reducer.ts
export const UserReducer: ActionReducer<IUser> = (user: IUser, action: Action) => {
if (user === null) {
return userFactory();
}
// ...
}
希望有一種更簡單的方法來編寫它,通過使用我們的 factory
函式返回一個物件,在 reducer 中使用(ES6) 預設引數值 :
export const UserReducer: ActionReducer<IUser> = (user: IUser = UserFactory(), action: Action) => {
// ...
}
然後,我們需要處理 reducer 中的每個動作: 提示 :使用 ES6 Object.assign
函式來保持我們的狀態不變
export const UserReducer: ActionReducer<IUser> = (user: IUser = UserFactory(), action: Action) => {
switch (action.type) {
case UserActions.USR_IS_CONNECTING:
return Object.assign({}, user, { isConnecting: true });
case UserActions.USR_IS_CONNECTED:
return Object.assign({}, user, { isConnecting: false, isConnected: true, username: action.payload.username });
case UserActions.USR_IS_DISCONNECTING:
return Object.assign({}, user, { isDisconnecting: true });
case UserActions.USR_IS_DISCONNECTED:
return Object.assign({}, user, { isDisconnecting: false, isConnected: false });
default:
return user;
}
};
5)將我們的 UserReducer
匯入我們的主模組以構建 Store
app.module.ts
@NgModule({
declarations: [
AppComponent
],
imports: [
// angular modules
// ...
// declare your store by providing your reducers
// (every reducer should return a default state)
StoreModule.provideStore({
user: UserReducer,
// of course, you can put as many reducers here as you want
// ...
}),
// other modules to import
// ...
]
});
6)使用 Store
中的資料在我們的檢視中顯示資訊
現在一切都準備好在邏輯方面,我們只需要在兩個元件中顯示我們想要的東西:
UserComponent
: [啞元件] 我們將使用@Input
屬性和async
管道從商店傳遞使用者物件。這樣,元件只有在可用時才會收到使用者(而user
的型別為IUser
而不是Observable<IUser>
!)LoginComponent
[智慧元件] 我們將Store
直接注入此元件,僅作為Observable
使用user
。
user.component.ts
@Component({
selector: 'user',
styles: [
'.table { max-width: 250px; }',
'.truthy { color: green; font-weight: bold; }',
'.falsy { color: red; }'
],
template: `
<h2>User information :</h2>
<table class="table">
<tr>
<th>Property</th>
<th>Value</th>
</tr>
<tr>
<td>username</td>
<td [class.truthy]="user.username" [class.falsy]="!user.username">
{{ user.username ? user.username : 'null' }}
</td>
</tr>
<tr>
<td>email</td>
<td [class.truthy]="user.email" [class.falsy]="!user.email">
{{ user.email ? user.email : 'null' }}
</td>
</tr>
<tr>
<td>isConnecting</td>
<td [class.truthy]="user.isConnecting" [class.falsy]="!user.isConnecting">
{{ user.isConnecting }}
</td>
</tr>
<tr>
<td>isConnected</td>
<td [class.truthy]="user.isConnected" [class.falsy]="!user.isConnected">
{{ user.isConnected }}
</td>
</tr>
<tr>
<td>isDisconnecting</td>
<td [class.truthy]="user.isDisconnecting" [class.falsy]="!user.isDisconnecting">
{{ user.isDisconnecting }}
</td>
</tr>
</table>
`
})
export class UserComponent {
@Input() user;
constructor() { }
}
login.component.ts
@Component({
selector: 'login',
template: `
<form
*ngIf="!(user | async).isConnected"
#loginForm="ngForm"
(ngSubmit)="login(loginForm.value.username)"
>
<input
type="text"
name="username"
placeholder="Username"
[disabled]="(user | async).isConnecting"
ngModel
>
<button
type="submit"
[disabled]="(user | async).isConnecting || (user | async).isConnected"
>Log me in</button>
</form>
<button
*ngIf="(user | async).isConnected"
(click)="logout()"
[disabled]="(user | async).isDisconnecting"
>Log me out</button>
`
})
export class LoginComponent {
public user: Observable<IUser>;
constructor(public store$: Store<AppState>, private userService: UserService) {
this.user = store$.select('user');
}
login(username: string) {
this.userService.login(username);
}
logout() {
this.userService.logout();
}
}
由於 Ngrx
是 Redux
和 RxJs
概念的合併,因此在開始時很難理解這些問題。但這是一個強大的模式,允許你像我們在此示例中看到的那樣擁有一個*被動應用程式,*並且你可以輕鬆地共享你的資料。不要忘記有一個可用的 Plunkr ,你可以將其分叉以進行自己的測試!
我希望即使這個話題很長,也很有幫助!