目前流行的状态管理Zustand
1、Redux 核⼼
Redux 介绍
JavaScript 状态容器,提供可预测化的状态管理
2、Redux的工作流程
Store:存储状态的容器,JavaScript对象
View: 视图,HTML⻚⾯
Actions: 对象,描述对状态进⾏怎样的操作
Reducers:函数,操作状态并返回新的状态
3、小案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="plus">+</button>
<span id="count">0</span>
<button id="minus">-</button>
<script src="redux.min.js"></script>
<script>
// 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. 触发action
store.dispatch(increment);
}
document.getElementById('minus').onclick = function () {
// 6. 触发action
store.dispatch(decrement);
}
// 7. 订阅 store
store.subscribe(() => {
// 获取store对象中存储的状态
// console.log(store.getState());
document.getElementById('count').innerHTML = store.getState().count;
})
</script>
</body>
</html>
4、Redux核心api
5、React + Redux
在 React 中不使⽤ Redux 时遇到的问题
在React中组件通信的数据流是单向的, 顶层组件可以通过props属性向下层组件传递数据, ⽽下层组件不能向上层组件传递数据, 要实现下层组件修改数据, 需要上层组件传递修改数据的⽅法到下层组件. 当项⽬越来越⼤的时候, 组件之间传递数据变得越来越困难
在 React 项⽬中加⼊ Redux 的好处
使⽤Redux管理数据,由于Store独⽴于组件,使得数据管理独⽴于组件,解决了组件与组件之间传递数据困难的问题。
6、react-redux
Counter组件
import React from 'react';
import { connect } from 'react-redux';
function Counter (props) {
return <div>
<button onClick={() => props.dispatch({type: 'increment'})}>+</button>
<span>{props.count}</span>
<button onClick={() => props.dispatch({type: 'decrement'})}>-</button>
</div>
}
// 1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件
// 2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法
const mapStateToProps = state => ({
count: state.count
});
export default connect(mapStateToProps)(Counter);
- connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件
- connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件
- connect 方法可以让我们获取 dispatch 方法
优化1
view部分
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-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)
ReactDOM.render(
// 通过provider组件 将 store 放在了全局的组件可以够的到的地方
<Provider store={store}><App/></Provider>,
document.getElementById('root')
);
/*
react-redux
Provider
connect
*/
count组件部分
import React from 'react';
import { connect } from 'react-redux';
function Counter ({count, increment, decrement}) {
return <div>
<button onClick={increment}>+</button>
<span>{count}</span>
<button onClick={decrement}>-</button>
</div>
}
// 1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件
// 2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法
const mapStateToProps = state => ({
count: state.count
});
const mapDispatchToProps = dispatch => ({
increment() {
dispatch({type: 'increment'})
},
decrement() {
dispatch({type: 'decrement'})
}
})
export default connect(mapStateToProps,mapDispatchToProps)(Counter);
优化2 避免重复调dispatch
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
function Counter ({count, increment, decrement}) {
return <div>
<button onClick={increment}>+</button>
<span>{count}</span>
<button onClick={decrement}>-</button>
</div>
}
// 1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件
// 2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法
const mapStateToProps = state => ({
count: state.count
});
// 避免重复调用dispatch
const mapDispatchToProps = dispatch => ({
...bindActionCreators({
increment() {
return {type: 'increment'}
},
decrement() {
return {type: 'decrement'}
}
}, dispatch)
})
export default connect(mapStateToProps,mapDispatchToProps)(Counter);
优化3
store/counter.action.js
export const increment = ()=> ({type: 'increment'});
export const decrement = () => ({type: 'decrement'});
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as couterActions from '../store/actions/counter.actions';
function Counter ({count, increment, decrement}) {
return <div>
<button onClick={increment}>+</button>
<span>{count}</span>
<button onClick={decrement}>-</button>
</div>
}
// 1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件
// 2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法
const mapStateToProps = state => ({
count: state.count
});
// 避免重复调用dispatch
const mapDispatchToProps = dispatch => (bindActionCreators(couterActions, dispatch))
export default connect(mapStateToProps,mapDispatchToProps)(Counter);
优化4
store/index.js
import { createStore } from "redux";
import reducer from './reducers/counter.reducer'
export const store = createStore(reducer)
store/reducers/reducer-counter.js
const initialState = {
count: 0
}
export default (state = initialState, action) => {
switch(action.type) {
case 'increment':
return {
count: state.count + 1
}
case 'decrement':
return {
count: state.count - 1
}
default:
return state;
}
}
view
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import { store } from './store';
ReactDOM.render(
// 通过provider组件 将 store 放在了全局的组件可以够的到的地方
<Provider store={store}><App/></Provider>,
document.getElementById('root')
);
/*
react-redux
Provider
connect
*/
优化6 加参数
reducer
import { INCREMENT, DECREMENT } from "../const/counter.const";
const initialState = {
count: 0
}
export default (state = initialState, action) => {
switch(action.type) {
case INCREMENT:
return {
...state,
count: state.count + action.payload
}
case DECREMENT:
return {
...state,
count: state.count - action.payload
}
default:
return state;
}
}
action
import { INCREMENT, DECREMENT, INCREMENT_ASYNC } from "../const/counter.const";
export const increment = payload => ({type: INCREMENT, payload});
export const decrement = payload => ({type: DECREMENT, payload});
export const increment_async = payload => ({type: INCREMENT_ASYNC, payload});
组件应用
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as couterActions from '../store/actions/counter.actions';
function Counter ({count, increment, decrement}) {
return <div>
<button onClick={() => increment(5)}>+</button>
<span>{count}</span>
<button onClick={() => decrement(5)}>-</button>
</div>
}
// 1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件
// 2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法
const mapStateToProps = state => ({
count: state.count
});
// 避免重复调用dispatch
const mapDispatchToProps = dispatch => (bindActionCreators(couterActions, dispatch))
export default connect(mapStateToProps,mapDispatchToProps)(Counter);
7、拆分合并reducer
combineReducers中间件
count-reducer.js
import { INCREMENT, DECREMENT } from "../const/counter.const";
const initialState = {
count: 0
}
export default (state = initialState, action) => {
switch(action.type) {
case INCREMENT:
return {
...state,
count: state.count + action.payload
}
case DECREMENT:
return {
...state,
count: state.count - action.payload
}
default:
return state;
}
}
root-reducer.js
import { combineReducers } from 'redux';
import CounterReducer from './counter.reducer';
import ModalReducer from './modal.reducer';
// { counter: { count: 0 }, model: { show: false } }
export default combineReducers({
counter: CounterReducer,
modal: ModalReducer
})
store/index.js
import { createStore } from "redux";
import rootReducer from './reducers/root.reducer'
export const store = createStore(rootReducer)
8、中间件概念介绍
什么是中间件?
中间件允许我们扩展redux应⽤程序。
9、开发redux中间件
开发中间件的模板代码
注册中间件
中间件在开发完成以后只有被注册才能在Redux的⼯作流程中⽣效
多个中间件
import { createStore, applyMiddleware } from "redux";
import RootReducer from "./reducers/root.reducer";
import logger from "./middleware/logger";
import test from "./middleware/test";
import thunk from './middleware/thunk';
export const store = createStore(RootReducer, applyMiddleware(thunk, logger, test));
帮助理解
logger.js 用于测试
export default store => next => action => {
console.log(store);
console.log(action);
next(action);
}
2秒之后执行
export default store => next => action => {
if(actiob.type === 'increment' || action.type === 'decrement') {
setTimeout(() => {
next(action);
}, 2000)
}
}
优化
export default ({dispatch}) => next => action => {
// 1. 当前这个中间件函数不关心你想执行什么样的异步操作 只关心你执行的是不是异步操作
// 2. 如果你执行的是异步操作 你在触发action的时候 给我传递一个函数 如果执行的是同步操作 就传递action对象
// 3. 异步操作代码要写在你传递进来的函数中
// 4. 当前这个中间件函数在调用你传递进来的函数时 要将dispatch方法传递过去
if (typeof action === 'function') {
return action(dispatch)
}
next(action)
}
action.js
export const increment_async = payload => dispatch => {
setTimeout(() => {
dispatch(increment(payload))
},2000)
}
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as couterActions from '../store/actions/counter.actions';
function Counter ({count, increment, decrement, increment_async}) {
return <div>
<button onClick={() => increment_async(20)}>+</button>
<span>{count}</span>
<button onClick={() => decrement(5)}>-</button>
</div>
}
const mapStateToProps = state => ({
count: state.counter.count
});
const mapDispatchToProps = dispatch => bindActionCreators(couterActions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
10、Redux常用中间件
执行异步操作
1、redux-thunk
npm install redux-thunk
import { createStore, applyMiddleware } from "redux";
import RootReducer from "./reducers/root.reducer";
import thunk from 'redux-thunk';
export const store = createStore(RootReducer, applyMiddleware(thunk));
上面的案例依然好用
11、redux-saga
redux-saga和redux-thunk功能一样处理异步代码
redux-saga 可以将异步操作从 Action Creator ⽂件中抽离出来,放在⼀个单独的⽂件中
npm install redux-saga
store/index.js
import { createStore, applyMiddleware } from "redux";
import RootReducer from "./reducers/root.reducer";
import createSagaMidddleware from 'redux-saga';
import rootSaga from './sagas/root.saga';
// 创建 sagaMiddleware
const sagaMiddleware = createSagaMidddleware();
export const store = createStore(RootReducer, applyMiddleware(sagaMiddleware));
// 启动 counterSaga
sagaMiddleware.run(rootSaga)
counter.action.js
import { INCREMENT, DECREMENT, INCREMENT_ASYNC } from "../const/counter.const";
export const increment = payload => ({type: INCREMENT, payload});
export const decrement = payload => ({type: DECREMENT, payload});
export const increment_async = payload => ({type: INCREMENT_ASYNC, payload});
store/saga/counter.js
import { takeEvery, put, delay } from 'redux-saga/effects';
import { increment } from '../actions/counter.actions';
import { INCREMENT_ASYNC } from '../const/counter.const';
// takeEvery 接收 action
// put 触发 action
function* increment_async_fn (action) {
yield delay(2000);
yield put(increment(action.payload))
}
export default function* counterSaga () {
// 接收action
yield takeEvery(INCREMENT_ASYNC, increment_async_fn)
}
12、saga文件的拆分与合并
没有拆分的情况下
import { takeEvery, put, delay } from 'redux-saga/effects';
import { increment } from '../actions/counter.actions';
import { INCREMENT_ASYNC } from '../const/counter.const';
import { SHOWMODAL_ASYNC } from '../const/modal.const';
import { show } from '../actions/modal.actions';
// takeEvery 接收 action
// put 触发 action
function* increment_async_fn (action) {
yield delay(2000);
yield put(increment(action.payload))
}
export default function* counterSaga () {
// 接收action
yield takeEvery(INCREMENT_ASYNC, increment_async_fn)
}
function* showModal_async_fn () {
yield delay(2000);
yield put(show());
}
export default function* modalSaga () {
yield takeEvery(SHOWMODAL_ASYNC, showModal_async_fn)
}
saga拆分
modal.saga.js
import { takeEvery, put, delay } from 'redux-saga/effects';
import { SHOWMODAL_ASYNC } from '../const/modal.const';
import { show } from '../actions/modal.actions';
function* showModal_async_fn () {
yield delay(2000);
yield put(show());
}
export default function* modalSaga () {
yield takeEvery(SHOWMODAL_ASYNC, showModal_async_fn)
}
root.saga.js
import { all } from 'redux-saga/effects';
import counterSaga from './counter.saga';
import modalSaga from './modal.saga';
export default function* rootSaga () {
yield all([
counterSaga(),
modalSaga()
])
}
store/index.js
import { createStore, applyMiddleware } from "redux";
import RootReducer from "./reducers/root.reducer";
import createSagaMidddleware from 'redux-saga';
import rootSaga from './sagas/root.saga';
// 创建 sagaMiddleware
const sagaMiddleware = createSagaMidddleware();
export const store = createStore(RootReducer, applyMiddleware(sagaMiddleware));
// 启动 counterSaga
sagaMiddleware.run(rootSaga)
13、redux-action的使用
redux流程中⼤量的样板代码读写很痛苦, 使⽤redux-actions可以简化Action和Reducer的处理
npm i redux-actions
actions原来的代码
import { INCREMENT, DECREMENT, INCREMENT_ASYNC } from "../const/counter.const";
export const increment = payload => ({type: INCREMENT, payload});
export const decrement = payload => ({type: DECREMENT, payload});
export const increment_async = payload => ({type: INCREMENT_ASYNC, payload});
使用redux-actions之后
reducer里面原来的代码
import { INCREMENT, DECREMENT } from "../const/counter.const";
const initialState = {
count: 0
}
export default (state = initialState, action) => {
switch(action.type) {
case INCREMENT:
return {
...state,
count: state.count + action.payload
}
case DECREMENT:
return {
...state,
count: state.count - action.payload
}
default:
return state;
}
}
使用redux-actions之后
import { handleActions as createReducer } from "redux-actions";
import { increment, decrement } from "../actions/counter.actions";
const initialState = {
count: 0
}
function handleIncrement(state,action) {
return {
count: state.count + action.payLoad
}
}
function handleDecrement(state,action) {
return {
count: state.count - action.payLoad
}
}
export default createReducer({
[increment]: handleIncrement,
[decrement]: handleDecrement
}, initialState)
14、shopping案例
npm i redux react-redux redux-saga redux-actions
看代码
15、redux源码实现
middleware/thunk.js
function thunk (store) {
return function (next) {
return function (action) {
console.log('thunk');
next(action)
}
}
}
middleware/logger.js
function logger (store) {
return function (next) {
return function (action) {
console.log('logger');
next(action)
}
}
}
myRedux.js
function createStore (reducer, preloadedState, enhancer) {
// reducer 类型判断
if (typeof reducer !== 'function') throw new Error('redcuer必须是函数');
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('enhancer必须是函数')
}
return enhancer(createStore)(reducer, preloadedState);
}
// 状态
var currentState = preloadedState;
// 订阅者
var currentListeners = [];
// 获取状态
function getState () {
return currentState;
}
// 用于触发action的方法
function dispatch (action) {
// 判断action是否是一个对象
if (!isPlainObject(action)) throw new Error('action必须是一个对象');
// 判断action中的type属性是否存在
if (typeof action.type === 'undefined') throw new Error('action对象中必须有type属性');
// 调用reducer函数 处理状态
currentState = reducer(currentState, action);
// 调用订阅者 通知订阅者状态发生了改变
for (var i = 0; i < currentListeners.length; i++) {
var listener = currentListeners[i];
listener();
}
}
// 订阅状态的改变
function subscribe (listener) {
currentListeners.push(listener);
}
// 默认调用一次dispatch方法 存储初始状态(通过reducer函数传递的默认状态)
dispatch({type: 'initAction'})
return {
getState,
dispatch,
subscribe
}
}
// 判断参数是否是对象类型
// 判断对象的当前原型对象是否和顶层原型对象相同
function isPlainObject (obj) {
if (typeof obj !== 'object' || obj === null) return false;
var proto = obj;
while (Object.getPrototypeOf(proto) != null) {
proto = Object.getPrototypeOf(proto) // 获取最顶层原型对象
}
return Object.getPrototypeOf(obj) === proto;
}
function applyMiddleware (...middlewares) {
return function (createStore) {
return function (reducer, preloadedState) {
// 创建 store
var store = createStore(reducer, preloadedState);
// 阉割版的 store
var middlewareAPI = {
getState: store.getState,
dispatch: store.dispatch
}
// 调用中间件的第一层函数 传递阉割版的store对象
var chain = middlewares.map(middleware => middleware(middlewareAPI));
var dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch
}
}
}
}
function compose () {
var funcs = [...arguments];
return function (dispatch) {
for (var i = funcs.length - 1; i >= 0; i--) {
dispatch = funcs[i](dispatch);
}
return dispatch;
}
}
function bindActionCreators (actionCreators, dispatch) {
var boundActionCreators = {};
for (var key in actionCreators) {
(function (key) {
boundActionCreators[key] = function () {
dispatch(actionCreators[key]())
}
})(key)
}
return boundActionCreators;
}
function combineReducers (reducers) {
// 1. 检查reducer类型 它必须是函数
var reducerKeys = Object.keys(reducers);
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
if (typeof reducers[key] !== 'function') throw new Error('reducer必须是函数');
}
// 2. 调用一个一个的小的reducer 将每一个小的reducer中返回的状态存储在一个新的大的对象中
return function (state, action) {
var nextState = {};
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
var reducer = reducers[key];
var previousStateForKey = state[key];
nextState[key] = reducer(previousStateForKey, action)
}
return nextState;
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button id="increment">+1</button>
<span id="box">0</span>
<button id="decrement">-1</button>
<script src="./myRedux.js"></script>
<script src="middlewares/logger.js"></script>
<script src="middlewares/thunk.js"></script>
<script>
function counterReducer(state, action) {
switch (action.type) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
default:
return state;
}
}
function enhancer (createStore) {
return function (reducer, preloadedState) {
var store = createStore(reducer, preloadedState);
var dispatch = store.dispatch;
function _dispatch (action) {
if (typeof action === 'function') {
return action(dispatch)
}
dispatch(action);
}
return {
...store,
dispatch: _dispatch
}
}
}
var rootReducer = combineReducers({counter: counterReducer})
var store = createStore(rootReducer, {counter: 100}, applyMiddleware(logger, thunk));
store.subscribe(function () {
document.getElementById("box").innerHTML = store.getState().counter;
});
var actions = bindActionCreators({increment, decrement}, store.dispatch);
function increment () {
return {type: "increment"}
}
function decrement () {
return {type: "decrement"};
}
document.getElementById("increment").onclick = function () {
// logger -> thunk -> reducer
// store.dispatch({ type: "increment" });
actions.increment()
};
document.getElementById("decrement").onclick = function () {
// store.dispatch({ type: "decrement" });
actions.decrement()
};
</script>
</body>
</html>