在React Native中使用Redux,RxJS和Redux-Observable构建响应式应用程序

本文概述

  • Redux
  • RxJS
  • 可观察到的Redux
  • 反应式Todo应用
  • 一个React, Redux和RxJS教程总结
在功能强大的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状态。
在React Native中使用Redux,RxJS和Redux-Observable构建响应式应用程序

文章图片
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格式, 该格式将动作的结构限制为只有四个键:
  1. 类型操作的任何字符串标识符。每个动作必须具有唯一的动作。
  2. 有效负载任何操作的可选数据。它可以是任何时间, 并且包含有关操作的信息。
  3. 错误如果操作表示错误, 则将任何可选的布尔属性设置为true。这类似于被拒绝的Promise。动作的字符串标识符。每个动作必须具有唯一的动作。按照惯例, 当错误为true时, 有效负载应为错误对象。
  4. 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
在React Native中使用Redux,RxJS和Redux-Observable构建响应式应用程序

文章图片
反应式编程
响应式编程是一种声明式编程范例, 用于处理” 流” 中的数据流及其传播和更改。 RxJS是用于JavaScript中的响应式编程的库, 它具有可观察的概念, 它是观察者可以订阅的数据流, 并且该观察者随时间传递数据。
可观察对象的观察者是具有三个功能的对象:下一步, 错误和完成。所有这些功能都是可选的。
observable.subscribe({ next: value =http://www.srcmini.com/> console.log(`Value is ${value}`), error: err => console.log(err), complete: () => console.log(`Completed`), })

.subscribe函数也可以具有三个函数来代替一个对象。
observable.subscribe( value =http://www.srcmini.com/> 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 =http://www.srcmini.com/> 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 =http://www.srcmini.com/> console.log(value)) // 1 2 3 Hello World

从 from将几乎所有内容转换为值流:
import { from } from 'rxjs'from([1, 2, 3]).subscribe(console.log) // 1 2 3from(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

在React Native中使用Redux,RxJS和Redux-Observable构建响应式应用程序

文章图片
过滤 过滤器接受一个参数, 并从流中删除对给定函数返回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

在React Native中使用Redux,RxJS和Redux-Observable构建响应式应用程序

文章图片
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 } ]

在React Native中使用Redux,RxJS和Redux-Observable构建响应式应用程序

文章图片
去 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

在React Native中使用Redux,RxJS和Redux-Observable构建响应式应用程序

文章图片
此处提供了操作员的完整列表。
可观察到的Redux
在React Native中使用Redux,RxJS和Redux-Observable构建响应式应用程序

文章图片
根据设计, Redux中的所有操作都是同步的。 Redux-observable是Redux的中间件, 它使用可观察的流执行异步工作, 然后使用该异步工作的结果在Redux中调度另一个动作。
Redux-observable基于史诗的思想。史诗是一种功能, 它接受一系列动作, 并可选地产生一个状态流, 并返回一系列动作。
函数(action $:Observable, state $:StateObservable):Observable;
【在React Native中使用Redux,RxJS和Redux-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 =http://www.srcmini.com/> 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 =http://www.srcmini.com/> 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应用 添加一些用户界面后, (最小)演示应用程序将如下所示:
在React Native中使用Redux,RxJS和Redux-Observable构建响应式应用程序

文章图片
该应用程序的源代码在Github上可用。在Expo上试用该项目, 或在Expo app中扫描上方的QR码。
一个React, Redux和RxJS教程总结 我们了解了什么是反应式应用程序。我们还了解了Redux, RxJS和redux-observable, 甚至在React中使用React Native创建了一个响应式Todo应用程序。对于React和React Native开发人员而言, 当前趋势提供了一些非常强大的状态管理选项。
同样, 此应用程序的源代码位于GitHub上。欢迎在下面的评论中分享你对反应式应用程序状态管理的想法。

    推荐阅读