Redux 中文文档(https://www.redux.org.cn/)
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事件流
是由组件dispatch
发起一个action
到reducer
,reducer根据type类型,重新计算(注意:reducer是一个纯函数),返回一个新的state
给store,再由store给对应的组件。其中涉及到异步的操作会使用到redux-thunk
或redux-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
immer的produce高阶函数(https://immerjs.github.io/immer/docs/produce)
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
redux-persist白名单和黑名单(https://www.npmjs.com/package/redux-persist#blacklist--whitelist)
redux persists migrations(https://github.com/rt2zz/redux-persist/blob/master/docs/migrations.md)
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)
redux_toolkit(https://www.51hint.com/posts/redux_toolkit/)
代码实例仓库(https://gitee.com/srect/redux_toolkit)
项目结构
├── 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
Storeon(https://www.npmjs.com/package/storeon)
在 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. 写在最后
文中存在错误之处或有更好的建议等,请及时指出,谢谢。