Article Directory
Source of learning content: React + React Hook + TS Best Practice - MOOC
Compared with the original tutorial, I used the latest version at the beginning of my study (2023.03):
item | Version |
---|---|
react & react-dom | ^18.2.0 |
react-router & react-router-dom | ^6.11.2 |
antd | ^4.24.8 |
@commitlint/cli & @commitlint/config-conventional | ^17.4.4 |
eslint-config-prettier | ^8.6.0 |
husky | ^8.0.3 |
lint-staged | ^13.1.2 |
prettier | 2.8.4 |
json-server | 0.17.2 |
craco-less | ^2.0.0 |
@craco/craco | ^7.1.0 |
qs | ^6.11.0 |
dayjs | ^1.11.7 |
react-helmet | ^6.1.0 |
@types/react-helmet | ^6.1.6 |
react-query | ^6.1.0 |
@welldone-software/why-did-you-render | ^7.0.1 |
@emotion/react & @emotion/styled | ^11.10.6 |
The specific configuration, operation and content will be different, and the "pit" will also be different. . .
1. Project launch: project initialization and configuration
2. React and Hook application: implement the project list
1. Create a new file
- New file: src\screens\ProjectList\index.jsx
import {
SearchPanel } from "./components/SearchPanel"
import {
List } from "./components/List"
export const ProjectListScreen = () => {
return <div>
<SearchPanel/>
<List/>
</div>
}
- New file: src\screens\ProjectList\components\List.jsx
export const List = () => {
return <table></table>
}
- New file: 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>
}
- Compared with the original video, here is the name used for components: Upper Camel Case
- Compared with the original video, the directory structure and variable names can be used according to your own habits!
- The coding process is very important, but the text is not easy to reflect. . .
- Vscode will not automatically complete HTML tags in JS files, please refer to: [Tips] Vscode completes HTML tags in JS files
2. Status improvement
Since list
and param
involve two different components, these two need to be state
promoted to their common parent component, and the child component props
is used by deconstruction:
list
List
consumed bylist
param
Acquired according to ;param
SearchPanel
consumed by
According to the database paradigm thinking, project
, users
are each a separate table, and list
are only intermediate products of associated queries. hard
In the mode, project
only the users
primary key of canpersonId
be obtained through .personId
personName
users
In DRY
principle , http://host:port
extract the in the interface call URL to the project global environment variable:
- .env
REACT_APP_API_URL=http://online.com
- .env.development
REACT_APP_API_URL=http://localhost:3001
webpack
Understanding of environment variable identification rules:
npm start
When executing ,webpack
read the environment variables.env.development
in ;npm run build
When executing ,webpack
read the environment variables.env
in ;
3. New utils
Commonly used tools and methods are put utils/index.js
in
- Since only one of multiple passable parameters is passed in the process of fetching parameters, empty parameters need to be filtered (during the filtering process, it is considered that
0
is valid parameter, so it is treated specially):
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
}
- When spelling parameters after the url, it will be cumbersome if there are many parameters, so introduce
qs
npm i qs
After the previous two steps, the state is improved and the parameters are processed with cleanObject
and , the source code is as follows: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;
Current effect: can be filtered by project name and person name (full match)
4.Custom Hook
Custom Hook is a tool for code reuse
- useMount: life cycle simulation - componentDidMount
export const useMount = cbk => useEffect(() => cbk(), [])
Under normal circumstances, useEffect is only executed once, but useEffect is executed twice by default in react@v18 strict mode. For details, see: [Solved] react@v18 useEffect is executed twice by default in strict mode
- useDebounce: debounce
/**
* @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 保留
Extended learning: [Notes] In-depth understanding and js handwriting of anti-shake functions in different scenarios
- used
Custom Hook
aftersrc\screens\ProjectList\index.js
(lastParam
defined immediatelyparam
after )
...
// 对 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());
}
});
});
...
This way, retyping 1s
within will not trigger a requestprojects
forfetch
Extended learning:
Some reference notes are still in draft stage, so stay tuned. . .