个性化阅读
专注于IT技术分析

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

本文概述

在功能强大的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

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

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中的所有操作都是同步的。 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应用

添加一些用户界面后, (最小)演示应用程序将如下所示:

反应式Todo应用

该应用程序的源代码在Github上可用。在Expo上试用该项目, 或在Expo app中扫描上方的QR码。

一个React, Redux和RxJS教程总结

我们了解了什么是反应式应用程序。我们还了解了Redux, RxJS和redux-observable, 甚至在React中使用React Native创建了一个响应式Todo应用程序。对于React和React Native开发人员而言, 当前趋势提供了一些非常强大的状态管理选项。

同样, 此应用程序的源代码位于GitHub上。欢迎在下面的评论中分享你对反应式应用程序状态管理的想法。

赞(0)
未经允许不得转载:srcmini » 在React Native中使用Redux,RxJS和Redux-Observable构建响应式应用程序

评论 抢沙发

评论前必须登录!