# 解决状态撕裂
# useImmer
import { produce, Draft, freeze, castImmutable } from "immer";
import { useCallback, useState, SetStateAction } from "react";
// Draft 函数类型定义
export type DraftFunction<T> = (draft: Draft<T>) => void | T;
// 更新器类型
export type Updater<T> = ((draft: Draft<T>) => void) | T;
// Hook 返回类型
export type ImmerHook<T> = [T, (updater: Updater<T>) => void];
export function useImmer<T = unknown>(
initialValue: T | (() => T)
): ImmerHook<T> {
// 初始化状态
const [val, updateValue] = useState<T>(() =>
freeze(
typeof initialValue === "function"
? (initialValue as () => T)()
: initialValue,
true
)
);
// 更新函数实现
const updateState = useCallback(
(updater: Updater<T>) => {
if (typeof updater === "function") {
// 使用类型断言确保类型安全
updateValue(
produce(val, (draft: Draft<T>) => {
return (updater as (draft: Draft<T>) => void)(draft);
}) as T
);
} else {
// 直接值更新
updateValue(freeze(updater, true));
}
},
[val]
);
return [val, updateState];
}
# jotai-immer
import React from "react";
import { atom, useAtom } from "jotai";
import { atomWithImmer } from "jotai-immer";
// 定义类型
interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}
// 创建初始购物车状态
const cartAtom = atomWithImmer<CartItem[]>([
{ id: 1, name: "iPhone", price: 999, quantity: 1 },
{ id: 2, name: "iPad", price: 799, quantity: 1 },
]);
// 创建派生状态计算总价
const totalPriceAtom = atom((get) => {
const cart = get(cartAtom);
return cart.reduce((total, item) => total + item.price * item.quantity, 0);
});
const SimpleCart: React.FC = () => {
const [cart, setCart] = useAtom(cartAtom);
const [total] = useAtom(totalPriceAtom);
const increaseQuantity = (id: number) => {
setCart((draft) => {
const item = draft.find((item) => item.id === id);
if (item) {
item.quantity += 1;
}
});
};
const decreaseQuantity = (id: number) => {
setCart((draft) => {
const item = draft.find((item) => item.id === id);
if (item && item.quantity > 1) {
item.quantity -= 1;
}
});
};
const removeItem = (id: number) => {
setCart((draft) => {
const index = draft.findIndex((item) => item.id === id);
if (index !== -1) {
draft.splice(index, 1);
}
});
};
return (
<div className="max-w-md mx-auto p-4 text-black">
<h1 className="text-2xl font-bold mb-4 text-[#fff]">Shopping Cart</h1>
<div className="space-y-4">
{cart.map((item) => (
<div
key={item.id}
className="flex items-center justify-between bg-white p-4 rounded shadow"
>
<div>
<h3 className="font-medium text-black">{item.name}</h3>
<p className="text-black">${item.price}</p>
</div>
<div className="flex items-center space-x-2">
<button
onClick={() => decreaseQuantity(item.id)}
className="px-2 py-1 bg-gray-200 rounded text-black hover:bg-gray-300"
>
-
</button>
<span className="text-black">{item.quantity}</span>
<button
onClick={() => increaseQuantity(item.id)}
className="px-2 py-1 bg-gray-200 rounded text-black hover:bg-gray-300"
>
+
</button>
<button
onClick={() => removeItem(item.id)}
className="ml-4 text-red-500 hover:text-red-600"
>
Remove
</button>
</div>
</div>
))}
</div>
<div className="mt-6 text-xl font-bold text-[#fff]">Total: ${total}</div>
</div>
);
};
export default SimpleCart;