React 状态管理
一、Redux
安装
bash
yarn add redux react-reduxyarn add redux react-redux为什么需要 Redux
组件之间存在大量通信,跨越多个组件或多个组件共享一套数据时,简单的父子传值不能满足需求。Redux 提供了一种管理公共状态的方案。
核心概念
- Store — 单一数据源,整个应用只有一个 store
- Reducer — 纯函数,接收 state 和 action,返回新 state
- Action — 描述状态变化的对象
二、创建 Store
store/index.js
jsx
import { createStore } from 'redux'
const initialState = { count: 0 }
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'add':
return { ...state, count: action.count }
default:
return state
}
}
const store = createStore(reducer)
export default storeimport { createStore } from 'redux'
const initialState = { count: 0 }
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'add':
return { ...state, count: action.count }
default:
return state
}
}
const store = createStore(reducer)
export default storestore 四个方法
jsx
store.dispatch(action) // 触发修改
store.subscribe(listener) // 监听变化
store.getState() // 获取状态
store.replaceReducer() // 替换 reducerstore.dispatch(action) // 触发修改
store.subscribe(listener) // 监听变化
store.getState() // 获取状态
store.replaceReducer() // 替换 reducer三、连接 React
Provider(入口文件)
jsx
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)import { Provider } from 'react-redux'
import store from './store'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)connect
jsx
import { connect } from 'react-redux'
// mapStateToProps — 将 state 映射为 props
// mapDispatchToProps — 将 dispatch 映射为 props
connect(mapStateToProps, mapDispatchToProps)(Component)
// 示例
const mapStateToProps = state => ({ count: state.count })
const mapDispatchToProps = (dispatch, ownProps) => ({
add: () => dispatch({ type: 'add' })
})
export default connect(mapStateToProps, mapDispatchToProps)(App)import { connect } from 'react-redux'
// mapStateToProps — 将 state 映射为 props
// mapDispatchToProps — 将 dispatch 映射为 props
connect(mapStateToProps, mapDispatchToProps)(Component)
// 示例
const mapStateToProps = state => ({ count: state.count })
const mapDispatchToProps = (dispatch, ownProps) => ({
add: () => dispatch({ type: 'add' })
})
export default connect(mapStateToProps, mapDispatchToProps)(App)bindActionCreators
jsx
import { bindActionCreators } from 'redux'
const mapDispatchToProps = (dispatch) => ({
fn: bindActionCreators(actions, dispatch)
// fn.changeName = name => dispatch(actions.changeName(name))
})import { bindActionCreators } from 'redux'
const mapDispatchToProps = (dispatch) => ({
fn: bindActionCreators(actions, dispatch)
// fn.changeName = name => dispatch(actions.changeName(name))
})四、Store 拆分
目录结构
store/
index.js — 创建 store
reducer.js — 根 reducer
type.js — action type 常量
action.js — action creator
modules/ — 模块
home.js
shop.jsstore/
index.js — 创建 store
reducer.js — 根 reducer
type.js — action type 常量
action.js — action creator
modules/ — 模块
home.js
shop.jscombineReducers
jsx
import { combineReducers } from 'redux'
import login from './login'
import bread from './bread'
export default combineReducers({
login,
bread
})import { combineReducers } from 'redux'
import login from './login'
import bread from './bread'
export default combineReducers({
login,
bread
})模块化
jsx
// store/modules/home.js
const initState = { name: 'home' }
const reducer = (state = initState, action) => {
switch (action.type) {
default: return state
}
}
// selector(类似 vuex getters)
export const getName = (state) => state.home.name
export default reducer// store/modules/home.js
const initState = { name: 'home' }
const reducer = (state = initState, action) => {
switch (action.type) {
default: return state
}
}
// selector(类似 vuex getters)
export const getName = (state) => state.home.name
export default reducer注意:
- selector 使用
state.模块名.状态名 - type 前加上模块名前缀避免冲突
五、Redux 异步处理
redux-thunk
bash
npm i redux-thunknpm i redux-thunkjsx
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(reducer, applyMiddleware(thunk))import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(reducer, applyMiddleware(thunk))异步 action:
jsx
export const increment = (count) => (dispatch, getState) => {
console.log('当前state:', getState())
dispatch({ type: 'add', count })
return new Promise((resolve) => {
resolve({ code: 200 })
})
}export const increment = (count) => (dispatch, getState) => {
console.log('当前state:', getState())
dispatch({ type: 'add', count })
return new Promise((resolve) => {
resolve({ code: 200 })
})
}redux-logger 日志
bash
npm i redux-loggernpm i redux-loggerjsx
import logger from 'redux-logger'
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk, logger)))
// logger 放在后面import logger from 'redux-logger'
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk, logger)))
// logger 放在后面六、Redux DevTools
jsx
import { composeWithDevTools } from 'redux-devtools-extension'
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))import { composeWithDevTools } from 'redux-devtools-extension'
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))七、数据持久化(redux-persist)
bash
yarn add redux-persistyarn add redux-persistjsx
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // localStorage
// import storageSession from 'redux-persist/lib/storage/session' // sessionStorage
const persistConfig = {
key: 'root',
storage,
blacklist: ['name'] // 不持久化的 reducer
}
const persistedReducer = persistReducer(persistConfig, reducer)
const store = createStore(persistedReducer)
export const persistor = persistStore(store)import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // localStorage
// import storageSession from 'redux-persist/lib/storage/session' // sessionStorage
const persistConfig = {
key: 'root',
storage,
blacklist: ['name'] // 不持久化的 reducer
}
const persistedReducer = persistReducer(persistConfig, reducer)
const store = createStore(persistedReducer)
export const persistor = persistStore(store)入口文件:
jsx
import { PersistGate } from 'redux-persist/integration/react'
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>import { PersistGate } from 'redux-persist/integration/react'
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>八、Redux Hooks(react-redux 7.1+)
useSelector
jsx
import { useSelector, shallowEqual } from 'react-redux'
const count = useSelector(state => state.count)
// 复杂取值的性能优化
const data = useSelector(
state => ({ title: state.title, content: state.content }),
shallowEqual
)import { useSelector, shallowEqual } from 'react-redux'
const count = useSelector(state => state.count)
// 复杂取值的性能优化
const data = useSelector(
state => ({ title: state.title, content: state.content }),
shallowEqual
)useDispatch
jsx
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
const handleClick = useCallback(() => dispatch({ type: '' }), [dispatch])import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
const handleClick = useCallback(() => dispatch({ type: '' }), [dispatch])useStore
jsx
import { useStore } from 'react-redux'
const store = useStore()
store.getState()import { useStore } from 'react-redux'
const store = useStore()
store.getState()九、createStore 原理
js
export function createStore(reducer, enhance) {
if (enhance) {
return enhance(createStore)(reducer)
}
let currentStore
let currentHandle = []
function getState() { return currentStore }
function dispatch(action) {
currentStore = reducer(currentStore, action)
currentHandle.forEach(v => v())
return action
}
function subscribe(fn) { currentHandle.push(fn) }
dispatch({ type: '@@INIT' })
return { getState, dispatch, subscribe }
}export function createStore(reducer, enhance) {
if (enhance) {
return enhance(createStore)(reducer)
}
let currentStore
let currentHandle = []
function getState() { return currentStore }
function dispatch(action) {
currentStore = reducer(currentStore, action)
currentHandle.forEach(v => v())
return action
}
function subscribe(fn) { currentHandle.push(fn) }
dispatch({ type: '@@INIT' })
return { getState, dispatch, subscribe }
}applyMiddleware 原理
js
export function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
let dispatch = store.dispatch
const middlewareAPI = {
getState: store.getState,
dispatch
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(dispatch)
return { ...store, dispatch }
}
}export function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
let dispatch = store.dispatch
const middlewareAPI = {
getState: store.getState,
dispatch
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(dispatch)
return { ...store, dispatch }
}
}十、中间件
js
const middleware = (store) => (next) => (action) => {
// ...逻辑
next(action)
}
// 自定义日志中间件
export const logAction = (store) => (next) => (action) => {
console.log('当前状态:', store.getState())
console.log('本次action:', action)
next(action)
console.log('修改后状态:', store.getState())
}const middleware = (store) => (next) => (action) => {
// ...逻辑
next(action)
}
// 自定义日志中间件
export const logAction = (store) => (next) => (action) => {
console.log('当前状态:', store.getState())
console.log('本次action:', action)
next(action)
console.log('修改后状态:', store.getState())
}十一、Dva
基于 redux 和 redux-saga 的数据流方案,内置 react-router 和 fetch
Model
js
export default {
namespace: 'list', // 模块名
state: [], // 初始状态
reducers: { // 同步 reducer
delete(state, { payload }) {
return state
}
},
effects: { // 异步处理
*deleteList({ payload }, { put, call, select }) {
const list = yield select(state => state.list)
yield call(addTodo, payload)
yield put({ type: 'delete', payload })
}
},
subscriptions: { // 订阅
setup({ dispatch, history }) {
history.listen((location) => {})
}
}
}export default {
namespace: 'list', // 模块名
state: [], // 初始状态
reducers: { // 同步 reducer
delete(state, { payload }) {
return state
}
},
effects: { // 异步处理
*deleteList({ payload }, { put, call, select }) {
const list = yield select(state => state.list)
yield call(addTodo, payload)
yield put({ type: 'delete', payload })
}
},
subscriptions: { // 订阅
setup({ dispatch, history }) {
history.listen((location) => {})
}
}
}connect(从 dva 引入)
jsx
import { connect } from 'dva'
const mapStateToProps = (state) => ({
list: state.list
})
export default connect(mapStateToProps)(Home)import { connect } from 'dva'
const mapStateToProps = (state) => ({
list: state.list
})
export default connect(mapStateToProps)(Home)jsx
dispatch({ type: 'home/reqData' }) // '命名空间/reducer'dispatch({ type: 'home/reqData' }) // '命名空间/reducer'路由
jsx
import { routerRedux } from 'dva/router'
// effects 中跳转
yield put(routerRedux.push('/logout'))import { routerRedux } from 'dva/router'
// effects 中跳转
yield put(routerRedux.push('/logout'))十二、Rematch
更简洁的 Redux,类似 Vuex 写法
安装
bash
npm install @rematch/corenpm install @rematch/coreModel
js
export default {
state: { count: 0 },
reducers: {
increment(state, payload) {
return { ...state, count: state.count + payload }
}
},
effects: (dispatch) => ({
async incrementAsync(payload) {
await new Promise(resolve => setTimeout(resolve, 2000))
this.increment(payload)
}
})
}export default {
state: { count: 0 },
reducers: {
increment(state, payload) {
return { ...state, count: state.count + payload }
}
},
effects: (dispatch) => ({
async incrementAsync(payload) {
await new Promise(resolve => setTimeout(resolve, 2000))
this.increment(payload)
}
})
}创建 store
js
import { init } from '@rematch/core'
import demo from './demo'
const store = init({ models: { demo } })
export default storeimport { init } from '@rematch/core'
import demo from './demo'
const store = init({ models: { demo } })
export default store使用
jsx
import { Provider } from 'react-redux'
import { dispatch } from '@rematch/core'
dispatch.count.increment(1)
// 或 dispatch({ type: 'count/increment', payload: 1 })import { Provider } from 'react-redux'
import { dispatch } from '@rematch/core'
dispatch.count.increment(1)
// 或 dispatch({ type: 'count/increment', payload: 1 })
Cao Note