redux、react-redux、redux-saga总结

前言

hello大家好,我是风不识途,最近始终在整顿redux系列文章,发现对于初学者不太敌对,关系盘根错节,难倒是不太难,就是比较复杂 (其实写比拟少),所以这篇带你全面理解redux、react-redux、redux-thunk还有redux-sage,immutable(多图预警),因为知识点比拟多,倡议先珍藏(珍藏等于学会了),对你有帮忙的话就给个赞

意识纯函数

JavaScript纯函数

  • 函数式编程中有一个概念叫纯函数, JavaScript合乎函数式编程的范式, 所以也有纯函数的概念
  • React中,纯函数的概念十分重要,在接下来咱们学习的Redux中也十分重要,所以咱们必须来回顾一下纯函数
  • 纯函数的定义简略总结一下:

    • 纯函数指的是, 每次给雷同的参数, 肯定返回雷同的后果
    • 函数在执行过程中, 不能产生副作用
  • 纯函数( Pure Function )的注意事项:

    • 在纯函数中不能应用随机数
    • 不能应用以后的工夫或日期, 因为后果是会变的
    • 不能应用或者批改全局状态, 比方DOM,文件、数据库等等(因为如果全局状态扭转了,它就会影响函数的后果)
    • 纯函数中的参数不能变动,否则函数的后果就会扭转

React中的纯函数

  • 为什么纯函数在函数式编程中十分重要呢?

    • 因为你能够安心的写和安心的用
    • 你在写的时候保障了函数的纯度,实现本人的业务逻辑即可,不须要关怀传入的内容或者函数体依赖了内部的变量
    • 你在用的时候,你确定你的输出内容不会被任意篡改,并且本人确定的输出,肯定会有确定的输入
  • React非常灵活,但它也有一个严格的规定:

    • 所有React组件都必须像"纯函数"一样爱护它们的"props"不被更改

意识Redux

为什么须要redux

  • JavaScript开发的应用程序, 曾经变得非常复杂了:

    • JavaScript须要治理的状态越来越多, 越来越简单了
    • 这些状态包含服务器返回的数据, 用户操作的数据等等, 也包含一些UI的状态
  • 治理一直变动的state是十分艰难的:

    • 状态之间互相存在依赖, 一个状态的变动会引起另一个状态的变动, View页面也有可能会引起状态的变动
    • 当程序简单时, state在什么时候, 因为什么起因产生了变动, 产生了怎么的变动, 会变得十分难以管制和追踪

React的作用

  • React只是在视图层帮忙咱们解决了DOM的渲染过程, 然而state仍然是留给咱们本人来治理:

    • 无论是组件定义本人的state,还是组件之间的通信通过props进行传递
    • 也包含通过Context进行数据之间的共享
    • React次要负责帮忙咱们治理视图,state如何保护最终还是咱们本人来决定

  • Redux就是一个帮忙咱们治理State的容器:

    • ReduxJavaScript的状态容器, 提供了可预测的状态治理
  • Redux除了和React一起应用之外, 它也能够和其余界面库一起来应用(比方Vue), 并且它十分小 (包含依赖在内,只有2kb)

Redux的核心理念-Store

  • Redux的核心理念非常简单
  • 比方咱们有一个敌人列表须要治理:

    • 如果咱们没有定义对立的标准来操作这段数据,那么整个数据的变动就是无奈跟踪的
    • 比方页面的某处通过products.push的形式减少了一条数据
    • 比方另一个页面通过products[0].age = 25批改了一条数据
  • 整个应用程序盘根错节,当呈现bug时,很难跟踪到底哪里产生的变动

Redux的核心理念-action

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

    • 所有数据的变动, 必须通过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.初始化stateconst initState = { counter: 0 }// 2.reducer纯函数 不能批改传递的statefunction 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 参数放一个reducerconst store = createStore(reducer)// 4.actionconst action1 = { type: 'INCREMENT' }const action2 = { type: 'ADD_COUNTER', num: 2 }// 5.订阅store的批改store.subscribe(() => {  console.log('state产生了扭转: ', store.getState().counter)})// 6.派发actionstore.dispatch(action1)store.dispatch(action2)

Redux构造划分

  • 如果咱们将所有的逻辑代码写到一起, 那么当redux变得复杂时代码就难以保护
  • 对代码进行拆分, 将store、reducer、action、constants拆分成一个个文件

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

</details>

Redux应用流程

Redux官网流程图

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和dispatchconst 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.jsimport { Provider } from 'react-redux'ReactDOM.render(  <Provider store={store}>    <App />  </Provider>,  document.getElementById('root'))// 2.home.jsimport { 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-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

  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,返回一个enhancerconst enhancer = applyMiddleware(thankMiddleware)// 通过enhancer作为第二个参数传递createStore中const store = createStore(reducer, composeEnhancers(enhancer))export default store

redux-sage

generator

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

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

// 生成器函数的定义// 默认返回: Generatorfunction* 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.jsimport 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.jsimport { 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 = obj1obj1.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'))// -> kobeconsole.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变得复杂时代码就难以保护

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

拆分目录

Redux应用流程

Redux官网流程图

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-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来保护