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. 初始化 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
拆分成一个个文件
<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 和 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) 的概念:- 这个 <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, 返回一个 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
的应用步骤如下- 装置
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 代码拆分
-
咱们来看一下目前咱们的
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 来保护
前言
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`
-
比方上面就是几个更新
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 的按钮
![](https://gitee.com/xmkm/cloudPic/raw/master/img/20201005132516.png)
-
外围代码次要是两个:
* 在 `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` 中 ![image-20200821182447344](https://gitee.com/xmkm/cloudPic/raw/master/img/20201005132723.png)
-
定义返回一个函数的
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,更多的形式能够参考官网
作用:不会批改原有数据结构,返回一个批改后新的拷贝对象
-
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 来保护