传统的方案
....
....
目前的方案: @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))
当前的状态
console.log(state)
最初的状态
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