记录

1. 懒推导

ts的类型推导方式是懒推导,不会实际执行代码。

function add(num1: number, num2: number) {
    if (num1 === 1) {
        return null;
    }
    if (num1 === 2) {
        return undefined;
    }
    return num1 + num2;
}

const c = add(1, 2); // c: number | null | undefined

这段代码如果实际执行了,c的类型是能确认为 null 的。

2. 泛型推导

function merge(a: object, b: object) {
    return {
        ...a,
        ...b,
    };
}

const u1 = {
    name: 'hello',
};

const u2 = {
    age: 1,
};

const u3 = merge(u1, u2); // u3: {}

可以看到按照上面写法,对象合并推导不出来,如果能推导出来u3应该等于 {name: string, age: number}

这时候我们可以借助范型来推导:

function merge<T, U>(a: T, b: U): T & U {
    return {
        ...a,
        ...b,
    };
}

const u1 = {
    name: 'hello',
};

const u2 = {
    age: 1,
};

const u3 = merge<typeof u1, typeof u2>(u1, u2);
// u3: { name: string } & { age: number }

可以给上面代码简写为这样,编辑器也能推导出来:

function merge<T, U>(a: T, b: U) {
    return {
        ...a,
        ...b,
    };
}

const u1 = {
    name: 'hello',
};

const u2 = {
    age: 1,
};

const u3 = merge(u1, u2);

实战

实现 pick 方法

从一个对象中,返回指定属性组成的新对象。

function pick<T, U extends keyof T>(data: T, keys: U[]): { [K in U]: T[K] } {
  const temp: any = {};

  for( const key of keys) {
    temp[key] = data[key];
  }

  return temp;
}

上面代码中定义了两个范型T和U,T表示对象,U被限定为T的属性名(U extends keyof T),返回值的类型为{[K in U]: T[K]},in的作用就是遍历U这个数组。

%E6%88%AA%E5%B1%8F2024-07-03%2017.26.42.png

可以看到数组元素被限制了只能是user对象里的key。

%E6%88%AA%E5%B1%8F2024-07-03%2017.34.36.png

也正确的推导出来了。

实现useRequest

先看一个例子

import { useEffect, useState } from 'react';

// 模拟请求接口,返回用户列表
function getUsers(): Promise<{ name: string }[]> {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve([
        {
          name: 'tom',
        },
        {
          name: 'jack',
        },
      ]);
    }, 1000);
  })
}

const App = () => {

  const [loading, setLoading] = useState(true);
  const [users, setUsers] = useState<Awaited<ReturnType<typeof getUsers>>>([]);
  const [error, setError] = useState(false);

  useEffect(() => {
    setLoading(true);
    getUsers().then((res) => {
      setUsers(res);
    }).catch(() => {
      setError(true);
    }).finally(() => {
      setLoading(false);
    })
  }, []);

  if (loading) {
    return (
      <div>loading...</div>
    )
  }

  if (error) {
    return (
      <div>error</div>
    )
  }

  return (
    <div
    >
      {users.map(u => (
        <div key={u.name}>{u.name}</div>
      ))}
    </div>
  );
};

export default App;

上面这个例子实现了从后端请求用户列表,然后渲染出来。为了提高用户体验,在加载数据时,加了一个loading,当请求出错时,告诉用户请求失败。

代码比较简单我就不一一讲解了,有行代码需要注意一下。

const [users, setUsers] = useState<Awaited<ReturnType<typeof getUsers>>>([]);

typeof getUsers 获取 getUsers 函数类型

ReturnType 获取某个函数的返回值
Awaited 如果函数返回值为Promise,这个可以获取到最终的值类型。

%E6%88%AA%E5%B1%8F2024-07-03%2017.39.57.png

可以看到,正确的获取到了getUsers函数的返回值类型。

然而一个很简单的功能需要写那么多代码,肯定是不合理的,那么我们给简化一下。目前市面上已经有不少库来解决这个问题了,比如react-query或ahooks库里的useRequest,都可以解决这个问题,我这里分享的不是具体代码实现,而是怎么写ts。

封装useRequest

import { useEffect, useState } from 'react';

export function useRequest<T extends () => Promise<unknown>>(
  fn: T,
): {
  loading: boolean;
  error: boolean;
  data: Awaited<ReturnType<T>>;
} {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(false);
  const [data, setData] = useState<any>();

  useEffect(() => {
    setLoading(true);
    fn().then(res => {
      setData(res);
    }).catch(() => {
      setError(true);
    }).finally(() => {
      setLoading(false);
    });
  }, [fn])

  return {
    loading,
    error,
    data,
  };
}

改造app.tsx文件,使用 useRequest

import { useRequest } from './useRequest';

// 模拟请求接口,返回用户列表
function getUsers(): Promise<{ name: string }[]> {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve([
        {
          name: 'tom',
        },
        {
          name: 'jack',
        },
      ]);
    }, 1000);
  })
}

const App = () => {

  const { loading, data: users, error } = useRequest(getUsers);

  if (loading) {
    return (
      <div>loading...</div>
    )
  }

  if (error) {
    return (
      <div>error</div>
    )
  }

  return (
    <div
    >
      {users.map(u => (
        <div
          key={u.name}
        >
          {u.name}
        </div>
      ))}
    </div>
  );
};

export default App;

对比最开始的代码,是不是简单了很多。

相关阅读


A Student on the way to full stack of Web3.