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


学习内容来源: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

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


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

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

三、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;

拓展学习:


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

猜你喜欢

转载自blog.csdn.net/qq_32682301/article/details/131321999