React Redux 中触发异步副作用

这篇博客介绍了在ReactRedux中如何处理异步副作用,包括组件内直接操作和使用custom action creator。通过示例展示了如何在`useEffect`中调用异步API并更新UI状态,以及创建自定义action creator来封装共用逻辑,减少重复代码。这种方式适用于多个组件间需要共享相同API调用和UI反馈的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

React Redux 中触发异步副作用

一些基本的配置(这里使用 toolkit)可以在这篇笔记中找到:react-redux 使用小结,这里不多赘述。

触发副作用主流的操作方式有两种:

  1. 组件内操作

    适合只会在当前组件中触发的 API 操作

  2. 写一个 action creator 进行操作

    适合跨组件操作

二者并没有谁好谁坏的区别,主要还是依赖于业务需求

组件内触发

cart slice 的实现如下:

import { createSlice } from '@reduxjs/toolkit';
import { uiActions } from './ui-slice';

const cartSlice = createSlice({
  name: 'cart',
  initialState: {
    items: [],
    totalQuantity: 0,
    totalAmount: 0,
  },
  reducers: {
    addItemToCart(state, action) {
      const newItem = action.payload;
      const existingItem = state.items.find((item) => item.id === newItem.id);
      state.totalQuantity++;
      if (!existingItem) {
        state.items.push({
          id: newItem.id,
          price: newItem.price,
          quantity: 1,
          total: newItem.price,
          title: newItem.title,
        });
        return;
      }

      existingItem.quantity++;
      existingItem.total += existingItem.price;
    },
    removeItemFromCart(state, action) {
      state.totalQuantity--;
      const id = action.payload;
      const existingItem = state.items.find((item) => item.id === id);
      if (existingItem.quantity === 1) {
        state.items = state.items.filter((item) => item.id !== id);
        return;
      }
      existingItem.quantity--;
      existingItem.total -= existingItem.price;
    },
  },
});

export const cartActions = cartSlice.actions;

export default cartSlice;

这个就相当于一个比较基本的购物车功能

UIslice:

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

const uiSlice = createSlice({
  name: 'ui',
  initialState: {
    cartIsVisible: false,
    notification: null,
  },
  reducers: {
    toggle(state) {
      state.cartIsVisible = !state.cartIsVisible;
    },
    showNotification(state, action) {
      state.notification = {
        status: action.payload.status,
        title: action.payload.title,
        message: action.payload.message,
      };
    },
  },
});

export const uiActions = uiSlice.actions;

export default uiSlice;

这就是一些 UI 相关的操作。

app.js:

import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { useSelector } from 'react-redux';
import Cart from './components/Cart/Cart';
import Layout from './components/Layout/Layout';
import Products from './components/Shop/Products';
import Notification from './components/UI/Notification';
import { uiActions } from './store/ui-slice';

// to avoid empty data being sent during initial mount
let isInitial = true;

function App() {
  const dispatch = useDispatch();
  const { cartIsVisible, notification } = useSelector((state) => state.ui);
  const cart = useSelector((state) => state.cart);

  useEffect(() => {
    const sendCartData = async () => {
      dispatch(
        uiActions.showNotification({
          status: 'pending',
          title: 'sending',
          message: 'Sending cart data',
        })
      );
      const res = await fetch('some api here', {
        method: 'PUT',
        body: JSON.stringify(cart),
      });

      if (!res.ok) {
        throw new Error('Sending cart data failed.');
      }

      dispatch(
        uiActions.showNotification({
          status: 'success',
          title: 'Success...',
          message: 'Sent cart data successfully.',
        })
      );
    };

    if (isInitial) {
      isInitial = false;
      return;
    }

    sendCartData().catch((error) => {
      dispatch(
        uiActions.showNotification({
          status: 'error',
          title: 'Error...',
          message: 'Sending cart data failed.',
        })
      );
    });
  }, [cart, dispatch]);

  return (
    <>
      {notification && (
        <Notification
          status={notification.status}
          title={notification.title}
          message={notification.message}
        />
      )}
      <Layout>
        {cartIsVisible && <Cart />}
        <Products />
      </Layout>
    </>
  );
}

export default App;

实现上来说如下:

在这里插入图片描述

这里的一些流程:

|- useEffect
|   |- sendCartData(async call)
|   |   |- multiple dispatches

这个就是比较直接的操作,即在 useEffect 中调用异步操作,并且在 thenable 中进行结果的处理(这里就是 redux 的触发)。

custom action creator

之前直接利用 redux toolkit 写的 action 如下:

在这里插入图片描述

这里就是在 slice 外写了一个 customer action creator,也就是一个 thunk。实现方式,就像是在 toolkit 出来之前就要手动写很多的 action 这种感觉。

thunk 的定义如下:

a function that delays an action until later

一个延迟触发 action 的函数

当然,thunk 之类的生态圈已经发展了很多年了(在 toolkit 之前就有了),比如说比较老牌的 Redux Thunk,相对而言比较新一些的 redux-saga,它们都已经在市面上运行的比较稳定,而且周下载量都很大:

在这里插入图片描述

![在这里插入图片描述](https://img-blog.csdnimg.cn/fc54633351ef4525afbc715d51d82df5

很多时候可以根据业务需求进行配置,比如说项目比较简单,又没有现成的脚手架,那么现成的 toolkit 的功能说不定就够了。如果业务需求比较复杂,那么可以考虑使用 thunk 或是 saga。

这里因为是对于购物车的操作,所以 custom action creator 会放在 cart slice 中:

import { createSlice } from '@reduxjs/toolkit';
import { uiActions } from './ui-slice';

const cartSlice = createSlice({
  name: 'cart',
  initialState: {
    items: [],
    totalQuantity: 0,
    totalAmount: 0,
  },
  reducers: {
    addItemToCart(state, action) {
      const newItem = action.payload;
      const existingItem = state.items.find((item) => item.id === newItem.id);
      state.totalQuantity++;
      if (!existingItem) {
        state.items.push({
          id: newItem.id,
          price: newItem.price,
          quantity: 1,
          total: newItem.price,
          title: newItem.title,
        });
        return;
      }

      existingItem.quantity++;
      existingItem.total += existingItem.price;
    },
    removeItemFromCart(state, action) {
      state.totalQuantity--;
      const id = action.payload;
      const existingItem = state.items.find((item) => item.id === id);
      if (existingItem.quantity === 1) {
        state.items = state.items.filter((item) => item.id !== id);
        return;
      }
      existingItem.quantity--;
      existingItem.total -= existingItem.price;
    },
  },
});

// custom action creator
export const sendCartData = (cart) => {
  return async (dispatch) => {
    dispatch(
      uiActions.showNotification({
        status: 'pending',
        title: 'sending',
        message: 'Sending cart data',
      })
    );

    const sendRequest = async () => {
      const res = await fetch('some api here', {
        method: 'PUT',
        body: JSON.stringify(cart),
      });

      if (!res.ok) {
        throw new Error('Sending cart data failed.');
      }
    };

    try {
      await sendRequest();
      dispatch(
        uiActions.showNotification({
          status: 'success',
          title: 'Success...',
          message: 'Sent cart data successfully.',
        })
      );
    } catch (e) {
      dispatch(
        uiActions.showNotification({
          status: 'error',
          title: 'Error...',
          message: 'Sending cart data failed.',
        })
      );
    }
  };
};

export const cartActions = cartSlice.actions;

export default cartSlice;

app.js 中的代码:

import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { useSelector } from 'react-redux';
import Cart from './components/Cart/Cart';
import Layout from './components/Layout/Layout';
import Products from './components/Shop/Products';
import Notification from './components/UI/Notification';
import { sendCartData } from './store/cart-slice';

let isInitial = true;

function App() {
  const dispatch = useDispatch();
  const { cartIsVisible, notification } = useSelector((state) => state.ui);
  const cart = useSelector((state) => state.cart);

  useEffect(() => {
    if (isInitial) {
      isInitial = false;
      return;
    }

    dispatch(sendCartData(cart));
  }, [cart, dispatch]);

  return (
    <>
      {notification && (
        <Notification
          status={notification.status}
          title={notification.title}
          message={notification.message}
        />
      )}
      <Layout>
        {cartIsVisible && <Cart />}
        <Products />
      </Layout>
    </>
  );
}

export default App;

可以看到,本质上来说,custom action creator 中的代码基本上就是将组件内部的代码移到了另一个地方进行中心化处理。这样的优点比较多,比如说 e-commerce 的项目来说,在每个商品详情页面中可以进行购物车的操作,也可以单独到购物车的页面进行操作,或是鼠标悬浮到购物车,都会弹出一个下拉框进行简单的操作等。

这样粗略一算就会有 3 个地方都会用到同样的 API 调用,同样的 UI 提示,这个情况下就可以考虑将这部分的代码封装到 custom action creator 中,减少重复代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值