【実戦】10. React-queryを使ったデータ取得とキャッシュ管理(前編) —— React17+React Hook+TS4のベストプラクティス、Jiraのエンタープライズレベルプロジェクトを模倣(21)


学習コンテンツのソース: 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. 反応クエリを使用してデータを取得し、キャッシュを管理する

コピーをブラッシングした後、メインブランチに戻ります

1. url パラメータを使用してプロジェクトのモーダル ボックスの状態を管理します

urlプロジェクトのモーダルボックスの状態をパラメータで管理するには、src\screens\ProjectList\utils.ts新しいモーダルボックスを追加します。Custom Hook

export const useProjectModal = () => {
    
    
  const [{
    
    projectCreate}, setProjectCreate] = useUrlQueryParam(['projectCreate'])

  const open = () => setProjectCreate({
    
    projectCreate: true})
  const close = () => setProjectCreate({
    
    projectCreate: false}) // 若在url显示 false,可以改为 undefined 隐藏

  return {
    
    
    projectModalOpen: projectCreate === 'true',
    open,
    close
  }
}

次に、前の組み合わせコンポーネントの使用を削除し、対応する位置でコンポーネントを直接使用し、useProjectModal状態とメソッドを呼び出します。

編集src\authenticated-app.tsx:

...
export const AuthenticatedApp = () => {
-   const [isOpen, setIsOpen] = useState(false);
  useDocumentTitle("项目列表", false);

  return (
    <Container>
-       <PageHeader
-         projectButton={
-           <ButtonNoPadding type="link" onClick={() => setIsOpen(true)}>
-             创建项目
-           </ButtonNoPadding>
-         }
-       />
+       <PageHeader/>
      <Main>
        <Router>
          <Routes>
-             <Route
-               path="/projects"
-               element={
-                 <ProjectList
-                   projectButton={
-                     <ButtonNoPadding
-                       type="link"
-                       onClick={() => setIsOpen(true)}
-                     >
-                       创建项目
-                     </ButtonNoPadding>
-                   }
-                 />
-               }
-             />
+             <Route path="/projects" element={<ProjectList/>}/>
            ...
          </Routes>
        </Router>
      </Main>
-       <ProjectModal isOpen={isOpen} onClose={() => setIsOpen(false)} />
+       <ProjectModal/>
    </Container>
  );
};
- const PageHeader = (props: { projectButton: JSX.Element }) => {
+ const PageHeader = () => {
  ...

  return (
    <Header between={true}>
      <HeaderLeft gap={true}>
        ...
-         <ProjectPopover {...props} />
+         <ProjectPopover/>
        <span>用户</span>
      </HeaderLeft>
      ...
    </Header>
  );
};
...

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

...
- import { useProjectsSearchParams } from "./utils";
+ import { useProjectModal, useProjectsSearchParams } from "./utils";
...
+ import { ButtonNoPadding } from "components/lib";

- export const ProjectList = ({ projectButton }: { projectButton: JSX.Element }) => {
+ export const ProjectList = () => {
+   const {open} = useProjectModal()
  ...

  return (
    <Container>
      <Row justify="space-between">
        <h1>项目列表</h1>
-         {projectButton}
+         <ButtonNoPadding type="link" onClick={open}>创建项目</ButtonNoPadding>
      </Row>
      <SearchPanel users={users || []} param={param} setParam={setParam} />
      {error ? (
        <Typography.Text type="danger">{error.message}</Typography.Text>
      ) : null}
      <List
-         projectButton={projectButton}
        ...
      />
    </Container>
  );
};
...

編集src\screens\ProjectList\components\List.tsx:

...
+ import { useProjectModal } from "../utils";

...
interface ListProps extends TableProps<Project> {
  users: User[];
  refresh?: () => void;
-   projectButton: JSX.Element;
}

// type PropsType = Omit<ListProps, 'users'>
export const List = ({ users, ...props }: ListProps) => {
+   const {open} = useProjectModal()
  ...
  return (
    <Table
      pagination={false}
      columns={[
        ...
        {
          render: (text, project) => {
            const items: MenuProps["items"] = [
              {
                key: "edit",
                label: "编辑",
+                 onClick: open
              },
            ];
            return (
-               <Dropdown dropdownRender={() => props.projectButton}>
+               <Dropdown menu={
   
   {items}}>
                <ButtonNoPadding
                  type="link"
                  onClick={(e) => e.preventDefault()}
                >
                  ...
                </ButtonNoPadding>
              </Dropdown>
            );
          },
        },
      ]}
      {...props}
    ></Table>
  );
};

編集src\screens\ProjectList\components\ProjectModal.tsx:

...
+ import { useProjectModal } from "../utils";

- export const ProjectModal = ({isOpen, onClose}: {isOpen: boolean; onClose: () => void;}) => {
+ export const ProjectModal = () => {
+   const {projectModalOpen, close} = useProjectModal()
  return (
-     <Drawer onClose={onClose} open={isOpen} width="100%">
+     <Drawer onClose={close} open={projectModalOpen} width="100%">
      <h1>Project Modal</h1>
-       <Button onClick={onClose}>关闭</Button>
+       <Button onClick={close}>关闭</Button>
    </Drawer>
  );
};

編集src\screens\ProjectList\components\ProjectPopover.tsx:

...
+ import { ButtonNoPadding } from "components/lib";
+ import { useProjectModal } from "../utils";

- export const ProjectPopover = ({projectButton}: {projectButton: JSX.Element;}) => {
+ export const ProjectPopover = () => {
+   const {open} = useProjectModal()
  ...

  const content = (
    <ContentContainer>
      ...
-       {projectButton}
+       <ButtonNoPadding type="link" onClick={open}>创建项目</ButtonNoPadding>
    </ContentContainer>
  );
  ...
};
...

変更後のページの効果を確認し、エラーを報告します。

useLocation() may be used only in the context of a <Router> component.

コードを見ると、 と が でラップされていないことがわかりますsrc\authenticated-app.tsxPageHeaderProjectModal使用RouterするuseUrlQueryParam関連関数をRouterに配置する必要があるため、それを変更します。

...
export const AuthenticatedApp = () => {
  ...
  return (
    <Container>
+       <Router>
        <PageHeader/>
        <Main>
-         <Router>
          <Routes>
            <Route path="/projects" element={<ProjectList/>}/>
            <Route path="/projects/:projectId/*" element={<ProjectDetail />} />
            <Route index element={<Navigate to="projects" />} />
          </Routes>
-         </Router>
        </Main>
        <ProjectModal/>
+       </Router>
    </Container>
  );
};
...

ページをもう一度確認してください。問題なく動作しています。

2. サーバー側のキャッシュを処理するには、react-query を使用します

概要 | 反応クエリの中国語ドキュメント

3. ガードと入力し、useQuery を使用してプロジェクト リストをキャッシュします

一部のビデオはオンラインで視聴できます: https://space.bilibili.com/2814498/video

react-query別のバージョンをインストールする前にjira-dev-toolインストールされています。インストール コマンドは次のとおりです。

npm i react-query # --force

次に、react-queryRetrofitProject関連のインターフェイス呼び出しを使用します。

編集src\utils\project.ts

...
import {
    
     useQuery } from "react-query";

export const useProjects = (param?: Partial<Project>) => {
    
    
  const client = useHttp();
  return useQuery<Project[]>(['projects', param], () => client("projects", {
    
     data: cleanObject(param || {
    
    }) }))
};
...
  • useQueryキャッシュがヒットした場合、onSuccess対応するメソッドはトリガーされません
  • 以前は、この部分の関数はreact-queryを模倣して記述されていました。一部の関数は置換後に直接使用できますが、一部の関数は使用できません。
    • 前のrerun(ビデオ内のretry) 関数は、最初のパラメーターを配列形式useQueryに変更できます。配列の最初の項目は伝統的な意味であり、後続のパラメーターは暗黙的な " " をトリガーする "依存"として使用できます。パラメータが変更されると、それがヒットしますキャッシュは依然として対応するメソッドをトリガーしますQueryKey[key, ...otherParams]keyrerunonSuccess
    • リクエストでエラーが報告される場合、useQuery同じErrorデータが で返されるが、型 (unknownではないError) が必要なものではなく、その後の使用で型エラーが報告される場合、解決策は 2 つあります。
      • 1. 宣言された戻り値の型を表示します。useQuery<Project[], Error>()
      • 2. タイプガードを使用します。詳細については以下を参照してください。

編集src\components\lib.tsxタイプガードとErrorBoxコンポーネントを追加しました:

...
// 类型守卫
const isError = (value: any): value is Error => value?.message

export const ErrorBox = ({
     
     error}: {
     
     error: unknown}) => {
    
    
  if (isError(error)) {
    
    
    return <Typography.Text type="danger">{
    
    error.message}</Typography.Text>
  }
  return null
}
...

istsコンパイラが変数の型を絞り込むのに役立つ型述語です。

ErrorBox使用する必要がある他の場所は順番に置き換えられます。

編集src\unauthenticated-app\index.tsx:

...
import {
    
     ErrorBox } from "components/lib";

export const UnauthenticatedApp = () => {
    
    
  ...
  return (
    <Container>
      ...
      <ShadowCard>
        <Title>{
    
    isRegister ? "请注册" : "请登录"}</Title>
        <ErrorBox error={
    
    error}/>
        ...
      </ShadowCard>
    </Container>
  );
};
...

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

...
- import { ButtonNoPadding } from "components/lib";
+ import { ButtonNoPadding, ErrorBox } from "components/lib";

export const ProjectList = () => {
  ...
  const {
    isLoading,
    error,
    data: list,
-     rerun
  } = useProjects(useDebounce(param));
  const { data: users } = useUsers();

  return (
    <Container>
      ...
      <SearchPanel users={users || []} param={param} setParam={setParam} />
-       {error ? (
-         <Typography.Text type="danger">{error.message}</Typography.Text>
-       ) : null}
+       <ErrorBox error={error}/>
      <List
-         refresh={rerun}
        loading={isLoading}
        users={users || []}
        dataSource={list || []}
      />
    </Container>
  );
};
...

rerun(ビデオ内retry)を使用している部分も削除します。

編集src\screens\ProjectList\components\List.tsx:

...
export const List = ({ users, ...props }: ListProps) => {
  ...
  const starProject = (id: number) => (star: boolean) =>
-    mutate({ id, star }).then(props.refresh);
+    mutate({ id, star })
  return (
    <Table
      pagination={false}
      columns={[
        ...
        {
          render: (text, project) => {
            const items: MenuProps["items"] = [
              {
                key: "edit",
                label: "编辑",
                onClick: open,
-               },
+               }, {
+                 key: "delete",
+                 label: "删除",
+                 // onClick: open,
+               },
            ];
            ...
          },
        },
      ]}
      {...props}
    ></Table>
  );
};

ページ効果、クエリ機能は正常、エラーメッセージ表示は正常であることを確認してください。


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

Guess you like

Origin blog.csdn.net/qq_32682301/article/details/132227011