Artikelverzeichnis
-
- 1. Projektstart: Projektinitialisierung und -konfiguration
- 2. React- und Hook-Anwendung: Implementieren Sie die Projektliste
- 3. TS-Anwendung: JS God Assist – Starker Typ
- 4. JWT, Benutzerauthentifizierung und asynchrone Anfrage
- 5. CSS ist eigentlich ganz einfach: Fügen Sie Stile mit CSS-in-JS hinzu
- 6. Optimierung der Benutzererfahrung – Laden und Fehlerstatusbehandlung
- 7. Hook-, Routing- und URL-Statusverwaltung
- 8. Benutzerauswahl und Elementbearbeitungsfunktion
- 9. Detaillierte React-Statusverwaltung und Redux-Mechanismus
- 10. Verwenden Sie React-Query, um Daten abzurufen und den Cache zu verwalten
- 11. Entwicklung von Kanban-Seiten und Aufgabengruppenseiten
Quelle der Lerninhalte: React + React Hook + TS Best Practice – MOOC
Im Vergleich zum Original-Tutorial habe ich zu Beginn meines Studiums die neueste Version (2023.03) verwendet:
Artikel | Ausführung |
---|---|
reagieren & reagieren-dom | ^18.2.0 |
React-Router & React-Router-Dom | ^6.11.2 |
antd | ^4.24.8 |
@commitlint/cli & @commitlint/config-conventional | ^17.4.4 |
eslint-config-prettier | ^8.6.0 |
heiser | ^8.0.3 |
fusselfrei | ^13.1.2 |
hübscher | 2.8.4 |
JSON-Server | 0,17,2 |
craco-los | ^2.0.0 |
@craco/craco | ^7.1.0 |
qs | ^6.11.0 |
dayjs | ^1.11.7 |
Reaktionshelm | ^6.1.0 |
@types/react-helmet | ^6.1.6 |
Reaktionsabfrage | ^6.1.0 |
@welldone-software/why-did-you-render | ^7.0.1 |
@emotion/react & @emotion/styled | ^11.10.6 |
Die spezifische Konfiguration, Bedienung und der Inhalt werden unterschiedlich sein, und auch die „Grube“ wird unterschiedlich sein. . .
1. Projektstart: Projektinitialisierung und -konfiguration
2. React- und Hook-Anwendung: Implementieren Sie die Projektliste
3. TS-Anwendung: JS God Assist – Starker Typ
4. JWT, Benutzerauthentifizierung und asynchrone Anfrage
5. CSS ist eigentlich ganz einfach: Fügen Sie Stile mit CSS-in-JS hinzu
6. Optimierung der Benutzererfahrung – Laden und Fehlerstatusbehandlung
7. Hook-, Routing- und URL-Statusverwaltung
8. Benutzerauswahl und Elementbearbeitungsfunktion
9. Detaillierte React-Statusverwaltung und Redux-Mechanismus
10. Verwenden Sie React-Query, um Daten abzurufen und den Cache zu verwalten
11. Entwicklung von Kanban-Seiten und Aufgabengruppenseiten
1~3
4~6
7. Aufgabenfunktion bearbeiten
Erstellen Sie als Nächstes eine Komponente zum Bearbeiten von Aufgaben:
Wenn Sie bereit sind, die Benutzeroberfläche für Bearbeitungsaufgaben aufzurufen und Aufgabendetails abzurufen Hook
, bearbeiten Sie Folgendes src\utils\task.ts
:
...
import {
useAddConfig, useEditConfig } from "./use-optimistic-options";
export const useEditTask = (queryKey: QueryKey) => {
const client = useHttp();
return useMutation(
(params: Partial<Task>) =>
client(`tasks/${
params.id}`, {
method: "PATCH",
data: params,
}),
useEditConfig(queryKey)
);
};
export const useTask = (id?: number) => {
const client = useHttp();
return useQuery<Task>(["task", id], () => client(`tasks/${
id}`), {
enabled: Boolean(id),
});
};
BEARBEITEN src\screens\ViewBoard\utils.ts
(hinzugefügt useTasksModal
):
...
// import { useDebounce } from "utils";
import {
useTask } from "utils/task";
...
export const useTasksSearchParams = () => {
const [param] = useUrlQueryParam([
"name",
"typeId",
"processorId",
"tagId",
]);
const projectId = useProjectIdInUrl();
// const debouncedName = useDebounce(param.name)
return useMemo(
() => ({
projectId,
typeId: Number(param.typeId) || undefined,
processorId: Number(param.processorId) || undefined,
tagId: Number(param.tagId) || undefined,
// name: debouncedName,
name: param.name,
}),
// [projectId, param, debouncedName]
[projectId, param]
);
};
...
export const useTasksModal = () => {
const [{
editingTaskId }, setEditingTaskId] = useUrlQueryParam(['editingTaskId'])
const {
data: editingTask, isLoading } = useTask(Number(editingTaskId))
const startEdit = useCallback((id: number) => {
setEditingTaskId({
editingTaskId: id})
}, [setEditingTaskId])
const close = useCallback(() => {
setEditingTaskId({
editingTaskId: ''})
}, [setEditingTaskId])
return {
editingTaskId,
editingTask,
startEdit,
close,
isLoading
}
}
Durch die Verwendung von im Video
useDebounce
wird die Suche gestartet, nachdem die Eingabe vollständig gestoppt wurde. Dadurch wird die Verschwendung von Systemressourcen durch häufige Suchvorgänge während des Eingabevorgangs vermieden und die Benutzererfahrung beeinträchtigt. Nachdem der Blogger solche Änderungen vorgenommen hat, ist dies bei der chinesischen Eingabemethode nicht mehr möglich normal verwendet werden. . . Nachverfolgen
Neue Komponente: src\screens\ViewBoard\components\taskModal.tsx
:
import {
useForm } from "antd/lib/form/Form"
import {
useTasksModal, useTasksQueryKey } from "../utils"
import {
useEditTask } from "utils/task"
import {
useEffect } from "react"
import {
Form, Input, Modal } from "antd"
import {
UserSelect } from "components/user-select"
import {
TaskTypeSelect } from "components/task-type-select"
const layout = {
labelCol: {
span: 8},
wrapperCol: {
span: 16}
}
export const TaskModal = () => {
const [form] = useForm()
const {
editingTaskId, editingTask, close } = useTasksModal()
const {
mutateAsync: editTask, isLoading: editLoading } = useEditTask(useTasksQueryKey())
const onCancel = () => {
close()
form.resetFields()
}
const onOk = async () => {
await editTask({
...editingTask, ...form.getFieldsValue()})
close()
}
useEffect(() => {
form.setFieldsValue(editingTask)
}, [form, editingTask])
return <Modal
forceRender={
true}
onCancel={
onCancel}
onOk={
onOk}
okText={
"确认"}
cancelText={
"取消"}
confirmLoading={
editLoading}
title={
"编辑任务"}
open={
!!editingTaskId}
>
<Form {
...layout} initialValues={
editingTask} form={
form}>
<Form.Item
label={
"任务名"}
name={
"name"}
rules={
[{
required: true, message: "请输入任务名" }]}
>
<Input />
</Form.Item>
<Form.Item label={
"经办人"} name={
"processorId"}>
<UserSelect defaultOptionName={
"经办人"} />
</Form.Item>
<Form.Item label={
"类型"} name={
"typeId"}>
<TaskTypeSelect />
</Form.Item>
</Form>
</Modal>
}
Hinweis: Wie
Drawer
beiModal
Verwendung deruseForm()
extrahiertenform
Bindung in der KomponenteForm
müssen SieforceRender
die Eigenschaft hinzufügen. Andernfalls wird beim Öffnen der Seite ein Fehler gemeldet, wenn die Bindung nicht verfügbar ist. useForm' ist mit keinem Form-Element verbunden . Vergessen…
EDIT: src\screens\ViewBoard\index.tsx
(eingeführt TaskModal
):
...
import {
TaskModal } from "./components/taskModal";
export const ViewBoard = () => {
...
return (
<ViewContainer>
...
<TaskModal/>
</ViewContainer>
);
};
...
BEARBEITEN: src\screens\ViewBoard\components\ViewboardCloumn.tsx
(eingeführt useTasksModal
, damit durch Klicken auf eine Aufgabenkarte die TaskModal
Bearbeitung geöffnet wird):
...
import {
useTasksModal, useTasksSearchParams } from "../utils";
...
export const ViewboardColumn = ({
viewboard }: {
viewboard: Viewboard }) => {
...
const {
startEdit } = useTasksModal()
return (
<Container>
...
<TasksContainer>
{
tasks?.map((task) => (
<Card onClick={
() => startEdit(task.id)} style={
{
marginBottom: "0.5rem", cursor: 'pointer' }} key={
task.id}>
...
</Card>
))}
...
</TasksContainer>
</Container>
);
};
...
Sehen Sie sich die Funktionen und Effekte an, klicken Sie auf die Aufgabenkarte, um TaskModal
sie anzuzeigen, bearbeiten und bestätigen Sie, um die geänderte Aufgabe anzuzeigen (mit optimistischem Update, völlig gefühllos):
8. Kanban- und Aufgabenlöschfunktion
Als nächstes implementieren Sie eine kleine Funktion, um Schlüsselwörter in den Suchergebnissen hervorzuheben
Neu src\screens\ViewBoard\components\mark.tsx
:
export const Mark = ({
name, keyword}: {
name: string, keyword: string}) => {
if(!keyword) {
return <>{
name}</>
}
const arr = name.split(keyword)
return <>
{
arr.map((str, index) => <span key={
index}>
{
str}
{
index === arr.length -1 ? null : <span style={
{
color: '#257AFD' }}>{
keyword}</span>
}
</span>)
}
</>
}
EDIT ( separat src\screens\ViewBoard\components\ViewboardCloumn.tsx
eingeführt Task
und extrahiert):TaskCard
...
import {
Task } from "types/Task";
import {
Mark } from "./mark";
...
const TaskCard = ({
task}: {
task: Task}) => {
const {
startEdit } = useTasksModal();
const {
name: keyword } = useTasksSearchParams()
return <Card
onClick={
() => startEdit(task.id)}
style={
{
marginBottom: "0.5rem", cursor: "pointer" }}
key={
task.id}
>
<p>
<Mark keyword={
keyword} name={
task.name}/>
</p>
<TaskTypeIcon id={
task.id} />
</Card>
}
export const ViewboardColumn = ({
viewboard }: {
viewboard: Viewboard }) => {
const {
data: allTasks } = useTasks(useTasksSearchParams());
const tasks = allTasks?.filter((task) => task.kanbanId === viewboard.id);
return (
<Container>
<h3>{
viewboard.name}</h3>
<TasksContainer>
{
tasks?.map((task) => <TaskCard task={
task}/>)}
<CreateTask kanbanId={
viewboard.id} />
</TasksContainer>
</Container>
);
};
...
Sehen Sie sich den Effekt an:
Beginnen wir mit der Entwicklung der Löschfunktion
BEARBEITEN src\utils\viewboard.ts
(erstellt und exportiert useDeleteViewBoard
):
...
export const useDeleteViewBoard = (queryKey: QueryKey) => {
const client = useHttp();
return useMutation(
(id?: number) =>
client(`kanbans/${
id}`, {
method: "DELETE",
}),
useDeleteConfig(queryKey)
);
};
BEARBEITEN src\screens\ViewBoard\components\ViewboardCloumn.tsx
:
...
import {
Button, Card, Dropdown, MenuProps, Modal, Row } from "antd";
import {
useDeleteViewBoard } from "utils/viewboard";
...
export const ViewboardColumn = ({
viewboard }: {
viewboard: Viewboard }) => {
const {
data: allTasks } = useTasks(useTasksSearchParams());
const tasks = allTasks?.filter((task) => task.kanbanId === viewboard.id);
return (
<Container>
<Row>
<h3>{
viewboard.name}</h3>
<More viewboard={
viewboard}/>
</Row>
<TasksContainer>
{
tasks?.map((task) => <TaskCard task={
task}/>)}
<CreateTask kanbanId={
viewboard.id} />
</TasksContainer>
</Container>
);
};
const More = ({
viewboard }: {
viewboard: Viewboard }) => {
const {
mutateAsync: deleteViewBoard} = useDeleteViewBoard(useViewBoardQueryKey())
const startDelete = () => {
Modal.confirm({
okText: '确定',
cancelText: '取消',
title: '确定删除看板吗?',
onOk() {
deleteViewBoard(viewboard.id)
}
})
}
const items: MenuProps["items"] = [
{
key: 1,
label: "删除",
onClick: startDelete,
},
];
return <Dropdown menu={
{
items }}>
<Button type="link" onClick={
(e) => e.preventDefault()}>
...
</Button>
</Dropdown>
}
...
Testen Sie, um das Kanban zu löschen, die Funktion ist normal
Das Folgende ist die Funktion zum Löschen von Aufgaben
BEARBEITEN src\utils\task.ts
(erstellt und exportiert useDeleteTask
):
...
export const useDeleteTask = (queryKey: QueryKey) => {
const client = useHttp();
return useMutation(
(id?: number) =>
client(`tasks/${
id}`, {
method: "DELETE",
}),
useDeleteConfig(queryKey)
);
};
BEARBEITEN src\screens\ViewBoard\components\taskModal.tsx
:
...
import {
useDeleteTask, useEditTask } from "utils/task";
export const TaskModal = () => {
...
const {
mutateAsync: deleteTask } = useDeleteTask(useTasksQueryKey());
...
const startDelete = () => {
close();
Modal.confirm({
okText: '确定',
cancelText: '取消',
title: '确定删除看板吗?',
onOk() {
deleteTask(Number(editingTaskId));
}
})
}
return (
<Modal {
...}>
<Form {
...}>
...
</Form>
<div style={
{
textAlign: 'right' }}>
<Button style={
{
fontSize: '14px'}} size="small" onClick={
startDelete}>删除</Button>
</div>
</Modal>
);
};
Testen Sie, um die Aufgabe zu löschen. Die Funktion ist normal
Einige Referenznotizen befinden sich noch im Entwurfsstadium, also bleiben Sie dran. . .