类型即正义:TypeScript 从入门到实践(三):类型别名和类

在这里插入图片描述

我们研发开源了一款基于 Git 进行技术实战教程写作的工具,我们图雀社区的所有教程都是用这款工具写作而成,欢迎 Star

如果你想快速了解如何使用,欢迎阅读我们的 教程文档

学习了注解函数,又了解了类型运算如联合类型和交叉类型,接下来我们来了解一些 TS 中独有的类型别名,它类似 JS 变量,是类型变量,接着我们还会学习 TS 中内容非常庞杂的内容之一:类,了解 TS 中类的独有特性,以及如何注解类,甚至用类去注解其他内容。

欢迎阅读 类型即正义,TypeScript 从入门到实践系列:

本文所涉及的源代码都放在了 Github 或者 Gitee 上,如果您觉得我们写得还不错,希望您能给❤️这篇文章点赞+GithubGitee仓库加星❤️哦~

此教程属于 React 前端工程师学习路线的一部分,欢迎来 Star 一波,鼓励我们继续创作出更好的教程,持续更新中~

运行代码

如果你偏爱 码云,那么你可以运行如下命令获取这一步的代码,然后你可以跟着文章的内容将代码做出修改:

git clone -b part-three https://gitee.com/tuture/typescript-tea.git
cd typescript-tea && npm install && npm start

如果你偏爱 Github,那么你可以运行如下命令来获取初始代码:

git clone -b part-thre [email protected]:tuture-dev/typescript-tea.git
cd typescript-tea && npm install && npm start

类型别名

就像我们为了在平时开发中更加灵活而创建变量或者干掉硬编码数据一样,TS 为我们提供了类型别名,它允许你为类型创建一个名字,这个名字就是类型的别名,进而你可以在多处使用这个别名,并且有必要的时候,你可以更改别名的值(类型),以达到一次替换,多处应用的效果。

我们来看一个简单的类型别名的例子,假如我们有一个获取一个人姓名的函数,它接收一个参数,这个参数有可能直接是要获取的姓名,它是一个 string 类型,也有可能是一个另外一个函数,需要调用它以获取姓名,它是一个函数类型,我们来看一下这个例子:

function getName(n) {
   
  if (typeof n === 'string') {
   
    return n;
  } else {
   
    return n();
  }
}

如果我们要给这个 n 进行类型注解,那么它应该同时是 string | () => string ,是 string 类型和 () => string 函数类型的联合类型,有过一定开发经验的同学可能会发觉,这样写可能很影响原代码的可读性,而且这个 n 的类型可能会变化,因为我们的函数可能扩展,所以如果我们用一个类型别名把这个 n 的类型表示出来,那么就类似我们用变量替代了硬编码,可扩展性就更强了,我们马上来尝试一下:

type NameParams = 'string' | () => 'string';

function getName(n: NameParams): string {
   
  // ... 其它一样
}

可以看到我们用了一个 NameParams 类型别名,它保存着原联合类型,类型别名就是等号左边是 type 关键字加上别名变量,等号右边是带保存的类型,这个类型很广,它可以是字面量类型,基础类型,元组、函数、联合类型和交叉类型、甚至还可以是其他类型别名的组合。

所以对于上面的 NameParams ,我们可以进一步拆解它为如下的样子:

type Name = string;
type NameResolver = () => string;
type NameParams = Name | NameResolver;

function getName(n: NameParams): Name {
   
  // ... 其他一样
}

我们看到,上面这个不仅更加细粒度,我们将 NameParams 拆成了两个类型别名:NameNameResolver ,分别处理 string() => string 的情况,然后通过联合操作符联合赋值给 NameParams ;还带来了一个优势,我们的返回值可以更加明确就是 Name 类型,这样 Name 变化,它可能变成 number 类型,那么也能很好的反应这个变化,且只需要修改一下 Name 的值为 number 类型就可以了,所有其他的 Name 类型会自动变化。

类型别名与接口

有同学读到这里,可能有疑问了,这个类型别名貌似无所不能嘛,那它和接口有什么区别了?

接口主要是用来定义一个结构的类型,比如定义一个对象的类型,而类型别名可以是任意细粒度的类型定义,比如我们前面讲的最原子的字母量类型如 'hello tuture' 类型,到对象类型如:

type tuture = {
   
  tutureCommunity: string;
  editure: string;
  tutureDocs: string;
}

上面这个类型我们定义了一个包含三个属性的对象类型,并用 tuture 别名来存储它们。

定义上面这个对象的类型我们可以用之前学到的接口这样写:

interface Tuture {
   
  tutureCommunity: string;
  editure: string;
  tutureDocs: string;
}

可以看到类型别名既可以表达接口所表达的类型,还比接口更加细粒度,它还可以是一个基础类型如 type name = 'string'

动手实践

还记得之前我们那个 src/TodoList.tsxAction 组件的 onClick 方法的参数 key 嘛?它是一个联合类型类型 "complete | delete" ,我们在多出处用到它,现在我们是硬编码写在了程序里,未来这个 key 可能会变化,所以我们需要换成类型别名来表达它们,打开 src/TodoList.tsx ,对其中的内容作出对应的修改如下:

import React from "react";
import { List, Avatar, Menu, Dropdown } from "antd";
import { DownOutlined } from "@ant-design/icons";
import { ClickParam } from "antd/lib/menu";

import { Todo, getUserById } from "./utils/data";

type MenuKey = "complete" | "delete";

interface ActionProps {
  onClick: (key: MenuKey) => void;
  isCompleted: boolean;
}

// ...

interface TodoListProps {
  todoList: Todo[];
  onClick: (todoId: string, key: MenuKey) => void;
}

function TodoList({ todoList, onClick }: TodoListProps) {
  return (
    <List
      className="demo-loadmore-list"
      itemLayout="horizontal"
      dataSource={todoList}
      renderItem={item => {
        const user = getUserById(item.user);

        return (
          <List.Item
            key={item.id}
            actions={[
              <Dropdown
                overlay={() => (
                  <Action
                    isCompleted={item.isCompleted}
                    onClick={(key: MenuKey) => onClick(item.id, key)}
                  />
                )}
              >
                <a key="list-loadmore-more">
                  操作 <DownOutlined />
                </a>
              </Dropdown>
            ]}
          >
            <List.Item.Meta
              avatar={<Avatar src={user.avatar} />}
              title={<a href="https://ant.design">{user.name}</a>}
              description={item.date}
            />
            <div
              style={
  {
                textDecoration: item.isCompleted ? "line-through" : "none"
              }}
            >
              {item.content}
            </div>
          </List.Item>
        );
      }}
    />
  );
}

export default TodoList;

可以看到,我们定义了一个 MenuKey 类型别名,它表示原联合类型 complete | delete ,然后我们替换了组件中三处使用到这个联合类型的 onClick 函数的参数 key ,将其用 MenuKey 来注解。

其次我们还删除了 antd@ant-design/icons 里面的多余导出。

继续改进

接着我们再来对 TodoList 做一点改变,导出一下我们刚刚定义的 MenuKey ,因为还有其他的地方使用到它,我们打开 src/TodoList.tsxMenuKey 添加 export 前缀,导出我们的类型别名:

// ...

import { Todo, getUserById } from "./utils/data";

export type MenuKey = "complete" | "delete";

interface ActionProps {
  onClick: (key: MenuKey) => void;
  isCompleted: boolean;
}
 // ...

接着我们在 src/App.tsx 里面导入我们的 MenuKey 类型别名,并替换对应的 onClick 的参数 key 的类型注解为 MenuKey

import React, { useRef, useState } from "react";
import { Button, Typography, Form, Tabs } from "antd";

import TodoInput from "./TodoInput";
import TodoList from "./TodoList";

import { todoListData } from "./utils/data";
import { MenuKey } from "./TodoList";

import "./App.css";
import logo from "./logo.svg";
 // ...
function App() {
  const [todoList, setTodoList] = useState(todoListData);

  // ...
  const activeTodoList = todoList.filter(todo => !todo.isCompleted);
  const completedTodoList = todoList.filter(todo => todo.isCompleted);

  const onClick = (todoId: string, key: MenuKey) => {
    if (key === "complete") {
      const newTodoList = todoList.map(todo => {
        if (todo.id === todoId) {
          return { ...todo, isCompleted: !todo.isCompleted };
        }

        return todo;
      });

      setTodoList(newTodoList);
    } else if (key === "delete") {
      const newTodoList = todoList.filter(todo => todo.id !== todoId);
      setTodoList(newTodoList);
    }
  };
 // ...
  return (
   
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值