redux是一个JavaScript状态容器,提供可预测化的状态管理。
redux主要有四个核心概念
接下来通过redux实现一个简单的计数器,加深对redux的认识:
0
// 3. 存储默认状态
var initialState = {count: 0
}
// 2. 创建 reducer 函数
function reducer (state = initialState, action) {switch (action.type) {case 'increment':return {count: state.count + 1};case 'decrement':return {count: state.count - 1}default:return state;}
}
// 1. 创建 store 对象
var store = Redux.createStore(reducer);
// 4. 定义 action
var increment = { type: 'increment' };
var decrement = { type: 'decrement' };
// 5. 获取按钮 给按钮添加点击事件
document.getElementById('plus').onclick = function () {// 6. 触发actionstore.dispatch(increment);
}
document.getElementById('minus').onclick = function () {// 6. 触发actionstore.dispatch(decrement);
}
// 7. 订阅 store
store.subscribe(() => {// 获取store对象中存储的状态// console.log(store.getState());document.getElementById('count').innerHTML = store.getState().count;
})
首先通过Redux.createStore创建redux实例,参数就是我们的reducer函数,reducer函数有两个参数,一个就是我们state仓库,第二个就是我们的action。通过上图,我们看到要更新视图,首先要dispatch来触发action,于是第五步、第六步就是通过点击函数来触发action,通过action去触发reducer更改我们的state仓库;仓库更改了,就需要更新视图,就需要subscribe函数出场了,在subscribe中对视图进行更新。
这个demo还是非常简单的,使用redux来更改数据,有点杀鸡用牛刀的感觉。在一个较大的项目中,数据管理一直都是一个难点,比如在react项目中,react组件间的通信都是单向数据流的,如果涉及层级过多,传参就会很困难,使用context如果数据过多,就会变得很难维护,这个时候redux就排上了用场。由于Store独立于组件,是的数据管理同样独立于组件,这就使得数据管理变得有迹可循,后期容易维护,组件通信也容易了许多。
上面为了了解redux,流程图画的较为的简单,接下来再重新介绍下redux工作流程
接下来使用react来重构上述的计数器
//index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
// import App from './App';
import {createStore} from 'redux';
const initialState = {count:0
}
function reducer(state = initialState,action){switch (action.type) {case 'increment':return {count: state.count + 1};case 'decrement':return {count: state.count - 1}default:return state;}
}
const store = createStore(reducer)
const increment = {type:'increment'}
const decrement = {type:'decrement'}
function Counter(){return ({store.getState().count})
}
store.subscribe(() => {root.render( );
})
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
);
这种写法实际上有个很大的问题,平常我们封装的组件都是在一个文件夹里单独导入的,在单独的组件内如何拿到Store实例,以及触发subscribe函数,这就需要一个插件react-redux出场了。这个插件中主要暴露了一个组件和一个方法,Provider组件和contect方法。
//需要包裹项目的所有的组件 * contect方法主要有以下几个功能* 会订阅store,当store中的状态发生更改时,会重新渲染组件* 可以获取store中的状态,将状态通过组件的props属性映射给组件* 可以获取dispatch方法接下来是代码编写:
//index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
// import App from './App';
import {createStore} from 'redux';
import { Provider } from 'react-redux';
import Counter from './components/Counter.js'
const initialState = {count:0
}
function reducer(state = initialState,action){switch (action.type) {case 'increment':return {count: state.count + 1};case 'decrement':return {count: state.count - 1}default:return state;}
}
const store = createStore(reducer)
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
);
// /components/Counter.js
import React from "react";
import {connect} from 'react-redux'
const increment = {type:'increment'}
const decrement = {type:'decrement'}
function Counter({count,increment,decrement}){return ({count})
}
const mapStateToProps = state => ({count:state.count
})
const mapDispatchToProps = dispatch => ({increment() {dispatch(increment)},decrement() {dispatch(decrement)}
})
// connect第一个参数就是state仓库组件中的属性可以通过props.state拿到state
//第二个参数是一个函数 返回一个对象该对象的属性都可以通过props拿到
export default connect(mapStateToProps,mapDispatchToProps)(Counter)
首先通过函数createStore创建一个store实例,参数就是reducer函数,用以更改state仓库;然后用Provider组件包裹我们的组件,这样我们的业务组件就可以拿到store实例;在组件中定义了一个Counter函数组件,返回时用被传入connect返回函数,connect函数有两个参数,第一个是我们的state仓库,第二个就是action,用以更改state仓库。在react中使用redux流程就如上述,虽然目前有点繁琐,那是因为代码少,一旦项目复杂起来,redux状态共享就会变得非常的有用。
稍微复杂一点的项目,都会将store单独拆分成一个模块出来,而不是都写在index.js中,接下来在优化一下代码:
// src/store/actions/counter.actions.js
import { INCREMENT, DECREMENT } from "../const/counter.const";
export const increment = () => ({type: INCREMENT});
export const decrement = () => ({type: DECREMENT});
// src/store/const/counter.const.js
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';
// src/store/reducer/counter.reducer.js
import { INCREMENT, DECREMENT } from "../const/counter.const";
const initialState = {count: 0
}
const reducer = (state = initialState, action) => {switch(action.type) {case INCREMENT:return {count: state.count + 1}case DECREMENT:return {count: state.count - 1}default: return state;}
}
export default reducer
src/store/index.js
import { createStore } from "redux";
import Reducer from './reducers/counter.reducer'
export const store = createStore(Reducer)
这样我们的根目录下的index.js就会非常的简洁了:
import React from 'react';
import ReactDOM from 'react-dom/client';
// import App from './App';
import { Provider } from 'react-redux';
import Counter from './components/Counter.js'
import { store } from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
);
实际上action也可以传参的:
在action中传入参数
定义action时接受参数
const reducer = (state = initialState, action) => {switch(action.type) {case INCREMENT:return {count: state.count + action.payload}case DECREMENT:return {count: state.count - action.payload}default: return state;}
}
中间件本质就是一个函数,允许我们来扩展redux程序,很大的扩展了我们对action的扩展能力。当我们增加了中间件以后,组件触发了action,首先执行中间件函数,当中间件处理完成后,才会执行reducer。
加入了中间件的redux,其工作流程是这样的
现在在点击按钮加减时需要延时1s才会数值才会改变,首先编写一个中间件:
//middleware/thunk.js
const thunkMd =({dispatch}) => next => action => {if(action.type === 'increment' || action.type === 'decrement'){setTimeout(() => {next(action)},1000)}// next(action)
}
export default thunkMd
然后注册这个中间件
// store/index.js
import thunk from './middleware/thunk'
export const store = createStore(Reducer,applyMiddleware(thunk))
功能已经出现了,然而这个中间件不够灵活,我们想实现一个延时中间件,不仅仅只有这个计数器案例可以使用,要足够的抽象,可以根据传入的参数来判断,如果传入的那参数是函数,就执行我们的函数,在函数中执行异步操作;不然就正常往后执行:
const thunkMd =({dispatch}) => next => action => {// 1. 当前这个中间件函数不关心你想执行什么样的异步操作 只关心你执行的是不是异步操作// 2. 如果你执行的是异步操作 你在触发action的时候 给我传递一个函数 如果执行的是同步操作 就传递action对象// 3. 异步操作代码要写在你传递进来的函数中// 4. 当前这个中间件函数在调用你传递进来的函数时 要将dispatch方法传递过去if (typeof action === 'function') {return action(dispatch)}next(action)
}
export default thunkMd
定义异步action
export const increment_async = payload => dispatch => {setTimeout(() => {dispatch(increment(payload))},1000)
}
然后触发action时
function Counter({count,increment,decrement,increment_async}){return ({count})
}
const loadPosts = () => async dispatch => { const posts = await axios,get('api').then(res => res.data) dispatch({type:LOADPOSTS,payload:posts})} ```* redux-sagaredux-saga和redux -thunk一样,都是在redux工作流程中添加异步代码 ,redux-saga更加强大,它允许将异步操作从Action Creator文件中抽离出来,放在一个单独的文件中。首先安装下这个插件npm install redux-saga```// store/index.jsimport createSagaMidddleware from 'redux-saga';import counterSaga from './sagas/counter.saga'// 创建 sagaMiddlewareconst sagaMiddleware = createSagaMidddleware();export const store = createStore(Reducer, applyMiddleware(sagaMiddleware));sagaMiddleware.run(counterSaga) //src/sagas/counter.saga.jsimport { takeEvery, put, delay } from ‘redux-saga/effects’;import { increment } from ‘…/actions/counter.actions’;import { INCREMENT_ASYNC } from ‘…/const/counter.const’;// takeEvery 接收 action// put 触发 actionfunction* increment_async_fn (action) {yield delay(2000);yield put(increment(action.payload))}export default function* counterSaga () {// 接收actionyield takeEvery(INCREMENT_ASYNC, increment_async_fn)} ``````export const INCREMENT_ASYNC = ‘increment_async’; ```触发actions```function Counter({count,increment,decrement,increment_async}){return (