redux 学习笔记

89 阅读2分钟

传统的方案

....

‍....

目前的方案: @reduxjs/toolkit

2022年4月19日发布的Redux 中正式将createStore方法标记为“已弃用”,并推荐用户使用Redux-Toolkit

useSelector/useDispatch

数据获取操作与操作

import React, { useState } from 'react'

import { useSelector, useDispatch } from 'app/hooks'

import { decrement, increment } from './counterSlice'

export function Counter() {
  // The `state` arg is correctly typed as `RootState` already
  const count = useSelector((state) => state.counter.value)
  const dispatch = useDispatch()

  // omit rendering logic
}

异步逻辑

使用createAsyncThunk来处理

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
    thunkAPI.dispatch(ageIncremented(8));
    // 返回值会作为action返回对象payload属性的值。
    return response;
  }
);

RTK Query

@reduxjs/toolkit 还附赠了一个 RTK Query模块, 这是一个将常见的数据获取和缓存功能集成到redux的模块

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({baseUrl: '/'}),
  endpoints: (builder) => ({
    // 定义查询
    getPost: builder.query({
      query: (id) => `post/${id}`,
    }),
    // 定义突变
    addPost: builder.mutation({
      query: (body) => ({
        url: `posts`,
        method: 'POST',
        body,
      }),
    }),
  })
})

// 自动生成了每个Endpoint对应的useQuery/useMutation Hook
export const { useGetPostsQuery, useAddPostMutation } = api

export const { endpoints, reducerPath, reducer, middleware } = api

// endpoints也包含hooks
api.endpoints.getPosts.useQuery // useGetPostsQuery
api.endpoints.updatePost.useMutation // useAddPostMutation

查看当前的state

是要使用current来获取

import { createSlice, current  } from '@reduxjs/toolkit'
import { CARTITEM } from '../data/dummy-data'

const initialState  = {
  items: CARTITEM,
  totalAmount: 0,
}
export const cartSlice = createSlice({
  name: 'cart',
  initialState,
  reducers: {
    addToCart: (state, action) => {
    //不可以直接console.log, 需要通过current函数
      console.log(current(state));
      console.log(state)
    },
  },
})

不可以直接console.log, 需要通过current函数

console.log(current(state))

image-20220914142036-kaqa4qs.png

当前的状态

console.log(state)

image-20220914141954-7rn86p0.png

最初的状态

Uncaught TypeError: Cannot assign to read only property--状态不可变

需要重新创建一个对象在返回


newItems = items.map((item:CartItem) => {
  if (item.productTitle === productTitle) {
    const newItem = {
      productPrice: item.productPrice,
      productTitle: item.productTitle,
      quantity: item.quantity - 1,
      sum: item.sum - productPrice
    }
    return newItem
  }
  return item
})

不可将非普通的可序列化对象、数组和原语放入的store

使用configureStore这个api创建的store默认采用了redux-immutable-state-invariant这个中间件,这个中间件不允许将非普通的可序列化对象、数组和原语放入的store

对多个slice进行更新

使用extraReducers

//cartSlice.ts
import { createSlice, current } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
import { CARTITEM } from '../data/dummy-data'
import { CartItem, Product } from '../../types'
import { deleteProduct } from './productSlice'



export interface CartState {
  items: CartItem[]
  totalAmount: number
}

const initialState: CartState = {
  items: [],
  totalAmount: 0,
}

export const cartSlice = createSlice({
  name: 'cart',
  initialState,
  reducers: {
    addToCart: (state, action: PayloadAction<Product>) => {
      const addedProduct = action.payload

      const prodPrice = addedProduct.price
      const proTitle = addedProduct.title
      let updatedOrNewCartItem: CartItem
      const { items, totalAmount }: { items: CartItem[]; totalAmount: number } =
        current(state)

      const addedItem = items.find(
        (item: CartItem) => item.productTitle === addedProduct.title
      )
      let newItems

      if (addedItem) {
        updatedOrNewCartItem = {
          quantity: addedItem.quantity + 1,
          productPrice: prodPrice,
          productTitle: proTitle,
          sum: addedItem.sum + prodPrice,
        }
        newItems = items.map((item: CartItem) => {
          if (item.productTitle === addedProduct.title) {
            return updatedOrNewCartItem
          }
          return item
        })
      } else {
        updatedOrNewCartItem = {
          quantity: 1,
          productPrice: prodPrice,
          productTitle: proTitle,
          sum: prodPrice,
        }
        newItems = [...items, updatedOrNewCartItem]
      }

      return {
        ...state,
        items: newItems,
        totalAmount: totalAmount + prodPrice,
      }
    },
    removeFromCart: (state, action: PayloadAction<CartItem>) => {
      const selectedCartItem = action.payload
      const { productTitle, productPrice } = selectedCartItem
      const { items } = current(state)
      const currentItem = items.find(
        (item) => item.productTitle === productTitle
      )

      if (!currentItem) {
        return state
      }

      let newItems

      if (currentItem.quantity > 1) {
        newItems = items.map((item: CartItem) => {
          if (item.productTitle === productTitle) {
            const newItem = {
              productPrice: item.productPrice,
              productTitle: item.productTitle,
              quantity: item.quantity - 1,
              sum: item.sum - productPrice,
            }
            return newItem
          }
          return item
        })
      } else {
        newItems = items.filter((item) => {
          item.productTitle === productTitle
        })
      }

      return {
        ...state,
        items: newItems,
        totalAmount: state.totalAmount - productPrice,
      }
    },
    clearCart: () => {
      return initialState
    },
    deleteCartProduct: (state, action: PayloadAction<Product>) => {
      const updatedItems = state.items.filter(item => item.productTitle !== action.payload.title)
      const deletedProd = state.items.find(item => item.productTitle === action.payload.title)
      console.log('deleteCartProduct')
      if (!deletedProd) {
        return state
      }
      return {
        ...state,
        items: updatedItems,
        totalAmount: state.totalAmount - deletedProd.sum,
      }
    },
  },
  extraReducers:(builder) => {
    builder.addCase(deleteProduct, (state,action) => {
      const updatedItems = state.items.filter(item => item.productTitle !== action.payload.title)
      const deletedProd = state.items.find(item => item.productTitle === action.payload.title)
      console.log('deleteCartProduct')
      if (!deletedProd) {
        return state
      }
      return {
        ...state,
        items: updatedItems,
        totalAmount: state.totalAmount - deletedProd.sum,
      }
    })
  }
})

export interface State {
  cart: CartState
}

export const totalAmount = (state: State) => state.cart.totalAmount
export const items = (state: State) => state.cart.items

// Action creators are generated for each case reducer function
export const { addToCart, removeFromCart, clearCart, deleteCartProduct } = cartSlice.actions

export const cartReducer = cartSlice.reducer

//productSlice.ts
import { useDispatch } from 'react-redux';
import { createSlice } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
import { PRODUCTS } from '../data/dummy-data'
import { Product } from '../../types'
import { deleteCartProduct } from './cartSlice';



export interface ProductState {
  availableProducts: Product[]
  userProducts: Product[]
}

const initialState: ProductState = {
  availableProducts: PRODUCTS,
  userProducts: PRODUCTS.filter((prod) => prod.ownerId === '1354214'),
}

export const productSlice = createSlice({
  name: 'product',
  initialState,
  reducers: {
    deleteProduct: (state, action: PayloadAction<Product>) => {
      console.log('deleteProduct')
      return {
        ...state,
        userProducts: state.userProducts.filter(prod => prod.id !== action.payload.id),
        availableProducts: state.availableProducts.filter(prod => prod.id !== action.payload.id),
      }
    },
  },

})
export interface State {
  product: ProductState
}
export const availableProducts = (state: State) => state.product.availableProducts
export const userProducts = (state: State) => state.product.userProducts
// Action creators are generated for each case reducer function
export const { deleteProduct } = productSlice.actions

export const productReducer = productSlice.reducer