記事ディレクトリ
学習コンテンツのソース: 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
および にはparam
2 つの異なるコンポーネントが含まれるため、これら 2 つは共通の親コンポーネントにstate
昇格、子コンポーネントprops
は分解によって使用されます。
list
によってList
消費されるlist
に従ってparam
取得されます。param
によってSearchPanel
消費される
データベース パラダイムの考え方によれば、project
はusers
それぞれ別個のテーブルであり、関連するクエリの中間生成物にlist
すぎません。hard
このモードでは、 の主キーproject
のみがを通じてusers
取得できますpersonId
。personId
personName
users
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 保留
Custom Hook
後に使用src\screens\ProjectList\index.js
(lastParam
のparam
直後)
...
// 对 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
拡張学習:
一部の参考ノートはまだ草案段階にあるため、今後の内容に注目してください。。。