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,并且也不可能都写在一个 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’
const mapStateToProps = state => {
return {
count:state.count
}
}
const mapDispatchToProps = dispatch => {
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
const weather = (state = {},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 入门学习以来的第一次总结吧,如有不对,敬请指正。