[実践] 9. 徹底した React 状態管理と Redux メカニズム (4) —— React17+React Hook+TS4 のベスト プラクティス、Jira エンタープライズ レベルのプロジェクトを模倣 (19)


学習コンテンツのソース: 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 メカニズム

1&2

3&4

5~8

9. redux-toolkitの設定

redux-toolkitこれはreduxの 2 番目のパッケージであり、主に次の 3 つの主要な問題点を解決します。

  • 複雑な構成
  • 追加するパッケージが多すぎます
  • 必要な定型コードが多すぎる

プロジェクトは最終的には使用されないためredux、学習と開発のために新しいブランチが開かれ、ブランチが作成されますredux-toolkit

インストールの依存関係:

npm i react-redux @reduxjs/toolkit # --force

新しいsrc\store\index.tsx:

import {
    
     configureStore } from "@reduxjs/toolkit"

export const rootReducer = {
    
    }

export const store = configureStore({
    
    
  reducer: rootReducer
})

export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>

新しいsrc\screens\ProjectList\projectList.slice.ts:

import {
    
     createSlice } from "@reduxjs/toolkit";

interface State {
    
    
  projectModalOpen: boolean;
}

const initialState: State = {
    
    
  projectModalOpen: false
}

export const projectListSlice = createSlice({
    
    
  name: 'projectListSlice',
  initialState,
  reducers: {
    
    
    openProjectModal(state, action) {
    
    },
    closeProjectModal(state, action) {
    
    }
  }
})

stateQ:の属性に値を直接割り当てることができるのはなぜですか?
回答:redux組み込みを使用してimmer不変データに処理し、「シャドウ状態」を作成し、最後に元の状態全体を置き換えます。

10.redux-toolkit管理モーダルボックスを適用します。

完璧src\screens\ProjectList\projectList.slice.ts

import {
    
     createSlice } from "@reduxjs/toolkit";
import {
    
     RootState } from "store";

interface State {
    
    
  projectModalOpen: boolean;
}

const initialState: State = {
    
    
  projectModalOpen: false,
};

export const projectListSlice = createSlice({
    
    
  name: "projectListSlice",
  initialState,
  reducers: {
    
    
    openProjectModal(state) {
    
    
      state.projectModalOpen = true
    },
    closeProjectModal(state) {
    
    
      state.projectModalOpen = false
    },
  },
});

export const projectListActions = projectListSlice.actions;

export const selectProjectModalOpen = (state: RootState) => state.projectList.projectModalOpen

その後の使用:

  • 導入
    • import { useDispatch, useSelector } from "react-redux";
    • import { projectListActions, selectProjectModalOpen } from "../projectList.slice";
  • hook取得するために使用しますdispatch:const dispatch = useDispatch()
  • 次の呼び出しを使用してdispatchモーダルを開きます。() => dispatch(projectListActions.openProjectModal())
  • 呼び出しを使用してdispatchモーダルを閉じます。() => dispatch(projectListActions.closeProjectModal())
  • hookモーダル ボックスの現在の開閉状態を取得するために使用します。useSelector(selectProjectModalOpen)
    • useSelectorルート状態ツリーを読み取るために使用されます

変更src\store\index.tsx(導入projectListSlice):

import {
    
     configureStore } from "@reduxjs/toolkit";
import {
    
     projectListSlice } from "screens/ProjectList/projectList.slice";

export const rootReducer = {
    
    
  projectList: projectListSlice.reducer,
};

export const store = configureStore({
    
    
  reducer: rootReducer,
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
  • ReturnType関数の戻り値を読み取るために使用される型

以前AuthenticatedApp(サブカプセル化モーダルボックスコンポーネントを導入する場所)は、状態量(const [isOpen, setIsOpen] = useState(false))をレイヤーごとに作成し、モーダルボックスを使用する場所に渡していました。

redux今のところはこれですべてです。ofを使用してdispatchモーダルの状態を変更してみましょう。

編集src\authenticated-app.tsx:

...
export const AuthenticatedApp = () => {
-  const [isOpen, setIsOpen] = useState(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>
-                  }
-                />
+                <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 { useDispatch } from "react-redux";
+ import { ButtonNoPadding } from "components/lib";
+ import { projectListActions } from "./projectList.slice";

- export const ProjectList = ({
-   projectButton,
- }: {
-   projectButton: JSX.Element;
- }) => {
+ export const ProjectList = () => {
  ...
+   const dispatch = useDispatch()

  return (
    <Container>
      <Row justify="space-between">
        <h1>项目列表</h1>
-        {projectButton}
+        <ButtonNoPadding type="link" onClick={() => dispatch(projectListActions.openProjectModal())}>
+          创建项目
+        </ButtonNoPadding>
      </Row>
      ...
      <List
-        projectButton={projectButton}
        ...
      />
    </Container>
  );
};
...

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

...
+ import { useDispatch } from "react-redux";
+ import { projectListActions } from "../projectList.slice";


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

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

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

...
+ import { useDispatch, useSelector } from "react-redux";
+ import { projectListActions, selectProjectModalOpen } from "../projectList.slice";

- export const ProjectModal = ({
-   isOpen,
-   onClose,
- }: {
-   isOpen: boolean;
-   onClose: () => void;
- }) => {
+ export const ProjectModal = () => {
+   const dispatch = useDispatch()
+   const projectModalOpen = useSelector(selectProjectModalOpen)

  return (
-     <Drawer onClose={onClose} open={isOpen} width="100%">
+     <Drawer
+       onClose={() => dispatch(projectListActions.closeProjectModal())}
+       open={projectModalOpen}
+       width="100%"
+     >
      <h1>Project Modal</h1>
-       <Button onClick={onClose}>关闭</Button>
+       <Button onClick={() => dispatch(projectListActions.closeProjectModal())}>关闭</Button>
    </Drawer>
  );
};

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

...
+ import { useDispatch } from "react-redux";
+ import { projectListActions } from "../projectList.slice";
+ import { ButtonNoPadding } from "components/lib";

- export const ProjectPopover = ({
-   projectButton,
- }: {
-   projectButton: JSX.Element;
- }) => {
+ export const ProjectPopover = () => {
+   const dispatch = useDispatch()
  ...

  const content = (
    <ContentContainer>
      <Typography.Text type="secondary">收藏项目</Typography.Text>
      <List>
        {starProjects?.map((project) => (
-           <List.Item>
+           <List.Item key={project.id}>
            <List.Item.Meta title={project.name} />
          </List.Item>
        ))}
      </List>
      <Divider />
-      {projectButton}
+      <ButtonNoPadding type="link" onClick={() => dispatch(projectListActions.openProjectModal())}>
+        创建项目
+      </ButtonNoPadding>
    </ContentContainer>
  );
  ...
};
...

このページにアクセスすると、次のエラーが表示されます。

could not find react-redux context value; please ensure the component is wrapped in a <Provider>

これは、 of がグローバルreduxバインドされていないためです。storecontext

編集src\context\index.tsx:

...
+ import { store } from "store";
+ import { Provider } from "react-redux";

export const AppProvider = ({ children }: { children: ReactNode }) => {
  return (
+   <Provider store={store}>
      <QueryClientProvider client={new QueryClient()}>
        <AuthProvider>{children}</AuthProvider>
      </QueryClientProvider>
+   </Provider>
  );
};

もう一度ページにアクセスしてください。機能は正常です


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

おすすめ

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