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
的容器: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
- 所有数据的变动, 必须通过
比方上面就是几个更新
friends
的action
:- 强制应用
action
的益处是能够清晰的晓得数据到底产生了什么样的变动,所有的数据变动都是可跟追踪、可预测的 - 当然,目前咱们的
action
是固定的对象,实在利用中,咱们会通过函数来定义,返回一个action
- 强制应用
Redux的核心理念-reducer
然而如何将
state
和action
分割在一起呢? 答案就是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
createStore
能够用来创立store
对象store.dispatch
用来派发action
,action
会传递给store
reducer
接管action
,reducer
计算出新的状态并返回它 (store
负责调用reducer
)store.getState
这个办法能够帮忙获取store
里边所有的数据内容store.subscribe
办法能够让让咱们订阅store
的扭转,只有store
产生扭转,store.subscribe
这个函数接管的这个回调函数就会被执行
小结
- 创立
sotore
, 决定 store 要保留什么状态 - 创立
action
, 用户在程序中实现什么操作 - 创立
reducer
, reducer 接管 action 并返回更新的状态
Redux的应用过程
- 创立一个对象, 作为咱们要保留的状态
创立
Store
来存储这个state
- 创立
store
时必须创立reducer
- 咱们能够通过
store.getState
来获取以后的state
- 创立
通过
action
来批改state
- 通过
dispatch
来派发action
- 通常
action
中都会有type
属性,也能够携带其余的数据
- 通过
批改
reducer
中的解决代码- 这里肯定要记住,
reducer
是一个纯函数,不能间接批改state
- 前面会讲到间接批改
state
带来的问题
- 这里肯定要记住,
- 能够在派发
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
- 在产生点击事件时,调用
store
的dispatch
来派发对应的action
- 在
自定义connect函数
当咱们多个组件应用
redux
时, 反复的代码太多了, 比方: 订阅state
勾销订阅state
或 派发action
获取state
将反复的代码进行封装, 将不同的
state
和dispatch
作为参数进行传递
// 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应用
- 开始之前须要强调一下,
redux
和react
没有间接的关系,你齐全能够在React, Angular, Ember, jQuery, or vanilla JavaScript中应用Redux - 只管这样说,redux仍然是和React或者Deku的库联合的更好,因为他们是通过state函数来形容界面的状态,Redux能够发射状态的更新,让他们作出相应。
- 尽管咱们之前曾经实现了
connect
、Provider
这些帮忙咱们实现连贯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 action
,state
就会被立刻更新。 - 然而实在开发中,
redux
中保留的很多数据可能来自服务器,咱们须要进行异步的申请,再将数据保留到redux
中
- 咱们能够间接通过同步的操作来
- 网络申请能够在
class
组件的componentDidMount
中发送,所以咱们能够有这样的构造:
redux中异步操作
下面的代码有一个缺点:
- 咱们必须将网络申请的异步代码放到组件的生命周期中来实现
为什么将网络申请的异步代码放在
redux
中进行治理?- 前期代码量的减少,如果把网络申请异步函数放在组件的生命周期里,这个生命周期函数会变得越来越简单,组件就会变得越来越大
- 事实上,网络申请到的数据也属于状态治理的一部分,更好的一种形式应该是将其也交给
redux
来治理
然而在
redux
中如何能够进行异步的操作呢?- 应用中间件 (Middleware)
- 学习过
Express
或Koa
框架的童鞋对中间件的概念肯定不生疏 - 在这类框架中,
Middleware
能够帮忙咱们在申请和响应之间嵌入一些操作的代码,比方cookie解析、日志记录、文件压缩等操作
了解中间件(重点)
redux
也引入了中间件 (Middleware) 的概念:- 这个<font color='red'>中间件的目标是在
dispatch
的action
和最终达到的reducer
之间,扩大一些本人的代码</font> - 比方日志记录、调用异步接口、增加代码调试性能等等
- 这个<font color='red'>中间件的目标是在
redux-thunk
是如何做到让咱们能够发送异步的申请呢?- 默认状况下的
dispatch(action)
,action
须要是一个JavaScript
的对象 redux-thunk
能够让dispatch
(action
函数),action
<font color='red'>能够是一个函数</font>该函数会被调用, 并且会传给这个函数两个参数: 一个
dispatch
函数和getState
函数dispatch
函数用于咱们之后再次派发action
getState
函数思考到咱们之后的一些操作须要依赖原来的状态,用于让咱们能够获取之前的一些状态
- 默认状况下的
redux-thunk的应用
装置
redux-thunk
yarn add redux-thunk
在创立
store
时传入利用了middleware
的enhance
函数- 通过
applyMiddleware
来联合多个Middleware
, 返回一个enhancer
将
enhancer
作为第二个参数传入到createStore
中
- 通过
定义返回一个函数的
action
- 留神:这里不是返回一个对象了,而是一个函数
- 该函数在
dispatch
之后会被执行
<details>
<summary>查看代码</summary>
<pre>import { createStore, applyMiddleware } from 'redux'
</pre></details>
import reducer from './reducer'
import thunk from 'redux-thunk'<br/>
const store = createStore(
reducer,
applyMiddleware(thunk) // applyMiddleware能够应用中间件模块
)
export default store
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
的应用步骤如下- 装置
redux-sage
:yarn add redux-saga
集成
redux-saga
中间件- 引入
createSagaMiddleware
后, 须要创立一个sagaMiddleware
- 而后通过
applyMiddleware
应用这个中间件,接着创立saga.js
这个文件 - 启动中间件的监听过程, 并且传入要监听的
saga
- 引入
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
中用到的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 = obj1obj1.name = 'kobe'console.log(obj2.name) // kobe
有没有方法解决下面的问题呢?
- 进行对象的拷贝即可:
Object.assign
或扩大运算符
- 进行对象的拷贝即可:
这种对象的浅拷贝有没有问题呢?
- 从代码的角度来说,没有问题,也解决了咱们理论开发中一些潜在危险
- 从性能的角度来说,有问题,如果对象过于宏大,这种拷贝的形式会带来性能问题以及内存节约
有人会说,开发中不都是这样做的吗?
- 从来如此,便是对的吗?
意识ImmutableJS
为了解决下面的问题,呈现了
Immutable
对象的概念:Immutable
对象的特点是只有批改了对象,就会返回一个新的对象,旧的对象不会产生扭转;
然而这样的形式就不会节约内存了吗?
- 为了节约内存,又呈现了一个新的算法:
Persistent Data Structure
(长久化数据结构或一致性数据结构)
- 为了节约内存,又呈现了一个新的算法:
当然,咱们一听到长久化第一反馈应该是数据被保留到本地或者数据库,然而这里并不是这个含意:
- 用一种数据结构来保留数据
- 当数据被批改时,会返回一个对象,然而新的对象会尽可能的利用之前的数据结构而不会对内存造成节约,如何做到这一点呢?构造共享:
- 装置
Immutable
:yarn add immutable
ImmutableJS常见API
留神:我这里只是演示了一些API,更多的形式能够参考官网
作用:不会批改原有数据结构,返回一个批改后新的拷贝对象
JavaScrip
和ImutableJS
间接的转换- 对象转换成
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治理数据
ImmutableJS
重构redux
- yarn add Immutable
- yarn add redux-immutable
- 应用redux-immutable中的combineReducers;
- 所有的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`如何保护最终还是咱们本人来决定

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`
比方上面就是几个更新
friends
的action
:* 强制应用`action`的益处是能够清晰的晓得数据到底产生了什么样的变动,所有的数据变动都是可跟追踪、可预测的 * 当然,目前咱们的`action`是固定的对象,实在利用中,咱们会通过函数来定义,返回一个`action`
Redux的核心理念-reducer
然而如何将
state
和action
分割在一起呢? 答案就是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
createStore
能够用来创立store
对象store.dispatch
用来派发action
,action
会传递给store
reducer
接管action
,reducer
计算出新的状态并返回它 (store
负责调用reducer
)store.getState
这个办法能够帮忙获取store
里边所有的数据内容store.subscribe
办法能够让让咱们订阅store
的扭转,只有store
产生扭转,store.subscribe
这个函数接管的这个回调函数就会被执行
小结
创立
sotore
, 决定 store 要保留什么状态创立
action
, 用户在程序中实现什么操作创立
reducer
, reducer 接管 action 并返回更新的状态
Redux的应用过程
创立一个对象, 作为咱们要保留的状态
创立
Store
来存储这个state
* 创立`store`时必须创立`reducer` * 咱们能够通过 `store.getState` 来获取以后的`state`
通过
action
来批改state
* 通过`dispatch`来派发`action` * 通常`action`中都会有`type`属性,也能够携带其余的数据
批改
reducer
中的解决代码* 这里肯定要记住,`reducer`是一个**纯函数**,不能间接批改`state` * 前面会讲到间接批改`state`带来的问题
能够在派发
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的按钮

外围代码次要是两个:
* 在 `componentDidMount`中订阅数据的变动,当数据发生变化时从新设置 `counter` * 在产生点击事件时,调用`store`的`dispatch`来派发对应的`action`
自定义connect函数
当咱们多个组件应用
redux
时, 反复的代码太多了, 比方: 订阅state
勾销订阅state
或 派发action
获取state
将反复的代码进行封装, 将不同的
state
和dispatch
作为参数进行传递
// 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应用
开始之前须要强调一下,
redux
和react
没有间接的关系,你齐全能够在React, Angular, Ember, jQuery, or vanilla JavaScript中应用Redux只管这样说,redux仍然是和React或者Deku的库联合的更好,因为他们是通过state函数来形容界面的状态,Redux能够发射状态的更新,让他们作出相应。
尽管咱们之前曾经实现了
connect
、Provider
这些帮忙咱们实现连贯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的应用
装置
redux-thunk
* `yarn add redux-thunk`
在创立
store
时传入利用了middleware
的enhance
函数* 通过`applyMiddleware`来联合多个`Middleware`, 返回一个`enhancer` * 将`enhancer`作为第二个参数传入到`createStore`中 
定义返回一个函数的
action
* 留神:这里不是返回一个对象了,而是一个**函数** * 该函数在`dispatch`之后会被执行

查看代码
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,更多的形式能够参考官网
作用:不会批改原有数据结构,返回一个批改后新的拷贝对象
JavaScrip
和ImutableJS
间接的转换* 对象转换成`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治理数据
ImmutableJS
重构redux
* yarn add Immutable * yarn add redux-immutable
应用redux-immutable中的combineReducers;
所有的reducer中的数据都转换成Immutable类型的数据
FAQ
React中的state如何治理
目前我的项目中采纳的state治理计划(参考即可):
* 相干的组件外部能够保护的状态,在组件外部本人来保护 * 只有是须要共享的状态,都交给redux来治理和保护 * 从服务器申请的数据(包含申请的操作) ,交给redux来保护