• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

TS优雅的使用泛型推导

武飞扬头像
碎_浪
帮助98

前言

JavaScript 是一种弱类型或者说动态语言。这意味着你不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。这也意味着你可以使用同一个变量保存不同类型的数据。因此,js的变量声明,只需选择合适的修饰符,不需要显式声明类型。

在编写 JavaScript 代码的时候,如何使用一个变量或调用一个函数,这些问题的答案经常需要我们自己记在脑子里,而且我们必须得祈祷自己处理好了所有细节。要让纯 JavaScript 告诉我们fn在给定特定参数的时候会做什么事,唯一的方法就是实际调用fn函数。

这样的行为使得我们很难在代码执行前进行相关的预测,也意味着我们在编写代码的时候,很难搞清楚代码会做什么事。

场景一:事件总线

一个稍大型的项目里,或多或少,都会有事件总线的身影。跟普通的变量或对象使用interfacetype定义不同,事件总线有不同的EventName及其对应的参数。

正常使用时,每次emit前,都要去接收方看下参数是啥,同理,每次on前,都要看下发送方是啥,一不小心就会多一个或少一个参数,或者变量名写错了。

使用TS限定事件总线势在必行~

const bus = new EventEmitter();
interface EventNameAndProps {
    composition_change: { isComposing: boolean };
    editor_focus: { focus: boolean }; 
}

interface PropEventSource<T> {
    on: <Key extends string & keyof T>(
        eventName: Key,
        handler: (opts: T[Key]) => void
    ) => Function;
    off: <Key extends string & keyof T>(
        eventName: Key,
        handler: (opts: T[Key]) => void
    ) => void;
    emit: <Key extends string & keyof T>(eventName: Key, opts: T[Key]) => void;
}

export const Bus: PropEventSource<EventNameAndProps> = {
    on: (eventName, handler) => {
        bus.on(eventName, handler);
        return () => {
            bus.off(eventName, handler);
        };
    },
    emit: (eventName, opts) => {
        bus.emit(eventName, opts);
    },
    off: (eventName, handler) => {
        bus.off(eventName, handler);
    },
};

这样一来,EventName被限定了,同时,opts的参数在确定是哪个EventName后,会自动推导出对应的类型,并给出语法提示。

Bus.emit('composition_change', { isComposing: true }); // 语法提示会直接告诉你在该eventName下应该传什么变量
Bus.emit('editor_focus', { focus: true }); // 不同的eventName会自动提示不同的变量

不管是发送方还是接收方,如果事件名及对应参数不匹配,就会给出错误提示,完美~

还不赶紧拿到本地试试?

useReduce同样可以使用,通过限定dispatch,来实现调用的时候自动推导type及其对应参数

场景二:useContext优化

在函数式组件中,使用useContext需要谨慎,当所引用的context发生变化时,该函数组件会自动重新执行,哪怕在函数组件外使用memo包裹起来也不行。

但是,如果重新声明一个函数,专门包裹一层context,用于获取组件需要的属性,由于ts声明问题以及组件可能引用不同的context,每个组件都要有一一对应的包裹函数,实现成本太高。

使用TS泛型及自动推导能力,定义了通用的高阶组件。

下面的示例中,组件A需要外部传参a,需要从EditorContext中获取initialized

如果直接在组件A中使用useContext,A会由于EditorContext中其他数据的变化,导致多次无效的更新,具体次数取决于context中其他变量的变化频率。

import { EditorContext } from '@/contexts/EditorContext';
import React, { FunctionComponent, memo, useContext, useMemo } from 'react';

const A = memo(({ a, initialized }: { a: number; initialized: boolean; }) => {
    console.log('renderA', initialized);
    return <div>123</div>;
});

function withContext<T, K extends string & keyof T, P extends Pick<T, K>>(
    context: React.Context<T>,
    keys: K[],
    Component: FunctionComponent<P>
) {
    return (props: Omit<P, K>) => {
        const Comp = useMemo(() => {            return memo(Component);        }, []);

        const value = useMemo(() => {            const opts: Pick<T, K> = {} as any;            keys.forEach((k) => {                opts[k] = d[k];            });            return opts;        }, [d]);
        console.log('renderContext', value);
        // @ts-ignore        return <Comp {...props} {...value} />;
    };
}
const S = withContext(EditorContext, ['initialized'], A); // 该组件可连续嵌套

export function THooks() {
    return <S a={1} />;
}

使用高阶组件withContext自动推导组件A所需参数,在withContext内部获取所需context变量,通过memo避免组件A的多次渲染,一个优化组件就这么完成了。

如果组件需要从多个context中获取变量,只需连续嵌套使用withContext即可。

出于某些考虑,我们项目不再使用redux或mobx进行状态管理,而是在顶层使用context存储一些全局变量,其他状态尽可能下放到子组件中,因此才有的这个高阶函数。

给个赞或关注再走吧~

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanffhig
系列文章
更多 icon
同类精品
更多 icon
继续加载