一文总结redux、react-redux、redux-saga

redux、react-redux、redux-saga总结

前言

hello你们好,我是风不识途,最近一直在整理 redux系列文章,发现对于初学者不太友好,关系错综复杂,难却是不太难,就是比较复杂 (其实写比较少),因此这篇带你全面了解 redux、react-redux、redux-thunk还有 redux-sage,immutable(多图预警),因为知识点比较多,建议先收藏(收藏等于学会了 ),对你有帮助的话就给个赞👍

认识纯函数

JavaScript纯函数

  • 函数式编程中有一个概念叫纯函数, JavaScript符合函数式编程的范式, 因此也有纯函数的概念
  • React中,纯函数的概念很是重要,在接下来咱们学习的Redux中也很是重要,因此咱们必须来回顾一下纯函数
  • 纯函数的定义简单总结一下:react

    • 纯函数指的是, 每次给相同的参数, 必定返回相同的结果
    • 函数在执行过程当中, 不能产生反作用
  • 纯函数( Pure Function )的注意事项:ios

    • 在纯函数中不能使用随机数
    • 不能使用当前的时间或日期, 由于结果是会变的
    • 不能使用或者修改全局状态, 好比DOM,文件、数据库等等(由于若是全局状态改变了,它就会影响函数的结果)
    • 纯函数中的参数不能变化,不然函数的结果就会改变

React中的纯函数

  • 为何纯函数在函数式编程中很是重要呢?git

    • 由于你能够安心的写和安心的用
    • 你在写的时候保证了函数的纯度,实现本身的业务逻辑便可,不须要关心传入的内容或者函数体依赖了外部的变量
    • 你在用的时候,你肯定你的输入内容不会被任意篡改,而且本身肯定的输入,必定会有肯定的输出
  • React很是灵活,但它也有一个严格的规则:web

    • 全部React组件都必须像"纯函数"同样保护它们的"props"不被更改

认识Redux

为何须要redux

  • JavaScript开发的应用程序, 已经变得很是复杂了:算法

    • JavaScript须要管理的状态愈来愈多, 愈来愈复杂了
    • 这些状态包括服务器返回的数据, 用户操做的数据等等, 也包括一些UI的状态
  • 管理不断变化的state是很是困难的:chrome

    • 状态之间相互存在依赖, 一个状态的变化会引发另外一个状态的变化, View页面也有可能会引发状态的变化
    • 当程序复杂时, state在何时, 由于什么缘由发生了变化, 发生了怎样的变化, 会变得很是难以控制和追踪

React的做用

  • React只是在视图层帮助咱们解决了DOM的渲染过程, 可是state依然是留给咱们本身来管理:数据库

    • 不管是组件定义本身的state,仍是组件之间的通讯经过props进行传递
    • 也包括经过Context进行数据之间的共享
    • React主要负责帮助咱们管理视图,state如何维护最终仍是咱们本身来决定

  • Redux就是一个帮助咱们管理State的容器:编程

    • ReduxJavaScript的状态容器, 提供了可预测的状态管理
  • Redux除了和React一块儿使用以外, 它也能够和其余界面库一块儿来使用(好比Vue), 而且它很是小 (包括依赖在内,只有2kb)

Redux的核心理念-Store

  • Redux的核心理念很是简单
  • 好比咱们有一个朋友列表须要管理:redux

    • 若是咱们没有定义统一的规范来操做这段数据,那么整个数据的变化就是没法跟踪的
    • 好比页面的某处经过products.push的方式增长了一条数据
    • 好比另外一个页面经过products[0].age = 25修改了一条数据
  • 整个应用程序错综复杂,当出现bug时,很难跟踪到底哪里发生的变化

Redux的核心理念-action

  • Redux要求咱们经过action来更新stateaxios

    • 全部数据的变化, 必须经过dispatch来派发action来更新
    • action是一个普通的JavaScript对象,用来描述此次更新的typecontent
  • 好比下面就是几个更新friendsaction:

    • 强制使用action的好处是能够清晰的知道数据到底发生了什么样的变化,全部的数据变化都是可跟追踪、可预测的
    • 固然,目前咱们的action是固定的对象,真实应用中,咱们会经过函数来定义,返回一个action

Redux的核心理念-reducer

  • 可是如何将stateaction联系在一块儿呢? 答案就是reducer

    • reducer是一个纯函数
    • reducer作的事情就是将传入的stateaction结合起来来生成一个新的state

Redux的三大原则

  • 单一数据源

    • 整个应用程序的state被存储在一颗object tree中, 而且这个object tree只存储在一个store
    • Redux并无强制让咱们不能建立多个Store,可是那样作并不利于数据的维护
    • 单一的数据源可让整个应用程序的state变得方便维护、追踪、修改
  • State是只读的

    • 惟一修改state的方法必定是触发action, 不要试图在其它的地方经过任何的方式来修改state
    • 这样就确保了View或网络请求都不能直接修改state,它们只能经过action来描述本身想要如何修改state
    • 这样能够保证全部的修改都被集中化处理,而且按照严格的顺序来执行,因此不须要担忧race condition(竟态)的问题
  • 使用纯函数来执行修改

    • 经过reducer将旧 stateaction 联系在一块儿, 而且返回一个新的state
    • 随着应用程序的复杂度增长,咱们能够将reducer拆分红多个小的reducers,分别操做不一样state tree的一部分
    • 可是全部的reducer都应该是纯函数,不能产生任何的反作用

Redux的基本使用

Redux中核心的API

redux的安装: yarn add redux

  1. createStore 能够用来建立 store对象
  2. store.dispatch 用来派发 action , action 会传递给 store
  3. reducer接收action,reducer计算出新的状态并返回它 (store负责调用reducer)
  4. store.getState 这个方法能够帮助获取 store 里边全部的数据内容
  5. store.subscribe方法可让让咱们订阅 store 的改变,只要 store 发生改变, store.subscribe 这个函数接收的这个回调函数就会被执行

小结

  1. 建立sotore, 决定 store 要保存什么状态
  2. 建立action, 用户在程序中实现什么操做
  3. 建立reducer, reducer 接收 action 并返回更新的状态

Redux的使用过程

  1. 建立一个对象, 做为咱们要保存的状态
  2. 建立Store来存储这个state

    • 建立store时必须建立reducer
    • 咱们能够经过 store.getState 来获取当前的state
  3. 经过action来修改state

    • 经过dispatch来派发action
    • 一般action中都会有type属性,也能够携带其余的数据
  4. 修改reducer中的处理代码

    • 这里必定要记住,reducer是一个纯函数,不能直接修改state
    • 后面会讲到直接修改state带来的问题
  5. 能够在派发action以前,监听store的变化
import { createStore } from 'redux'

// 1.初始化state
const initState = { counter: 0 }

// 2.reducer纯函数 不能修改传递的state
function reducer(state = initState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, counter: state.counter + 1 }
    case 'ADD_COUNTER':
      return { ...state, counter: state.counter + action.num }
    default:
      return state
  }
}

// 3.store 参数放一个reducer
const store = createStore(reducer)

// 4.action
const action1 = { type: 'INCREMENT' }
const action2 = { type: 'ADD_COUNTER', num: 2 }

// 5.订阅store的修改
store.subscribe(() => {
  console.log('state发生了改变: ', store.getState().counter)
})

// 6.派发action
store.dispatch(action1)
store.dispatch(action2)

redux原理图

Redux结构划分

  • 若是咱们将全部的逻辑代码写到一块儿, 那么当redux变得复杂时代码就难以维护
  • 对代码进行拆分, 将store、reducer、action、constants拆分红一个个文件

<details>
<summary>拆分目录</summary>

</details>

Redux使用流程

redux

Redux官方流程图

redux-flow

React-Redux的使用

redux融入react代码(案例)

  • redux融入react代码案例:

    • Home组件:其中会展现当前的counter值,而且有一个+1和+5的按钮
    • Profile组件:其中会展现当前的counter值,而且有一个-1和-5的按钮

  • 核心代码主要是两个:

    • componentDidMount 中订阅数据的变化,当数据发生变化时从新设置 counter
    • 在发生点击事件时,调用storedispatch来派发对应的action

自定义connect函数

当咱们多个组件使用redux时, 重复的代码太多了, 好比: 订阅state取消订阅state 或 派发action获取state

将重复的代码进行封装, 将不一样的statedispatch做为参数进行传递

//  connect.js 
import React, { PureComponent } from 'react'
import { StoreContext } from './context'
/**
 * 1.调用该函数: 返回一个高阶组件
 *      传递须要依赖 state 和 dispatch 来使用state或经过dispatch来改变state
 *
 * 2.调用高阶组件:
 *      传递该组件须要依赖 store 的组件
 *
 * 3.主要做用:
 *      将重复的代码抽取到高阶组件中,并将该组件依赖的 state 和 dispatch
 *      经过调用mapStateToProps()或mapDispatchToProps()函数
 *      并将该组件依赖的state和dispatch供该组件使用,其余使用store的组件没必要依赖store
 *
 * 4.connect.js: 优化依赖
 *      目的:可是上面的connect函数有一个很大的缺陷:依赖导入的 store
 *      优化:正确的作法是咱们提供一个Provider,Provider来自于咱们
 *             Context,让用户将store传入到value中便可;
 */
export function connect(mapStateToProps, mapDispatchToProps) {
  return function enhanceComponent(WrapperComponent) {
    class EnhanceComponent extends PureComponent {
      constructor(props, context) {
        super(props, context)

        // 组件依赖的state
        this.state = {
          storeState: mapStateToProps(context.getState()),
        }
      }

      // 订阅数据发生变化,调用setState从新render
      componentDidMount() {
        this.unsubscribe = this.context.subscribe(() => {
          this.setState({
            centerStore: mapStateToProps(this.context.getState()),
          })
        })
      }

      // 组件被卸载取消订阅
      componentWillUnmount() {
        this.unsubscribe()
      }

      render() {
        // 下面的WrapperComponent至关于 home 组件(就是你传递的组件)
        // 你须要将该组件须要依赖的state和dispatch做为props进行传递
        return (
          <WrapperComponent
            {...this.props}
            {...mapStateToProps(this.context.getState())}
            {...mapDispatchToProps(this.context.dispatch)}
          />
        )
      }
    }
    // 取出Provider提供的value
    EnhanceComponent.contextType = StoreContext
    return EnhanceComponent
  }
}

// home.js
// 定义组件依赖的state和dispatch
const mapStateToProps = state => ({
  counter: state.counter,
})

const mapDispatchToProps = dispatch => ({
  increment() {
    dispatch(increment())
  },
  addNumber(num) {
    dispatch(addAction(num))
  },
})
export default connect(mapStateToProps,mapDispatchToProps)(依赖redux的组件)

react-redux使用

  • 开始以前须要强调一下,reduxreact没有直接的关系,你彻底能够在React, Angular, Ember, jQuery, or vanilla JavaScript中使用Redux
  • 尽管这样说,redux依然是和React或者Deku的库结合的更好,由于他们是经过state函数来描述界面的状态,Redux能够发射状态的更新,让他们做出相应。
  • 虽然咱们以前已经实现了connectProvider这些帮助咱们完成链接redux、react的辅助工具,可是实际上redux官方帮助咱们提供了 react-redux 的库,能够直接在项目中使用,而且实现的逻辑会更加的严谨和高效
  • 安装react-redux

    • yarn add react-redux
// 1.index.js
import { Provider } from 'react-redux'
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

// 2.home.js
import { connect } from 'react-redux'
// 定义须要依赖的state和dispatch (函数须要返回一个对象)
export default connect(mapStateToProps, mapDispatchToProps)(About)

react-redux源码导读

Redux-Middleware中间件

组件中异步操做

  • 在以前简单的案例中,redux中保存的counter是一个本地定义的数据

    • 咱们能够直接经过同步的操做来dispatch actionstate就会被当即更新。
    • 可是真实开发中,redux中保存的不少数据可能来自服务器,咱们须要进行异步的请求,再将数据保存到redux
  • 网络请求能够在class组件的componentDidMount中发送,因此咱们能够有这样的结构:

redux中异步操做

  • 上面的代码有一个缺陷:

    • 咱们必须将网络请求的异步代码放到组件的生命周期中来完成
  • 为何将网络请求的异步代码放在redux中进行管理?

    • 后期代码量的增长,若是把网络请求异步函数放在组件的生命周期里,这个生命周期函数会变得愈来愈复杂,组件就会变得愈来愈大
    • 事实上,网络请求到的数据也属于状态管理的一部分,更好的一种方式应该是将其也交给redux来管理

  • 可是在redux中如何能够进行异步的操做呢?

    • 使用中间件 (Middleware)
    • 学习过ExpressKoa框架的童鞋对中间件的概念必定不陌生
    • 在这类框架中,Middleware能够帮助咱们在请求和响应之间嵌入一些操做的代码,好比cookie解析、日志记录、文件压缩等操做

理解中间件(重点)

  • redux也引入了中间件 (Middleware) 的概念:

    • 这个<font color='red'>中间件的目的是在dispatchaction和最终达到的reducer之间,扩展一些本身的代码</font>
    • 好比日志记录、调用异步接口、添加代码调试功能等等

redux-middlware

  • redux-thunk是如何作到让咱们能够发送异步的请求呢?

    • 默认状况下的dispatch(action)action须要是一个JavaScript的对象
    • redux-thunk可让dispatch(action函数), action<font color='red'>能够是一个函数</font>
    • 该函数会被调用, 而且会传给这个函数两个参数: 一个dispatch函数和getState函数

      • dispatch函数用于咱们以后再次派发action
      • getState函数考虑到咱们以后的一些操做须要依赖原来的状态,用于让咱们能够获取以前的一些状态

redux-thunk的使用

  1. 安装redux-thunk

    • yarn add redux-thunk
  2. 在建立store时传入应用了middlewareenhance函数

    • 经过applyMiddleware来结合多个Middleware, 返回一个enhancer
    • enhancer做为第二个参数传入到createStore

      image-20200821182447344

  3. 定义返回一个函数的action

    • 注意:这里不是返回一个对象了,而是一个函数
    • 该函数在dispatch以后会被执行

<details>
<summary>查看代码</summary>
<pre>import { createStore, applyMiddleware } from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'<br/>
const store = createStore(
reducer,
applyMiddleware(thunk) // applyMiddleware可使用中间件模块
)
export default store
</pre></details>

redux-devtools

redux-devtools插件

  • 咱们以前讲过,redux能够方便的让咱们对状态进行跟踪和调试,那么如何作到呢?

    • redux官网为咱们提供了redux-devtools的工具
    • 利用这个工具,咱们能够知道每次状态是如何被修改的,修改先后的状态变化等等
  • 使用步骤:

    • 第一步:在浏览器上安装redux-devtools扩展插件
    • 第二步:在redux中集成devtools的中间件
// store.js 开启redux-devtools扩展
import { createStore, applyMiddleware, compose } from 'redux'

// composeEnhancers函数
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) || compose

// 经过applyMiddleware来结合多个Middleware,返回一个enhancer
const enhancer = applyMiddleware(thankMiddleware)

// 经过enhancer做为第二个参数传递createStore中
const store = createStore(reducer, composeEnhancers(enhancer))

export default store

redux-sage

generator

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数彻底不一样

Generator 函数有多种理解角度。语法上,首先能够把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

// 生成器函数的定义
// 默认返回: Generator
function* foo() {
  console.log('111')
  yield 'hello'
  console.log('222')
  yield 'world'
  console.log('333')
  yield 'jane'
  console.log('444')
}
// iterator: 迭代器
const result = foo()
console.log(result)

// 使用迭代器
// 调用next,就会消耗一次迭代器
const res1 = result.next()
console.log(res1) // {value: "hello", done: false}
const res2 = result.next()
console.log(res2) // {value: "world", done: false}
const res3 = result.next()
console.log(res3) // {value: "jane", done: false}
const res4 = result.next()
console.log(res4) // {value: undefined, done: true}

redux-sage流程

redux-saga的使用

  • redux-saga是另外一个比较经常使用在redux发送异步请求的中间件,它的使用更加的灵活
  • Redux-saga的使用步骤以下

    1. 安装redux-sage: yarn add redux-saga
    2. 集成redux-saga中间件

      • 引入 createSagaMiddleware 后, 须要建立一个 sagaMiddleware
      • 而后经过 applyMiddleware 使用这个中间件,接着建立 saga.js 这个文件
      • 启动中间件的监听过程, 而且传入要监听的saga
    3. saga.js文件的编写

      • takeEvery:能够传入多个监听的actionType,每个均可以被执行(对应有一个takeLatest,会取消前面的)
      • put:在saga中派发action再也不是经过dispatch, 而是经过put
      • all:能够在yield的时候put多个action
// store.js
import createSageMiddleware from 'redux-saga'
import saga from './saga'
// 1.建立sageMiddleware中间件
const sagaMiddleware = createSageMiddleware()
// 2.应用一些中间件
const enhancer = applyMiddleware(sagaMiddleware)
const store = createStore(reducer,composeEnhancers(enhancer))

sagaMiddleware.run(saga)
export default store

// saga.js
import { takeEvery, put, all } from 'redux-saga/effects'
import { FETCH_HOME_DATA } from './constant'

function* fetchHomeData(action) {
  const res = yield axios.get('http://123.207.32.32:8000/home/multidata')
  const banners = res.data.data.banner.list
  const recommends = res.data.data.recommend.list
  // dispatch action 提交action,redux-sage提供了put
  yield all([
    yield put(changeBannersAction(banners)),
    yield put(changeRecommendAction(recommends)),
  ])
}

function* mySaga() {
  // 参数一:要拦截的actionType
  // 参数二:生成器函数
  yield all([
    takeEvery(FETCH_HOME_DATA, fetchHomeData),
  ])
}

export default mySaga

reducer代码拆分

Reducer代码拆分

  • 咱们来看一下目前咱们的reducer

    • 当前这个reducer既有处理counter的代码,又有处理home页面的数据
    • 后续counter相关的状态或home相关的状态会进一步变得更加复杂
    • 咱们也会继续添加其余的相关状态,好比购物车、分类、歌单等等
    • 若是将全部的状态都放到一个reducer中进行管理,随着项目的日趋庞大,必然会形成代码臃肿、难以维护
  • 所以,咱们能够对reducer进行拆分:

    • 咱们先抽取一个对counter处理的reducer
    • 再抽取一个对home处理的reducer
    • 将它们合并起来

Reducer文件拆分

  • 目前咱们已经将不一样的状态处理拆分到不一样的reducer中,咱们来思考:

    • 虽然已经放到不一样的函数了,可是这些函数的处理依然是在同一个文件中,代码很是的混乱
    • 另外关于reducer中用到的constantaction等咱们也依然是在同一个文件中;

combineReducers函数

  • 目前咱们合并的方式是经过每次调用reducer函数本身来返回一个新的对象
  • 事实上,redux给咱们提供了一个combineReducers函数能够方便的让咱们对多个reducer进行合并
import { combineReducers } from 'redux'
import { reducer as counterReducer } from './count'
import { reducer as homeReducer } from './home'

export const reducer = combineReducers({
  counterInfo: counterReducer,
  homeInfo: homeReducer,
})
  • 那么combineReducers是如何实现的呢?

    • 它将咱们传递的reducer合并成一个对象, 最终返回一个combination函数
    • 在执行combination函数过程当中, 会经过判断先后返回的数据是否相同来决定返回以前的state仍是新的state

immutableJs

数据可变形的问题

  • React开发中,咱们老是会强调数据的不可变性:

    • 不管是类组件中的state,仍是reduex中管理的state
    • 事实上在整个JavaScript编码的过程当中,数据的不可变性都是很是重要的
  • 数据的可变性引起的问题(案例):

    • 咱们明明没有修改obj,只是修改了obj2,可是最终obj也被咱们修改掉了
    • 缘由很是简单,对象是引用类型,它们指向同一块内存空间,两个引用均可以任意修改
const obj1 = { name: 'jane', age: 18 }
const obj2 = obj1
obj1.name = 'kobe'
console.log(obj2.name) // kobe
  • 有没有办法解决上面的问题呢?

    • 进行对象的拷贝便可:Object.assign或扩展运算符
  • 这种对象的浅拷贝有没有问题呢?

    • 从代码的角度来讲,没有问题,也解决了咱们实际开发中一些潜在风险
    • 从性能的角度来讲,有问题,若是对象过于庞大,这种拷贝的方式会带来性能问题以及内存浪费
  • 有人会说,开发中不都是这样作的吗?

    • 历来如此,即是对的吗?

认识ImmutableJS

  • 为了解决上面的问题,出现了Immutable对象的概念:

    • Immutable对象的特色是只要修改了对象,就会返回一个新的对象,旧的对象不会发生改变;
  • 可是这样的方式就不会浪费内存了吗?

    • 为了节约内存,又出现了一个新的算法:Persistent Data Structure(持久化数据结构或一致性数据结构)
  • 固然,咱们一听到持久化第一反应应该是数据被保存到本地或者数据库,可是这里并非这个含义:

    • 用一种数据结构来保存数据
    • 当数据被修改时,会返回一个对象,可是新的对象会尽量的利用以前的数据结构而不会对内存形成浪费,如何作到这一点呢?结构共享:

  • 安装Immutable: yarn add immutable

ImmutableJS常见API

注意:我这里只是演示了一些API,更多的方式能够参考官网

做用:不会修改原有数据结构,返回一个修改后新的拷贝对象

  • JavaScripImutableJS直接的转换

    • 对象转换成Immutable对象:Map
    • 数组转换成Immtable数组:List
    • 深层转换:fromJS
const im = Immutable
// 对象转换成Immutable对象
const info = {name: 'kobe', age: 18}
const infoIM = im.Map()

// 数组转换成Immtable数组
const names = ["abc", "cba", "nba"]
const namesIM = im.List(names)
  • ImmutableJS的基本操做:

    • 修改数据:set(property, newVal)

      • 返回值: 修改后新的数据结构
    • 获取数据:get(property/index)
    • 获取深层Immutable对象数据(子属性也是Immutable对象): getIn(['recommend', 'topBanners'])
// set方法 不会修改infoIM原有数据结构,返回修改后新的数据结构
const newInfo2IM = infoIM.set('name', 'james')
const newNamesIM = namesIM.set(0, 'why')

// get方法
console.log(infoIM.get('name'))// -> kobe
console.log(namesIM.get(0))// -> abc

结合Redux管理数据

  1. ImmutableJS重构redux

    • yarn add Immutable
    • yarn add redux-immutable
  2. 使用redux-immutable中的combineReducers;
  3. 全部的reducer中的数据都转换成Immutable类型的数据

FAQ

React中的state如何管理

  • 目前项目中采用的state管理方案(参考便可):

    • 相关的组件内部能够维护的状态,在组件内部本身来维护
    • 只要是须要共享的状态,都交给redux来管理和维护
    • 从服务器请求的数据(包括请求的操做) ,交给redux来维护

前言

hello你们好,我是风不识途,最近一直在整理 redux系列文章,发现对于初学者不太友好,关系错综复杂,难却是不太难,就是比较复杂 (其实写比较少),因此这篇带你全面了解 redux、react-redux、redux-thunk还有 redux-sage,immutable(多图预警),因为知识点比较多,建议先收藏(收藏等于学会了 ),对你有用的话就给个赞👍

认识纯函数

JavaScript纯函数

  • 函数式编程中有一个概念叫纯函数, JavaScript符合函数式编程的范式, 因此也有纯函数的概念

  • React中,纯函数的概念很是重要,在接下来咱们学习的Redux中也很是重要,因此咱们必须来回顾一下纯函数

  • 纯函数的维基百科定义(了解便可)
    
    ​
  • 纯函数的定义简单总结一下:

    *   纯函数指的是, 每次给相同的参数, 必定返回相同的结果
        
    *   函数在执行过程当中, 不能产生反作用
  • **纯函数( `Pure Function` )的注意事项:**
    
    ​

React中的纯函数

  • 为何纯函数在函数式编程中很是重要呢?

    *   由于你能够安心的写和安心的用
        
    *   你在写的时候保证了函数的纯度,实现本身的业务逻辑便可,不须要关心传入的内容或者函数体依赖了外部的变量
        
    *   你在用的时候,你肯定你的输入内容不会被任意篡改,而且本身肯定的输入,必定会有肯定的输出
  • React很是灵活,但它也有一个严格的规则:

    *   全部React组件都必须像"纯函数"同样保护它们的"props"不被更改

认识Redux

为何须要redux

  • JavaScript开发的应用程序, 已经变得很是复杂了:

    *   `JavaScript`**须要管理的状态愈来愈多**, 愈来愈复杂了
        
    *   这些状态包括服务器返回的数据, 用户操做的数据等等, 也包括一些`UI`的状态
  • 管理不断变化的state是很是困难的:

    *   **状态之间相互存在依赖**, 一个状态的变化会引发另外一个状态的变化, `View`页面也有可能会引发状态的变化
        
    *   当程序复杂时, `state`在何时, 由于什么缘由发生了变化, 发生了怎样的变化, 会变得很是难以控制和追踪

React的做用

  • React只是在视图层帮助咱们解决了DOM的渲染过程, 可是state依然是留给咱们本身来管理:

    *   不管是组件定义本身的`state`,仍是组件之间的通讯经过`props`进行传递
        
    *   也包括经过`Context`进行数据之间的共享
        
    *   `React`主要负责帮助咱们管理视图,`state`如何维护最终仍是咱们本身来决定
![](https://gitee.com/xmkm/cloudPic/raw/master/img/20201005132319.png)
  • Redux就是一个帮助咱们管理State的容器:

    *   `Redux`是`JavaScript`的状态容器, 提供了可预测的状态管理
  • Redux除了和React一块儿使用以外, 它也能够和其余界面库一块儿来使用(好比Vue), 而且它很是小 (包括依赖在内,只有2kb)

Redux的核心理念-Store

  • Redux的核心理念很是简单

  • 好比咱们有一个朋友列表须要管理:

    *   **若是咱们没有定义统一的规范来操做这段数据,那么整个数据的变化就是没法跟踪的**
        
    *   好比页面的某处经过`products.push`的方式增长了一条数据
        
    *   好比另外一个页面经过`products[0].age = 25`修改了一条数据
  • 整个应用程序错综复杂,当出现bug时,很难跟踪到底哪里发生的变化

Redux的核心理念-action

  • Redux要求咱们经过action来更新state

    *   **全部数据的变化, 必须经过**`dispatch`来派发`action`来更新
        
    *   `action`是一个普通的`JavaScript`对象,用来描述此次更新的`type`和`content`
  • 好比下面就是几个更新friendsaction:

    *   强制使用`action`的好处是能够清晰的知道数据到底发生了什么样的变化,全部的数据变化都是可跟追踪、可预测的
        
    *   固然,目前咱们的`action`是固定的对象,真实应用中,咱们会经过函数来定义,返回一个`action`

Redux的核心理念-reducer

  • 可是如何将stateaction联系在一块儿呢? 答案就是reducer

    *   `reducer`是一个纯函数
        
    *   `reducer`作的事情就是将传入的`state`和`action`结合起来来生成一个新的`state`

Redux的三大原则

  • 单一数据源

    *   整个应用程序的`state`被存储在一颗`object tree`中, 而且这个`object tree`只存储在一个`store`
        
    *   `Redux`并无强制让咱们不能建立多个`Store`,可是那样作并不利于数据的维护
        
    *   单一的数据源可让整个应用程序的`state`变得方便维护、追踪、修改
  • State是只读的

    *   惟一修改`state`的方法必定是触发`action`, 不要试图在其它的地方经过任何的方式来修改`state`
        
    *   这样就确保了`View`或网络请求都不能直接修改`state`,它们只能经过`action`来描述本身想要如何修改`state`
        
    *   这样能够保证全部的修改都被集中化处理,而且按照严格的顺序来执行,因此不须要担忧`race condition`(竟态)的问题
  • 使用纯函数来执行修改

    *   经过`reducer`将旧 `state` 和 `action` 联系在一块儿, 而且返回一个新的`state`
        
    *   随着应用程序的复杂度增长,咱们能够将`reducer`拆分红多个小的`reducers`,分别操做不一样`state tree`的一部分
        
    *   可是全部的`reducer`都应该是纯函数,不能产生任何的反作用

Redux的基本使用

Redux中核心的API

redux的安装: yarn add redux

  1. createStore 能够用来建立 store对象

  2. store.dispatch 用来派发 action, action会传递给 store

  3. reducer接收action,reducer计算出新的状态并返回它 (store负责调用reducer)

  4. store.getState 这个方法能够帮助获取 store 里边全部的数据内容

  5. store.subscribe方法可让让咱们订阅 store 的改变,只要 store 发生改变, store.subscribe 这个函数接收的这个回调函数就会被执行

小结

  1. 建立sotore, 决定 store 要保存什么状态

  2. 建立action, 用户在程序中实现什么操做

  3. 建立reducer, reducer 接收 action 并返回更新的状态

Redux的使用过程

  1. 建立一个对象, 做为咱们要保存的状态

  2. 建立Store来存储这个state

    *   建立`store`时必须建立`reducer`
        
    *   咱们能够经过 `store.getState` 来获取当前的`state`
  3. 经过action来修改state

    *   经过`dispatch`来派发`action`
        
    *   一般`action`中都会有`type`属性,也能够携带其余的数据
  4. 修改reducer中的处理代码

    *   这里必定要记住,`reducer`是一个**纯函数**,不能直接修改`state`
        
    *   后面会讲到直接修改`state`带来的问题
  5. 能够在派发action以前,监听store的变化

import { createStore } from 'redux'

// 1.初始化state
const initState = { counter: 0 }

// 2.reducer纯函数 不能修改传递的state
function reducer(state = initState, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, counter: state.counter + 1 }
case 'ADD_COUNTER':
return { ...state, counter: state.counter + action.num }
default:
return state
}
}

// 3.store 参数放一个reducer
const store = createStore(reducer)

// 4.action
const action1 = { type: 'INCREMENT' }
const action2 = { type: 'ADD_COUNTER', num: 2 }

// 5.订阅store的修改
store.subscribe(() => {
console.log('state发生了改变: ', store.getState().counter)
})

// 6.派发action
store.dispatch(action1)
store.dispatch(action2)

redux原理图

Redux结构划分

  • 若是咱们将全部的逻辑代码写到一块儿, 那么当redux变得复杂时代码就难以维护

  • 对代码进行拆分, 将store、reducer、action、constants拆分红一个个文件

拆分目录

Redux使用流程

redux

Redux官方流程图

redux-flow

React-Redux的使用

redux融入react代码(案例)

  • redux融入react代码案例:

    *   `Home`组件:其中会展现当前的`counter`值,而且有一个+1和+5的按钮
        
    *   `Profile`组件:其中会展现当前的`counter`值,而且有一个-1和-5的按钮
![](https://gitee.com/xmkm/cloudPic/raw/master/img/20201005132516.png)
  • 核心代码主要是两个:

    *   在 `componentDidMount`中订阅数据的变化,当数据发生变化时从新设置 `counter`
        
    *   在发生点击事件时,调用`store`的`dispatch`来派发对应的`action`

自定义connect函数

当咱们多个组件使用redux时, 重复的代码太多了, 好比: 订阅state取消订阅state 或 派发action获取state

将重复的代码进行封装, 将不一样的statedispatch做为参数进行传递

// connect.js
import React, { PureComponent } from 'react'
import { StoreContext } from './context'
/**

  • 1.调用该函数: 返回一个高阶组件
  •     传递须要依赖 state 和 dispatch 来使用state或经过dispatch来改变state
    *
  • 2.调用高阶组件:
  •     传递该组件须要依赖 store 的组件
    *
  • 3.主要做用:
  •     将重复的代码抽取到高阶组件中,并将该组件依赖的 state 和 dispatch
  •     经过调用mapStateToProps()或mapDispatchToProps()函数
  •     并将该组件依赖的state和dispatch供该组件使用,其余使用store的组件没必要依赖store
    *
  • 4.connect.js: 优化依赖
  •     目的:可是上面的connect函数有一个很大的缺陷:依赖导入的 store
  •     优化:正确的作法是咱们提供一个Provider,Provider来自于咱们
  • Context,让用户将store传入到value中便可;
    */

export function connect(mapStateToProps, mapDispatchToProps) {
return function enhanceComponent(WrapperComponent) {
class EnhanceComponent extends PureComponent {
constructor(props, context) {
super(props, context)

// 组件依赖的state
this.state = {
storeState: mapStateToProps(context.getState()),
}
}

// 订阅数据发生变化,调用setState从新render
componentDidMount() {
this.unsubscribe = this.context.subscribe(() => {
this.setState({
centerStore: mapStateToProps(this.context.getState()),
})
})
}

// 组件被卸载取消订阅
componentWillUnmount() {
this.unsubscribe()
}

render() {
// 下面的WrapperComponent至关于 home 组件(就是你传递的组件)
// 你须要将该组件须要依赖的state和dispatch做为props进行传递
return (
<WrapperComponent
{...this.props}
{...mapStateToProps(this.context.getState())}
{...mapDispatchToProps(this.context.dispatch)}
/>
)
}
}
// 取出Provider提供的value
EnhanceComponent.contextType = StoreContext
return EnhanceComponent
}
}

// home.js
// 定义组件依赖的state和dispatch
const mapStateToProps = state => ({
counter: state.counter,
})

const mapDispatchToProps = dispatch => ({
increment() {
dispatch(increment())
},
addNumber(num) {
dispatch(addAction(num))
},
})
export default connect(mapStateToProps,mapDispatchToProps)(依赖redux的组件)

react-redux使用

  • 开始以前须要强调一下,reduxreact没有直接的关系,你彻底能够在React, Angular, Ember, jQuery, or vanilla JavaScript中使用Redux

  • 尽管这样说,redux依然是和React或者Deku的库结合的更好,由于他们是经过state函数来描述界面的状态,Redux能够发射状态的更新,让他们做出相应。

  • 虽然咱们以前已经实现了connectProvider这些帮助咱们完成链接redux、react的辅助工具,可是实际上redux官方帮助咱们提供了react-redux 的库,能够直接在项目中使用,而且实现的逻辑会更加的严谨和高效

  • 安装react-redux

    *   `yarn add react-redux`

// 1.index.js
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

// 2.home.js
import { connect } from 'react-redux'
// 定义须要依赖的state和dispatch (函数须要返回一个对象)
export default connect(mapStateToProps, mapDispatchToProps)(About)

react-redux源码导读

Redux-Middleware中间件

组件中异步操做

  • 在以前简单的案例中,redux中保存的counter是一个本地定义的数据

    *   咱们能够直接经过同步的操做来`dispatch action`,`state`就会被当即更新。
        
    *   可是真实开发中,`redux`中保存的**不少数据可能来自服务器**,咱们须要进行**异步的请求**,再将数据保存到`redux`中
  • 网络请求能够在class组件的componentDidMount中发送,因此咱们能够有这样的结构:

redux中异步操做

  • 上面的代码有一个缺陷:

    *   咱们必须将**网络请求**的异步代码放到组件的生命周期中来完成
  • 为何将网络请求的异步代码放在redux中进行管理?

    *   后期代码量的增长,若是把网络请求异步函数放在组件的生命周期里,这个生命周期函数会变得愈来愈复杂,组件就会变得愈来愈大
        
    *   事实上,**网络请求到的数据也属于状态管理的一部分**,更好的一种方式应该是将其也交给`redux`来管理

  • 可是在redux中如何能够进行异步的操做呢?

    *   **使用中间件 (Middleware)**
        
    *   学习过`Express`或`Koa`框架的童鞋对中间件的概念必定不陌生
        
    *   在这类框架中,`Middleware`能够帮助咱们在**请求和响应之间嵌入一些操做的代码**,好比cookie解析、日志记录、文件压缩等操做

理解中间件(重点)

  • redux也引入了中间件 (Middleware) 的概念:

    *   这个中间件的目的是在`dispatch`的`action`和最终达到的`reducer`之间,扩展一些本身的代码
        
    *   好比日志记录、**调用异步接口**、添加代码调试功能等等

redux-middlware

  • redux-thunk是如何作到让咱们能够发送异步的请求呢?

    *   默认状况下的`dispatch(action)`,`action`须要是一个`JavaScript`的对象
        
    *   `redux-thunk`可让`dispatch`(`action`函数), `action`**能够是一个函数**
        
    *   该函数会被调用, 而且会传给这个函数两个参数: 一个`dispatch`函数和`getState`函数
        
        *   `dispatch`函数用于咱们以后再次派发`action`
            
        *   `getState`函数考虑到咱们以后的一些操做须要依赖原来的状态,用于让咱们能够获取以前的一些状态

redux-thunk的使用

  1. 安装redux-thunk

    *   `yarn add redux-thunk`
  2. 在建立store时传入应用了middlewareenhance函数

    *   经过`applyMiddleware`来结合多个`Middleware`, 返回一个`enhancer`
        
    *   将`enhancer`做为第二个参数传入到`createStore`中
        
        ![image-20200821182447344](https://gitee.com/xmkm/cloudPic/raw/master/img/20201005132723.png)
  3. 定义返回一个函数的action

    *   注意:这里不是返回一个对象了,而是一个**函数**
        
    *   该函数在`dispatch`以后会被执行
![](https://gitee.com/xmkm/cloudPic/raw/master/img/20201005132817.png)

查看代码

redux-devtools

redux-devtools插件

  • 咱们以前讲过,redux能够方便的让咱们对状态进行跟踪和调试,那么如何作到呢?

    *   `redux`官网为咱们提供了`redux-devtools`的工具
        
    *   利用这个工具,咱们能够知道每次状态是如何被修改的,修改先后的状态变化等等
  • 使用步骤:

    *   第一步:在浏览器上安装[redux-devtools](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd/related?utm_source=chrome-ntp-icon)扩展插件
        
    *   第二步:在`redux`中集成`devtools`的中间件

// store.js 开启redux-devtools扩展
import { createStore, applyMiddleware, compose } from 'redux'

// composeEnhancers函数
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) || compose

// 经过applyMiddleware来结合多个Middleware,返回一个enhancer
const enhancer = applyMiddleware(thankMiddleware)

// 经过enhancer做为第二个参数传递createStore中
const store = createStore(reducer, composeEnhancers(enhancer))

export default store

redux-sage

generator

Generator函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数彻底不一样

Generator 函数有多种理解角度。语法上,首先能够把它理解成,Generator函数是一个状态机,封装了多个内部状态。

// 生成器函数的定义
// 默认返回: Generator
function* foo() {
console.log('111')
yield 'hello'
console.log('222')
yield 'world'
console.log('333')
yield 'jane'
console.log('444')
}
// iterator: 迭代器
const result = foo()
console.log(result)

// 使用迭代器
// 调用next,就会消耗一次迭代器
const res1 = result.next()
console.log(res1) // {value: "hello", done: false}
const res2 = result.next()
console.log(res2) // {value: "world", done: false}
const res3 = result.next()
console.log(res3) // {value: "jane", done: false}
const res4 = result.next()
console.log(res4) // {value: undefined, done: true}

redux-sage流程

redux-saga的使用

  • redux-saga是另外一个比较经常使用在redux发送异步请求的中间件,它的使用更加的灵活

  • Redux-saga的使用步骤以下

    1.  安装`redux-sage`: `yarn add redux-saga`
        
    2.  集成`redux-saga`中间件
        
        *   引入 `createSagaMiddleware` 后, 须要建立一个 `sagaMiddleware`
            
        *   而后经过 `applyMiddleware` 使用这个中间件,接着建立 `saga.js` 这个文件
            
        *   启动中间件的监听过程, 而且传入要监听的`saga`
            
    3.  `saga.js`文件的编写
        
        *   `takeEvery`:能够传入多个监听的`actionType`,每个均可以被执行(对应有一个`takeLatest`,会取消前面的)
            
        *   `put`:在`saga`中派发`action`再也不是经过`dispatch`, 而是经过`put`
            
        *   `all`:能够在`yield`的时候`put`多个`action`

// store.js
import createSageMiddleware from 'redux-saga'
import saga from './saga'
// 1.建立sageMiddleware中间件
const sagaMiddleware = createSageMiddleware()
// 2.应用一些中间件
const enhancer = applyMiddleware(sagaMiddleware)
const store = createStore(reducer,composeEnhancers(enhancer))

sagaMiddleware.run(saga)
export default store

// saga.js
import { takeEvery, put, all } from 'redux-saga/effects'
import { FETCH_HOME_DATA } from './constant'

function* fetchHomeData(action) {
const res = yield axios.get('http://123.207.32.32:8000/hom...
const banners = res.data.data.banner.list
const recommends = res.data.data.recommend.list
// dispatch action 提交action,redux-sage提供了put
yield all([
yield put(changeBannersAction(banners)),
yield put(changeRecommendAction(recommends)),
])
}

function* mySaga() {
// 参数一:要拦截的actionType
// 参数二:生成器函数
yield all([
takeEvery(FETCH_HOME_DATA, fetchHomeData),
])
}

export default mySaga

reducer代码拆分

Reducer代码拆分

  • 咱们来看一下目前咱们的reducer

    *   当前这个`reducer`既有处理`counter`的代码,又有处理`home`页面的数据
        
    *   后续`counter`相关的状态或`home`相关的状态会进一步变得更加复杂
        
    *   咱们也会继续添加其余的相关状态,好比购物车、分类、歌单等等
        
    *   若是将全部的状态都放到一个`reducer`中进行管理,随着项目的日趋庞大,必然会形成代码臃肿、难以维护
  • 所以,咱们能够对reducer进行拆分:

    *   咱们先抽取一个对`counter`处理的`reducer`
        
    *   再抽取一个对`home`处理的`reducer`
        
    *   将它们合并起来

Reducer文件拆分

  • 目前咱们已经将不一样的状态处理拆分到不一样的reducer中,咱们来思考:

    *   虽然已经放到不一样的函数了,可是这些函数的处理依然是在同一个文件中,代码很是的混乱
        
    *   另外关于`reducer`中用到的`constant`、`action`等咱们也依然是在同一个文件中;

combineReducers函数

  • 目前咱们合并的方式是经过每次调用reducer函数本身来返回一个新的对象

  • 事实上,redux给咱们提供了一个combineReducers函数能够方便的让咱们对多个reducer进行合并

import { combineReducers } from 'redux'
import { reducer as counterReducer } from './count'
import { reducer as homeReducer } from './home'

export const reducer = combineReducers({
counterInfo: counterReducer,
homeInfo: homeReducer,
})

  • 那么combineReducers是如何实现的呢?

    *   它将咱们传递的`reducer`合并成一个对象, 最终返回一个`combination`函数
        
    *   在执行`combination`函数过程当中, 会经过判断先后返回的数据是否相同来决定返回以前的`state`仍是新的`state`

immutableJs

数据可变形的问题

  • React开发中,咱们老是会强调数据的不可变性:

    *   不管是类组件中的`state`,仍是`reduex`中管理的`state`
        
    *   事实上在整个`JavaScript`编码的过程当中,数据的不可变性都是很是重要的
  • 数据的可变性引起的问题(案例):

    *   咱们明明没有修改obj,只是修改了obj2,可是最终obj也被咱们修改掉了
        
    *   缘由很是简单,对象是引用类型,它们指向同一块内存空间,两个引用均可以任意修改

const obj1 = { name: 'jane', age: 18 }
const obj2 = obj1
obj1.name = 'kobe'
console.log(obj2.name) // kobe

  • 有没有办法解决上面的问题呢?

    *   进行对象的拷贝便可:`Object.assign`或扩展运算符
  • 这种对象的浅拷贝有没有问题呢?

    *   从代码的角度来讲,没有问题,也解决了咱们实际开发中一些潜在风险
        
    *   从性能的角度来讲,有问题,若是对象过于庞大,这种拷贝的方式会带来性能问题以及内存浪费
  • 有人会说,开发中不都是这样作的吗?

    *   历来如此,即是对的吗?

认识ImmutableJS

  • 为了解决上面的问题,出现了Immutable对象的概念:

    *   `Immutable`对象的特色是只要修改了对象,就会返回一个新的对象,旧的对象不会发生改变;
  • 可是这样的方式就不会浪费内存了吗?

    *   为了节约内存,又出现了一个新的算法:`Persistent Data Structure`(持久化数据结构或一致性数据结构)
  • 固然,咱们一听到持久化第一反应应该是数据被保存到本地或者数据库,可是这里并非这个含义:

    *   用一种数据结构来保存数据
        
    *   当数据被修改时,会返回一个对象,可是**新的对象会尽量的利用以前的数据结构而不会对内存形成浪费**,如何作到这一点呢?结构共享:

  • 安装Immutable: yarn add immutable

ImmutableJS常见API

注意:我这里只是演示了一些API,更多的方式能够参考官网

做用:不会修改原有数据结构,返回一个修改后新的拷贝对象

  • JavaScripImutableJS直接的转换

    *   对象转换成`Immutable`对象:`Map`
        
    *   数组转换成`Immtable`数组:`List`
        
    *   深层转换:`fromJS`

const im = Immutable
// 对象转换成Immutable对象
const info = {name: 'kobe', age: 18}
const infoIM = im.Map()

// 数组转换成Immtable数组
const names = ["abc", "cba", "nba"]
const namesIM = im.List(names)

  • ImmutableJS的基本操做:

    *   修改数据:`set(property, newVal)`
        
        *   返回值: 修改后新的数据结构
            
    *   获取数据:`get(property/index)`
        
    *   获取深层`Immutable`对象数据(子属性也是`Immutable`对象): `getIn(['recommend', 'topBanners'])`

// set方法 不会修改infoIM原有数据结构,返回修改后新的数据结构
const newInfo2IM = infoIM.set('name', 'james')
const newNamesIM = namesIM.set(0, 'why')

// get方法
console.log(infoIM.get('name'))// -> kobe
console.log(namesIM.get(0))// -> abc

结合Redux管理数据

  1. ImmutableJS重构redux

    *   yarn add Immutable
        
    *   yarn add redux-immutable
  2. 使用redux-immutable中的combineReducers;

  3. 全部的reducer中的数据都转换成Immutable类型的数据

FAQ

React中的state如何管理

  • 目前项目中采用的state管理方案(参考便可):

    *   相关的组件内部能够维护的状态,在组件内部本身来维护
        
    *   只要是须要共享的状态,都交给redux来管理和维护
        
    *   从服务器请求的数据(包括请求的操做) ,交给redux来维护
相关文章
相关标签/搜索