文章目录
- 一、项目起航:项目初始化与配置
- 二、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 |
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 |
具体配置、操作和内容会有差异,“坑”也会有所不同。。。
一、项目起航:项目初始化与配置
- 【实战】 一、项目起航:项目初始化与配置 —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(一)
二、React 与 Hook 应用:实现项目列表
- 【实战】 二、React 与 Hook 应用:实现项目列表 —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二)
三、TS 应用:JS神助攻 - 强类型
1.TS 的必要性
作为正常人,我们在开发过程中难免会犯以下错误:
- 变量名写错
- 参数少传、多传
- 数组或对象变量层次弄错
相对 JS
在运行时(runtime)才会发现错误,TS
可以帮助我们在 静态代码 中及时定位错误,将 弱类型 的 JS
转为 强类型 的 TS
能够极大地降低我们编码过程中的误码率
2.代码更改
将项目中 src
下 js
文件后缀改为 ts
,jsx
文件后缀改为 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
部分引用笔记还在草稿阶段,敬请期待。。。