Redux简介
Redux是一个库,是JavaScript状态容器,提供可预测化的状态管理,他解决的组件间数据共享的问题。当然,在React中,要达到组件间数据共享的目的不一定非要用Redux,React提供了context api,在小型项目里面用context当然没什么问题,但是当页面复杂起来的时候,可能就需要统一并且易管理的Redux库了。
Redux三大原则
单一数据源
所有数据都存在唯一一棵树中
状态是只可读
简单的说,就是只有get方法,没有set方法,状态只能通过dispatch(action)更改
状态修改均由纯函数完成
1. 函数的返回结果只依赖于它的参数。 2. 函数执行过程里面没有副作用。
Redux核心
Redux学的时候可能觉得有点绕,但是学会了回头看,其实就三个东西
- action(对要分发(dispatch)的数据进行包装)
- reducers(识别发送的action的类型(type),返回一个新的状态(state)给store树更新)
- store(store 就是用来维持应用所有的 state 树 的一个对象。)
这是来自阮老师官网的图。它很好的解释了redux的工作原理,举例,当用户点击时(onClick),分发一个action (dispatch(action)),然后reducers就会被调用,reducers会被传入两个参数,一个当前的(旧的)state,一个是你dispatch的action,然后reducers根据action.type进行相应的数据处理,最后返回一个新的state给store树,store树更新后,React Components就会根据store进行重新渲染,就达到了状态更新的效果了。
React中的目录
├── src/
├── action// 存放action
├── components // 存放展示组件(组件的渲染,它不依赖store,即与redux无关)
├── contrainers// 存放容器组件 (在这里作了数据更新的定义,与redux相关)
├── reducers // 存放reducers
...
Action
action是对要分发的数据进行包装的,当我们要改变store树时,只需要分发对应的action dispatch(add()),然后经过reducers处理返回一个新的state,store树就得到了更新。
以计数器为例,就应该有两个action,一个加一,一个减一。return的内容可以不只有type,还可以有自己自定义的数据(参照如下),但是必定要有type,因为reducers是根据action.type进行识别处理数据的。
// src/action/index.js
export const add = () => {
return {
type: "ADD"
};
};
export const less = () => {
return {
type: "LESS"
};
};
// let id = 0;
// export const getWeatherSuccess = payload => {
// return {
// type:"SUCCESS",
// payload,
// id:id++
// };
// };
Reducers
Reducers的写法基本都是固定的,形式如下,state初始值根据需求自定,唯一要注意的时,我们不可以更改state,只能返回一个新的state,因为Reducers是一个纯函数。
为什么Reducers一定要是纯函数,其实Redux只通过比较新旧两个对象的存储位置来比较新旧两个对象是否相同(也就是Javascript对象浅比较)。如果在reducer内部直接修改旧的state对象的属性值,那么新的state和旧的state将都指向同一个对象。因此Redux认为没有任何改变,返回的state将为旧的state。
Reducers这么设计的目的其实是出于性能的考虑,如果Reducers设计成直接修改原state,则每次修改都要进行遍历对比,即JavaScript深比较,对性能消耗大很多,所以设计成纯函数的形式,每次仅需要一次浅对比即可。
好吧,上面巴拉巴拉那么多,其实暂时不看也不影响,下面继续举Count的reducerrs例子
// src/reducers/count
const count = (state = 0,action) => {
switch (action.type) {
case 'ADD':
return state+1
case 'LESS':
return state-1
default:
return state
}
}
export default count;
这里只是一个Count的Reducer,在实际情况中,肯定不止一个Reducer,每个state也不是单单是一个数值,state一般都是对象的形式,然后再在state对象里面存该类的值,并且也不可能都写在一个js文件中,那么就需要用到combineReducers进行Reducer的合并。
// src/reducers/index
import {combineReducers} from 'redux'
import count from './count'
//假设有个weather的reducer
import weather from './weather'
const reducers = combineReducers({
count,
weather
})
export default reducers;
Store
Store提供了三个API:
- 提供 getState() 方法获取 state;
- 提供 dispatch(action) 方法更新 state;
- 通过 subscribe(listener) 注册监听器,注销监听器;
store一般都是在顶层组件创建,然后通过react-redux提供的Provider对子组件进行连接
// src/components/App
import React, { Component } from 'react';
import {Provider} from 'react-redux'
import { createStore } from 'redux';
import reducers from '../reducers'
import Count from '../contrainers/Count'
let store = createStore(reducers)
class App extends Component {
render() {
return (
<Provider store={store}>
<CountAndWeather/>
</Provider>
);
}
}
export default App;
react-redux
react-redux是一个官方提供的redux连接库,它提供了一个组件<Provider></Provider>和一个API connect(),<Provider>接受一个store作为props,而connect提供了在整个React应用的任意组件中获取store中数据的功能。即在<Provider>包裹的子组件里面,我们可以通过connect()获取store。
connect是一个高阶函数,第一个括号的两个参数,第一个是对state的映射,第二个是dispatch的映射,第二个括号的参数是需要连接Redux的组件。
👇一个容器组件,负责获取store的state,以及dispatch对应的处理,最后export一个connect,该connect连接的是Count这个展示组件。
// src/contrainers/CountCotr
import { connect } from 'react-redux'
import Count from '../components/Count'
import { add, less } from '../action'
// ownProps:展示组件的props
const mapStateToProps = (state,ownProps) => {
return {
count:state.count
}
}
const mapDispatchToProps = (dispatch,ownProps) => {
return {
onClickAdd: () => {
dispatch(add())
},
onClickLess: () => {
dispatch(less())
}
}
}
//mapStateToProps,mapDispatchToProps注意先后顺序
export default connect(mapStateToProps,mapDispatchToProps)(Count);
👇这是一个展示组件,它经过上面容器组件的connect之后,可以在this.props获取到mapStateToProps和mapDispatchToProps的返回值。
// src/components/Count
import React, { Component } from 'react'
class Count extends Component {
render() {
const {count, onClickAdd, onClickLess } = this.props
return (
<div>
Count : {count}
<br/><br/>
<button onClick={onClickAdd}>增加</button>
<br/><br/>
<button onClick={onClickLess}>减少</button>
</div>
);
}
}
export default Count;
至此,一个基于redux的同步计数器就完成了。但是这仅仅只是一个同步redux应用,我们还有异步请求需要处理,于是乎就需要用到middleware。
Middleware
middleware顾名思义,是一个中间件。它提供的是位于action被发起之后,到达 reducer之前的扩展点。你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。
redux的异步方案有很多,redux-thunk、redux-promise、redux-saga等等,甚至也可以自己写一个。这里以redux-thunk为例,顺便加入redux-logger日记中间件方便查看。
使用方法:npm install,引入,在createStore里面添加applyMiddleware(thunk,logger)即可。注意!因为middleware是类似串串一样处在action和reducer之间,所以是有顺序的,而一些middleware对顺序有要求,如redux-looger则要求放在最后。
// src/components/App
...
import { createStore ,applyMiddleware} from 'redux';
import logger from 'redux-logger'
import thunk from 'redux-thunk'
let store = createStore(reducers,applyMiddleware(thunk,logger));
//这么写也可以
// let store = applyMiddleware(thunk,logger)(createStore)(reducers)
...
然后,就可以在action里面写异步方法了。我们的异步action是一个高阶函数,第一个参数自定义,return的函数可以接受两个参数,dispatch和getState。这里以一个fetch获取天气的请求为例。
// src/action/index
const getWeatherSuccess = (payload) => {
return {
type:"SUCCESS",
payload
}
}
const getWeatherError = () => {
return {
type:"ERROR"
}
}
export const getWeather = () => {
return async (dispatch, getState) => {
try {
//这个console只是为了验证getState方法
console.log(getState())
const response = await fetch(
"http://www.weather.com.cn/data/sk/101280101.html"
);
const data = await response.json();
dispatch(getWeatherSuccess(data.weatherinfo));
} catch () {
dispatch(getWeatherError());
}
};
};
最后,再写个weahter的reducer。
// src/reducers/weather
// state初始值经量详细,以避免获取数据前因属性undefined而造成错误
const weather = (state = { city:'', WD:'' }, action) =>{
switch (action.type) {
case 'SUCCESS':
return {state:"success",weatherInfo:action.payload}
case 'ERROR':
return {state:"error"}
default:
return state
}
}
export default weather;
合并一下reducer
// src/reducers/index
import {combineReducers} from 'redux'
import count from './count'
import weather from './weather'
const reducers = combineReducers({
//这里使用了es6语法,实际上是count:count,对应state的key
count,
weather
})
export default reducers;
将展示组件里添加一个按钮和显示,容器组件里添加个获取weahter的state和dispatch请求。
// src/contrainers/CouAndWeaContrainer
import {connect} from 'react-redux'
import CountAndWeahter from '../components/CountAndWeahter'
import {add,less,getWeather} from '../action'
const mapStateToProps = state => {
return {
count:state.count,
weather:state.weather.weatherInfo,
state:state.weather.state
}
}
const mapDispatchToProps = dispatch => {
return {
onClickAdd: () => {
dispatch(add())
},
onClickLess: () => {
dispatch(less())
},
onClickFetch:() => {
dispatch(getWeather())
}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(CountAndWeahter);
// src/components/CountAndWeahter
import React, { Component } from 'react'
class CountAndWeahter extends Component {
render() {
const {count, onClickAdd, onClickLess,weather,state,onClickFetch} = this.props
return (
<div>
Count : {count}
<br/><br/>
<button onClick={onClickAdd}>增加</button>
<br/><br/>
<button onClick={onClickLess}>减少</button>
<br/><br/>
{state === "success"?<p>城市:{weather.city},风向:{weather.WD}</p>:''}
<button onClick={onClickFetch}>获取天气</button>
</div>
);
}
}
export default CountAndWeahter;
👇这是打印出来的state,其中count和weather这两个名字是由combineReducers传入时的决定的。
{
count: 0,
weather:
state: "success"
weatherInfo:
AP: "1001.4hPa"
Radar: "JC_RADAR_AZ9200_JB"
SD: "83%"
WD: "东南风"
WS: "小于3级"
WSE: "<3"
city: "广州"
cityid: "101280101"
isRadar: "1"
njd: "暂无实况"
sm: "1.7"
temp: "26.6"
time: "17:50"
}
现在,一个既有同步又有异步action的react-redux应用就完成了。
自定义Middleware
有时候,我们可能有特殊的需求,却找不到合适的middleware,这时候,就需要自定义自己的middleware了。以一个简化版的logger为例,写法如下👇
//将import logger from 'redux-logger'备注掉
const logger = store =>next => action =>{
console.log('prevState:',store.getState())
console.log('action:'+action.type)
next(action)
console.log('nextState:',store.getState())
}
这么几行,就已经完成了一个logger的middleware了,其中,store =>next => action =>{}这是一个柯里化的写法,至于为什么要这么写,就需要看redux的applyMiddleware源码来解释了。
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
applyMiddleware亦是一个三级柯里化(Currying)的函数,第一个参数是middleware数组,第二个是redux的createStore,第三个…args,其实就是reducers。
applyMiddleware首先利用createStore(…args)创建了一个store,然后再将store.getState和store.dispatch传给每个middleware,即对应logger的第一个参数store,最后dispatch = compose(…chain)(store.dispatch)将所有middleware串起来。
👇compose负责串联middleware,假设传入dispatch = compose(f1,f2,f3)(store.dispatch),则相当于f1(f2(f3(store.dispatch)))
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
applyMiddleware相当于重新包装了一遍dispatch,这样,当我们dispatch就会经过层层middleware处理,达到中间件的效果了。
案例源码https://github.com/Y-qwq/coun…
小结
redux学习小结,如有不对,敬请指正。