【実戦】ReactとHookアプリケーション:実装プロジェクト一覧 - React17+React Hook+TS4のベストプラクティス、Jiraのエンタープライズレベルプロジェクトを模倣(2)


学習コンテンツのソース: 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 アプリケーション: プロジェクト リストを実装します。

1. 新しいファイルを作成します

  • 新しいファイル: src\screens\ProjectList\index.jsx
import {
    
     SearchPanel } from "./components/SearchPanel"
import {
    
     List } from "./components/List"

export const ProjectListScreen = () => {
    
    
  return <div>
    <SearchPanel/>
    <List/>
  </div>
}
  • 新しいファイル: src\screens\ProjectList\components\List.jsx
export const List = () => {
    
    
  return <table></table>
}
  • 新しいファイル: src\screens\ProjectList\components\SearchPanel.jsx
import {
    
     useEffect, useState } from "react"

export const SearchPanel = () => {
    
    
  const [param, setParam] = useState({
    
    
    name: '',
    personId: ''
  })
  const [users, setUsers] = useState([])
  const [list, setList] = useState([])

  useEffect(() => {
    
    
    fetch('').then(async res => {
    
    
      if (res.ok) {
    
    
        setList(await res.json())
      }
    })
  }, [param])

  return <form>
    <div>
      {
    
    /* setParam(Object.assign({}, param, { name: evt.target.value })) */}
      <input type="text" value={
    
    param.name} onChange={
    
    evt => setParam({
    
    
        ...param,
        name: evt.target.value
      })}/>
      <select value={
    
    param.personId} onChange={
    
    evt => setParam({
    
    
        ...param,
        personId: evt.target.value
      })}>
        <option value="">负责人</option>
        {
    
    
          users.map(user => (<option key={
    
    user.id} value={
    
    user.id}>{
    
    user.name}</option>))
        }
      </select>
    </div>
  </form>
}
  • 元のビデオと比較すると、コンポーネントに使用されている名前は次のとおりです: Upper Camel Case
  • 元のビデオと比較して、ディレクトリ構造と変数名は自分の習慣に従って使用できます。
  • コーディングのプロセスは非常に重要ですが、テキストを反映するのは簡単ではありません。
  • Vscode は JS ファイル内の HTML タグを自動的に補完しません。次を参照してください: [ヒント] Vscode は JS ファイル内の HTML タグを補完します

2. ステータスの向上

listおよび にはparam2 つの異なるコンポーネントが含まれるため、これら 2 つは共通の親コンポーネントにstate昇格、子コンポーネントpropsは分解によって使用されます。

  • listによってList消費される
  • listに従ってparam取得されます。
  • paramによってSearchPanel消費される

データベース パラダイムの考え方によれば、projectusersそれぞれ別個のテーブルであり、関連するクエリの中間生成物にlistすぎません。hardこのモードでは、 の主キーprojectのみがを通じてusers取得できますpersonIdpersonIdpersonNameusers


DRY原則として、http://host:portインターフェイス呼び出し URL をプロジェクトのグローバル環境変数に抽出します。

  • .env
REACT_APP_API_URL=http://online.com
  • .env.開発
REACT_APP_API_URL=http://localhost:3001

webpack環境変数識別ルールの理解:

  • を実行するnpm startときに、webpack環境変数を読み取ります。.env.development
  • を実行するnpm run buildときに、webpack環境変数を読み取ります。.env

3. 新しいユーティリティ

よく使用されるツールとメソッドがutils/index.js組み込ま

  • パラメータをフェッチするプロセスでは、複数の渡せるパラメータのうち 1 つだけが渡されるため、空のパラメータはフィルタリングする必要があります (フィルタリング プロセス中、それは有効なパラメータ0である
export const isFalsy = val => val === 0 ? false : !val

// 在函数里,不可用直接赋值的方式改变传入的引用类型变量
export const cleanObject = obj => {
    
    
  const res = {
    
     ...obj }
  Object.keys(res).forEach(key => {
    
    
    const val = res[key]
    if (isFalsy(val)) {
    
    
      delete res[key]
    }
  })
  return res
}
  • URLの後にパラメータを記述する場合、パラメータが多いと面倒なので、qs
npm i qs

前の 2 つの手順の後、状態が改善され、パラメーターがcleanObjectで処理されます。ソース コードは次のようになります。qs

  • src\screens\ProjectList\index.jsx
import {
    
     SearchPanel } from "./components/SearchPanel";
import {
    
     List } from "./components/List";
import {
    
     useEffect, useState } from "react";
import {
    
     cleanObject } from "utils";
import * as qs from 'qs'

const apiUrl = process.env.REACT_APP_API_URL;

export const ProjectListScreen = () => {
    
    
  const [users, setUsers] = useState([]);
  const [param, setParam] = useState({
    
    
    name: "",
    personId: "",
  });
  const [list, setList] = useState([]);

  useEffect(() => {
    
    
    fetch(
      // name=${param.name}&personId=${param.personId}
      `${
      
      apiUrl}/projects?${
      
      qs.stringify(cleanObject(param))}`
    ).then(async (res) => {
    
    
      if (res.ok) {
    
    
        setList(await res.json());
      }
    });
  }, [param]);

  useEffect(() => {
    
    
    fetch(`${
      
      apiUrl}/users`).then(async (res) => {
    
    
      if (res.ok) {
    
    
        setUsers(await res.json());
      }
    });
  }, []);

  return (
    <div>
      <SearchPanel users={
    
    users} param={
    
    param} setParam={
    
    setParam} />
      <List users={
    
    users} list={
    
    list} />
    </div>
  );
};
  • src\screens\ProjectList\components\List.jsx
export const List = ({
     
      users, list }) => {
    
    
  return (
    <table>
      <thead>
        <tr>
          <th>名称</th>
          <th>负责人</th>
        </tr>
      </thead>
      <tbody>
        {
    
    list.map((project) => (
          <tr key={
    
    project.id}>
            <td>{
    
    project.name}</td>
            {
    
    /* undefined.name */}
            <td>
              {
    
    users.find((user) => user.id === project.personId)?.name ||
                "未知"}
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
};
  • src\screens\ProjectList\components\SearchPanel.jsx
export const SearchPanel = ({
     
      users, param, setParam }) => {
    
    
  return (
    <form>
      <div>
        {
    
    /* setParam(Object.assign({}, param, { name: evt.target.value })) */}
        <input
          type="text"
          value={
    
    param.name}
          onChange={
    
    (evt) =>
            setParam({
    
    
              ...param,
              name: evt.target.value,
            })
          }
        />
        <select
          value={
    
    param.personId}
          onChange={
    
    (evt) =>
            setParam({
    
    
              ...param,
              personId: evt.target.value,
            })
          }
        >
          <option value="">负责人</option>
          {
    
    users.map((user) => (
            <option key={
    
    user.id} value={
    
    user.id}>
              {
    
    user.name}
            </option>
          ))}
        </select>
      </div>
    </form>
  );
};
  • src\App.tsx
import "./App.css";
import {
    
     ProjectListScreen } from "screens/ProjectList";

function App() {
    
    
  return (
    <div className="App">
      <ProjectListScreen />
    </div>
  );
}

export default App;

現在の効果: プロジェクト名と人物名でフィルタリング可能 (完全一致)
レンダリング

4.カスタムフック

カスタムフックはコードを再利用するためのツールです

  • useMount: ライフサイクル シミュレーション -ComponentDidMount
export const useMount = cbk => useEffect(() => cbk(), [])

通常の状況では、useEffect は 1 回しか実行されませんが、react@v18 strict モードではデフォルトで useEffect が 2 回実行されます。詳細については、次を参照してください: [解決済み] reverse@v18 useEffect は strict モードでデフォルトで 2 回実行されます

  • useDebounce: デバウンス
/**
 * @param { 值 } val 
 * @param { 延时:默认 1000 } delay 
 * @returns 在某段时间内多次变动后最终拿到的值(delay 延迟的是存储在队列中的上一次变化)
 */
export const useDebounce = (val, delay = 1000) => {
    
    
  const [tempVal, setTempVal] = useState(val)

  useEffect(() => {
    
    
    // 每次在 val 变化后,设置一个定时器
    const timeout = setTimeout(() => setTempVal(val), delay)
    // 每次在上一个 useEffect 处理完以后再运行(useEffect 的天然功能即是在运行结束的 return 函数中清除上一个(同一) useEffect)
    return () => clearTimeout(timeout)
  }, [val, delay])

  return tempVal
}

// 日常案例,对比理解

// const debounce = (func, delay) => {
    
    
//   let timeout;
//   return () => {
    
    
//     if (timeout) {
    
    
//       clearTimeout(timeout);
//     }
//     timeout = setTimeout(function () {
    
    
//       func()
//     }, delay)
//   }
// }

// const log = debounce(() => console.log('call'), 5000)
// log()
// log()
// log()
//   ...5s
// 执行!

// debounce 原理讲解:
// 0s ---------> 1s ---------> 2s --------> ...
//     这三个函数是同步操作,它们一定是在 0~1s 这个时间段内瞬间完成的;
//     log()#1 // timeout#1
//     log()#2 // 发现 timeout#1!取消之,然后设置timeout#2
//     log()#3 // 发现 timeout#2! 取消之,然后设置timeout#3
//             // 所以,log()#3 结束后,就只有最后一个 —— timeout#3 保留

拡張学習: [メモ] さまざまなシナリオにおける手ぶれ補正機能の深い理解と js 手書き

  • Custom Hook後に使用src\screens\ProjectList\index.js(lastParamparam直後)
  ...
  // 对 param 进行防抖处理
  const lastParam = useDebounce(param)
  const [list, setList] = useState([]);

  useEffect(() => {
    
    
    fetch(
      // name=${param.name}&personId=${param.personId}
      `${
      
      apiUrl}/projects?${
      
      qs.stringify(cleanObject(lastParam))}`
    ).then(async (res) => {
    
    
      if (res.ok) {
    
    
        setList(await res.json());
      }
    });
  }, [lastParam]);

  useMount(() => {
    
    
    fetch(`${
      
      apiUrl}/users`).then(async (res) => {
    
    
      if (res.ok) {
    
    
        setUsers(await res.json());
      }
    });
  });
  ...

こうすることで、1s内でもリクエストはprojectsトリガーされません。fetch

拡張学習:


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

おすすめ

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