欢迎光临散文网 会员登陆 & 注册

【D1N910】一线大厂React实践宝典 使用Redux (进度 6/9)

2020-05-17 06:46 作者:爱交作业的D1N910  | 我要投稿

正常操作,正常分析,大家好,我是 D1n910。

在大约两年前,我购买了 腾讯课堂【NEXT】一线大厂 React 实践宝典 这门课。 

因为我一直基本上是使用Vue来进行前端页面的开发,但是一直没有时间去实践看完。 两年没去看了,真的很惭愧,时间,唉,过得真快啊。

为了在这门课过期之前看完,所以我要抓紧时间学完了。

本系列的专栏记录了我学习 一线大厂React实践宝典  的笔记。 

下面的内容版权归属是  腾讯课堂【NEXT】一线大厂 React 实践宝典 这门课。

当然,如果我微不足道的笔记也算是我自己的版权,那么也是有我的小小一份。

你可以用来学习 React。

但是如果你用来商业化盈利,请先获得  腾讯课堂【NEXT】一线大厂 React 实践宝典 制作方的许可。

BTW,作为挖坑狂魔,如果这篇专栏没有更新,那么当我没说过。

这是一个系列的文章,如果你没有看过前面的内容,那么建议你先看一看


加油加油


Redux 基本概念

数据太复杂眼看就要失去控制(小剧场)

A:诶,你这个页面怎么这么多状态呀?

B:是的,页面不断更新,所以状态越来越多了。

A:那你是怎么管理这些状态的呢?

B:这还真的是个问题,状态引用复杂,想要知道怎么变换的,只能靠推断了。

A:你为什么不用 Redux 进行管理呢? 这样你就可以知道是什么时候因为什么原因进行的改变了。


Redux 简介

一般来说,学 React 都会有学 Redux。

首先回顾:Flux的特点

  • 一种架构模式,而不是一种框架。

  • 数据单向流动

那么 Redux 是什么呢?

  • Redux 是 Flux 架构的一个实现。

  • 一个可预测的状态容器。

  • Flux 架构与函数式编程思想的结合。

那么 Redux 是如何实现 Flux 的架构呢?

先来看看 Redux 的数据流

1、只有唯一一个 Store(Flux 的 Dispatcher 唯一,但是 store 可以有多个);

2、因为 Dispathcer 和 Store 在 Redux 是唯一的,所以它们绑定在一起了。平时想用到 Dispathcer ,要使用 Store.dispatch;

3、Store 里的 reduce 被提出为 Reducers;

整个数据处理流程是这样的:

展示层 -> 进行操作 -> ActionCoerator > Dispathcer -> Reducers -> State -> Reducers -> State  -> View


Redux  的基本原则

1、唯一数据源:比如,只有唯一的 Store;

2、保持状态只读:Store 中的 state 只能读取,不能改变;我们是在 actions 中,根据数据的不同,而返回新的不同的 state,不会改变原来的 state;

3、数据改变只能通过纯函数完成。


什么时候使用 Redux

我们使用 Redux 是真正需要 Redux 的时候才用。功能:

(1)想要获得存在本地存储或从服务器带来的初始数据。

(2)需要记录用户操作、实现撤销功能或不同状态跳转。比如想知道我们的数据是做了什么才变成这样的,什么时候 + 1,什么时候 -1。

(3)组件之间只状态相互依赖严重,组件数据源复杂。用 Redux 可以抽象得比较好。

本小节完毕


纯函数的定义

纯函数是函数式编程中的非常重要的概念。

Redux 是 Flux 和纯函数的结合。在我们的 Redux 中要使用纯函数的形式。

1、给定相同的输入,总能获得相同的输出。

- 输出的结果,只能和入参相关联。

2、函数过程中不会带有副作用

- 不会改变外部的东西

3、不依赖外部状态

- 不依赖外部状态(全局变量等)


类似数学上的提供一个固定的 x,给出一个固定的 y。


下面来看一些非纯函数的例子。

给定相同的输入,不能获得相同的输出,违反第一条。


给定相同的输入,不能获得相同的输出;有副作用(pop 修改了原数组)。违反第一条,第二条。

给定相同的输入,不能获得相同的输出;依赖外部状态。违反第一条,第三条。


Redux 与函数式编程

什么是函数式编程?

函数式编程(Functional programming)是指一种编程典范,它避免使用程序状态和易变对象,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推到复杂的运算,而不是设计一个复杂的执行过程。

函数式编程的特点

1、函数是第一等公民

在 Javascript 中,函数本就是第一等公民。JavaScript 中的函数可以作为参数传递,可以赋值给变量,可以作为函数返回值,还可以把它们存储到数组中,它与其他数据类型一样,处于平等地位。

2、只使用表达式,不使用语句

函数式编程的每一步都是一个有返回值的表达式(expression),而不是多个语句(statemanet)。它强调将复杂的计算过程拆分多个可复用的函数。

3、没有纯作用的纯函数

函数式编程要求我们编写的每个函数都是独立而无副作用的,不会修改系统中的状态值,且只会返回一个新的值。同时,函数在相同输入值时,需产生相同的输出。

函数式编程的基本运算

1、柯里化(curry)

函数的柯里化,即对于一个多参数的函数进行一次转化,转化后的函数只传递给函数一部门参数来调用它,让它返回一个函数去处理剩下的参数。

这里的 curry 我举两个很常见的例子。

例子一、


例子二、


2、数据的合成(compose)

数据需要经过多个函数才可以变成另一个值,把这多个函数像一系列管道拼接起来,这个过程就叫函数合成(compose)。

这就是函数合成:

var compose = function(f,g) {

    return function(x) {

        return f(g(x))

    }

}

例如,我们要是实现一个函数,它接收一个字符串,然后需要将该字符串转换成大写,并在尾部添加一个感叹号。我们可以把这两个职责分派给 toUpperCase 和 exclaim 函数:


函数嵌套的写法


相比在函数中嵌套一大堆函数的调用,借用 compose 方法,我们可以写出更为灵活易读的函数组合。


Redux 中函数式编程的应用:中间件

中间件的组合: applyMiddleware 函数解析

我们先来看看 applyMiddleware 函数的用法


如上,applyMiddleware 函数接收多个中间件函数,并将其串联起来。这种引入中间件的方式就很适合使用函数的组合(compose),事实上该函数也是这么实现的。

applyMiddleware 源码:

如上,applyMiddleware 函数通过组合函数 compose,对新生成的中间函数实现链式调用。相比嵌套函数调用的写法,这种写法十分直观,可维护性也极佳。
除了compose 的使用,还可以看到,我们传入的中间件函数并不会只执行一次: middlewares 作为原始中间件的存储数组,其包含的中间件函数会被遍历执行,接收一个中间件 Api middlewareAPI, 并返回一个新的中间件函数。事实上中间件函数在此处执行了部分运算,得到一个更为强大的新函数,让新函数去执行后面的操作。这正是一种柯里化的写法,不需要一次性传递所有的参数,而是将多个参数分多次传入。事实上中间件函数总共会被执行三次之多,下文我们会对其每一次的执行展开详解。


Redux 中的 compose

compose 源码的实现也十分简洁:

compose 函数巧妙的利用 reduce 函数对中间件函数进行累加,这种方式一开始看起来可能会比较绕,我们可以看下 compose 所生成的函数是什么样的:


测试一波

中间件函数的组合也正是如此,例如当我们引入 thunk 和 logger 这两个中间件时,compose 方式组合的函数如下:

_dispath = compose.apply(undefined, [newTunk, newLogger])(store.dispatch)

非 compose 组合的函数

_dispath = (function(args) {

    newTunk(newLogger(args))

})(store.dispatch)

当中间件的数量不断增多的情况下,中间件嵌套调用的方式可读性十分糟糕。compose 组合的方式赋予了中间件更大的灵活性:可以像管道一样随意且无限制的组合,而且不用担心代码的可读性。


中间件实现

我们先来实现一个简单的日志中间件 actionLogger,用于记录我们分发的 action 类型:

它的整个调用过程可以写成

actionLogger(store)(next)(action)

可以看到,中间件函数把参数分为三次传入调用,很典型的柯里化写法。我们来分析一下这个中间件函数 actionLogger 每一次的部分运算是如何执行的。

第一次调用:actionLogger(store)


actionLogger 第一次执行所接收的 store 参数其实就是 applyMiddleware 函数为中间件函数封装的一个简洁版的 Store,包含了 Store 中的 getState 方法和 一个被中间件加强的 dispatch 方法,我们在上面介绍 applyMiddleware 函数的时候也有提到,代码如下:

chain = middlewares.map(function (middleware) {

    // 为中间件函数传递 Store 的 Api, 返回一个新的中间件函数

    return middleware(middlewareAPI);

});

第一次执行后返回的新函数形态如下:

通过第一次的部分运算,我们可以得到一个能够访问中间件 Api 的新函数,而这可以帮助我们实现更强大的中间件。

第二次调用:actionLogger(store)(next)

我们上面有提到,_dispatch 在不使用 compose 组合的时候如下:

可以看到,最末端的中间件 actionLogger 接收的正是 Store 原始的 dispatch 方法。

我们再来看中间件第二次执行后返回的函数形态:

很显然,中间件此时返回的正是一个类 dispatch 函数,而它也将作为下一级的中间件函数的 next 参数。简单来说,只有最末端的中间件函数的 next 参数是不经过加工的 store.dispatch,其余中间件的 next 参数都是其后一个中间件函数加强过的 dispatch 方法。这是一种链式的关系,dispatch 的控制权在中间件中由内而外进行传递,对于 compose 组合的顺序来说则是从右至左。


第三次调用:actionLogger(store)(next)(action)

最后一次的调用正是用于 action 的分发,和第二次从右至左传递 next 参数顺序相反,此时的调用顺序是从左往右。此时会先执行排最前面的中间件函数,通过对 next 参数的调用才一步步执行到最末端的中间件函数的 next 方法,也就是 store.dispatch。

Redux 中更多的函数式编程

Redux 中间件系统十分灵活强大,得益于代码中函数式编程的思想。不仅仅是中间件,Redux 中的Reducer 也是一个很典型的函数式编程,它要求我们保证函数的‘纯’,不允许 Reducer 函数带有任何的副作用,而这种函数式的编程模式也可以帮助我们杜绝多数意想不到的问题。

关于 Redux 中的函数式编程,建议大家通过阅读它的源码来细细体会。

( 这些内容,我觉得在看完了 Redux 中间件以后再回顾会好一些 )

本小节完毕,部分代码如下:

https://github.com/D1N910/learn-react/tree/master/Chapter5/demo-1-pure-function

Redux 基本元素

怎么在React中使用Redux(小剧场)

A:Redux 的确是一个不错的状态管理框架,但是发现直接在 React 中使用不是很方便。子组件使用 Redux 的属性和方法,往往需要层层传递,或者组件把一整个 state 引入进来。

B:React-Redux 这个包可以帮我们解决这个问题。使用了它之后,我们只需要在最顶层的父组件传入 store 就可以了。在子组件中,如果我们想要使用 Redux 的一些功能,只需要使用 context,把子组件做一个包装就行。

Redux 是 Flux 的实现,我们可以用 Redux 替换 Flux。

上一节我们用 Flux 实现了 todoList,我们可以直接用 Redux 来替换。


Redux元素详解 - Action

  • store的唯一信息源,我们要想修改 stroe,只能通过发起 action 的方式

  • store.dispatch(action): 通过传入 aciton,就可以发起调用。

在 Redux中,Dispatcher 已经被继承在 Store 当中了,所以不用引入 Dispatcher,直接返回 action 对象即可。


使用的时候,通过 Store 对象的 dispatch 方法进行派发(感觉也和原来的差不多,只不过 dispatch 集中在了 store 上面)

Redux元素详解 - Reducer

  • 作用处理 state 变化的纯函数

  • 一般结构 (state, action) => new State。是修改 state,但是不是直接改 state,而是返回一个新的 state。

  • immutable。不可变。


首先导出 reducer 的纯函数,根据 action 分发,通过 switch 实现。(你也可以用对象属性来实现这种 switch 的效果。)

能观察到,我们是不会改变 state 的。

使用 concat 、 assign 的方法,可以让我们构建新的对象、新的数组。

当然,要注意到使用类似 default 的方法。

和 Flux 的差不多,这里我们专门提取为 reducer.js 文件来使用。

Redux元素详解 - Store

保存现在状态的数据。

和 flux 不一样的是,引入了 redux 的 createStore 来创建了一个 store。

createStore 传递三个参数。

第一个参数 reducer,相应事件处理 state 方法;

第二个参数 initialState,初始化 state 参数;

第三个参数这里没写,是 store 的增强器。


事件的派发:通过 store 的 dispatch 方法进行

e.g. TodoStore.dispatch(Actions.addTodo(text));


获取当前状态:

e.g. TodoStore.getSate().list


订阅变化:

e.g. TodoStore.subscribe(this.onChange)


View 层的使用不同

(1)绑定 action 方法

e.g. TodoStore.dispatch(Action.addTodo(value))

(2)监听 store 变化办法

e.g. TodoStore.subscribe(this.onStoreChange)

(3)取消监听 store 变化办法

e.g. TodoStore.unsubscribe(this.onStoreChange)

(4)绑定初始值

this.state = {

     // 获取初始值

     todoList: TodoStore.getState().list

   }

Redux 的运作方式【资料】

核心模块

Redux 作为一个状态管理框架,包含三大核心模块:

  • Action:视图层发出的通知,通知 Store 执行数据数据更新,同时它作为 Store 唯一的数据来源。

  • Store:Redux 中的数据容器,且 Store 在 Redux 中是唯一的。

  • Reducer: Reducer 指定了 Sotre 应当如何响应 Action 的更新。

除了以上三个模块,还有一个 View 层,在 Redux 中一般指 React 视图层。Redux 的数据在这四个模块之间的流转严格遵循单向数据流的原则。


Action

在 Redux 中,state 是不可变的,数据的更新需要通过分发 Action 来实现。Action 本质是一个 JavaScript 对象,type 是其必要的一个属性,代表 Action 的名称,该属性的缺失将导致 Action 不能被识别而无法更新数据。

我们一般通过 action creator 函数来生成 Action:

const editTitle = function(title) {

        return {

                type: 'EDIT_TITLE',

                title

        }

}

// 使用一个 editTitle 方法生成一个 action

const action = editTitle('hello Redux')


Reducer

Reducer 是 Redux 中唯一能识别 Action 的模块。它接收 Store 当前的状态和分发的 Action,并返回一个新的 State 给到 Store。需要注意,Reducer 是一个纯函数,函数中不能修改 State,而是创建一个新的 State 对象,而修改的状态也将被合并到该对象。

Reducer.js

可以看到,这里可以根据 Action 的 type 做不同的数据更新。如果无法识别,会返回原来的 state。

这里还多设置了一个 initialState,作为 state 传入 undefined 的时候的默认值。每一个 Reducer都需要设置一个初始状态。


Store

Store 存储了 Redux 应用中的所有状态,并提供了相关访问 Store 当前状态的方法,同时 Store 也作为 Action 和 Reducer 之间的纽带,将两者联结在一起。

1、创建 Store:createStore 方法

Redux 提供了一个 createStore 方法,用于创建 Store。

该方法接收三个参数:

  • 第一个参数接收一个 Reducer。

  • 第二个参数是一个对象,它为 Store 指定了应用的初始状态,当项目中涉及服务器端的 Redux 时,这个参数可以帮助我们同步服务器端和客户端的数据结构,

  • 第三个参数是一个函数,用于指定 Redux 中间件。

需要注意,当需要指定中间件且不需要指定初始状态的时候,第三个参数可提到第二个参数的位置,createStore 方法会根据第二个参数的类型将其识别为初始状态或者中间件函数。

创建一个 Store 时,我们一般只需要传递第一个参数(如果 Reducer 指定了默认 state 的话):

数据的初始化

Store 状态的初始化可分为两个阶段。第一阶段正是 createStore 方法所接收的第二个参数实现的初始化。这个阶段的初始化常见于服务端的 Redux 渲染时,服务端的渲染会把请求到的状态注入到 window.__PRELOADED_STATE__,客户端可以通过该全局变量得到初始的状态对象,并将其作为 createStore 的第二个参数传入,进而完成初次状态的初始化。

这个场景下初始化的状态一般都会包含首屏所需的数据,这也是使用服务端渲染的原因:用于加快首屏的展示。但是在 Reducer 被切分成多个模块的时候,参数实现的初始化难免有遗漏,这个时候就需要我们进行第二次的数据初始化。

我们来看一段 createStore 方法的实现源码:

通过 createStore 的实现源码中可以看到,它在最后一步调用内置的 dispatch 方法,分发了一个 type 为 ActionTypes.INIT 的 action 对象。

我们知道,Reducer 根据 action 的 type 做不同的数据处理。如果 action 中的 type 与其不相匹配,则需要执行 ‘default’ 操作: 返回原先的 State。

createStore 方法正是利用了 Reducer 的这个特性,通过分发一个不会被 Reducer 所识别的 action,让 Reducer 返回各自的初始化状态,进而完成了 Store 数据的第二次初始化。

我们总结一下 Store 的初始化流程:

开始创建 Store

⬇️

执行 createStore

⬇️

preloadedState 是否是对象

(是 👉 初次初始化完成)

⬇️

(否)

⬇️

执行初始化的 dispatch

⬇️

Reducer 返回配置的默认 State

⬇️

第二次初始化完成

数据的更新:dispatch 方法

Store 内置的 dispatch 方法,充当了 Action 与 Reducer 之间的桥梁。通过 dispatch 方法,实现了 Action 从 Store 层到 Reducer 层的转发。我们可以在 View 层通过该方法分发 Action,进而实现 Store 层数据的更新。

我们看 dispatch 的源码是如何实现 Action 的分发:

  源码中的 currentReducer 正是我们调用 createStore 方法时传入的 Reducer,currentState 对应 Store 当前的状态。也就是说,dispatch 方法直接调用了传入的 Reducer 函数,并向其转发了 Store 当前状态和被分发的 action。同时的,Reducer 函数处理返回的结果也将直接被保存到 currentState 中。

需要注意,Redux 的数据更新只支持同步处理,如果需要执行异步处理,则需要引入 react-thunk 中间件。

数据的监听:subscribe

数据从 Store 层到 View 层的流向,正是依赖于 Store 中的内置 subscribe 方法。该方法接收一个回调函数,并返回一个取消监听的函数。

添加 Store 的数据监听:

import store form './store';

const unSubscribe = store.subscribe(() => {

        let state = store.getState()

})

每一次数据的更新,都会执行 subscribe 中传入的回调函数。我们可以在 View 层添加这个回调函数,回调函数中则通过 store.getState() 获取Store当前的数据,进而执行视图层的更新。

数据监听原理解析

Store 中执行监听回调的方法在 dispatch 方法内部。也就是说,在执行 dispatch 方法的时候,我们添加的监听回调都会被执行一遍。这一点在 dispath 方法的源码中也有体现:

可以看到,Store 内部有一个存储监听函数的数组 nextListener,每次执行 dispatch 方法都会遍历调用该数组中所有的监听函数。

我们新增的监听回调是作为 subscribe 函数的参数传入的,所以我们可以推断,subscribe函数的作用正是帮助我们把新增的监听回调添加到 nextListeners 数组总。

我们的推断可以在 subscribe 源码中得到验证:

function sbscribe(listener) {
    if (typeif listener !=== 'function') {

            throw new Error('这个要被监听的玩意儿得是函数啊兄台')

    }

    let isSubscribed = true

    ensureCanMutateNextListeners()

    nextListeners.push(listener)

    return function unsubscribe() {

        if (!isSubscribed) {

            return

        }

        isSubscribed = false

        ensuerCanMutateNextListeners()

        const index = nextListeners.indexOf(listener)

        nextListeners.splice(index, 1)

    }

}

nextListeners 是一个数组,所以我们可以在多个视图层添加 Store 的监听回调。同时,subscribe 方法会返回一个取消监听的函数。有的时候我们希望不在接收第一次的数据更新,这个时候我们可以使用该方法移除监听。


本小节结束,相关代码如下:

https://github.com/D1N910/learn-react/tree/master/Chapter5/demo-2-redux

更加 Redux 的方式

容器组件和傻瓜组件

上面主要是了解了 Redux 的基本内容。

了解一下 React 组件的架构,容器组件和傻瓜组件。这将对我们的组件进行区分。

首先看看我们的组件做了什么

  • 和 Store 保持同步:监听 Store 的变化,从 Store 中获取数据。

  • 渲染界面:根据 props 和 state 来渲染界面

通过这两个内容,我们可以进行拆分。

容器组件 ContainerComponent:与 Store 保持同步,负责管理数据,存放业务逻辑。

傻瓜组件 DumbComponent:与 UI 保持同步,只负责渲染 UI,存放所有 UI 渲染方法;

这两个组件可以通过 props 的方式进行沟通。

ContainerComponent ➡️props ➡️ DumbComponent

通过这样的方式,将功能和界面解绑。

处理更复杂的数据

了解了上面的内容后,可以看看我们现在的 React 应用(TodoList)的问题。

  • 容器组件和傻瓜组件强耦合,代码冗余

  • Redux 应用只有一个 Store,处处都要 import

通过 props 传递什么

根据功能来看

傻瓜组件:渲染界面;反馈用户行为。

所以主要传递的是数据和方法

  • Store 中的 state,容器向核心组件传递数据

  • Store 中的 dispatch 方法,核心组件向容器发起行为

我们可以利用高阶组件来重用上面抽象出来的功能。


首先我们需要提供两个参数。

mapStateToProps:映射到props,传入到内部的傻瓜组件中的数据。

它可能是这样的

mapDispatchToProps:映射到props,即导入到内部的傻瓜组件中的绑定方法

它可能是这样的:

最后我们得到的

<Component {...this.props} {...this.state} />

实际上就是一个容器组件。传入了一个傻瓜组件,得到了一个容器组件。


我们可以像下面这样进行使用

当然别忘了,傻瓜组件中要对传入的 props 进行验证,这样能够规定我们 props 传入的是我们需要的内容

验证无误 ⬇️


使用容器组件-傻瓜组件模式的代码如下

https://github.com/D1N910/learn-react/tree/master/Chapter5/demo-3-more-redux-cf


设置全局可访问的 Store

在我们上面说到的高阶组件李,要使用 store,需要专门引入,感觉很麻烦,而且如果在不同的组件中使用,还需要再次引入。

虽然我本人不觉得麻烦,但是视频的作者觉得是麻烦事。他想要设置全局组件都可以访问 store。那我们之前学过 React 组件传递参数的办法,其中 Context 非常适合全局传递 Store。

这相当于是一个全局的属性。只需要在父级声明Context,就可以在子组件中使用了。

设置一个可全局访问的 Context

首先注意:

1、React 在 16.3 版本之后提供了新的 Context API,我们这里使用的是旧的 API

2、Context 需要在最顶层的组件日共,这样所有下层组件才能访问

3、在最外层包裹一层组件,传入 Store 作为 props,并将其赋值给 Context

4、需要使用 Store 的子组件通过 Context 访问


首先提供一个组件,这个组件是所有我们需要使用 Redux 组件最顶层的应用 —— Provider

通过这个 Provider 组件作为顶级组件,就可以让子组件通过 context 获得这里存储的值。


想要引入 store,需要在 constructor 中声明继承 context,声明 Connext.contextTypes类型,然后就可以用 this.context 使用

本小节代码如下

https://github.com/D1N910/learn-react/tree/master/Chapter5/demo-3-more-redux-context


react-redux

到这里,我们说完了使用做 Redux 的时候,可以优化的两个方式

(1)使用高阶组件抽离成容器组件、傻瓜组件

(2)使用 context 实现全局组件使用 store

这两个其实就是 react-redux 这个库的内容。

react-redux 将 React 和 Redux 链接到了一起,结构如下。


我们如何使用呢?

首先安装 react-redux。

在引入 connect 的高阶函数,使用  import { connect } from 'react-redux' 引入 connect,其他不变。


在顶级组件,引入 Provider 然后可以直接使用。

(搞了一堆心智负担以后,现在发现用了 react-redux 就不用了……感觉自己变蠢了)

使用 react-redux 的代码如下

https://github.com/D1N910/learn-react/tree/master/Chapter5/demo-3-more-redux-react_redux


使用 React-Redux

上面有实际使用 react-redux 来完成简易的 todoList 的功能了,下面将使用 react-redux 完成具有更复杂功能的 todoList 来辅助理解使用场景。

新增 - 能够点击切换完成状态;

新增 - 能够筛选展示列表(有全部、已完成、未完成 三种展示情况)。

效果如下

按照视频,我把文件结构修改成如下的样子。

把组件拆分成容器组件(containers)、傻瓜组件(components)


使用 app 文件来包裹傻瓜组件。

这里说一下,store 的第二个参数传入的是 store 的 __REDUX_DEVTOOLS_EXTENSION__。


通过这个可以在谷歌浏览器上调用 react-redux 的谷歌插件 Redux DevTools。这个工具可以监控我们 store 的变化,主要是发生了什么行为。同时可以选择任意一个行为,然后跳转进入。

上面的相当于小的实践项目,这里附带上工程代码

https://github.com/D1N910/learn-react/tree/master/Chapter5/demo-3-more-redux-z-more_react_redux



Redux 的高阶应用

异步操作如何使用 Action

目前 redux 里的 action 都是同步执行。异步操作的时候使用 Action,可以通过使用 Redux 中间件的方式增强 React 的能力。

Redux 中间件

通过查看 Redux 数据流来查看哪里最适合拓展。

View:不适合,因为这些都是在展示层实现的,和 React 有关,和 Redux 关系不大。

在 State -> View 也不适合,因为这里主要是和 action 传递数据有关,这时候数据已经处理完毕了。

 Store  内部:不太适合,我们不能直接改变 state,我们只能返回一个新的 state,而不改变原有的 state

Reducer 也不适合,已经是发出数据了。

最后发现最好在 ActionCreator 到 Dispatcher 中做处理。在发出和接收的这段时间里进行增强处理。

通过 applyMiddleware(中间件1, 中间件2, 中间件3)  的方法来实现加入中间件。

action creatir ->  action -> 中间件1 -> 中间件2 -> 中间件3 -> dispatcher -> reducers 


加入中间件以后,我们的流程可以发现在 dispatcher 上变成了套娃模式。

中间件的实现是高级中的高级,使用了柯里化的办法完成。(上面的资料有描述过这个中间件的实现)

({getState, dispatch}) => next => action => next(action)

中间件模版整理如下

function middleware({getState, dispatch}) {

   return function (next) {

       return function (action) {

            // do something ...

             return next(action)

        }

    }

}

一定要返回 next(action),才能够把我们进行的操作进行返回。

查看 Redux 源码中 applyMiddle ware 的实现

这里拿到了中间件之后,他会使用 middlewareAPI 进行赋于获得 state 还有 action 的能力。

这里用 map 实现一个对中间件的调用。

使用 compose 方法可以把里面所有的参数按照从左到右的方式,将右边的参数当作左边参数的传入值。这里将中间件串联起来了。也就是说,next 指向的是右边最近的中间件方法。最后完成循环的调用。

而我们的 dispath 也就经过了多层的调用。

这里列举一个栗子。

下面两个中间件,

authorMiddleware 实现了为每个 action 方法打上作者;

loggerMiddleware 实现打印旧 state、action 以及 处理后的数据。

中间件有执行数据,所以要注意 loggerMiddleware 要放到后面。

执行下我们上一节的方法, 发现我们的中间件能够正确在每个 action 触发后被调用了。


分解 ajax 操作

了解上面的中间件知识以后,回顾我们刚刚的提问,如果在 Action 中使用 AJAX 这样的异步操作,我们需要分解 AJAX。

要分解 AJAX原因如下:

  • Redux 中的 Action 是同步的,收到 Action 之后数据就会改变

  • AJAX 是异步的,等待服务器返回后数据才会改变

  • 所以,将异步的 AJAX 拆解为同步的状态变化过程

分析一下。

init 初始化以后,发起了 REQUEST (请求),一段 loading (等待期)后。

会有两种情况,分别是 REQUEST_SUCCESS (请求成功),那么就要去设置新的 data值。REQUEST_FAILURE (请求失败),抛出 error。

在 React 中我们应该如何拆解呢?

常见的会拆解成下面的写法。

这里有一个问题,就是从架构上来说,我们的数据处理动作是放到 ActionCreator 中处理的,React 中主要处理数据渲染、视图相关的内容。

回忆下我们刚刚学习的中间件,我们可以进行改造。

改造方案:

  • 在 ActionCreator 中不再返回简单的 Action 对象,而是返回一个函数或 Promise

  • 使用中间件来解析特殊类型的 Action 对象,自动拆解为同步的简单 Action

解决方案有两种:

可以使用 Redux-thunk 中间件,它允许我们的 ActionCreator 返回函数 ;

可以使用 Redux-promise 中间件,它允许我们的 ActionCreator 返回 Promise 对象;

视频课程主要讲述的是使用 tunk 的方式。

使用例子:

这里我们创建了三个普通的 action。

但是我们实际返回的 action creator 不是前面的 action 一样返回一个 对象,而是返回一个函数。

这个函数体的内部,先 dispatch(requestStart()),然后发起 ajax 请求,在请求成功和请求失败的时候分别做 dispatch 请求。


查看thunk 的源码,我们可以看到它很简单。

获得了传入的 dispath,如果我们传入的是 function类型的话,我们可以传入 dispatch、getState 等让他得到对应的能力。这样我们可以在 actionCreator 中使用 dispath 的能力。

接下来可以在我们的源码中看看如何使用。

这里我们的 store 改造如下。

需要注意的是,这里我们用上了 window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__,这样能够在使用到 redux 开发者工具的时候使用 compose。

reducer.js 针对开始请求、请求完毕做处理。

造了 app 的容器组件去 connect 了 api 加载


根据之前的课程,我们的 ajax 加载要放到 componentDidMount 里


因为是本地服务器,所以我们可以利用 webpack-dev-server 开启一个简单的服务器

最后效果正常,在控制器里调到 'GET_CONFIG/REQUEST_START',能够展示“加载中”

本节代码如下

https://github.com/D1N910/learn-react/tree/master/Chapter5/demo-4-redux-applyMiddleware



这两周因为头脑不清醒,所以没有很好地进行学习,目前剩余进度 3

有点晕乎乎得

我有点高估我自己了

这个 Redux 的心智负担有点高的

继续加油吧

from 蛋糕

【D1N910】一线大厂React实践宝典 使用Redux (进度 6/9)的评论 (共 条)

分享到微博请遵守国家法律