TypeScript 函数重载,重新认识 Mobx

740 阅读4分钟

函数重载

这个概念只会在一些强类型语言中才有,在 JavaScript 中依据不同参数类型或参数个数执行一些不同函数体的实现很常见,TypeScript 就会需要用到这种声明的地方。
函数重载定义:函数名相同,函数的参数列表不同(包括参数个数和参数类型),根据参数的不同去执行不同的操作。
关于函数重载,必须要把精确的定义放在前面,最后函数实现时,需要使用操作符或者?操作符,把所有可能的输入类型全部包含进去。

JavaScript 函数重载

function overload(a){
  console.log('一个参数')
}

function overload(a,b){
  console.log('两个参数')
}

// 在支持重载的编程语言中,比如 java
overload(1);         //一个参数
overload(1,2);    //两个参数

// 在 JavaScript 中
overload(1);         //两个参数
overload(1,2);    //两个参数

JavaScript 中,同一个作用域,出现两个名字一样的函数,后者会覆盖前者,所以 JavaScript 没有真正意义的重载。

但是有各种办法,能在 JavaScript 中模拟实现重载的效果。
我们要在 users 对象中添加一个 find 方法,当不传任何参数时,返回整个 users.values
当传一个参数时,就把 first-name 和这个参数匹配的元素返回;
当传两个参数时,则把 first-namelast-name 都匹配返回。

    let users = {
        values: ["Dean Edwards", "Alex Russell", "Dean Tom"]
    };
    
    /**
     * @param object 绑定方法的对象
     * @param name 绑定的方法名称
     * @param fn 需要绑定的方法
    */
    function addMethod(object, name, fn) {
        let old = object[name]

        object[name] = function () {
            if (fn.length === arguments.length) return fn.apply(this, arguments)
            else if (typeof old === 'function') return old.apply(this, arguments)
        }
    }

    function find0() {
        return this.values
    }

    function find1(firstName) {
        let ret = []
        this.values.forEach(item => item.includes(firstName) && ret.push(item))
        return ret
    }

    function find2(firstName, lastName) {
        let ret = []
        this.values.forEach(item => item === `${firstName} ${lastName}` && ret.push(item))
        return ret
    }

    addMethod(users, "find", find0);
    addMethod(users, "find", find1);
    addMethod(users, "find", find2);

    console.log(users.find()); // ["Dean Edwards", "Alex Russell", "Dean Tom"]
    console.log(users.find("Dean")); // ["Dean Edwards", "Dean Tom"]
    console.log(users.find("Dean", "Edwards")); // ["Dean Edwards"]

addMethod 函数是利用了闭包的特性,通过变量 old 将每个函数连接起来,让所有函数都留在内存中。
每调用一个 addMethod 函数,就会产生一个 old,形成一个闭包。我们可以通过 console.dir(users.find),把 find 方法打印到控制台看看。

WechatIMG441.png

TypeScript 函数重载

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
function pickCard(x): any {
  if (typeof x === "object") {
    let pickedCard = Math.floor(Math.random() * x.length);
    return pickedCard;
  }

  if (typeof x === "number") {
    let pickedSuit = Math.floor(x / 13);
    return { suit: suits[pickedSuit], card: x % 13 };
  }
}

let myDeck = [
  { suit: "diamonds", card: 2 },
  { suit: "spades", card: 10 },
  { suit: "hearts", card: 4 },
];
let pickedCard1 = myDeck[pickCard(myDeck)];
console.log("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
console.log("card: " + pickedCard2.card + " of " + pickedCard2.suit);

注意:function pickCard(x): any 并不是重载列表的一部分,因此这里只有两个重载:一个是接收对象另一个接受数字。以其它参数调用 pickCard 会产生错误。

重载好处

重载其实是把多个功能相近的函数合并为一个函数,重复利用了函数名。假如jQuery中的css( )方法不使用 重载,那么就要有5个不同的函数,来完成功能,那我们就需要记住5个不同的函数名,和各个函数相对应的参数的个数和类型,显然就麻烦多了。虽然,重载能为我们带来许多的便利,但是也不能滥用,不要把一些根本不相关的函数合为一个函数,那样并没有什么意义。

如果函数的返回值类型相同,那么就不需要使用函数重载。

function func (a: number): number
function func (a: number, b: number): number
// 参数个数的区别,使用可选参数来代替函数重载的定义
function func (a: number, b?: number): number


function func (a: number): number
function func (a: string): number
// 使用联合类型来代替函数重载
function func (a: number | string): number

Mobx + 函数重载

创建 counterStore

import { observable } from "mobx";

export interface CounterStore {
  counter: number;
  increment: () => void;
  decrement: () => void;
  incrementAsync: () => void;
}

const counterStore: CounterStore = observable({
  counter: 0,
  increment() {
    this.counter++;
  },
  decrement() {
    this.counter--;
  },
  incrementAsync() {
    setTimeout(() => {
      this.counter++;
    }, 1000);
  },
});

export default counterStore;

Store 类型定义

import counterStore from "./counter";

const _store = {
  counterStore,
};

export type StoreType = typeof _store;

export default _store;

创建 useStores 自定义Hook

import { useContext } from "react";
import { MobXProviderContext } from "mobx-react";
import { StoreType } from "src/stores";

interface ContextType {
  stores: StoreType;
}

function useStores(): StoreType;
function useStores<T extends keyof StoreType>(storeName: T): StoreType[T];

/**
 * 获取根 store 或者指定 store 名称数据
 * @param storeName 指定子 store 名称
 * @returns typeof StoreType[storeName]
 */
function useStores<T extends keyof StoreType>(storeName?: T) {
  const rootStore = useContext(MobXProviderContext);
  const { stores } = rootStore as ContextType;
  return storeName ? stores[storeName] : stores;
}

export default useStores;

使用

import { observer } from "mobx-react";
import useStores from "src/hooks/useStores";

const Home = () => {
  const counterStore = useStores("counterStore");

  return (
    <>
      <div>current counter:{counterStore.counter}</div>
      <div onClick={() => counterStore.increment()}>增加</div>
      <div onClick={() => counterStore.decrement()}>减少</div>
    </>
  );
};

export default observer(Home);

参考文章:TypeScript学习(二)函数重载