对比React-Redux看看Redux Toolkit有哪些优点

2,863 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情

简介

Redux的使用一直是很多人在React开发中无法逃开的痛,各种中间件,各种配置,各种目录规范,让很多想尝试的人选择了放弃。不过Redux官方推出了Redux Toolkit这个库,肯定符合大家的胃口,可谓的完美验证了真香定律。借这次机会就简单的给大家普及一下这个库的使用。

我们知道在React中使用Redux需要借助React-ReduxReact-Redux我们知道,是用来连接ReactRedux的桥梁,但是Redux Toolkit又是干什么的呢?

Redux Toolkit简答总结就是一个Redux工具包,用来简化Redux操作的。

本文分为两部份,第一部分介绍传统的Redux、React-Redux的使用方式。第二部分我们引入Redux Toolkit,看看Redux Toolkit到底是怎样来简化我们Redux、React-Redux的使用。

React-Redux

我们平时使用React-Redux的时候一般分为以下七步。

  1. 安装
  2. 创建types
  3. 创建actions
  4. 创建reducers
  5. 创建store
  6. 将Redux连接到React
  7. 在React中使用

为方便理解,笔者例子文件总体目录结构如下:

store
    ├── actions
    │   ├── counterActions.js
    │   └── userSlice.js
    ├── reducers
    │   ├── counterReducer.js
    │   └── userReducer.js
    ├── types
    └── index.js

安装

首先我们需要安装redux react-redux两个包。

npm i redux react-redux

创建types

然后我们需要创建需要用到的types,并且需要保持每个type唯一性。

export const counterIncrementType = "counter/incremented";
export const counterDecrementType = "counter/decremented";

export const userNameIncrementType = "userName/incremented";
export const userNameDecrementType = "userName/decremented";
export const userAgeIncrementType = "userAge/incremented";
export const userAgeDecrementType = "userAge/decremented";

创建action

然后我们需要创建各模块的action

counterActions

import { counterIncrementType, counterDecrementType } from "../types";

export const counterIncrementAction = () => {
  return {
    type: counterIncrementType,
  };
};

export const counterDecrementAction = () => {
  return {
    type: counterDecrementType,
  };
};

userActions

import {
  userNameIncrementType,
  userNameDecrementType,
  userAgeIncrementType,
  userAgeDecrementType,
} from "../types";

export const userNameIncrementAction = () => {
  return {
    type: userNameIncrementType,
  };
};

export const userNameDecrementAction = () => {
  return {
    type: userNameDecrementType,
  };
};

export const userAgeIncrementAction = (payload) => {
  return {
    type: userAgeIncrementType,
    payload,
  };
};

export const userAgeDecrementAction = (payload) => {
  return {
    type: userAgeDecrementType,
    payload,
  };
};

创建 Reducer

然后需要创建我们处理statereducer,这里需要注意,不是直接修改state,而是每次返回的新的state

一般在项目中,为了保证每次返回的state都是全新的一般会搭配immutable-jsredux-immutable来使用。 counterReducer

import { counterIncrementType, counterDecrementType } from "../types";

export default function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case counterIncrementType:
      return { value: state.value + 1 };
    case counterDecrementType:
      return { value: state.value - 1 };
    default:
      return state;
  }
}

userReducer

import {
  userNameIncrementType,
  userNameDecrementType,
  userAgeIncrementType,
  userAgeDecrementType,
} from "../types";

export default function userReducer(
  state = { name: "randy", age: 24 },
  action
) {
  switch (action.type) {
    case userNameIncrementType:
      return { ...state, name: state.name + "!" };
    case userNameDecrementType:
      return { ...state, name: state.name.slice(0, state.name.length - 1) };
    case userAgeIncrementType:
      return { ...state, age: state.age + action.payload };
    case userAgeDecrementType:
      return { ...state, age: -action.payload };
    default:
      return state;
  }
}

合并Reducer

当我们系统庞大后就不可能只使用一个reducer,使用需要使用多个reducer

多个reducer使用combineReducers合并起来。

const redicers = combineReducers({
  user: userReducer,
  counter: counterReducer,
});

当然如果我们只有单个reducer的话,直接传递给combineReducers就可以了。

const redicers = combineReducers(reducer);

创建store

传递redicers来创建store

这里我们如果想用chrome插件来查看我们的Redux的话必须结合redux-devtools-extension来使用。

import { createStore, combineReducers } from "redux";
import counterReducer from "./reducers/counterReducer";
import userReducer from "./reducers/userReducer";

const redicers = combineReducers({
  counter: counterReducer,
  user: userReducer,
});

export default createStore(
  redicers,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

将Redux连接到React

import { Provider } from "react-redux";
import store from "./store";

<Provider store={store}>
  <App />
</Provider>

在React组件中使用

类组件我们使用connect

在类组件我们使用connectReactRedux连接起来,并通过mapStateToProps、mapDispatchToPropsstate和操作action的方法放到组件的props上。

在组件,我们使用this.props就能获取到这两个方法里面返回的属性。

import { userAgeIncrementAction } from "../store/actions/userActions";
import { counterIncrementAction } from "../store/actions/counterActions";

class Store extends React.Component{
  ...
}

// 从store 取 state 放到组件的props里面
const mapStateToProps = (state, ownProps) => {
  // 这里的state是store里面的state而不是组件的state
  // ownProps也是组件的props,不会包括这里返回的props
  console.log(state, ownProps);
  return {
    user: state.user,
    name: state.user.name,
    age: state.user.age,
    counter: state.counter.value,
  };
};

// 从store 取 dispatch 然后触发相应action,并把方法放到组件的props里面
const mapDispatchToProps = (dispatch, ownProps) => {
  console.log(ownProps);
  return {
    counterIncremented() {
      dispatch(counterIncrementAction());
    },
    // 传递参数
    userAgeIncremented() {
      dispatch(userAgeIncrementAction(10));
    },
  };
};

// 使用connect把React和Redux连接起来
export default connect(mapStateToProps, mapDispatchToProps)(Store);

函数组件我们使用useSelector、useDispatch

函数组件使用useSelector、useDispatch两个Hook来操作store

import { useSelector, useDispatch } from "react-redux";
import { userAgeIncrementAction } from "../store/actions/userActions";

funciton Store() {
  const counter = useSelector((state) => state.counter.value);
  const age = useSelector((state) => state.user.age);
  const dispatch = useDispatch();

  const userAgeIncremented = () => {
    dispatch(userAgeIncrementAction(5));
  };
  
  ...
}

异步操作需要借助 Redux-Thunk

普通action返回的是对象,引入Redux-Thunk后我们的action可以返回一个带dispatch参数的函数。

首先需要安装

npm i redux-thunk

然后作为中间件配置使用,这里是结合了redux-devtools-extension的使用。

import { createStore, applyMiddleware, compose } from "redux";

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(redicers, composeEnhancers(applyMiddleware(thunk)));

这样我们就可以进行异步操作啦,比如去后台请求数据。

export const userThunk = (payload) => {
  return async (dispatch) => {
    const res = await fetch( `https://jsonplaceholder.typicode.com/todos/${payload}` );
    const response = await res.json();
    // 异步请求完后,再触发同步action,更新state
    dispatch(userAgeIncrementAction(5));
  };
};

总体来说,在React中使用Redux是非常复杂的。下面我们使用Redux Toolkit来简化我们Redux的使用。

Redux Toolkit

使用Redux Toolkit我们可以简化为以下五步。

  1. 安装
  2. 创建slices
  3. 创建store
  4. 将Redux连接到React
  5. 在React中使用

为方便理解,笔者例子文件总体目录结构如下:

store
    ├── slices
    │   ├── counterSlice.js
    │   └── userSlice.js
    └── index.js

安装

首先我们需要安装redux react-redux @reduxjs/toolkit三个包。

npm i redux react-redux @reduxjs/toolkit

创建slices

现在的slices,就相当于以前的types、actions、reducers,以对象的形式存在,是不是一目了然。

并且我们不用再返回新state了,我们可以直接操作state,它自己内部实现了immutable的功能。

counterSlice

import { createSlice } from "@reduxjs/toolkit";

const counterSlice = createSlice({
  name: "counter",

  initialState: {
    value: 0,
  },

  reducers: {
    incremented: (state) => {
      state.value += 1;
    },
    decremented: (state) => {
      state.value -= 1;
    },
    add: (state, action) => {
      state.value += action.payload;
    },
  },
});

// 相当于以前的actions
export const { incremented, decremented, add } = counterSlice.actions;

// 相当于以前的reducers
export default counterSlice.reducer;

userSlice

import { createSlice } from "@reduxjs/toolkit";

const userSlice = createSlice({
  name: "user",

  initialState: {
    name: "randy",
    age: 24,
  },

  reducers: {
    nameIncrement: (state) => {
      state.name += "!";
    },
    nameDecrement: (state) => {
      state.name += state.name.slice(0, state.name.length - 1);
    },
    ageIncremented: (state, action) => {
      state.age += action.payload;
    },
    ageDecremented: (state, action) => {
      state.age -= action.payload;
    },
  },
});

// 相当于以前的actions
export const { nameIncrement, nameDecrement, ageIncremented, ageDecremented } =
  userSlice.actions;

// 相当于以前的reducers
export default userSlice.reducer;

创建store

@reduxjs/toolkit已经封装好了redux-devtools-extension,我们不用配置直接使用即可。

import { configureStore } from "@reduxjs/toolkit";
import counterSlice from "./slices/counterSlice";
import userSlice from "./slices/userSlice";

const store = configureStore({
  reducer: {
    counter: counterSlice,
    user: userSlice,
  },
});

export default store;

将Redux连接到React

Redux连接到React这一块没变化。

import { Provider } from "react-redux";
import store from "./store";

<Provider store={store}>
  <App />
</Provider>

在React组件中使用

使用方法基本没变,只是获取action变了,以前从actions里获取,现在是从slice里获取。

类组件我们使用connect

在类组件我们使用connectReactRedux连接起来,并通过mapStateToProps、mapDispatchToPropsstate和操作action的方法放到组件的props上。

import { incremented, decremented, add } from "../store/slices/counterSlice";
import { nameIncrement, ageIncremented } from "../store/slices/userSlice";

class Store extends React.Component{
  ...
}

const mapStateToProps = (state, preProps) => {
  return {
    counter: state.counter.value,
    userName: state.user.name,
    userAge: state.user.age,
  };
};

const mapDispatchToProps = (dispatch, preProps) => {
  return {
    handleIncremented() {
      dispatch(incremented());
    },
    handleDecremented() {
      dispatch(decremented());
    },
    handleAdd() {
      dispatch(add(10));
    },

    handleUserName() {
      dispatch(nameIncrement());
    },
    handleUserAge() {
      dispatch(ageIncremented(10));
    },
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(Store);

函数组件我们使用useSelector、useDispatch

函数组件使用useSelector、useDispatch两个Hook来操作store

import { useSelector, useDispatch } from "react-redux";
import { ageIncremented } from "../store/slices/userSlice";

funciton Store() {
  const counter = useSelector((state) => state.counter.value);
  const age = useSelector((state) => state.user.age);
  const dispatch = useDispatch();

  const userAgeIncremented = () => {
    dispatch(ageIncremented(5));
  };
  
  ...
}

异步操作可以直接使用Redux-Thunk

@reduxjs/toolkit已经内置了Redux-Thunk,不需要另外安装和配置。

我们只需要使用@reduxjs/toolkitcreateAsyncThunk就能完成异步action的创建。

import { createAsyncThunk } from "@reduxjs/toolkit";

// 创建异步action
export const userThunk = createAsyncThunk(
  "user/thunk",
  async (payload, thunkAPI) => {
    console.log(payload);
    const res = await fetch(
      `https://jsonplaceholder.typicode.com/todos/${payload}`
    );
    const response = await res.json();
    // 可以调用普通action来修改state
    thunkAPI.dispatch(ageIncremented(8));
    // 返回值会作为action返回对象payload属性的值。
    return response;
  }
);

createAsyncThunk创建成功后,return出去的值,会在extraReducers中接收,有三种状态:

  1. pending: 运行中;
  2. fulfilled: 完成;
  3. rejected: 拒绝;

我们还可以在这里面把我们异步请求的数据存放到state中。

extraReducers: (builder) => {
  builder
    .addCase(userThunk.pending, (state, action) => {
      console.log(state, action);
    })
    .addCase(userThunk.fulfilled, (state, action) => {
      console.log(state, action);
      // state.lists = action.payload // 数据存储到state
    })
    .addCase(userThunk.rejected, (state, action) => {
      console.log(state, action);
    });
},

如果不想存放到state里,而是想直接获取也是可以的。createAsyncThunk第二个函数的返回值也会作为action返回对象payload属性的值。

import { userThunk } from "../store/slices/userSlice";

...

const mapDispatchToProps = (dispatch, preProps) => {
  return {
    // thunk
    async handleUserThunk() {
      // 这里也是能获取到异步请求的结果的。
      const res = await dispatch(userThunk(1));
      console.log(res); // {meta: xxx, payload: userThunk的返回值response, type: "user/thunk/fulfilled"}
    },
  };
};

本文只是简单介绍了@reduxjs/toolkit的使用,算是一个入门吧,详细使用可以查看Redux Toolkit 官方文档

总结

Redux Toolkit优点:

  1. Redux Toolkit已经集成了redux-devtools-extension,不需要额外配置,非常方便。

  2. Redux Toolkit已经集成了immutable-js的功能,不需要我们额外安装配置使用,大大提升了开发效率。

  3. Redux Toolkit已经集成了redux-thunk的功能,不需要我们额外安装配置使用,大大提升了开发效率。

  4. Redux Toolkittypes、actions、reducers放在了一起组成全新的slices一目了然简单易懂,简化了我们的使用。

参考文档

Redux Toolkit 官方文档

系列文章

React-Router6路由新特性(React-Router4/5和React-Router6对比总结)

后记

感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!