看完zustand源码后,我的TypeScript水平突飞猛进。

本文详细介绍了TypeScript中的类型推断在Zustand库中的应用,包括变量和函数返回值的自动推导,以及如何使用范型处理对象合并和中间件。作者通过实例展示了如何简化状态管理代码并确保类型安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

过年期间在家里没事,把zustand的源码看了一遍,看完后我最大的收获就是ts水平突飞猛进,再去刷那些类型体操题目就变得简单了,下面和大家分享一下zustand库是怎么定义ts类型的。

ts类型推断

个人认为ts最大的作用有两个,一个是类型约束,另外一个是类型推断。

  • 类型约束也叫类型安全,在编译阶段就能发现语法错误,可以有效减少低级错误。
  • 类型推断,当你没有标明变量的类型时,编译器会根据一些简单的规则来推断你定义的变量的类型

这一篇主要和大家分享类型推断,类型推断主要有以下几种情况。

根据变量的值自动推导类型

image.png

image.png

函数返回值自动推断

image.png

函数中如果有条件分支,推导出来的返回值类型是所有分支的返回值类型的联合类型

image.png

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

image.png

上图中如果实际执行了,c的类型是能确认为null的。

使用范型推导

image.png

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

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

image.png

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

image.png

实战

实现pick方法

从一个对象中,返回指定的属性名称。

image.png

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

image.png

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

image.png

image.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,这个可以获取到最终的值类型。

image.png

image.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;

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

useRequest.tsx代码也很简单,首先使用了范型限制fn只能是一个函数,返回值还必须是Promise。这个hooks返回值loading和error就不说了,主要是data,这个data要求和传进来的方法返回值一致,前面说过,可以使用Awaited<ReturnType<T>>获取函数的返回类型。

image.png

但是上面代码可能会导致bug,看下面代码,如果请求失败,users应该是空的,直接这样使用就会报错了。改造一下,当error为false的时候data为正常类型,error为true的时候data为null,这里可以使用联合类型。

image.png

image.png

image.png

image.png

加了一个判断后,下面就不会报错了。ts在某些时候,真的可以避免一些低级错误,我相信如果没有这个限制,肯定有人在写代码的时候不加判断直接用users。

如果请求接口的函数需要参数怎么办,下面来实现一下。

image.png

使用Parameters获取传入函数的参数类型

image.png

image.png

多个参数也是支持的

image.png

zustand

zustand是一个react状态管理库,使用起来比较简单没啥心智负担,所以我一直在用。

上面带着大家入门了ts的类型推断,下面给大家分享一下zustand的ts定义。我看完zustand源码后,发现这个库的ts定义比功能实现还复杂,这里我只给大家分享ts,具体实现掘金已经有很多大佬写过了,我就不分享了。

先从一个最简单的例子开始

import { create } from 'zustand';

interface State {
  count: number;
}

interface Action {
  inc: () => void;
}

export const useStore = create<State & Action>((set) => ({
  count: 1,
  inc: () => set((state) => ({count: state.count + 1})),
}));

image.png

create方法的定义

type Create = {
   
  <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(
    initializer: StateCreator<T, [], Mos>,
  ): UseBoundStore<Mutate<StoreApi<T>, Mos>>
  <T>(): <Mos extends [StoreMutatorIdentifier, unknown][] = []>(
    initializer
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值