完成示例 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 ,你可以将其分叉以进行自己的测试!
我希望即使这个话题很长,也很有帮助!