跳到主要内容

SSE、Motion、DnD 与 Immer

SSE(Server-Sent Events)

@vef-framework-react/core 提供了基于 @microsoft/fetch-event-source 构建的 SSE 客户端,支持自动 Token 注入和重试。

SseClient

import { SseClient } from "@vef-framework-react/core";

const sseClient = new SseClient({
getAuthTokens: () => tokenStore.getTokens(),
enableRetry: true,
maxRetries: 3,
showErrorMessage: message => notification.error(message),
onTokenExpired: async () => {
const refreshed = await http.ensureTokenRefreshed();
return refreshed;
}
});

await sseClient.stream(
{
url: "/api/chat/stream",
method: "POST",
body: { message: "你好" }
},
{
onOpen: response => console.log("已连接", response.status),
onMessage: event => {
console.log(event.data);
},
onError: error => console.error(error),
onClose: () => console.log("已关闭")
}
);

// 中止所有活跃流
sseClient.abort();

createSseClient

创建 SseClient 的工厂函数:

import { createSseClient } from "@vef-framework-react/core";

const sseClient = createSseClient({
getAuthTokens: () => tokenStore.getTokens()
});

SseClientOptions

选项类型默认值说明
getAuthTokens() => Awaitable<{ accessToken: string } | undefined>获取访问 Token
enableRetrybooleantrue启用自动重试
maxRetriesnumber3最大重试次数
showErrorMessage(msg) => void错误消息处理器
onTokenExpired() => Awaitable<boolean>Token 刷新回调;返回 true 则重试

SseRequestConfig

选项类型默认值说明
urlstring请求 URL
method"GET" | "POST" | "PUT" | "DELETE""POST"HTTP 方法
headersRecord<string, string>请求头
bodystring | object请求体
signalAbortSignal中止信号

SseMessageEvent

字段类型说明
idstring | undefined事件 ID
eventstring | undefined事件类型
datastring消息数据

Motion

motion/react 重导出,提供动画支持。

import { motion, AnimatePresence, LayoutGroup, Reorder, MotionProvider } from "@vef-framework-react/core";

// 动画元素
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
内容
</motion.div>

// 出现/消失动画
<AnimatePresence>
{isVisible && <motion.div key="item">...</motion.div>}
</AnimatePresence>

// 可排序列表
<Reorder.Group values={items} onReorder={setItems}>
{items.map(item => (
<Reorder.Item key={item.id} value={item}>
{item.name}
</Reorder.Item>
))}
</Reorder.Group>

MotionProvider

用 motion 功能配置包裹应用,由 starter.App 内部使用。

类型导出

  • MotionProps
  • Variants
  • Variant
  • VariantLabels
  • Transition
  • TargetAndTransition
  • ResolvedValues

拖拽(Drag and Drop)

@dnd-kit@hello-pangea/dnd 重导出,提供拖拽支持。

dnd-kit(现代 API)

import {
DragDropProvider,
DragOverlay,
useDraggable,
useDroppable,
useSortable,
PointerSensor,
KeyboardSensor,
useDragDropMonitor
} from "@vef-framework-react/core";

<DragDropProvider>
<SortableList />
</DragDropProvider>

数组辅助函数

import { moveArrayItem, swapArrayItem, moveDragItem, swapDragItem } from "@vef-framework-react/core";

// 将索引 0 的元素移动到索引 2
const newItems = moveArrayItem(items, 0, 2);

// 交换索引 0 和 2 的元素
const newItems = swapArrayItem(items, 0, 2);

修饰器

import {
RestrictToVerticalAxis,
RestrictToHorizontalAxis,
RestrictToWindow,
RestrictToElement,
SnapModifier,
AxisModifier
} from "@vef-framework-react/core";

@hello-pangea/dnd(兼容旧版 API)

import { DragDropContext, Droppable, Draggable } from "@vef-framework-react/core";

Immer

immeruse-immer 重导出,用于不可变状态更新。

produce

import { produce } from "@vef-framework-react/core";

const nextState = produce(state, draft => {
draft.user.name = "张三";
draft.items.push({ id: 1 });
});

produceWithPatches

import { produceWithPatches } from "@vef-framework-react/core";

const [nextState, patches, inversePatches] = produceWithPatches(state, draft => {
draft.count += 1;
});

applyPatches

import { applyPatches } from "@vef-framework-react/core";

const undoneState = applyPatches(nextState, inversePatches);

currentStateoriginalState

import { currentState, originalState } from "@vef-framework-react/core";

produce(state, draft => {
const snapshot = currentState(draft); // 当前 draft 值
const base = originalState(draft); // 原始基础值
});

useImmer

import { useImmer } from "@vef-framework-react/core";

const [state, updateState] = useImmer({ count: 0, name: "" });

updateState(draft => {
draft.count += 1;
});

useImmerReducer

import { useImmerReducer } from "@vef-framework-react/core";

const [state, dispatch] = useImmerReducer(
(draft, action) => {
if (action.type === "increment") draft.count += 1;
},
{ count: 0 }
);