hello你们好,我是风不识途,最近一直在整理redux
系列文章,发现对于初学者不太友好,关系错综复杂,难却是不太难,就是比较复杂(其实写比较少),因此这篇带你全面了解
redux、react-redux、redux-thunk
还有redux-sage,immutable
(多图预警),因为知识点比较多,建议先收藏(收藏等于学会了),对你有帮助的话就给个赞👍
JavaScript
符合函数式编程的范式, 因此也有纯函数的概念React
中,纯函数的概念很是重要,在接下来咱们学习的Redux
中也很是重要,因此咱们必须来回顾一下纯函数纯函数的定义简单总结一下:react
纯函数( Pure Function
)的注意事项:ios
为何纯函数在函数式编程中很是重要呢?git
React很是灵活,但它也有一个严格的规则:web
JavaScript
开发的应用程序, 已经变得很是复杂了:算法
JavaScript
须要管理的状态愈来愈多, 愈来愈复杂了UI
的状态管理不断变化的state
是很是困难的:chrome
View
页面也有可能会引发状态的变化state
在何时, 由于什么缘由发生了变化, 发生了怎样的变化, 会变得很是难以控制和追踪React
只是在视图层帮助咱们解决了DOM
的渲染过程, 可是state
依然是留给咱们本身来管理:数据库
state
,仍是组件之间的通讯经过props
进行传递Context
进行数据之间的共享React
主要负责帮助咱们管理视图,state
如何维护最终仍是咱们本身来决定Redux
就是一个帮助咱们管理State
的容器:编程
Redux
是JavaScript
的状态容器, 提供了可预测的状态管理Redux
除了和React
一块儿使用以外, 它也能够和其余界面库一块儿来使用(好比Vue
), 而且它很是小 (包括依赖在内,只有2kb)Redux
的核心理念很是简单好比咱们有一个朋友列表须要管理:redux
products.push
的方式增长了一条数据products[0].age = 25
修改了一条数据bug
时,很难跟踪到底哪里发生的变化Redux
要求咱们经过action
来更新state
:axios
dispatch
来派发action
来更新action
是一个普通的JavaScript
对象,用来描述此次更新的type
和content
好比下面就是几个更新friends
的action
:
action
的好处是能够清晰的知道数据到底发生了什么样的变化,全部的数据变化都是可跟追踪、可预测的action
是固定的对象,真实应用中,咱们会经过函数来定义,返回一个action
可是如何将state
和action
联系在一块儿呢? 答案就是reducer
reducer
是一个纯函数reducer
作的事情就是将传入的state
和action
结合起来来生成一个新的state
单一数据源
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
的安装: 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 并返回更新的状态建立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
变得复杂时代码就难以维护store、reducer、action、constants
拆分红一个个文件<details>
<summary>拆分目录</summary>
</details>
redux
融入react
代码案例:
Home
组件:其中会展现当前的counter
值,而且有一个+1和+5的按钮Profile
组件:其中会展现当前的counter
值,而且有一个-1和-5的按钮核心代码主要是两个:
componentDidMount
中订阅数据的变化,当数据发生变化时从新设置 counter
store
的dispatch
来派发对应的action
当咱们多个组件使用
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的组件)
redux
和react
没有直接的关系,你彻底能够在React, Angular, Ember, jQuery, or vanilla JavaScript中使用Reduxconnect
、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)
在以前简单的案例中,redux
中保存的counter
是一个本地定义的数据
dispatch action
,state
就会被当即更新。redux
中保存的不少数据可能来自服务器,咱们须要进行异步的请求,再将数据保存到redux
中class
组件的componentDidMount
中发送,因此咱们能够有这样的结构:上面的代码有一个缺陷:
为何将网络请求的异步代码放在redux
中进行管理?
redux
来管理可是在redux
中如何能够进行异步的操做呢?
Express
或Koa
框架的童鞋对中间件的概念必定不陌生Middleware
能够帮助咱们在请求和响应之间嵌入一些操做的代码,好比cookie解析、日志记录、文件压缩等操做redux
也引入了中间件 (Middleware) 的概念:
dispatch
的action
和最终达到的reducer
之间,扩展一些本身的代码</font>redux-thunk
是如何作到让咱们能够发送异步的请求呢?
dispatch(action)
,action
须要是一个JavaScript
的对象redux-thunk
可让dispatch
(action
函数), action
<font color='red'>能够是一个函数</font>该函数会被调用, 而且会传给这个函数两个参数: 一个dispatch
函数和getState
函数
dispatch
函数用于咱们以后再次派发action
getState
函数考虑到咱们以后的一些操做须要依赖原来的状态,用于让咱们能够获取以前的一些状态安装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
能够方便的让咱们对状态进行跟踪和调试,那么如何作到呢?
redux
官网为咱们提供了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
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-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.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
既有处理counter
的代码,又有处理home
页面的数据counter
相关的状态或home
相关的状态会进一步变得更加复杂reducer
中进行管理,随着项目的日趋庞大,必然会形成代码臃肿、难以维护所以,咱们能够对reducer
进行拆分:
counter
处理的reducer
home
处理的reducer
目前咱们已经将不一样的状态处理拆分到不一样的reducer
中,咱们来思考:
reducer
中用到的constant
、action
等咱们也依然是在同一个文件中;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
在React
开发中,咱们老是会强调数据的不可变性:
state
,仍是reduex
中管理的state
JavaScript
编码的过程当中,数据的不可变性都是很是重要的数据的可变性引起的问题(案例):
const obj1 = { name: 'jane', age: 18 } const obj2 = obj1 obj1.name = 'kobe' console.log(obj2.name) // kobe
有没有办法解决上面的问题呢?
Object.assign
或扩展运算符这种对象的浅拷贝有没有问题呢?
有人会说,开发中不都是这样作的吗?
为了解决上面的问题,出现了Immutable
对象的概念:
Immutable
对象的特色是只要修改了对象,就会返回一个新的对象,旧的对象不会发生改变;可是这样的方式就不会浪费内存了吗?
Persistent Data Structure
(持久化数据结构或一致性数据结构)固然,咱们一听到持久化第一反应应该是数据被保存到本地或者数据库,可是这里并非这个含义:
Immutable
: yarn add immutable
注意:我这里只是演示了一些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
ImmutableJS
重构redux
目前项目中采用的state管理方案(参考便可):
hello你们好,我是风不识途,最近一直在整理redux
系列文章,发现对于初学者不太友好,关系错综复杂,难却是不太难,就是比较复杂(其实写比较少),因此这篇带你全面了解
redux、react-redux、redux-thunk
还有redux-sage,immutable
(多图预警),因为知识点比较多,建议先收藏(收藏等于学会了),对你有用的话就给个赞👍
函数式编程中有一个概念叫纯函数, JavaScript
符合函数式编程的范式, 因此也有纯函数的概念
在React
中,纯函数的概念很是重要,在接下来咱们学习的Redux
中也很是重要,因此咱们必须来回顾一下纯函数
纯函数的维基百科定义(了解便可)
纯函数的定义简单总结一下:
* 纯函数指的是, 每次给相同的参数, 必定返回相同的结果 * 函数在执行过程当中, 不能产生反作用
**纯函数( `Pure Function` )的注意事项:**
为何纯函数在函数式编程中很是重要呢?
* 由于你能够安心的写和安心的用 * 你在写的时候保证了函数的纯度,实现本身的业务逻辑便可,不须要关心传入的内容或者函数体依赖了外部的变量 * 你在用的时候,你肯定你的输入内容不会被任意篡改,而且本身肯定的输入,必定会有肯定的输出
React很是灵活,但它也有一个严格的规则:
* 全部React组件都必须像"纯函数"同样保护它们的"props"不被更改
JavaScript
开发的应用程序, 已经变得很是复杂了:
* `JavaScript`**须要管理的状态愈来愈多**, 愈来愈复杂了 * 这些状态包括服务器返回的数据, 用户操做的数据等等, 也包括一些`UI`的状态
管理不断变化的state
是很是困难的:
* **状态之间相互存在依赖**, 一个状态的变化会引发另外一个状态的变化, `View`页面也有可能会引发状态的变化 * 当程序复杂时, `state`在何时, 由于什么缘由发生了变化, 发生了怎样的变化, 会变得很是难以控制和追踪
React
只是在视图层帮助咱们解决了DOM
的渲染过程, 可是state
依然是留给咱们本身来管理:
* 不管是组件定义本身的`state`,仍是组件之间的通讯经过`props`进行传递 * 也包括经过`Context`进行数据之间的共享 * `React`主要负责帮助咱们管理视图,`state`如何维护最终仍是咱们本身来决定

Redux
就是一个帮助咱们管理State
的容器:
* `Redux`是`JavaScript`的状态容器, 提供了可预测的状态管理
Redux
除了和React
一块儿使用以外, 它也能够和其余界面库一块儿来使用(好比Vue
), 而且它很是小 (包括依赖在内,只有2kb)
Redux
的核心理念很是简单
好比咱们有一个朋友列表须要管理:
* **若是咱们没有定义统一的规范来操做这段数据,那么整个数据的变化就是没法跟踪的** * 好比页面的某处经过`products.push`的方式增长了一条数据 * 好比另外一个页面经过`products[0].age = 25`修改了一条数据
整个应用程序错综复杂,当出现bug
时,很难跟踪到底哪里发生的变化
Redux
要求咱们经过action
来更新state
:
* **全部数据的变化, 必须经过**`dispatch`来派发`action`来更新 * `action`是一个普通的`JavaScript`对象,用来描述此次更新的`type`和`content`
好比下面就是几个更新friends
的action
:
* 强制使用`action`的好处是能够清晰的知道数据到底发生了什么样的变化,全部的数据变化都是可跟追踪、可预测的 * 固然,目前咱们的`action`是固定的对象,真实应用中,咱们会经过函数来定义,返回一个`action`
可是如何将state
和action
联系在一块儿呢? 答案就是reducer
* `reducer`是一个纯函数 * `reducer`作的事情就是将传入的`state`和`action`结合起来来生成一个新的`state`
单一数据源
* 整个应用程序的`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
的安装: 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 并返回更新的状态
建立一个对象, 做为咱们要保存的状态
建立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
变得复杂时代码就难以维护
对代码进行拆分, 将store、reducer、action、constants
拆分红一个个文件
拆分目录
redux
融入react
代码案例:
* `Home`组件:其中会展现当前的`counter`值,而且有一个+1和+5的按钮 * `Profile`组件:其中会展现当前的`counter`值,而且有一个-1和-5的按钮

核心代码主要是两个:
* 在 `componentDidMount`中订阅数据的变化,当数据发生变化时从新设置 `counter` * 在发生点击事件时,调用`store`的`dispatch`来派发对应的`action`
当咱们多个组件使用
redux
时, 重复的代码太多了, 好比: 订阅state
取消订阅state
或 派发action
获取state
将重复的代码进行封装, 将不一样的
state
和dispatch
做为参数进行传递
// connect.js
import React, { PureComponent } from 'react'
import { StoreContext } from './context'
/**
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的组件)
开始以前须要强调一下,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)
在以前简单的案例中,redux
中保存的counter
是一个本地定义的数据
* 咱们能够直接经过同步的操做来`dispatch action`,`state`就会被当即更新。 * 可是真实开发中,`redux`中保存的**不少数据可能来自服务器**,咱们须要进行**异步的请求**,再将数据保存到`redux`中
网络请求能够在class
组件的componentDidMount
中发送,因此咱们能够有这样的结构:
上面的代码有一个缺陷:
* 咱们必须将**网络请求**的异步代码放到组件的生命周期中来完成
为何将网络请求的异步代码放在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
* `yarn add redux-thunk`
在建立store
时传入应用了middleware
的enhance
函数
* 经过`applyMiddleware`来结合多个`Middleware`, 返回一个`enhancer` * 将`enhancer`做为第二个参数传入到`createStore`中 
定义返回一个函数的action
* 注意:这里不是返回一个对象了,而是一个**函数** * 该函数在`dispatch`以后会被执行

查看代码
咱们以前讲过,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
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-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`既有处理`counter`的代码,又有处理`home`页面的数据 * 后续`counter`相关的状态或`home`相关的状态会进一步变得更加复杂 * 咱们也会继续添加其余的相关状态,好比购物车、分类、歌单等等 * 若是将全部的状态都放到一个`reducer`中进行管理,随着项目的日趋庞大,必然会形成代码臃肿、难以维护
所以,咱们能够对reducer
进行拆分:
* 咱们先抽取一个对`counter`处理的`reducer` * 再抽取一个对`home`处理的`reducer` * 将它们合并起来
目前咱们已经将不一样的状态处理拆分到不一样的reducer
中,咱们来思考:
* 虽然已经放到不一样的函数了,可是这些函数的处理依然是在同一个文件中,代码很是的混乱 * 另外关于`reducer`中用到的`constant`、`action`等咱们也依然是在同一个文件中;
目前咱们合并的方式是经过每次调用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`
在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`或扩展运算符
这种对象的浅拷贝有没有问题呢?
* 从代码的角度来讲,没有问题,也解决了咱们实际开发中一些潜在风险 * 从性能的角度来讲,有问题,若是对象过于庞大,这种拷贝的方式会带来性能问题以及内存浪费
有人会说,开发中不都是这样作的吗?
* 历来如此,即是对的吗?
为了解决上面的问题,出现了Immutable
对象的概念:
* `Immutable`对象的特色是只要修改了对象,就会返回一个新的对象,旧的对象不会发生改变;
可是这样的方式就不会浪费内存了吗?
* 为了节约内存,又出现了一个新的算法:`Persistent Data Structure`(持久化数据结构或一致性数据结构)
固然,咱们一听到持久化第一反应应该是数据被保存到本地或者数据库,可是这里并非这个含义:
* 用一种数据结构来保存数据 * 当数据被修改时,会返回一个对象,可是**新的对象会尽量的利用以前的数据结构而不会对内存形成浪费**,如何作到这一点呢?结构共享:
安装Immutable
: yarn add immutable
注意:我这里只是演示了一些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
ImmutableJS
重构redux
* yarn add Immutable * yarn add redux-immutable
使用redux-immutable中的combineReducers;
全部的reducer中的数据都转换成Immutable类型的数据
目前项目中采用的state管理方案(参考便可):
* 相关的组件内部能够维护的状态,在组件内部本身来维护 * 只要是须要共享的状态,都交给redux来管理和维护 * 从服务器请求的数据(包括请求的操做) ,交给redux来维护