首页 前端知识 【实战】三、TS 应用:JS神助攻 - 强类型 —— React17 React Hook TS4 最佳实践,仿 Jira 企业级项目(三)

【实战】三、TS 应用:JS神助攻 - 强类型 —— React17 React Hook TS4 最佳实践,仿 Jira 企业级项目(三)

2024-02-09 20:02:11 前端知识 前端哥 808 551 我要收藏

文章目录

    • 一、项目起航:项目初始化与配置
    • 二、React 与 Hook 应用:实现项目列表
    • 三、TS 应用:JS神助攻 - 强类型
      • 1.TS 的必要性
      • 2.代码更改


学习内容来源:React + React Hook + TS 最佳实践-慕课网


相对原教程,我在学习开始时(2023.03)采用的是当前最新版本:

版本
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
prettier2.8.4
json-server0.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

具体配置、操作和内容会有差异,“坑”也会有所不同。。。


一、项目起航:项目初始化与配置

  • 【实战】 一、项目起航:项目初始化与配置 —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(一)

二、React 与 Hook 应用:实现项目列表

  • 【实战】 二、React 与 Hook 应用:实现项目列表 —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二)

三、TS 应用:JS神助攻 - 强类型

1.TS 的必要性

作为正常人,我们在开发过程中难免会犯以下错误:

  • 变量名写错
  • 参数少传、多传
  • 数组或对象变量层次弄错

相对 JS 在运行时(runtime)才会发现错误,TS 可以帮助我们在 静态代码 中及时定位错误,将 弱类型JS 转为 强类型TS 能够极大地降低我们编码过程中的误码率

2.代码更改

将项目中 srcjs 文件后缀改为 tsjsx 文件后缀改为 tsx,并对文件代码做如下修改:

  • 有参数的组件使用 interface 声明参数类型
  • 公用类型的可以导出+引入
  • 不明确类型的显性赋予 unknow 类型 (严格版 any)
  • 不确定参数是否会传的使用 ?: 赋予类型
  • 用泛型来规范类型

更多 ts 知识学习可见:【笔记】TS入门

更改后的文件如下:

  • src\utils\index.ts
import { useEffect, useState } from "react";
export const isFalsy = (val: unknown) => (val === 0 ? false : !val);
// 在函数里,不可用直接赋值的方式改变传入的引用类型变量
export const cleanObject = (obj: object) => {
const res = { ...obj };
Object.keys(res).forEach((key) => {
//@ts-ignore
const val = res[key];
if (isFalsy(val)) {
//@ts-ignore
delete res[key];
}
});
return res;
};
export const useMount = (cbk: () => void) => useEffect(() => cbk(), []);
/**
* @param {} val
* @param { 延时:默认 1000 } delay
* @returns 在某段时间内多次变动后最终拿到的值(delay 延迟的是存储在队列中的上一次变化)
*/
export const useDebounce = <V>(val: V, delay: number = 1000) => {
// V 泛型,表示传入与返回类型相同
const [tempVal, setTempVal] = useState(val);
useEffect(() => {
// 每次在 val 变化后,设置一个定时器
const timeout = setTimeout(() => setTempVal(val), delay);
// 每次在上一个 useEffect 处理完以后再运行(useEffect 的天然功能即是在运行结束的 return 函数中清除上一个(同一) useEffect)
return () => clearTimeout(timeout);
}, [val, delay]);
return tempVal;
};
复制
  • src\screens\ProjectList\index.jsx
import { SearchPanel } from "./components/SearchPanel";
import { List } from "./components/List";
import { useEffect, useState } from "react";
import { cleanObject, useDebounce, useMount } 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: "",
});
// 对 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());
}
});
});
return (
<div>
<SearchPanel users={users} param={param} setParam={setParam} />
<List users={users} list={list} />
</div>
);
};
复制
  • src\screens\ProjectList\components\List.jsx
import { User } from "./SearchPanel";
interface Project {
id: string;
name: string;
personId: string;
star: boolean;
organization: string;
}
interface ListProps {
users: User[];
list: Project[];
}
export const List = ({ users, list }: ListProps) => {
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 interface User {
id: string;
name: string;
email: string;
title: string;
organization: string;
}
interface SearchPanelProps {
users: User[];
param: {
name: string;
personId: string;
};
setParam: (param: SearchPanelProps["param"]) => void;
}
export const SearchPanel = ({ users, param, setParam }: SearchPanelProps) => {
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;
复制

拓展学习:

  • 【笔记】TS 泛型
  • 【实战】用 Custom Hook + TS泛型实现 useArray

部分引用笔记还在草稿阶段,敬请期待。。。

转载请注明出处或者链接地址:https://www.qianduange.cn//article/1569.html
标签
react.js
评论
会员中心 联系我 留言建议 回顶部
复制成功!