Skip to content

React 状态管理 ​

一、Redux ​

安装 ​

bash
yarn add redux react-redux
yarn 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 store
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 store

store 四个方法 ​

jsx
store.dispatch(action)       // 触发修改
store.subscribe(listener)    // 监听变化
store.getState()             // 获取状态
store.replaceReducer()       // 替换 reducer
store.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.js
store/
  index.js         — 创建 store
  reducer.js       — 根 reducer
  type.js          — action type 常量
  action.js        — action creator
  modules/         — 模块
    home.js
    shop.js

combineReducers ​

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-thunk
npm i redux-thunk
jsx
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-logger
npm i redux-logger
jsx
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-persist
yarn add redux-persist
jsx
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/core
npm install @rematch/core

Model ​

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 store
import { 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 })