【実戦】11. カンバンページとタスクグループページの開発 (1) —— React17+React Hook+TS4 ベストプラクティス、Jira エンタープライズレベルのプロジェクトを模倣 (23)


学習コンテンツのソース: React + React Hook + TS ベスト プラクティス - MOOC


元のチュートリアルと比較して、学習の開始時に最新バージョン (2023.03) を使用しました。

アイテム バージョン
反応&反応ダム ^18.2.0
反応ルーターと反応ルーターダム ^6.11.2
^4.24.8
@commitlint/cli および @commitlint/config-conventional ^17.4.4
eslint-config-prettier ^8.6.0
ハスキー ^8.0.3
糸くずステージ ^13.1.2
より美しい 2.8.4
jsonサーバー 0.17.2
クラコレス ^2.0.0
@クラコ/クラコ ^7.1.0
qs ^6.11.0
デイジェス ^1.11.7
反応ヘルメット ^6.1.0
@types/react-helmet ^6.1.6
反応クエリ ^6.1.0
@welldone-software/why-did-you-render ^7.0.1
@emotion/反応 & @emotion/styled ^11.10.6

具体的な構成や動作、内容は異なりますし、「落とし穴」も異なります。


1. プロジェクトの起動: プロジェクトの初期化と構成

2. React と Hook アプリケーション: プロジェクト リストを実装します。

3. TSアプリ:JSゴッドアシスト・強力タイプ

4. JWT、ユーザー認証、非同期リクエスト


5. CSS は実際には非常にシンプルです - CSS-in-JS を使用してスタイルを追加します


6. ユーザー エクスペリエンスの最適化 - 読み込みとエラー状態の処理



7. フック、ルーティング、および URL 状態の管理



8. ユーザーセレクターと項目編集機能


9. 詳細な React 状態管理と Redux メカニズム





10. 反応クエリを使用してデータを取得し、キャッシュを管理する


11. カンバンページとタスクグループページの開発

1. かんばんリスト作成の準備作業

以前のプロジェクトの詳細をカンバン ページにルーティングする際に小さな問題があり、ブラウザの [戻る] ボタンをクリックしても戻ることができません。理由は次のとおりです。

  • ルート リストはスタック構造です。ルートにアクセスするたびにpush、新しいルートが入力されます。[戻る] をクリックすると、前のルートがスタックの一番上に配置されます。また、プロジェクトの詳細ページに入るとき (~から'projects') 'projects/1')、デフォルトのリダイレクト サブルートはカンバン ページ ( projects/1/viewboard) です。前のルートに戻ると、デフォルトでカンバン ページ ルートにリダイレクトされます。リストスタックの例は次のとおりです。
  • ['projects', 'projects/1', 'projects/1/viewboard']

次に、この問題を解決するには、次のように編集しますsrc\screens\ProjectDetail\index.tsx(リダイレクト タグに新しい属性を追加しreplace、リダイレクト時に元のルートを直接置き換えます)。

...
export const ProjectDetail = () => {
    
    
  return (
    <div>
      ...
      <Routes>
        ...
        <Route index element={
    
    <Navigate to="viewboard" replace/>} />
      </Routes>
    </div>
  );
};

後続の型の統合呼び出しを容易にするために、src\screens\ProjectList\components\List.tsxディレクトリinterface Projectに抽出中src\types

ビデオでは WebStorm が使用されており、ブロガーは VSCode を使用しています。

  • リファクタリングする必要がある変数を右クリックし、[リファクタリング] (ショートカット キーCtrl+ Shift+ R) を選択し、 を選択するとMove to a new file、同じ変数名のデフォルト ファイルが現在のファイルと同じレベルのディレクトリに作成され、他の参照場所も作成されます。参照場所を含めて、それに応じて変更されます。
    • src\utils\project.ts
    • src\screens\ProjectList\components\SearchPanel.tsx
    • src\screens\ProjectList\components\List.tsx
  • 新しく生成されたファイルをsrc\typesディレクトリにドラッグすると、他の参照場所もそれに応じて変更されていることがわかります。

src\screens\ProjectList\components\SearchPanel.tsxin もinterface User同じことを行い、場所を引用します。

  • src\screens\ProjectList\components\SearchPanel.tsx
  • src\screens\ProjectList\components\List.tsx
  • src\auth-provider.ts
  • src\context\auth-context.tsx
  • src\utils\use-users.ts

カンバン ページには次の 2 つのタイプも必要なので、新しいタイプを作成します。

  • src\types\Viewboard.ts:
export interface Viewboard {
    
    
  id: number;
  name: string;
  projectId: number;
}
  • src\types\Task.ts
export interface Task {
    
    
  id: number;
  name: string;
  projectId: number;
  processorId: number; // 经办人
  taskGroupId: number; // 任务组
  kanbanId: number;
  typeId: number;      // bug or task
  note: string;
}

次に、データリクエストのフックを作成します。

src\utils\viewboard.ts:

import {
    
     cleanObject } from "utils";
import {
    
     useHttp } from "./http";
import {
    
     Viewboard } from "types/Viewboard";
import {
    
     useQuery } from "react-query";

export const useViewboards = (param?: Partial<Viewboard>) => {
    
    
  const client = useHttp();

  return useQuery<Viewboard[]>(["viewboards", param], () =>
    client("kanbans", {
    
     data: cleanObject(param || {
    
    }) })
  );
};

src\utils\task.ts:

import {
    
     cleanObject } from "utils";
import {
    
     useHttp } from "./http";
import {
    
     Task } from "types/Task";
import {
    
     useQuery } from "react-query";

export const useTasks = (param?: Partial<Task>) => {
    
    
  const client = useHttp();

  return useQuery<Task[]>(["tasks", param], () =>
    client("tasks", {
    
     data: cleanObject(param || {
    
    }) })
  );
};

2. かんばんリストの事前開発

url次に、使用する必要があるプロジェクト データを表示するためのかんばんリストの開発を開始します。 Getから 1 つを抽出しprojectId、 を使用してidプロジェクト データを取得します。hook

新しいsrc\screens\ViewBoard\utils.ts:

import {
    
     useLocation } from "react-router"
import {
    
     useProject } from "utils/project"

export const useProjectIdInUrl = () => {
    
    
  const {
    
     pathname } = useLocation()
  const id = pathname.match(/projects\/(\d+)/)?.[1]
  return Number(id)
}

export const useProjectInUrl = () => useProject(useProjectIdInUrl())

export const useViewBoardSearchParams = () => ({
    
    projectId: useProjectIdInUrl()})

export const useViewBoardQueryKey = () => ['viewboards', useViewBoardSearchParams()]

export const useTasksSearchParams = () => ({
    
    projectId: useProjectIdInUrl()})

export const useTasksQueryKey = () => ['tasks', useTasksSearchParams()]

注: それぞれの戻り値の最初の項目が後続のuseXXXQueryKeyリスト要求の最初のパラメーターと一致していることを確認する必要があります。そうでないと、後続の追加、削除、変更でリストを自動的に再要求できなくなり、トラブルシューティングが困難になります。useXXXuseQuery

カンバンの表示列コンポーネント(タスクリスト)をタイプごとにカスタマイズして使用する

新しいsrc\screens\ViewBoard\components\ViewboardCloumn.tsx:

import {
    
     Viewboard } from "types/Viewboard";
import {
    
     useTasks } from "utils/task";
import {
    
     useTasksSearchParams } from "../utils";

export const ViewboardColumn = ({
     
     viewboard}:{
     
     viewboard: Viewboard}) => {
    
    
  const {
    
     data: allTasks } = useTasks(useTasksSearchParams())
  const tasks = allTasks?.filter(task => task.kanbanId === viewboard.id)
  return <div>
    <h3>{
    
    viewboard.name}</h3>
    {
    
    
      tasks?.map(task => <div key={
    
    task.id}>{
    
    task.name}</div>)
    }
  </div>
}

編集src\screens\ViewBoard\index.tsx:

import {
    
     useDocumentTitle } from "utils";
import {
    
     useViewboards } from "utils/viewboard";
import {
    
     useProjectInUrl, useViewBoardSearchParams } from "./utils";
import {
    
     ViewboardColumn } from "./components/ViewboardCloumn"
import styled from "@emotion/styled";

export const ViewBoard = () => {
    
    
  useDocumentTitle('看板列表')

  const {
    
    data: currentProject} = useProjectInUrl()
  const {
    
    data: viewboards, } = useViewboards(useViewBoardSearchParams())

  return <div>
    <h1>{
    
    currentProject?.name}看板</h1>
    <ColumnsContainer>
    {
    
    
      viewboards?.map(vbd => <ViewboardColumn viewboard={
    
    vbd} key={
    
    vbd.id}/>)
    }
    </ColumnsContainer>
  </div>;
};

const ColumnsContainer = styled.div`
  display: flex;
  overflow: hidden;
  margin-right: 2rem;
`

コードからわかるように、ViewboardColumn は viewboards.map の後に複数回レンダリングされ、useTasks も同時に複数回実行されますが、ブラウザーの開発者ツールをよく見ると、対応するリクエストがこれは、react-query のキャッシュメカニズムによるものです (デフォルトでは、2 秒以内に送信された同じキーと同じパラメーターを持つ複数のリクエストは最後にのみ実行されます)

カンバン リストにアクセスすると、次の内容が表示されます。3 つのステータス タスクの水平方向の配置は正常です。

待完成
管理登录界面开发

开发中
管理注册界面开发
权限管理界面开发
UI开发
自测

已完成
单元测试
性能优化

3. タスク、バグアイコンを追加

タスク タイプ インターフェイスは直接返さず、typeId のみを返します。これではタスク タイプを明確に識別できず、特定のタスク タイプを取得するには別のアクセス インターフェイスが必要です。

新しいsrc\types\TaskType.ts:

export interface TaskType {
    
    
  id: number;
  name: string;
}

新しいsrc\utils\task-type.ts:

import {
    
     useHttp } from "./http";
import {
    
     useQuery } from "react-query";
import {
    
     TaskType } from "types/TaskType";

export const useTaskTypes = () => {
    
    
  const client = useHttp();

  return useQuery<TaskType[]>(["taskTypes"], () =>
    client("tasks")
  );
};

次の 2 つの svg ファイルをコピーします。src\assets

バグ.svg
ここに画像の説明を挿入

<svg xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xlinkHref="http://www.w3.org/1999/xlink">
    <!-- Generator: Sketch 3.5.2 (25235) - http://www.bohemiancoding.com/sketch -->
    <title>bug</title>
    <desc>Created with Sketch.</desc>
    <defs/>
    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
        <g id="bug" sketch:type="MSArtboardGroup">
            <g id="Bug" sketch:type="MSLayerGroup" transform="translate(1.000000, 1.000000)">
                <rect id="Rectangle-36" fill="#E5493A" sketch:type="MSShapeGroup" x="0" y="0" width="14" height="14" rx="2"/>
                <path d="M10,7 C10,8.657 8.657,10 7,10 C5.343,10 4,8.657 4,7 C4,5.343 5.343,4 7,4 C8.657,4 10,5.343 10,7" id="Fill-2" fill="#FFFFFF" sketch:type="MSShapeGroup"/>
            </g>
        </g>
    </g>
</svg>

タスク.svg
ここに画像の説明を挿入

<svg xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" width="16px" height="16px" viewBox="0 0 16 16" version="1.1">
    <!-- Generator: Sketch 3.5.2 (25235) - http://www.bohemiancoding.com/sketch -->
    <title>task</title>
    <desc>Created with Sketch.</desc>
    <defs/>
    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
        <g id="task" sketch:type="MSArtboardGroup">
            <g id="Task" sketch:type="MSLayerGroup" transform="translate(1.000000, 1.000000)">
                <rect id="Rectangle-36" fill="#4BADE8" sketch:type="MSShapeGroup" x="0" y="0" width="14" height="14" rx="2"/>
                <g id="Page-1" transform="translate(4.000000, 4.500000)" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" sketch:type="MSShapeGroup">
                    <path d="M2,5 L6,0" id="Stroke-1"/>
                    <path d="M2,5 L0,3" id="Stroke-3"/>
                </g>
            </g>
        </g>
    </g>
</svg>

直接使用すると、次のエラーが発生する可能性があります。

Compiled with problems:X

ERROR in ./src/assets/task.svg

Module build failed (from ./node_modules/@svgr/webpack/lib/index.js):
SyntaxError: unknown file: Namespace tags are not supported by default. React's JSX doesn't support namespace tags. You can set `throwIfNamespace: false` to bypass this warning.

skety:typeこのタイプのタグ属性をキャメルケースに変更して、受け入れられるsketchTypeようにしますJSX

ソースファイルsvgの修正後のソースコードは以下のとおりです。

  • バグ.svg
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xlinkHref="http://www.w3.org/1999/xlink" xmlnsSketch="http://www.bohemiancoding.com/sketch/ns">
    <!-- Generator: Sketch 3.5.2 (25235) - http://www.bohemiancoding.com/sketch -->
    <title>bug</title>
    <desc>Created with Sketch.</desc>
    <defs></defs>
    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketchType="MSPage">
        <g id="bug" sketchType="MSArtboardGroup">
            <g id="Bug" sketchType="MSLayerGroup" transform="translate(1.000000, 1.000000)">
                <rect id="Rectangle-36" fill="#E5493A" sketchType="MSShapeGroup" x="0" y="0" width="14" height="14" rx="2"></rect>
                <path d="M10,7 C10,8.657 8.657,10 7,10 C5.343,10 4,8.657 4,7 C4,5.343 5.343,4 7,4 C8.657,4 10,5.343 10,7" id="Fill-2" fill="#FFFFFF" sketchType="MSShapeGroup"></path>
            </g>
        </g>
    </g>
</svg>
  • タスク.svg
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg"
     xmlnsSketch="http://www.bohemiancoding.com/sketch/ns">
    <!-- Generator: Sketch 3.5.2 (25235) - http://www.bohemiancoding.com/sketch -->
    <title>task</title>
    <desc>Created with Sketch.</desc>
    <defs></defs>
    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketchType="MSPage">
        <g id="task" sketchType="MSArtboardGroup">
            <g id="Task" sketchType="MSLayerGroup" transform="translate(1.000000, 1.000000)">
                <rect id="Rectangle-36" fill="#4BADE8" sketchType="MSShapeGroup" x="0" y="0" width="14" height="14" rx="2"></rect>
                <g id="Page-1" transform="translate(4.000000, 4.500000)" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" sketchType="MSShapeGroup">
                    <path d="M2,5 L6,0" id="Stroke-1"></path>
                    <path d="M2,5 L0,3" id="Stroke-3"></path>
                </g>
            </g>
        </g>
    </g>
</svg>

編集src\screens\ViewBoard\components\ViewboardCloumn.tsx(アイコンの導入、美化):

import {
    
     Viewboard } from "types/Viewboard";
import {
    
     useTasks } from "utils/task";
import {
    
     useTasksSearchParams } from "../utils";
import {
    
     useTaskTypes } from "utils/task-type";
import taskIcon from "assets/task.svg";
import bugIcon from "assets/bug.svg";
import styled from "@emotion/styled";
import {
    
     Card } from "antd";

const TaskTypeIcon = ({
     
      id }: {
     
      id: number }) => {
    
    
  const {
    
     data: taskTypes } = useTaskTypes();
  const name = taskTypes?.find((taskType) => taskType.id === id)?.name;
  if (!name) {
    
    
    return null;
  }
  return <img alt='task-icon' src={
    
    name === "task" ? taskIcon : bugIcon} />;
};

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) => (
          <Card style={
    
    {
    
    marginBottom: '0.5rem'}} key={
    
    task.id}>
            <div>{
    
    task.name}</div>
            <TaskTypeIcon id={
    
    task.id} />
          </Card>
        ))}
      </TasksContainer>
    </Container>
  );
};

export const Container = styled.div`
  min-width: 27rem;
  border-radius: 6px;
  background-color: rgb(244, 245, 247);
  display: flex;
  flex-direction: column;
  padding: .7rem .7rem 1rem;
  margin-right: 1.5rem;
`

const TasksContainer = styled.div`
  overflow: scroll;
  flex: 1;
  ::-webkit-scrollbar {
    display: none;
  }
`

効果を確認します。
レンダリング


一部の参考ノートはまだ草案段階にあるため、今後の内容に注目してください。

おすすめ

転載: blog.csdn.net/qq_32682301/article/details/132321120