哈喽大家好,我是Range。上次分享的typescript使用技巧,很多同学表示很有用,解决了实际开发中的很多疑惑。今天再带来一篇关于typescript的文章,从基础的用法开始,到zustand类库里的一些高级用法,希望大家能进一步理解typescript,拒绝 any 党。
下面是正文部分。
在编程世界的某个安静角落,TypeScript(TS)以其强大的类型系统静静地守护着代码的纯洁和安全。在这段探索旅程中,我们将跟随一个好奇心旺盛的开发者的脚步,一探 zustand 库的源码深处,揭开 TS 类型推断的神秘面纱。
TS 的两大法宝——类型约束和类型推断,就像是编程世界的守夜人。类型约束确保我们的代码在破晓前就远离了语法的怪兽,而类型推断则是那位在幕后默默工作的智者,它用无形的手指引编译器,让变量的类型在没有明确声明的情况下也能被精准地识别。
想象一下,变量是一群渴望被发现的探险家,它们带着初始的宝藏(值)出发,而 TS 的类型推断就是那神秘的地图,总能引领它们找到真正的自我(类型)。
let numArray: number[] = [1, 2, 3];
let str: string = 'hello';
函数是 TS 世界的多面镜,它们不仅反射出自身的类型,还能在条件分支中展现出多重身份。每一面镜子都映照出一个可能的返回类型,而 TS 智者总能巧妙地将这些身份融合为一个联合体。
function getFirstItem(array: number[] | string[]): number | string {
return array[0];
}
泛型是 TS 世界的魔法,它赋予了代码以无穷的变化和灵活性。通过泛型,我们可以创造出能够适应任何类型的神奇函数,就像一个万能钥匙,打开了类型安全的大门。
function pick<T, K extends keyof T>(obj: T, keys: K[]): Partial<T> {
const result: Partial<T> = {} as any;
for (const key of keys) {
result[key] = obj[key];
}
return result;
}
在对象的海洋中,我们需要一种法术来挑选出我们所需要的属性。这就是 pick 方法,一个简单而强大的咒语,能够让我们在众多属性中找到那一颗璀璨的珍珠。
interface User {
name: string;
age: number;
}
type UserSubset = 'name' | 'age';
function getUserSubset(user: User, keys: UserSubset[]): Pick<User, UserSubset>;
在一个充满异步请求的王国里,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 [users, setUsers] = useState<Awaited<ReturnType<typeof getUsers>>>([]);
const [loading, setLoading] = useState(true);
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;
zustand 库是 React 世界的一块宝石,它的状态管理能力让无数开发者为之倾倒。让我们一起深入它的源码,探索 TS 类型定义的奥秘。
在这段源码的探秘中,我们首先会遇到 create
方法,它是 zustand 库的心脏,负责初始化我们的状态和动作。通过这个方法,我们可以定义一个状态机,它能够响应各种操作并保持数据的一致性。
export const useStore = create<State & Action>((set) => ({
count: 1,
inc: () => set((state) => ({ count: state.count + 1 })),
}));
create
方法的定义是一系列重载的函数,它们允许我们以不同的方式定义状态和动作。这些重载函数的类型定义是如此精妙,以至于它们能够适应各种复杂的场景,同时保持类型的准确性和灵活性。
type Create = {
<T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(
initializer: StateCreator<T, [], Mos>,
): UseBoundStore<Mutate<StoreApi<T>, Mos>>;
<T>() => <Mos extends [StoreMutatorIdentifier, unknown][] = []>(
initializer: StateCreator<T, [], Mos>,
) => UseBoundStore<Mutate<StoreApi<T>, Mos>>,
/**
* @deprecated Use `useStore` hook to bind store
*/
<S extends StoreApi<unknown>>(store: S): UseBoundStore<S>
};
在 zustand 库的类型定义中,我们还可以定义中间件,这些中间件可以改变 store 的行为,同时保持类型的准确性。
import { StateCreator, StoreMutatorIdentifier } from 'zustand';
type Test = <
T,
Mps extends [StoreMutatorIdentifier, unknown][] = [],
Mcs extends [StoreMutatorIdentifier, unknown][] = [],
U = T
>(
initializer: StateCreator<T, [...Mps, ['test', unknown]], Mcs>
) => StateCreator<T, Mps, [['test', U], ...Mcs]>;
type Write<T, U> = Omit<T, keyof U> & U;
declare module 'zustand' {
interface StoreMutators<S, A> {
test: Write<S, {test: {log: () => void}}>;
}
}
function a() {
console.log(444);
}
export const test = a as unknown as Test;
通过这段旅程,我们不仅领略了 TypeScript 类型推断的魅力,还深入探索了 zustand 库的源码。这一路上,我们学会了如何使用 TS 的类型系统来编写更加安全、灵活和可维护的代码。就像所有的探险故事一样,我们的旅程也许结束了,但 TS 的魔法和 zustand 的奇妙世界仍将继续引领我们走向更多的发现和创造。
还没有使用过我们刷题网站(https://fe.ecool.fun/)或者前端面试题宝典的同学,如果近期准备或者正在找工作,千万不要错过,题库主打无广告和更新快哦~。
老规矩,也给我们团队的辅导服务打个广告。