redux异步action_Redux数据状态管理

83eb31e93d87a6ef63a0af01f1ffaa78.png

  1. Redux 中文文档(https://www.redux.org.cn/)

  2. Redux入门教程(快速上手)(https://segmentfault.com/a/1190000011474522?from=timelin)

1. Redux基础

简单几行代码,诠释了Redux的设计理念

// 定义计算规则,即reducer
function counter(state = 0, action) {
switch(action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
}

// 根据计算规则生成store
let store = createStore(counter);

// 定义数据(即state)变化之后的派发规则
store.subscribe(() => {
console.log(store.getState());
})

// 触发数据变化
store.dispatch({type: "INCREMENT"});
store.dispatch({type: "DECREMENT"});

2. Redux事件流

b704994e9bb46a38bb4c211ac9089beb.png

是由组件dispatch发起一个actionreducer,reducer根据type类型,重新计算(注意:reducer是一个纯函数),返回一个新的state给store,再由store给对应的组件。其中涉及到异步的操作会使用到redux-thunkredux-saga中间件(中间件指的是store和action的中间,对dispatch方法做了封装升级,可以dispatch函数)。

3. Redux Devtools安装及使用

redux-devtools-extension(https://github.com/zalmoxisus/redux-devtools-extension)

  • 基础用法

import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';

const composeEnhancers = composeWithDevTools({
// Specify name here, actionsBlacklist, actionsCreators and other options if needed
});
const store = createStore(reducer, /* preloadedState, */ composeEnhancers(
applyMiddleware(...middleware),
// other store enhancers if any
));
  • 多个中间件的使用及生产模式下禁用Redux DevTools

import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducer';

const composeEnhancers =
process.env.NODE_ENV === 'development'
? typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
// eslint-disable-next-line
})
: compose
: compose;

const enhancer = composeEnhancers(
applyMiddleware(thunk)
// other store enhancers if any
);

const store = createStore(rootReducer, enhancer);

4. react+redux+redux-persist+immer(redux持久化)

  • 项目结构

src--||-App.js|-store-|
| |-index.js| |-reducer.js|
|-Login -|
|-index.js|-store - |
|-index.js|-actionTypes.js|-actions.js|-reducer.js|-state.js
  • utils/producer.js

  1. immer的produce高阶函数(https://immerjs.github.io/immer/docs/produce)

  2. 2020要用immer来代替immutable优化你的React项目(https://www.jianshu.com/p/820ab7faab39)

使用immer.js替换之前的immutable.js,更轻量,代码更简洁,学习成本降低

  • utils/producer.js

import produce from 'immer';
// const array = [{ value: 0 }, { value: 1 }, { value: 2 }];
// const producer = produce(draft => {
// draft[0].value = 10;
// });
// const arr = producer(array);
// console.log(array === arr);

// 高阶函数
const producer = (state, fn) => produce(fn)(state);
export default producer;
  • login/store/actionTypes.js

// 定义常量
export const SET_TOKEN = 'SET_TOKEN';
  • login/store/state.js

// 设置state
export const loginData = {
token: null,
userName: null,
}
  • login/store/actions.js

// 创建action
export const setLoginToken = token => ({
type: types.SET_TOKEN,
token,
});
  • login/store/reducer.js

// 创建reducer
import * as types from './actionTypes';
import { loginData } from './state';
import { producer } from 'utils';

export default (state = loginData, action) => {
switch (action.type) {
case types.SET_TOKEN:
return {
...state,
...producer(state, draft => {
draft['token'] = action.token;
}),
};
default:
return state;
}
};
  • login/store/index.js

import reducer from './reducer';
import * as actions from './actions';
// 导出reducer和action
export { reducer, actions };
  • login/index.js

import React, { memo } from 'react';
import { connect } from 'react-redux';
import { setLoginToken } from './store/actions';

function Login(props) {
// 从props中拿到dispatchToken,下面已经通过connect注入了
const { dispatchToken } = props;
}

Login.propTypes = {
dispatchToken: PropTypes.func.isRequired,
};

const mapDispatchToProps = dispatch => ({
dispatchToken(token) {
dispatch(setLoginToken(token));
},
}

// 使用connect注入state和dispatch
export default connect(null, mapDispatchToProps)(memo(Login));
  • store/reducer.js

import { combineReducers } from 'redux';
import { reducer as loginReducer } from '@/pages/Login/store';

const allReducer = {
loginReducer
};
const rootReducer = combineReducers(allReducer);

export default rootReducer;
  • store/index.js

  1. redux-persist白名单和黑名单(https://www.npmjs.com/package/redux-persist#blacklist--whitelist)

  2. redux persists migrations(https://github.com/rt2zz/redux-persist/blob/master/docs/migrations.md)

  3. redux persists 使用AES 对数据做加密(https://github.com/maxdeviant/redux-persist-transform-encrypt)

import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducer';
import { persistStore, persistReducer, createMigrate } from 'redux-persist';
import immutableTransform from 'redux-persist-transform-immutable';
// import createEncryptor from 'redux-persist-transform-encrypt';
// import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
import storage from 'redux-persist/lib/storage';
// import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly'; // 生产模式下禁用Redux DevTools

// 使用AES 对数据做加密 https://github.com/maxdeviant/redux-persist-transform-encrypt
// const encryptor = createEncryptor({
// secretKey: 'my-super-secret-key',
// onError: function (error) {
// console.log('root', error);
// },
// });

const migrations = {
0: state => {
// migration clear out device state
return {
...state,
device: undefined,
};
},
1: state => {
// migration to keep only device state
return {
device: state.device,
};
},
};

const persistConfig = {
key: 'root',
version: 1,
storage,
// https://github.com/rt2zz/redux-persist/blob/master/docs/migrations.md
migrate: createMigrate(migrations, { debug: false }),
// https://www.jianshu.com/p/8a2b9be974a7
// stateReconciler: autoMergeLevel2,
// https://github.com/rt2zz/redux-persist-transform-immutable
// transforms: [immutableTransform(), encryptor],
transforms: [immutableTransform()],
blacklist: ['authorizationReducer'], // authorizationReducer不会被缓存
};

const persistedReducer = persistReducer(persistConfig, rootReducer);

// https://github.com/zalmoxisus/redux-devtools-extension
// 生产模式下禁用Redux DevTools
const composeEnhancers =
process.env.NODE_ENV === 'development'
? typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
// eslint-disable-next-line
})
: compose
: compose;

const enhancer = composeEnhancers(
applyMiddleware(thunk)
// other store enhancers if any
);

const store = createStore(persistedReducer, enhancer);

export const persistor = persistStore(store);
export default store;
  • App.js

react-router-config(https://www.npmjs.com/package/react-router-config)

import React, { Suspense } from 'react';
import {
BrowserRouter,
// Route,
Switch,
} from 'react-router-dom';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
// renderRoutes 读取路由配置转化为 Route 标签
// renderRoutes 这个方法只渲染一层路由
import { renderRoutes } from 'react-router-config';
import routes from './routes';
import { ConfigProvider } from 'antd';
import store, { persistor } from 'store';
import Loading from '@/baseUI/Loading';
import ErrorBoundary from '@/components/ErrorBoundary';
import zhCN from 'antd/es/locale/zh_CN';

function App() {
return (
<Provider store={store}><ConfigProvider locale={zhCN}><Suspense fallback={<Loading />}><BrowserRouter><Switch>
{/* {
routes.map(route => {
const { path, exact, component } = route;
return <Route key={path} path={path} exact={exact} component={component} />
})
} */}<PersistGate persistor={persistor}><ErrorBoundary>
{renderRoutes(routes)}ErrorBoundary>PersistGate>Switch>BrowserRouter>Suspense>ConfigProvider>Provider>
);
}
export default App;

5. react+redux+redux-toolkit(自带immer)

  1. redux_toolkit(https://www.51hint.com/posts/redux_toolkit/)

  2. 代码实例仓库(https://gitee.com/srect/redux_toolkit)

2c4c0ad320a6ef7216a55531194ae2c2.gif

  • 项目结构

├── LICENSE
├── package.json
├── public
| ├── favicon.ico| ├── index.html├── README.md├── src| ├── App.js| ├── components| | ├── ComA| | | ├── index.js| | | └── store| | | ├── comASlice.js| | | └── state.js| | ├── ComB| | | ├── index.js| | | └── store| | | ├── comBSlice.js| | | └── state.js| | └── Header| | └── index.js| ├── index.js| └── store| ├── index.js| └── reducer.js└── yarn.lock
  • src/components/ComB/store/comBSlice.js

通过createSlice将action和reducer建立在一起,代码更加简洁

import { createSlice } from '@reduxjs/toolkit';
import { num } from './state';

const comBSlice = createSlice({
name: 'comB',
initialState: num,
reducers: {
increment: (state, action) => state + action.payload,
decrement: (state, action) => state - action.payload
}
})

export default comBSlice;
  • src/components/ComB/index.js hooks下使用useSelector和useDispatch代替之前connect的mapStateToProps和mapDispatchToPrpops,之前通过props获取state和dispatch,现在直接通过hooks的方式获取,代码变得更加的简洁

redux中使用useSelector、useDispatch替代connect

import React, { memo } from 'react';
// import { connect } from 'react-redux';
import { useSelector, useDispatch } from 'react-redux';
import comBSlice from './store/comBSlice';

const { actions: { increment, decrement } } = comBSlice;

const ComA = (props) => {
// const { num } = props;
// const { handleIncrement, handleDecrement } = props;

const dispatch = useDispatch();
const num = useSelector(state => state.comBReducer);
const handleIncrement = num => dispatch(increment(num));
const handleDecrement = num => dispatch(decrement(num));

return (
<div><p>ComAp><p>num: <b>{num}b>p><button onClick={() => handleIncrement(1)}>increase +1button><button onClick={() => handleDecrement(1)}>decrease -1button>div>
)
}

// const mapStateToProps = state => ({
// num: state.comBReducer
// });

// const mapDispatchToProps = dispatch => ({
// handleIncrement(num) {
// dispatch(increment(num));
// },
// handleDecrement(num) {
// dispatch(decrement(num));
// }
// })

// export default connect(mapStateToProps, mapDispatchToProps)(memo(ComA));
export default memo(ComA);
  • src/store/reducer.js

import {combineReducers} from '@reduxjs/toolkit';
import comASlice from '../components/ComA/store/comASlice';
import comBSlice from '../components/ComB/store/comBSlice';

const rootReducer = combineReducers({
comAReducer: comASlice.reducer,
comBReducer: comBSlice.reducer,
})

export default rootReducer;
  • src/store/index.js

import {configureStore} from '@reduxjs/toolkit';
import rootReducer from './reducer';

// 这里使用configureStore代替之前的createStore
const store = configureStore({
reducer: rootReducer
})

export default store;
  • index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

ReactDOM.render(
<React.StrictMode><Provider store={store}><App />Provider>React.StrictMode>,
document.getElementById('root')
);

6. react+redux+redux-persist+immer(redux持久化) VS react+redux+redux-toolkit(自带immer)

对比上面的redux + immer 和 redux + redux toolkit的写法,可以清楚得看到,代码量大大的减少,重复代码量降低,维护更加方便

7. 额外说下Storeon,使用Storeon实现一个todolist

  1. Storeon(https://www.npmjs.com/package/storeon)

  2. 在 React 中进行事件驱动的状态管理

  • Storeon 是一个微型的、事件驱动的 React 状态管理库,其原理类似于 Redux。用 Redux DevTools 可以查看并可视化状态操作。Storeon 内部使用 Context API 来管理状态,并采用事件驱动的方法进行状态操作。

  • store 有三种方法

store.get() // 用于检索状态中的当前数据
store.on(event, callback) // 用于把事件侦听器注册到指定的事件名称
store.dispatch(event, data) // 用于发出事件,并根据定义的事件要求将可选数据传递进来
  • Storeon 是基于事件的状态管理库,状态更改由状态模块中定义的事件发出。Storeon 中有三个内置事件,它们以 @ 开头。其他事件不带 @ 前缀定义。三个内置事件是:

@init – 在应用加载时触发此事件。它用于设置应用的初始状态,并执行传递给它的回调中的所有内容
@dispatch – 此事件在每个新动作上触发。这对于调试很有用
@changed – 当应用状态发生更改时,将触发此事件
  • 安装

yarn add storeon @storeon/localstorage
  • 项目结构

├── package.json
├── node_modues
├── public
| ├── favicon.ico
| ├── index.html
├── README.md
├── src
| ├── App.js
| ├── index.js
| └── store
| └── index.js
└── yarn.lock
  • src/store/index.js

import { createStoreon } from 'storeon';
import { persistState } from '@storeon/localstorage';
import { v4 as uuidv4 } from 'uuid';
import { storeonDevtools, storeonLogger } from 'storeon/devtools';
// notes数据
let notes = store => {
store.on('@init', () => ({
notes: [
{ id: uuidv4(), item: 'hello' },
{ id: uuidv4(), item: 'world' },
]
}));

store.on('addNote', ({ notes }, note) => ({
notes: [...notes, {
id: uuidv4(),
item: note
}]
}));

store.on('deleteNote', ({ notes }, id) => {
return {
notes: notes.filter(note => note.id !== id),
}
})
}
// lists数据
let lists = store => {
store.on('@init', () => ({
lists: [{a: 1, b: 2}]
}))
}
// 创建store
const store = createStoreon([
notes,
lists,
persistState(['notes']),
process.env.NODE_ENV !== 'production' && storeonDevtools,
process.env.NODE_ENV !== 'production' && storeonLogger,
]);

export default store;
  • src/App.js

import React, { useState, useEffect, useContext } from 'react';
import { useStoreon } from 'storeon/react';
import { Context } from './index';

function App() {
const { dispatch, notes, lists } = useStoreon('notes', 'lists');
const [value, setValue] = useState('');
const [store] = useContext(Context);

const deleteNote = id => {
dispatch('deleteNote', id);
};

const addNote = () => {
dispatch('addNote', value);
setValue('');
}

useEffect(() => {
const notesValue = store.get().notes;
const listsValue = store.get().lists;
console.log('notesValue', notesValue)
console.log('listsValue', listsValue)
}, [notes, lists, store]);

return (
<div className="App" style={{ textAlign: 'center' }}><h2>to do listh2><div className='addNote'><input type="text" onChange={(e) => setValue(e.target.value)} value={value} /><button onClick={() => addNote()}> Add A Note button>div><ul>
{notes.map(note => (<li key={note.id}><div className='todo'><span>{note.item} span><button onClick={() => deleteNote(note.id)}>Delete notebutton>div>li>
))}ul>div>
);
}
export default App;
  • src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { StoreContext } from 'storeon/react';
import store from './store';

export const Context = React.createContext(null);

ReactDOM.render(
,
document.getElementById('root')
);

8. 写在最后

文中存在错误之处或有更好的建议等,请及时指出,谢谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值