做网站 怎么赚钱吗,oa系统是什么意思啊,网站运营怎么做,外贸led网站建设文章目录 一、项目起航#xff1a;项目初始化与配置二、React 与 Hook 应用#xff1a;实现项目列表三、TS 应用#xff1a;JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook… 文章目录 一、项目起航项目初始化与配置二、React 与 Hook 应用实现项目列表三、TS 应用JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook路由与 URL 状态管理八、用户选择器与项目编辑功能九、深入React 状态管理与Redux机制十、用 react-query 获取数据管理缓存十一、看板页面及任务组页面开发1~34~6789.拖拽实现10.拖拽持久化 学习内容来源React React Hook TS 最佳实践-慕课网 相对原教程我在学习开始时2023.03采用的是当前最新版本
项版本react react-dom^18.2.0react-router react-router-dom^6.11.2antd^4.24.8commitlint/cli commitlint/config-conventional^17.4.4eslint-config-prettier^8.6.0husky^8.0.3lint-staged^13.1.2prettier2.8.4json-server0.17.2craco-less^2.0.0craco/craco^7.1.0qs^6.11.0dayjs^1.11.7react-helmet^6.1.0types/react-helmet^6.1.6react-query^6.1.0welldone-software/why-did-you-render^7.0.1emotion/react emotion/styled^11.10.6
具体配置、操作和内容会有差异“坑”也会有所不同。。。 一、项目起航项目初始化与配置 一、项目起航项目初始化与配置 二、React 与 Hook 应用实现项目列表 二、React 与 Hook 应用实现项目列表 三、TS 应用JS神助攻 - 强类型 三、 TS 应用JS神助攻 - 强类型 四、JWT、用户认证与异步请求 四、 JWT、用户认证与异步请求(上) 四、 JWT、用户认证与异步请求(下) 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(上) 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(下) 六、用户体验优化 - 加载中和错误状态处理 六、用户体验优化 - 加载中和错误状态处理(上) 六、用户体验优化 - 加载中和错误状态处理(中) 六、用户体验优化 - 加载中和错误状态处理(下) 七、Hook路由与 URL 状态管理 七、Hook路由与 URL 状态管理(上) 七、Hook路由与 URL 状态管理(中) 七、Hook路由与 URL 状态管理(下) 八、用户选择器与项目编辑功能 八、用户选择器与项目编辑功能(上) 八、用户选择器与项目编辑功能(下) 九、深入React 状态管理与Redux机制 九、深入React 状态管理与Redux机制(一) 九、深入React 状态管理与Redux机制(二) 九、深入React 状态管理与Redux机制(三) 九、深入React 状态管理与Redux机制(四) 九、深入React 状态管理与Redux机制(五) 十、用 react-query 获取数据管理缓存 十、用 react-query 获取数据管理缓存上 十、用 react-query 获取数据管理缓存下 十一、看板页面及任务组页面开发
1~3 十一、看板页面及任务组页面开发一 4~6 十一、看板页面及任务组页面开发二 78 十一、看板页面及任务组页面开发三 9.拖拽实现 接下来的内容将会是整个课程最难的部分相关知识也不是很常用 安装拖拽功能库 react-beautiful-dnd 及相关类型文件库
npm i react-beautiful-dnd ## --force
npm i types/react-beautiful-dnd -D ## --force 接下来在原功能库的基础上自行二次封装
新建 src\components\grag-and-drop.tsx
import React, { ReactNode } from react
import { Draggable, DraggableProps, Droppable, DroppableProps, DroppableProvided, DroppableProvidedProps } from react-beautiful-dnd// 删除原有 “函数类型” children使用 ReactNode 类型的 children
type DropProps OmitDroppableProps, children { children: ReactNode }export const Drop ({ children, ...props }: DropProps) {return (Droppable {...props}{(provided) {if (React.isValidElement(children)) {return React.cloneElement(children, {...provided.droppableProps,ref: provided.innerRef,provided,});}return div /;}}/Droppable);
};type DropChildProps Partial{ provided: DroppableProvided } DroppableProvidedProps React.HTMLAttributesHTMLDivElementexport const DropChild React.forwardRefHTMLDivElement, DropChildProps(({children, ...props}, ref) div ref{ref} {...props} {children}{props.provided?.placeholder}/div
);type DragProps OmitDraggableProps, children { children: ReactNode }
export const Drag ({children, ...props}: DragProps) {return Draggable {...props}{(provided {if(React.isValidElement(children)) {return React.cloneElement(children, {...provided.draggableProps,...provided.dragHandleProps,ref: provided.innerRef})}return div/})}/Draggable
}forwardRef 是用作转发的经过包裹后的组件可以传入 ref 属性 Refs 转发 – React 这步报错ref: provided.innerRef
Argument of type { ref: (element: HTMLElement | null) void; data-rbd-drag-handle-draggable-id?: string | undefined; data-rbd-drag-handle-context-id?: string | undefined; aria-describedby?: string | undefined; ... 7 more ...; onTransitionEnd?: React.TransitionEventHandler... | undefined; } is not assignable to parameter of type Partialunknown Attributes.Object literal may only specify known properties, and ref does not exist in type Partialunknown Attributes.接下来使用这个组件
编辑 src\screens\ViewBoard\index.tsx
...
import { DragDropContext } from react-beautiful-dnd;
import { Drag, Drop, DropChild } from components/grag-and-drop;export const ViewBoard () {useDocumentTitle(看板列表);const { data: currentProject } useProjectInUrl();const { data: viewboards, isLoading: viewBoardIsLoading } useViewboards(useViewBoardSearchParams());const { isLoading: taskIsLoading } useTasks(useTasksSearchParams());const isLoading taskIsLoading || viewBoardIsLoading;return (DragDropContext onDragEnd{() {}}ViewContainerh1{currentProject?.name}看板/h1SearchPanel /{isLoading ? (Spin /) : (Drop typeCOLUMN directionhorizontal droppableIdviewboardColumnsContainer{viewboards?.map((vbd, index) (Drag key{vbd.id} draggableId{viewboard vbd.id} index{index}ViewboardColumn viewboard{vbd} key{vbd.id} //Drag))}CreateViewBoard //ColumnsContainer/Drop)}TaskModal //ViewContainer/DragDropContext);
};export const ColumnsContainer styled(DropChild)display: flex;overflow-x: scroll;flex: 1;
;编辑 src\screens\ViewBoard\components\ViewboardCloumn.tsx 使组件可以透传 props 以及通过 forwardRef 转发 传入 ref:
...
export const ViewboardColumn React.forwardRefHTMLDivElement, { viewboard: Viewboard }(({ viewboard, ...props }, ref) {const { data: allTasks } useTasks(useTasksSearchParams());const tasks allTasks?.filter((task) task.kanbanId viewboard.id);return (Container {...props} ref{ref}Rowh3{viewboard.name}/h3More viewboard{viewboard} key{viewboard.id}//RowTasksContainer{tasks?.map((task) (TaskCard key{task.id} task{task} /))}CreateTask kanbanId{viewboard.id} //TasksContainer/Container);
});10.拖拽持久化
拖拽的时候 看板之间的间隔应该是不变的
编辑 src\screens\ViewBoard\index.tsx调整组件层级并显式使用 DropChild:
...
export const ViewBoard () {...return (DragDropContext onDragEnd{() {}}ViewContainerh1{currentProject?.name}看板/h1SearchPanel /{isLoading ? (Spin /) : (ColumnsContainerDrop typeCOLUMN directionhorizontal droppableIdviewboardDropChild style{{display: flex}}{viewboards?.map((vbd, index) (Dragkey{vbd.id}draggableId{viewboard vbd.id}index{index}ViewboardColumn viewboard{vbd} key{vbd.id} //Drag))}/DropChild/DropCreateViewBoard //ColumnsContainer)}TaskModal //ViewContainer/DragDropContext);
};export const ColumnsContainer styled.divdisplay: flex;overflow-x: scroll;flex: 1;
;接下来做 任务拖拽排序
编辑 src\screens\ViewBoard\components\ViewboardCloumn.tsx:
...
import { Drag, Drop, DropChild } from components/grag-and-drop;
...
export const ViewboardColumn React.forwardRefHTMLDivElement,{ viewboard: Viewboard }
(({ viewboard, ...props }, ref) {const { data: allTasks } useTasks(useTasksSearchParams());const tasks allTasks?.filter((task) task.kanbanId viewboard.id);return (Container {...props} ref{ref}Rowh3{viewboard.name}/h3More viewboard{viewboard} key{viewboard.id} //RowTasksContainerDrop typeRow directionvertical droppableId{task viewboard.id}DropChild{tasks?.map((task, taskIndex) (Dragkey{task.id}draggableId{task task.id}index{taskIndex}TaskCard key{task.id} task{task} //Drag))}/DropChild/DropCreateTask kanbanId{viewboard.id} //TasksContainer/Container);
});拖拽功能好了,接下来将拖拽结果持久化到数据库中
编辑 src\utils\use-optimistic-options.ts(看板和任务排序 获取URL参数为后续乐观更新做准备):
...
export const useReorderViewboardConfig (queryKey: QueryKey) useConfig(queryKey, (target, old) old ? [old, ...target] : []);export const useReorderTaskConfig (queryKey: QueryKey) useConfig(queryKey, (target, old) old || []);编辑 src\utils\viewboard.ts(新增看板排序接口的 Custom Hook, SortProps 与 useReorderTask 共用):
...
export interface SortProps {// 要重新排序的 itemfromId: number;// 目标 itemreferenceId: number;// 放在目标 Item 的前还是后type: before | after;fromKanbanId?: number;toKanbanId?: number;
}export const useReorderViewboard () {const client useHttp();return useMutation((params: SortProps) {return client(kanbans/reorder, {data: params,method: POST,});}, useReorderViewboardConfig(queryKey));
};编辑 src\utils\task.ts(新增看板排序接口的 Custom Hook):
...
export const useReorderTask (queryKey: QueryKey) {const client useHttp();return useMutation((params: SortProps) {return client(tasks/reorder, {data: params,method: POST,});}, useReorderTaskConfig(queryKey));
};编辑 src\screens\ViewBoard\index.tsx完善之前预留的 onDragEnd:
...
import { useReorderViewboard, useViewboards } from utils/viewboard;
import {useProjectInUrl,useTasksQueryKey,useTasksSearchParams,useViewBoardQueryKey,useViewBoardSearchParams,
} from ./utils;
...
import { useReorderTask, useTasks } from utils/task;
...
import { useCallback } from react;export const ViewBoard () {...const onDragEnd useDragEnd();return (DragDropContext onDragEnd{onDragEnd}.../DragDropContext);
};export const useDragEnd () {const { data: viewboards } useViewboards(useViewBoardSearchParams());const { mutate: reorderViewBoard } useReorderViewboard(useViewBoardQueryKey());const { mutate: reorderTask } useReorderTask(useTasksQueryKey());const { data: allTasks [] } useTasks(useTasksSearchParams());return useCallback(({ source, destination, type }: DropResult) {if (!destination) {return;}// 看板排序if (type COLUMN) {const fromId viewboards?.[source.index].id;const toId viewboards?.[destination.index].id;if (!fromId || !toId || fromId toId) {return;}const type destination.index source.index ? after : before;reorderViewBoard({ fromId, referenceId: toId, type });}if (type ROW) {const fromKanbanId source.droppableId;const toKanbanId destination.droppableId;if (fromKanbanId toKanbanId) {return;}const fromTask allTasks.filter((task) task.kanbanId fromKanbanId)[source.index];const toTask allTasks.filter((task) task.kanbanId toKanbanId)[destination.index];if (fromTask?.id toTask?.id) {return;}reorderTask({fromId: fromTask?.id,referenceId: toTask?.id,fromKanbanId,toKanbanId,type:fromKanbanId toKanbanId destination.index source.index? after: before,});}},[viewboards, reorderViewBoard, allTasks, reorderTask]);
};
...至此拖拽持久化完成查看效果验证 部分引用笔记还在草稿阶段敬请期待。。。