本文概述
在功能强大的Web和移动应用程序不断发展的生态系统中, 越来越多的状态需要管理, 例如当前用户, 加载的项目列表, 加载状态, 错误等。通过将状态保留在全局对象中, Redux是解决此问题的一种方法。
Redux的局限性之一是它不支持开箱即用的异步行为。一种解决方案是redux-observable, 它基于RxJS, RxJS是一个强大的JavaScript反应库。 RxJS是ReactiveX的实现, ReactiveX是一种起源于Microsoft的用于反应式编程的API。 ReactiveX结合了反应式范例, 函数式编程, 观察者模式和迭代器模式的一些最强大的功能。
在本教程中, 我们将学习Redux及其在React中的用法。我们还将探讨使用RxJS进行反应式编程, 以及如何使繁琐而复杂的异步工作变得非常简单。
最后, 我们将学习redux-observable, 该库利用RxJS进行异步工作, 然后将使用Redux和redux-observable在React Native中构建应用程序。
Redux
正如在GitHub上描述的那样, Redux是” JavaScript应用程序的可预测状态容器”。它为你的JavaScript应用提供了全局状态, 使状态和操作远离React组件。
在没有Redux的典型React应用程序中, 我们必须通过属性或prop将数据从根节点传递到子节点。对于小型应用程序, 这种数据流是可管理的, 但是随着应用程序的增长, 它可能变得非常复杂。 Redux允许我们拥有彼此独立的组件, 因此我们可以将其用作单一事实来源。
Redux可以在React中使用react-redux来使用, 它为React组件提供绑定以从Redux读取数据并调度动作以更新Redux状态。
Redux可以描述为三个简单的原则:
1.真理的单一来源
整个应用程序的状态存储在单个对象中。 Redux中的该对象由商店持有。任何Redux应用程序中都应该有一个商店。
» console.log(store.getState())
« { user: {...}, todos: {...} }
要从React组件中的Redux读取数据, 我们使用react-redux的connect函数。 connect接受四个参数, 所有参数都是可选的。现在, 我们将专注于第一个, 名为mapStateToProps。
/* UserTile.js */
import { connect } from 'react-redux';
class UserTile extends React.Component {
render() {
return <p>{ this.props.user.name }</p>
}
}
function mapStateToProps(state) {
return { user: state.user }
}
export default connect(mapStateToProps)(UserTile)
在上面的示例中, mapStateToProps接收全局Redux状态作为其第一个参数, 并返回一个对象, 该对象将与其父组件传递给<UserTile />的props合并。
2.状态为只读
Redux状态对于React组件是只读的, 更改状态的唯一方法是发出一个动作。动作是一个简单的对象, 代表改变状态的意图。每个操作对象必须具有一个类型字段, 并且值必须是一个字符串。除此之外, 动作的内容完全由你决定, 但是大多数应用程序都遵循flux-standard-action格式, 该格式将动作的结构限制为只有四个键:
- 类型操作的任何字符串标识符。每个动作必须具有唯一的动作。
- 有效负载任何操作的可选数据。它可以是任何时间, 并且包含有关操作的信息。
- 错误如果操作表示错误, 则将任何可选的布尔属性设置为true。这类似于被拒绝的Promise。动作的字符串标识符。每个动作必须具有唯一的动作。按照惯例, 当错误为true时, 有效负载应为错误对象。
- meta Meta可以是任何类型的值。它用于不属于有效负载的任何其他信息。
这是两个动作示例:
store.dispatch({
type: 'GET_USER', payload: '21', });
store.dispatch({
type: 'GET_USER_SUCCESS', payload: {
user: {
id: '21', name: 'Foo'
}
}
});
3.状态通过纯函数改变
使用称为reducers的纯函数来更改全局Redux状态。减速器采用上一个状态和操作, 然后返回下一个状态。精简器创建一个新的状态对象, 而不是对现有状态对象进行突变。根据应用程序的大小, Redux存储区可以具有单个Reducer或多个Reducer。
/* store.js */
import { combineReducers, createStore } from 'redux'
function user(state = {}, action) {
switch (action.type) {
case 'GET_USER_SUCCESS':
return action.payload.user
default:
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO_SUCCESS':
return [
...state, {
id: uuid(), // a random uuid generator function
text: action.text, completed: false
}
]
case 'COMPLETE_TODO_SUCCESS':
return state.map(todo => {
if (todo.id === action.id) {
return {
...todo, completed: true
}
}
return todo
})
default:
return state
}
}
const rootReducer = combineReducers({ user, todos })
const store = createStore(rootReducer)
与从状态读取类似, 我们可以使用connect函数来分派动作。
/* UserProfile.js */
class Profile extends React.Component {
handleSave(user) {
this.props.updateUser(user);
}
}
function mapDispatchToProps(dispatch) {
return ({
updateUser: (user) => dispatch({
type: 'GET_USER_SUCCESS', user, }), })
}
export default connect(mapStateToProps, mapDispatchToProps)(Profile);
RxJS
反应式编程
响应式编程是一种声明式编程范例, 用于处理”流”中的数据流及其传播和更改。 RxJS是用于JavaScript中的响应式编程的库, 它具有可观察的概念, 它是观察者可以订阅的数据流, 并且该观察者随时间传递数据。
可观察对象的观察者是具有三个功能的对象:下一步, 错误和完成。所有这些功能都是可选的。
observable.subscribe({
next: value => console.log(`Value is ${value}`), error: err => console.log(err), complete: () => console.log(`Completed`), })
.subscribe函数也可以具有三个函数来代替一个对象。
observable.subscribe(
value => console.log(`Value is ${value}`), err => console.log(err), () => console.log(`Completed`)
)
我们可以通过创建一个可观察对象来创建一个新的可观察对象, 并传入一个接收订户(也称为观察者)的函数。订户有三种方法:下一步, 错误和完成。订户可以根据需要多次呼叫下一个呼叫, 最后完成或出错。调用完成或错误后, 可观察对象将不会将任何值向下推。
import { Observable } from 'rxjs'
const observable$ = new Observable(function subscribe(subscriber) {
const intervalId = setInterval(() => {
subscriber.next('hi');
subscriber.complete()
clearInterval(intervalId);
}, 1000);
});
observable$.subscribe(
value => console.log(`Value is ${value}`), err => console.log(err)
)
上面的示例将在1000毫秒后打印Value is hi。
每次手动创建一个可观察的对象会变得冗长而乏味。因此, RxJS具有许多创建可观察对象的功能。一些最常用的是of, from和ajax。
of
of采用一系列值并将其转换为流:
import { of } from 'rxjs'
of(1, 2, 3, 'Hello', 'World').subscribe(value => console.log(value))
// 1 2 3 Hello World
从
from将几乎所有内容转换为值流:
import { from } from 'rxjs'
from([1, 2, 3]).subscribe(console.log)
// 1 2 3
from(new Promise.resolve('Hello World')).subscribe(console.log)
// 'Hello World'
from(fibonacciGenerator).subscribe(console.log)
// 1 1 2 3 5 8 13 21 ...
阿贾克斯
ajax接受字符串URL或创建一个可观察的HTTP请求。 ajax具有ajax.getJSON函数, 该函数仅从AJAX调用返回嵌套的响应对象, 而没有由ajax()返回的任何其他属性:
import { ajax } from 'rxjs/ajax'
ajax('https://jsonplaceholder.typicode.com/todos/1').subscribe(console.log)
// {request, response: {userId, id, title, completed}, responseType, status}
ajax.getJSON('https://jsonplaceholder.typicode.com/todos/1').subscribe(console.log)
// {userId, id, title, completed}
ajax({ url, method, headers, body }).subscribe(console.log)
// {...}
还有许多其他方法可以使观察到(你可以在此处查看完整列表)。
操作符
运算符是RxJS的真正强大之处, 它具有可满足你几乎所有需要的运算符。从RxJS 6开始, 运算符不是可观察对象的方法, 而是使用.pipe方法应用于可观察对象的纯函数。
地图
map采用单个参数函数, 并在流中的每个元素上应用投影:
import { of } from 'rxjs'
import { map } from 'rxjs/operators'
of(1, 2, 3, 4, 5).pipe(
map(i=> i * 2)
).subscribe(console.log)
// 2, 4, 6, 8, 10
过滤
过滤器接受一个参数, 并从流中删除对给定函数返回false的值:
import { of } from 'rxjs'
import { map, filter } from 'rxjs/operators'
of(1, 2, 3, 4, 5).pipe(
map(i => i * i), filter(i => i % 2 === 0)
).subscribe(console.log)
// 4, 16
flatMap
flatMap运算符采用一个函数, 将蒸汽中的每个项目映射到另一个流中, 并展平这些流的所有值:
import { of } from 'rxjs'
import { ajax } from 'rxjs/ajax'
import { flatMap } from 'rxjs/operators'
of(1, 2, 3).pipe(
flatMap(page => ajax.toJSON(`https://example.com/blog?size=2&page=${page}`)), ).subscribe(console.log)
// [ { blog 1 }, { blog 2 }, { blog 3 }, { blog 4 }, { blog 5 }, { blog 6 } ]
去
merge将两个流中的项目按到达的顺序合并:
import { interval, merge } from 'rxjs'
import { pipe, take, mapTo } from 'rxjs/operators'
merge(
interval(150).pipe(take(5), mapTo('A')), interval(250).pipe(take(5), mapTo('B'))
).subscribe(console.log)
// A B A A B A A B B B
此处提供了操作员的完整列表。
可观察到的Redux
根据设计, Redux中的所有操作都是同步的。 Redux-observable是Redux的中间件, 它使用可观察的流执行异步工作, 然后使用该异步工作的结果在Redux中调度另一个动作。
Redux-observable基于史诗的思想。史诗是一种功能, 它接受一系列动作, 并可选地产生一个状态流, 并返回一系列动作。
函数(action $:Observable, state $:StateObservable):Observable;
按照约定, 作为流(_aka _observable)的每个变量都以$结尾。在使用redux-observable之前, 我们必须将其作为中间件添加到我们的商店中。由于史诗是可观察到的流, 离开此蒸汽的每个动作都通过管道返回到流中, 因此返回相同的动作将导致无限循环。
const epic = action$ => action$.pipe(
filter(action => action.type === 'FOO'), mapTo({ type: 'BAR' }) // not changing the type of action returned
// will also result in an infinite loop
)
// or
import { ofType } from 'redux-observable'
const epic = action$ => action$.pipe(
ofType('FOO'), mapTo({ type: BAZ' })
)
将这种反应式架构视为管道系统, 每个管道的输出均反馈到每个管道(包括其自身)以及Redux的异径管中。这些管道上方的过滤器决定了流入和阻塞的内容。
让我们来看看乒乓球史诗将如何运作。它会执行ping操作, 然后将其发送到服务器, 并在请求完成后将pong发送回应用程序。
const pingEpic = action$ => action$.pipe(
ofType('PING'), flatMap(action => ajax('https://example.com/pinger')), mapTo({ type: 'PONG' })
)
Now, we are going to update our original todo store by adding epics and retrieving users.
import { combineReducers, createStore } from 'redux'
import { ofType, combineEpics, createEpicMiddleware } from 'redux-observable';
import { map, flatMap } from 'rxjs/operators'
import { ajax } from 'rxjs/ajax'
// ...
/* user and todos reducers defined as above */
const rootReducer = combineReducers({ user, todos })
const epicMiddleware = createEpicMiddleware();
const userEpic = action$ => action$.pipe(
ofType('GET_USER'), flatMap(() => ajax.getJSON('https://foo.bar.com/get-user')), map(user => ({ type: 'GET_USER_SUCCESS', payload: user }))
)
const addTodoEpic = action$ => action$.pipe(
ofType('ADD_TODO'), flatMap(action => ajax({
url: 'https://foo.bar.com/add-todo', method: 'POST', body: { text: action.payload }
})), map(data => data.response), map(todo => ({ type: 'ADD_TODO_SUCCESS', payload: todo }))
)
const completeTodoEpic = action$ => action$.pipe(
ofType('COMPLETE_TODO'), flatMap(action => ajax({
url: 'https://foo.bar.com/complete-todo', method: 'POST', body: { id: action.payload }
})), map(data => data.response), map(todo => ({ type: 'COMPLEE_TODO_SUCCESS', payload: todo }))
)
const rootEpic = combineEpics(userEpic, addTodoEpic, completeTodoEpic)
const store = createStore(rootReducer, applyMiddleware(epicMiddleware))
epicMiddleware.run(rootEpic);
_重要:Epic就像RxJS中的其他可观察流一样。它们可能最终处于完整或错误状态。在这种状态之后, 史诗级应用程序和你的应用程序将停止工作。因此, 你必须抓住所有潜在的错误。你可以为此使用__catchError__运算符。更多信息:redux-observable中的错误处理。
反应式Todo应用
添加一些用户界面后, (最小)演示应用程序将如下所示:
该应用程序的源代码在Github上可用。在Expo上试用该项目, 或在Expo app中扫描上方的QR码。
一个React, Redux和RxJS教程总结
我们了解了什么是反应式应用程序。我们还了解了Redux, RxJS和redux-observable, 甚至在React中使用React Native创建了一个响应式Todo应用程序。对于React和React Native开发人员而言, 当前趋势提供了一些非常强大的状态管理选项。
同样, 此应用程序的源代码位于GitHub上。欢迎在下面的评论中分享你对反应式应用程序状态管理的想法。
评论前必须登录!
注册