React中使用redux的方法

187 阅读6分钟

为什么需要状态管理,方便组件间的传参,页面也由静态页面变为动态页面.可以更好的处理异步逻辑.

Redux核心概念

image.png

可以理解为在页面中点击按钮,然后通过逻辑判断对应的操作,在Store进行对应的逻辑操作,完成操作后通知页面更新.页面也可以直接读取store中的状态,直接进行显示.是一个单向数据流的显示.

redux中间件

  • redux 中间件可以对数据进行追溯(可预测、集中管理、可调式)
  • 中间件即为中间的处理函数,接收上一个中间件的处理结果
  • 核心库中的中间件是同步的,有了中间件可以支持异步

常见的redux中间件

  • redux-thunk: 增加函数类型的action
  • redux-promise: 流程控制,reject之后不再执行之后逻辑
  • redux-sage: 独立管理副作用(高效、可控)

redux-thunk地址

redux 与 redux-thunk结合使用

# 安装包
npm install react-redux
npm install redux
npm install redux-thunk

分别创建以下三个文件:store.js、reducer.js、actios.js

以下是简易案例

//reducer.js
const initialState = {
  count: 0
};

export default 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;
  }
}
//action.js
//增加
export const increment = () => ({
  type: 'INCREMENT'
});
//异步增加
export const incrementAsync = () => {
  return (dispatch) => {
    setTimeout(() => {
      dispatch(increment());
    }, 2000);
  };
};
//减少
export const decrement = () => ({
  type: 'DECREMENT'
});
//store.js
//增加处理中间件方法applyMiddleware
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';
export default createStore(reducer, applyMiddleware(thunk));

页面中的使用

//App.jsx
import React from 'react';
import store from './store';
//useSelector 获取store的数据
//useDispatch 执行状态管理中的方法
//Provider 传递store中的数据
import { Provider, useDispatch, useSelector } from 'react-redux';
import { increment, decrement, incrementAsync } from './action';
function Counter(props) {
  const count = useSelector((state) => state.count);
  const dispatch = useDispatch();
  return (
    <>
      <h3>{count}</h3>
      <button
        onClick={() => {
          dispatch(incrementAsync());
        }}
      >
        +1
      </button>
      <button
        onClick={() => {
          dispatch(decrement());
        }}
      >
        -1
      </button>
    </>
  );
}

function App() {
  return (
    <Provider store={store}>
      <div>adasd</div>
      <Counter />
    </Provider>
  );
}
export default App;

手写中间件案例

import { createStore, applyMiddleware } from 'redux';
function logger(store) {
  return function (next) {
    return function (action) {
      console.log('当前的state', store.getState());
      console.log('触发的action', action);
      //不执行next将不会执行action中的相关方法
      next(action);
      console.log('next之后的state', store.getState());
    };
  };
}
export default createStore(reducer, applyMiddleware(logger));

redux 与 redux-sage结合使用

redux-saga地址 redux-saga官网

redux-sage理解

  • redux sage是redux的中间件 ,管理副作用(异步操作,浏览器缓存操作)
  • redux sage使用了generator语法,不是主流的async/await
  • redux sage设计了一些列的概念,有学习的门槛

redux sage应用场景

  • 相比于redux thunk, 封装更进一层, 流程可控
  • 集中处理所有的异步操作
  • 以同步的方式来书写异步的代码
# 安装依赖
npm install redux-saga

分别创建以下三个文件:store.js、reducer.js、sagas.js 以下是简易案例

//reducer.js

const initialState = {
  count: 0
};

export default 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;
  }
}
//sagas.js

import { delay, put, takeEvery } from 'redux-saga/effects';
function* incrementAsync(action) {
  try {
    yield delay(2000);
    yield put({ type: 'INCREMENT' });
  } catch (error) {}
}

function* decrementAsync(action) {
  try {
    yield delay(2000);
    yield put({ type: 'DECREMENT' });
  } catch (error) {}
}

export default function* rootSaga() {
  yield takeEvery('INCREMENT_ASYNC', incrementAsync);
  yield takeEvery('DECREMENT_ASYNC', decrementAsync);
}

//store.js
import { createStore, applyMiddleware } from 'redux';
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas';
const sagaMiddleware = createSagaMiddleware();

function logger(store) {
  return function (next) {
    return function (action) {
      console.log('当前的state', store.getState());
      console.log('触发的action', action);
      next(action);
      console.log('next之后的state', store.getState());
    };
  };
}

export default createStore(reducer, applyMiddleware(sagaMiddleware, logger));
sagaMiddleware.run(rootSaga);

页面中的使用

import React from 'react';
import store from './store';
import { Provider, useDispatch, useSelector } from 'react-redux';
function Counter(props) {
  const count = useSelector((state) => state.count);
  const dispatch = useDispatch();
  const action = (type) => dispatch({ type });
  return (
    <>
      <h3>{count}</h3>
      <button
        onClick={() => {
          action('INCREMENT_ASYNC');
        }}
      >
        +1
      </button>
      <button
        onClick={() => {
          action('DECREMENT_ASYNC');
        }}
      >
        -1
      </button>
    </>
  );
}

function App() {
  return (
    <Provider store={store}>
      <div>adasd</div>
      <Counter />
    </Provider>
  );
}
export default App;

redux toolkit

为什么使用redux toolkit

  • redux的项目会有很多模板代码, 还可能写错、冲突等
  • redux的store过于复杂,需要比较清晰的目录结构
  • redux需要配合immer、thunk、reselect...
  • redux团队官方插件
  • 提供架构思想: 合并reducer&action , 自动异步逻辑
  • 代码简洁高效,并自带一些列整合好的功能

redux中文网

#安装
npm install @reduxjs/toolkit

分别创建以下三个文件:store.js、reducer.js、action.js 以下是简易案例

//action.js
import { createAction } from '@reduxjs/toolkit';

export const increment = createAction('INCREMENT');
export const decrement = createAction('DECREMENT');
//reducer.js
import { createReducer } from '@reduxjs/toolkit';
import { increment, decrement } from './action';

const initialState = {
  count: 0
};

const todosReducer = createReducer(initialState, (builder) => {
  builder
    .addCase(increment, (state, action) => {
      state.count += action.payload || 1;
    })
    .addCase(decrement, (state, action) => {
      state.count -= 1;
    })
    .addDefaultCase((state, action) => {
      state.count = 0;
    });
});

export default todosReducer;
//store.js
import { createStore, applyMiddleware } from 'redux';

import reducer from './reducer';

function logger(store) {
  return function (next) {
    return function (action) {
      console.log('当前的state1', store.getState());
      console.log('触发的action2', action);
      next(action);
      console.log('next之后的state3', store.getState());
    };
  };
}

export default createStore(reducer, applyMiddleware(logger));

页面中的使用

import React from 'react';
import store from './store';
import { Provider, useDispatch, useSelector } from 'react-redux';
import { increment, decrement } from './action';
function Counter(props) {
  const count = useSelector((state) => state.count);
  const dispatch = useDispatch();
  return (
    <>
      <h3>{count}</h3>
      <button
        onClick={() => {
          dispatch(increment());
        }}
      >
        +1
      </button>
      <button
        onClick={() => {
          dispatch(decrement());
        }}
      >
        -1
      </button>
    </>
  );
}

function App() {
  return (
    <Provider store={store}>
      <div>adasd</div>
      <Counter />
    </Provider>
  );
}
export default App;

项目实战案例

#安装需要使用到的插件
#状态管理:react-redux redux
#中间件:redux-thunk(简化使用)
#数据持久化:redux-persist
yarn add react-redux redux redux-persist redux-thunk

在项目中新建store文件夹,用于配置状态管理

image.png

文件夹/文件描述
store/config/action设置操作的方法
store/config/actionTypes定义类型
store/config/reducer数据
store/getters/config定义获取存储在store中的数据
store/getters/index统一导出数据
store/index状态管理配置入口
store/action统一导出各个模块的action文件

以下将以config目录下的配置文件做为展示

// store/config/actionTypes.ts

//水印控制
const WATER_MASK = "WATER_MASK"
//面包屑控制
const BREAD_CRUMB = 'BREAD_CRUMB'
//标签栏控制
const TAB = 'TAB'
//主题色
const PRIMARY_COLOR = 'PRIMARY_COLOR'
//导航模式
const NAVIGATION_MODE = 'NAVIGATION_MODE'

export {
  WATER_MASK,
  BREAD_CRUMB,
  TAB,
  PRIMARY_COLOR,
  NAVIGATION_MODE
}

// store/config/action.ts

///<reference path="../../model/store/config.ts"/>
import * as ActionTypes from '@/store/config/actionTypes'

/**
 * 设置水印
 * @param {boolean} waterMask
 */
export const setWaterMask = (waterMask:boolean) => ({
  type:ActionTypes.WATER_MASK,
  waterMask
})

/**
 * 设置面包屑
 * @param {boolean} breadCrumb
 */
export const setBreadCrumb = (breadCrumb:boolean) => ({
    type: ActionTypes.BREAD_CRUMB,
    breadCrumb
})

/**
 * 设置标签栏
 * @param {boolean} tab
 */
export const setTab = (tab:boolean) => ({
  type:ActionTypes.TAB,
  tab
})

/**
 * 设置主题色
 * @param {string} primaryColor
 */
export const setPrimaryColor = (primaryColor:string) => ({
  type:ActionTypes.PRIMARY_COLOR,
  primaryColor
})

/**
 * 设置导航模式
 * @param {ModelStoreConfig.Navigation} navigationMode
 */
export const setNavigationMode = (navigationMode:ModelStoreConfig.Navigation) => ({
  type:ActionTypes.NAVIGATION_MODE,
  navigationMode
})

// store/config/reducer.ts

///<reference path="../../model/store/config.ts"/>
import * as actionTypes from '@/store/config/actionTypes'
import * as globalConfig from "@/config/global_config"
interface ModelStoreConfig {
  waterMask:boolean, //水印
  breadCrumb:boolean, // 面包屑
  tab:boolean, //标签栏
  diyLayout:boolean //自定义布局
  openColor:boolean // 自定义颜色
  primaryColor:string // 主题色
  navigationMode:ModelStoreConfig.Navigation // 导航模式
}

const config:ModelStoreConfig = {
  waterMask:globalConfig.waterMask,
  breadCrumb:globalConfig.breadCrumb,
  tab:globalConfig.tab,
  diyLayout:globalConfig.diyLayout,
  openColor:globalConfig.openColor,
  primaryColor:globalConfig.primaryColor,
  navigationMode:globalConfig.navigationMode
}

export default function reducer(state = config,action:any){
  const {type,waterMask,breadCrumb,tab,primaryColor,navigationMode} = action

  switch (type) {
    case actionTypes.WATER_MASK:
      return {...state,waterMask}
    case actionTypes.BREAD_CRUMB:
      return {...state,breadCrumb}
    case actionTypes.TAB:
      return {...state,tab}
    case actionTypes.PRIMARY_COLOR:
      return {...state,primaryColor}
    case actionTypes.NAVIGATION_MODE:
      return {...state,navigationMode}
  }
  return state
}

配置getters实现类似于双向数据获取

// store/getters/config.ts

/**
 * 获取水印
 * @param state
 */
export const getWaterMask = (state:any) => state.config.waterMask

/**
 * 获取面包屑
 * @param state
 */
export const getBreadCrumb = (state:any) => state.config.breadCrumb

/**
 * 获取标签栏
 * @param state
 */
export const getTab = (state:any) => state.config.tab

/**
 * 获取主题色
 * @param state
 */
export const getPrimaryColor = (state:any) => state.config.primaryColor

/**
 * 获取导航模式
 * @param state
 */
export const getNavigationMode = (state:any) => state.config.navigationMode
// store/getters/index.ts

export * from "./config"

配置入口

// store/index.ts

import {
  createStore,
  combineReducers,
  applyMiddleware,
  compose
} from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import UserReducer from './user/reducer'
import RouteReducer from './route/reducer'
import NotPersistenceReducer from './notPersistence/reducer'
import ConfigReducer from './config/reducer'
import thunk from 'redux-thunk'

//整合各个模块的数据
const reducer = combineReducers({
  user: UserReducer,
  route: RouteReducer,
  notPersistence: NotPersistenceReducer,
  config: ConfigReducer
})

//配置数据持久化
const persistConfig = {
  key: 'root', // localstorage存储的key名为 root
  storage, // 以localstorage方式存储
  whitelist: ['user', 'route', 'config'] // whitelist 意思是只对这几个文件做持久化的处理
}

//整合后的数据
const myReducer = persistReducer(persistConfig, reducer)

const store = createStore(
  myReducer,
  //使用compose 整合多个方法,类似于数组. 使用applyMiddleware 利用中间件,优化调用redux的使用
  compose(
    applyMiddleware(thunk),
    //开启redux的调试工具栏
    window.__REDUX_DEVTOOLS_EXTENSION__ &&
      window.__REDUX_DEVTOOLS_EXTENSION__()
  )
)

const persistor = persistStore(store)
export { store, persistor }

页面的使用

import { useSelector, useDispatch } from 'react-redux'
import { setWaterMask  } from "@/store/action"
import { getWaterMask } from "@/store/getters"

const ThemeContainer = () => {
    //useSelector 获取redux数据
    //useDispatch 执行redux的方法
    const dispath = useDispatch()
    return (
        <>
           <div>是否显示水印:{useSelector(getWaterMask)}</div>
           <button onClick={() => dispatch(setWaterMask(!useSelector(getWaterMask)))}>修改水印显示</button>
        </>
    )
}

export default ThemeContainer

感谢阅读!