[译]<<Effective TypeScript>> 高效TypeScript62个技巧 技巧19

171 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情

本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.

技巧19: 避免 类型推断扰乱你的代码

ts新手往往会给所有变量写类型申明.但是很多类型申明是没必要的: 例如不要这样写:

let x: number = 12;

直接这样写:

let x = 12;

如果把鼠标悬浮在x上面, 就会显示(需要ide支持):

image.png

这种简单类型变量的申明是多余的. 如果不确定ts的类型推断, 可以上面的方法试一试. ts也可以推断复杂类型: 不要如下面这么写:

const person: {
  name: string;
  born: {
    where: string;
    when: string;
  };
  died: {
    where: string;
    when: string;
  }
} = {
  name: 'Sojourner Truth',
  born: {
    where: 'Swartekill, NY',
    when: 'c.1797',
  },
  died: {
where: 'Battle Creek, MI',
    when: 'Nov. 26, 1883'
  }
};

而是这么写:

const person = {
  name: 'Sojourner Truth',
  born: {
    where: 'Swartekill, NY',
    when: 'c.1797',
  },
  died: {
    where: 'Battle Creek, MI',
    when: 'Nov. 26, 1883'
  }
};

同样的, 在这写类型申明也是多余的. 和object类似的, array也是一样:

function square(nums: number[]) {
  return nums.map(x => x * x);
}
const squares = square([1, 2, 3, 4]); // Type is number[]

有的时候, ts 的类型推荐比你期待的更精确:

const axis1: string = 'x';  // Type is string
const axis2 = 'y';  // Type is "y"

运行类型推断还能有利于重构. 比如你有一个Product 的类型, 和一个函数去log 它:

interface Product {
  id: number;
  name: string;
  price: number;
}

function logProduct(product: Product) {
  const id: number = product.id;
  const name: string = product.name;
  const price: number = product.price;
  console.log(id, name, price);
}

当你想把 Product 的 id 类型从number改为string.发现报错:

interface Product {
  id: string;
  name: string;
  price: number;
}

function logProduct(product: Product) {
  const id: number = product.id;
     // ~~ Type 'string' is not assignable to type 'number'
  const name: string = product.name;
  const price: number = product.price;
  console.log(id, name, price);
}

当你把 logProduct里面的类型申明删除就不会有这个报错:

function logProduct(product: Product) {
  const {id, name, price} = product;
  console.log(id, name, price);
}

上面这个版本允许所有变量进行类型推断. 但是当显示类型注释的版本则是重复且混乱的:

function logProduct(product: Product) {
  const {id, name, price}: {id: string; name: string; price: number } = product;
  console.log(id, name, price);
}

当ts无法根据上下文推断变量类型, 显示类型注释的版本是有用的.有一种常见情况: 函数参数. 有些语言会根据函数最终用途, 来推断函数参数类型, 但是ts不会.

在ts中, 变量类型在其第一次被引入的时候确认了. 理想的ts函数拥有类型签名而不是内部的局部变量. 能让读者专注于实现函数的逻辑.

有几种情况函数参数不需要显示类型注释:

  1. 函数参数有默认值:

    function parseNumber(str: string, base=10) {
      // ...
    }
    
  2. 函数作为某些库的回调函数, 这些库需要有类型注释:

    / Don't do this:
    app.get('/health', (request: express.Request, response: express.Response) => {
      response.send('OK');
    });
    
    // Do this:
    app.get('/health', (request, response) => {
      response.send('OK');
    });
    

有几种情况, 你需要显示类型注释:

  1. 定义object时;
    const elmo: Product = {
      name: 'Tickle Me Elmo',
      id: '048188 627152',
      price: 28.99,
    };
    
    这样能执行类型检查, 尽早捕捉类型错误.特别当你有用可选属性.如果没有显示类型注释, elmo会在使用时报错, 而不是定义的时候报错 :
    const furby = {
      name: 'Furby',
      id: 630509430963,
      price: 35,
    };
    logProduct(furby);
            // ~~~~~ Argument .. is not assignable to parameter of type 'Product'
            //         Types of property 'id' are incompatible
            //         Type 'number' is not assignable to type 'string'
    
    当拥有显示注释, 就能尽早发现错误:
    const furby: Product = {
       name: 'Furby',
       id: 630509430963,
    // ~~ Type 'number' is not assignable to type 'string'
       price: 35,
     };
     logProduct(furby);
    
  2. 函数返回值: 相同的, 当拥有显示类型注释,就能在函数实现的时候发现错误, 而不是函数使用的时候. 假如你有一个函数用来查询股票价格:
    function getQuote(ticker: string) {
      return fetch(`https://quotes.example.com/?q=${ticker}`)
          .then(response => response.json());
    }
    
    你决定增加缓存cache, 避免重复请求:
    const cache: {[ticker: string]: number} = {};
    function getQuote(ticker: string) {
      if (ticker in cache) {
        return cache[ticker];
      }
      return fetch(`https://quotes.example.com/?q=${ticker}`)
          .then(response => response.json())
          .then(quote => {
            cache[ticker] = quote;
            return quote;
          });
    }
    
    函数体里面有两种类型的返回值, 当调用 getQuote, 可能会出错:
    getQuote('MSFT').then(considerBuying);
                  // ~~~~ Property 'then' does not exist on type
                  //        'number | Promise<any>'
                  //      Property 'then' does not exist on type 'number'
    
    当你显示标注了返回值:那么就能在正确的地方报错:
    const cache: {[ticker: string]: number} = {};
    function getQuote(ticker: string): Promise<number> {
      if (ticker in cache) {
        return cache[ticker];
            // ~~~~~~~~~~~~~ Type 'number' is not assignable to 'Promise<number>'
      }
      // ...
    }
    
    写清楚返回类型能帮助你在实现之前思考: 输入输出类型是什么? 实现的代码可能会改变, 但是输入输出类型一般不会变.这有点像 TDD(测试驱动编程)
  3. 当ts无法正确推断的时候:
    interface Vector2D { x: number; y: number; }
    function add(a: Vector2D, b: Vector2D) {
      return { x: a.x + b.x, y: a.y + b.y };
    }
    
    你想要ts能推断出返回值类型为: Vector2D. 但是ts却推断出:{ x: number; y: number; }