胖蔡说技术
随便扯扯

使用Docker部署nextjs应用

胖蔡阅读(275)

最近使用nextjs网站开发,希望使用docker进行生产环境的部署,减少环境的依赖可重复部署操作。我采用的是Dockerfile编写应用镜像方式+ docker-compose实现容器部署的功能。

编写Dockerfile文件

1、在项目根目录创建Dockerfile文件

构建应用镜像的基础是创建对应的dockerfile文件,常规的我们会选择将dockerfile文件创建在项目的根目录,当然也可以自定义指定位置,只要在之后的构建过程指定路径就可以了

2、dockerfile编写依赖

我这里为了减少容器的大小,所以dockerfile使用了多阶段构建的方式实现,首先是依赖阶段实现依赖库的下载,具体如下:

FROM node:18-alpine AS deps
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN apk update
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn config set registry https://registry.npm.taobao.org
RUN yarn install --frozen-lockfile

需要注意:

  • nodejs对应版本,查看package.json中的版本要求
  "engines": {
    "node": ">=18.17.0"
  }
  • 我这里将软件源修改为国内镜像,默认的国外镜像可能导致软件无法更新下载
  • 更新前端库镜像为淘宝镜像

3、构建阶段

由于已经完成依赖的下载,构建阶段只需要进行编译打包即可:

FROM node:18-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN yarn config set registry https://registry.npm.taobao.org
RUN yarn build && yarn install --production 

4、运行阶段

完成构建后,就需要我们配置容器的基础配置,如环境变量、工作目录、监听端口、应用启动命令等:

FROM node:18-alpine AS runner

WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED 1
ENV NODE_ENV production

RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
RUN yarn config set registry https://registry.npm.taobao.org

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json

USER nextjs

EXPOSE 3000

ENV PORT 3000



CMD ["node_modules/.bin/next", "start"]

运行配置

1、编写docker-compse.yml文件

完成dockerfile文件编写后,我们就需要通过编写docker-compose.yml配置文件来启动容器了。我这里是使用nginx来部署,配置如下:

version: "3.1"

services:
  db:
    image: mysql:8
    command:
      --default-authentication-plugin=mysql_native_password
      --sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
      --group_concat_max_len=102400
    restart: unless-stopped
    volumes:
      - ./data/mysql/:/var/lib/mysql/
    environment:
      TZ: Asia/Shanghai # 指定时区
      MYSQL_ROOT_PASSWORD: "nextjs" # 配置root用户密码
      MYSQL_DATABASE: "nextjs" # 业务库名
      MYSQL_USER: "nextjs" # 业务库用户名
      MYSQL_PASSWORD: "nextjs" # 业务库密码
    ports:
      - 3306:3306

  nextjs:
    build: .
    ports:
      - "3000:3000"
    environment:
      TZ: Asia/Shanghai # 指定时区
    container_name: nextjs
    volumes:
      - ./:/app/
    depends_on:
      - db
    restart: unless-stopped

  nginx:
    image: nginx:mainline-alpine
    container_name: nginxserver
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - ./.next:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d


networks:
  nodeapp-network:
    driver: bridge

2、添加nginx配置

如上可以看出,我一共创建了三个容器,一个数据容器db,一个nextjs容器是我们的应用还有一个nginx容器是web服务,nginx里还需要将我们的配置文件映射到容器内,nginx-conf内容如下:

server {
    listen 80;
    listen [::]:80;

    root /var/www/html;
    index index.html index.htm index.nginx-debian.html;

    server_name patientplatypus.com www.patientplatypus.com localhost;

    # location /back {
    #   proxy_set_header X-Real-IP $remote_addr;
    #   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    #   proxy_set_header Host $http_host;
    #   proxy_set_header X-NginX-Proxy true;

    #   proxy_http_version 1.1;
    #   proxy_set_header Upgrade $http_upgrade;
    #   proxy_set_header Connection "upgrade";

    #   proxy_pass http://nodejs:8000;
    # }

    location / {
      proxy_pass http://nextjs:3000;
    }

    location ~ /.well-known/acme-challenge {
      allow all;
      root /var/www/html;
    }
}

3、启动容器

只需要我们的宿主机器安装了docker-compse即可(docker compose 使用教程)直接使用如下命令:

$docker-compose up -d

Dockerfile镜像构建基础

胖蔡阅读(235)

Docker可以通过从Dockerfile中读取指令来自动构建图像。Dockerfile是一个文本文档,其中包含用户可以在命令行上调用以组装图像的所有命令。本文介绍了可以在Dockerfile中使用的命令。

概述

编写Dockerfile文件来构建Docker镜像之前,我们需要了解Dockerfile文件编写支持的命令、关键词具体有哪些,各自有代表什么意思,如下给出一些DockerFile中的一些指令:

ADD指令

添加本地或远程文件和目录。指令格式如下:

ADD [--chown=<user>:<group>] [--chmod=<perms>] [--checksum=<checksum>] <src>... <dest>  #或
ADD [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"]

后一种形式对于包含空白的路径是必需的。--chown--chmod功能仅在用于构建Linux容器的Dockerfiles上受支持,而在Windows容器上不起作用。由于用户和组所有权概念不会在LinuxWindows之间转换,因此使用/etc/passwd/etc/group将用户和组名称转换为ID会限制此功能仅适用于基于Linux操作系统的容器。--chmodDockerfile 1.3开始支持。当前仅支持八进制表示法。

ADD指令从<src>复制新文件、目录或远程文件URL,并将它们添加到路径<dest>的映像文件系统中。可以指定多个<src>资源,但如果它们是文件或目录,则它们的路径将被解释为相对于构建上下文的源。每个<src>都可能包含通配符,并且将使用Go的文件路径进行匹配。匹配规则。例如:

ADD hom* /mydir/ # 要添加所有以“hom”开头的文件:

ARG指令

使用生成时变量。ARG指令定义了一个变量,用户可以在构建时通过使用--build ARG<varname>=<value>标志的docker构建命令将该变量传递给构建器。如果用户指定的构建参数未在Dockerfile中定义,则该构建会输出警告。格式如下:

ARG <name>[=<default value>]

Dockerfile可以包括一个或多个ARG指令。例如,以下是有效的Dockerfile

FROM busybox
ARG user1
ARG buildno=1 #ARG指令可以选择性地包括一个默认值:
# ...

CMD指令

指定默认命令。CMD指令设置从映像运行容器时要执行的命令。您可以使用shell或exec形式指定CMD指令:

CMD ["executable","param1","param2"]
CMD ["executable","param1","param2"]
CMD command param1 param2

COPY指令

复制文件和目录。命令格式如下:

COPY [--chown=<user>:<group>] [--chmod=<perms>] <src>... <dest>
COPY [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"]

使用功能字面意思就是复制文件,将当前目录下的指定路径文件复制到镜像或容器指定地址。这个命令在使用过程中用的频率较高,示例如下:

PY hom* /mydir/

ENTRYPOINT指令

指定默认可执行文件。ENTRYPOINT允许您配置将作为可执行文件运行的容器。你也可以不配置,它会执行默认的可执行文件。使用格式如下:

ENTRYPOINT ["executable", "param1", "param2"] #或
ENTRYPOINT command param1 param2 

ENV指令

设置环境变量。用的比较多,如系统设置运行环境参数,常规的我们的前端项目可能会设置NODE_ENV区分开发环境。其常用格式如下:

ENV <key>=<value> ...

ENV指令将环境变量<key>设置为值<value>。该值将在构建阶段的所有后续指令的环境中,并且可以在许多指令中内联替换。该值将被解释为其他环境变量,因此如果引号字符没有转义,它们将被删除。与命令行解析一样,引号和反斜杠可以用于在值中包含空格。使用示例如下:

ENV MY_NAME="John Doe"
ENV MY_DOG=Rex\ The\ Dog
ENV MY_CAT=fluffy

ENV指令允许多个<key>=<value>。。。一次设置变量,下面的例子将在最终图像中产生相同的净结果:

ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
    MY_CAT=fluffy

当从生成的映像运行容器时,使用ENV设置的环境变量将保持不变。您可以使用docker inspect查看这些值,并使用docker run--env=更改它们。

EXPOSE指令

描述您的应用程序正在监听哪些端口。需要注意的是这个只是描述当前应用容器正在监听的端口并不能指定映射的宿主机的端口。使用格式如下:

EXPOSE <port> [<port>/<protocol>...]

EXPOSE指令通知Docker容器在运行时侦听指定的网络端口。您可以指定端口侦听TCP还是UDP,如果未指定协议,则默认为TCP。

EXPOSE指令实际上并没有发布端口。它的作用是构建映像的人和运行容器的人之间的一种文档,说明要发布哪些端口。要在运行容器时发布端口,请在docker运行时使用-p标志来发布和映射一个或多个端口,或者使用-p标志来发布所有公开的端口并将其映射到高阶端口。

EXPOSE 80/udp

FROM指令

从基本映像创建新的构建阶段。即当前构建镜像的依赖镜像,一般多为系统级别的基础镜像信息,使用格式如下:

FROM [--platform=<platform>] <image> [AS <name>] # 或
FROM [--platform=<platform>] <image> [AS <name>] # 或
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

FROM指令初始化一个新的构建阶段,并为后续指令设置基本映像。因此,有效的Dockerfile必须以FROM指令开头。该镜像可以是任何有效的镜像。

  • ARGDockerfileFROM之前的唯一指令。
  • FROM可以在单个Dockerfile中多次出现,以创建多个映像,或者将一个构建阶段用作另一个的依赖项。只需在每条新的FROM指令之前记下提交输出的最后一个映像ID即可。每个FROM指令都会清除先前指令创建的任何状态。
  • 可选地,可以通过将AS名称添加到FROM指令来为新的构建阶段指定名称。该名称可以在随后的FROMCOPY-FROM=指令中使用,以引用在此阶段构建的镜像。
  • 标记值或摘要值是可选的。如果省略其中任何一个,则默认情况下构建器将采用最新的标记。如果生成器找不到标记值,则返回一个错误。

可选的--platform标志可用于在FROM引用多平台映像的情况下指定映像的平台。例如,linux/amd64、linux/arm64windows/amd64。默认情况下,将使用构建请求的目标平台。全局构建参数可以用于此标志的值,例如,自动平台ARG允许您将阶段强制到本机构建平台(--platform=$BUILDPORM),并使用它交叉编译到阶段内的目标平台。

HEALTHCHECK指令

启动时检查容器的运行状况。HEALTHCHECK指令告诉Docker如何测试容器以检查它是否仍在工作。这可以检测到一些情况,例如web服务器陷入无限循环,无法处理新的连接,即使服务器进程仍在运行。格式如下:

HEALTHCHECK [OPTIONS] CMD command #通过在容器内运行命令来检查容器运行状况
HEALTHCHECK NONE # 禁用从基本映像继承的任何健康检查

LABEL指令

向镜像中添加元数据。LABEL指令将元数据添加到图像中。LABEL是一个键值对。若要在LABEL值中包含空格,请像在命令行分析中一样使用引号和反斜杠。一些用法示例:

LABEL <key>=<value> <key>=<value> <key>=<value> ... #格式

#示例
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."

MAINTAINER指令

指定镜像的作者信息。MAINTAINER指令设置生成镜像的“作者”字段。LABEL指令是一个更灵活的版本,您应该使用它,因为它可以设置您需要的任何元数据,并且可以很容易地查看,例如使用docker inspect。要设置与MAINTAINER字段相对应的标签,可以使用:

MAINTAINER <name> # 指令格式

# LABEL 使用
LABEL org.opencontainers.image.authors="SvenDowideit@home.org.au"

ONBUILD指令

指定在生成中使用镜像的时间说明。ONBUILD指令向映像中添加一条触发器指令,该指令将在以后映像用作另一个生成的基础时执行。触发器将在下游构建的上下文中执行,就好像它是在下游Dockerfile中的FROM指令之后立即插入的一样。格式如下:

ONBUILD INSTRUCTION

# 示例
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src

需要注意的是:

  • 不允许使用ONBUILD-ONBUILD链接ONBUILD指令。
  • ONBUILD指令可能不会触发FROMMAINTAINER指令。

RUN指令

执行构建命令。RUN指令将执行任何命令,在当前图像的顶部创建一个新层。添加的层将在Dockerfile的下一步中使用。

RUN apt-get update
RUN apt-get install -y curl

可以使用shell或exec形式指定RUN指令:

RUN ["executable","param1","param2"] #或
RUN command param1 param2 

shell形式是最常用的,使用换行符或heredocs,可以更容易地将较长的指令分解为多行:

RUN <<EOF
apt-get update
apt-get install -y curl
EOF

SHELL指令

设置镜像的默认执行shellSHELL指令允许覆盖用于命令SHELL形式的默认SHELLLinux上的默认shell[“/bin/sh”,“-c”]Windows上的默认shell为[“cmd”,“/S”,“/c”]SHELL指令必须在Dockerfile中以JSON形式编写。

SHELL ["executable", "parameters"] #指令格式

#使用示例
FROM microsoft/windowsservercore

# Executed as cmd /S /C echo default
RUN echo default

# Executed as cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default

# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello

# Executed as cmd /S /C echo hello
SHELL ["cmd", "/S", "/C"]
RUN echo hello

STOPSIGNAL指令

指定退出容器的系统调用信号。STOPSSIGNAL指令设置将发送到容器以退出的系统调用信号。该信号可以是SIG<name>格式的信号名称,例如SIGKILL,也可以是与内核系统调用表中的位置匹配的无符号数字,例如9。如果未定义,则默认值为SIGTERM

STOPSIGNAL signal

USER指令

设置用户和组ID。USER指令设置用户名(或UID)和可选的用户组(或GID),用作当前阶段剩余部分的默认用户和组。指定的用户用于执行RUN指令,并在运行时运行相关的ENTRYPOINT和CMD命令。格式如下:

USER <user>[:<group>] # 或者
USER UID[:GID]

#使用配置
FROM microsoft/windowsservercore
# Create Windows user in the container
RUN net user /add patrick
# Set it for subsequent commands
USER patrick

VOLUME指令

设置卷挂载地址。VOLUME指令使用指定的名称创建一个装载点,并将其标记为容纳来自本机主机或其他容器的外部装载卷。该值可以是JSON数组,VOLUME[“/var/log/”],或带有多个参数的纯字符串,如VOLUME/var/logVOLUME/var/log/var/db

docker run命令使用基本映像中指定位置存在的任何数据初始化新创建的卷。例如,考虑以下Dockerfile片段:

FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

Dockerfile会生成一个映像,该映像使docker run/myvol上创建一个新的装载点,并将问候文件复制到新创建的卷中。

WORKDIR指令

更改工作目录。WORKDIR指令为Dockerfile中的任何RUN、CMD、ENTRYPOINT、COPYADD指令设置工作目录。如果WORKDIR不存在,即使它没有在任何后续的Dockerfile指令中使用,它也会被创建。格式如下:

WORKDIR /path/to/workdir

WORKDIR指令可以在Dockerfile中多次使用。如果提供了一个相对路径,它将是相对于上一个WORKDIR指令的路径。例如:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

这个Dockerfile中的最后一个pwd命令的输出将是/a/b/c

WORKDIR指令可以解析先前使用ENV设置的环境变量。只能使用Dockerfile中显式设置的环境变量。例如:

ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

这个Dockerfile中最后一个pwd命令的输出将是/path/$DIRNAME

NextUI使用原子样式实现Tabs左右排列

胖蔡阅读(249)

使用NextUI作为UI库进行网站开发,页面需要使用到左右布局的切换样式的UI来分离界面的分类和内容,这也是较为常用的一种样式,但偏偏不巧的就是NextUI中的Tab凑巧没有支持这种样式,这估计也和其主要是为手机端设计的缘故导致的,它的样式都是这种类型:

为此,就需要将其样式进行改造一下,以符合我们需要的样式结构:

这里不得不说下tailwindcss这种原子样式的强大之处了,原子样式的使用使得我们可以最大限度的修改现有布局的绝大数样式,那么如何将tabs修改成如上左右的方式:

  • 自定义tablist样式为flex,设置为column布局,其样式:sm:flex sm:flex-col sm:border sm:p-0 sm:border-divide sm:min-h-full sm:border-r
  • 将tab样式设置一个固定的高度,宽度设置为100%:sm:min-w-full px-0 h-12
  • 最后就是设置选中后的光标了,修改其定位方式,由左右改为上下,靠边显示:sm:w-[2px] sm:h-[80%] sm:right-0  sm:bottom-auto

其最后代码如下:

import { Tabs, Tab, CardBody, Card, Image } from '@nextui-org/react';

const Page = () => {
  const tabs = [
    {
      key: 1,
      label: '第一',
    },
    {
      key: 2,
      label: '第二',
    },
    {
      key: 3,
      label: '第三',
    },
    {
      key: 4,
      label: '第四',
    },
    {
      key: 5,
      label: '第五',
    },
  ];
  return (
    <Card
      className="ml-[-5px] mr-[-5px] mt-5 h-full border-none bg-white p-0"
      radius="none"
    >
      <CardBody className="overflow-hidden p-0 sm:flex sm:flex-row">
        <Tabs
          aria-label="Options"
          color="primary"
          variant="underlined"
          radius="none"
          size="lg"
          classNames={{
            tabList:
              'sm:flex sm:flex-col sm:border sm:p-0 sm:border-divide sm:min-h-full sm:border-r',
            tab: 'sm:min-w-full px-0 h-12',
            cursor: 'sm:w-[2px] sm:h-[80%] sm:right-0  sm:bottom-auto',
          }}
        >
          {tabs.map((item: any) => {
            return (
              <Tab key={item.key} title={item.label} className="pl-4 pr-4">
                测试页面
              </Tab>
            );
          })}
        </Tabs>
      </CardBody>
    </Card>
  );
};

export default Page;

页面呈现如下:

Nodejs使用typeorm框架实现数据库的管理(1)

胖蔡阅读(240)

最近希望使用Nodejs给我提供后端服务,但是数据库操作这块实在是麻烦,所以想着找一个线程的ORM框架来进行数据库映射、连接、操作功能。可能是真的因为Nodejs这块的使用比例不高的问题,ORM框架并不是很多,相应的开发文档信息也是少的可能,和其他主流后端语言没法比。我这里也只是找到几个ORM框架。

ORM框架

  • sequelize :适配主流数据库,各种查询模式相对稳定,主流开源 node + js 框架(例如:egg)的数据库默认 orm 框架。
  • Typeorm:同样适配主流数据库,另外有更好的类型推导,主流开源 node + ts 框架(例如:midway)的数据库默认 orm 框架。
  • Prisma:有一套已经很成熟的 dsl,用这个dsl 描述数据之间的关系,比用底层 sql 要简单。相比 typeorm,类型推导上更加出色(属性查询的实体等),简洁的实体声明语法(语法高亮提示用 vscode 插件),还免费带有 可视化桌面端应用,整个生态相对比较完备。

TypeORM的使用

由于我的软件准备采用midway + TS框架,所以我这边使用TypeORM做为ORM框架。这里记录总结一些TypeORM常用的操作。

安装

我这里使用mysql作为数据库,ts作为主要开发语言,所以本文所有使用基于mysql数据库驱动之上。

$ npm install mysql typeorm reflect-metadata --save
$ npm install @types/node --save // ts类型声明

tsconfig.json配置

需要保证TypeScript的编译器版本在2.3之上,并在tsconfig.json中启用如下两个模块:


// tsconfig.json
{
  "compileOnSave": true,
  "compilerOptions": {
    "target": "es2018",
    "module": "commonjs",
    "moduleResolution": "node",
    "experimentalDecorators": true,  //  typeorm需要启用
    "emitDecoratorMetadata": true,  // typeorm需要启用
    "inlineSourceMap":true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "stripInternal": true,
    "skipLibCheck": true,
    "pretty": true,
    "declaration": true,
    "noImplicitAny": false,
    "typeRoots": [ "./typings", "./node_modules/@types"],
    "outDir": "dist"
  },
  "exclude": [
    "dist",
    "node_modules",
    "test"
  ]
}

配置

新建ormconfig.json文件,并添加数据库连接配置:

{
  "type": "mysql",
  "host": "localhost", 
  "port": 3306,
  "username": "root",
  "password": "root",
  "database": "test",
  "synchronize": true,
  "logging": false,
  "entities": ["src/entity/**/*.ts"], // 实体类位置
  "migrations": ["src/migration/**/*.ts"], // 迁移
  "subscribers": ["src/subscriber/**/*.ts"] // 订阅
}

我们也可以在代码中通过配置连接:

import { createConnection, Connection } from "typeorm";

const connection = await createConnection({
  type: "mysql",
  host: "localhost",
  port: 3306,
  username: "test",
  password: "test",
  database: "test",
  entities: ["src/entity/**/*.ts"],
  migrations: ["src/migration/**/*.ts"],

  subscribers: ["src/subscriber/**/*.ts"]
});

同时,TypeORM也支持主从复制配置和读写分离配置,参考:Nodejs使用typeorm实现mysql的读写分离

实体创建

TypeORM中配置指定位置的实体类其实际回主动映射到对应的数据表中,我们创建一个实体类其实就是创建一个数据表。代码如下:

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";

// 通过entity注入创建数据表,可通过传入表名创建自定义表,若未传入则创建一个和类名相同的表
@Entity("sys_base_user")
export class User {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    firstName: string;

    @Column()
    lastName: string;

    @Column()
    isActive: boolean;
}

创建的表结构如下:

+-------------+--------------+----------------------------+
|                          sys_base_user|
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| firstName   | varchar(255) |                            |
| lastName    | varchar(255) |                            |
| isActive    | boolean      |                            |
+-------------+--------------+----------------------------+

基本实体由列和关系组成。 每个实体必须有一个主键(primary key),每个实体都需要在连接时被注册,但由于我们上面已经配置注册完成了,这里可以不用管。如上可知,我们可以通过 @Column()来定义我们的列数据,typeorm会根据列数据定义的类型自动在对应表中创建一个相同的列,其常见格式如下:

@Column("varchar", { length: 200 }) // 或者
@Column({ type: "int",comment })

Column支持配置参数如下:

  • type?: ColumnType:列数据类型,其支持参数如下
export type PrimaryGeneratedColumnType = "int" | "int2" | "int4" | "int8" | "integer" | "tinyint" | "smallint" | "mediumint" | "bigint" | "dec" | "decimal" | "smalldecimal" | "fixed" | "numeric" | "number";
/**
 * Column types where spatial properties are used.
 */
export type SpatialColumnType = "geometry" | "geography" | "st_geometry" | "st_point";
/**
 * Column types where precision and scale properties are used.
 */
export type WithPrecisionColumnType = "float" | "double" | "dec" | "decimal" | "smalldecimal" | "fixed" | "numeric" | "real" | "double precision" | "number" | "datetime" | "datetime2" | "datetimeoffset" | "time" | "time with time zone" | "time without time zone" | "timestamp" | "timestamp without time zone" | "timestamp with time zone" | "timestamp with local time zone";
/**
 * Column types where column length is used.
 */
export type WithLengthColumnType = "character varying" | "varying character" | "char varying" | "nvarchar" | "national varchar" | "character" | "native character" | "varchar" | "char" | "nchar" | "national char" | "varchar2" | "nvarchar2" | "alphanum" | "shorttext" | "raw" | "binary" | "varbinary" | "string";
export type WithWidthColumnType = "tinyint" | "smallint" | "mediumint" | "int" | "bigint";
/**
 * All other regular column types.
 */
export type SimpleColumnType = "simple-array" | "simple-json" | "simple-enum" | "int2" | "integer" | "int4" | "int8" | "int64" | "unsigned big int" | "float" | "float4" | "float8" | "float64" | "smallmoney" | "money" | "boolean" | "bool" | "tinyblob" | "tinytext" | "mediumblob" | "mediumtext" | "blob" | "text" | "ntext" | "citext" | "hstore" | "longblob" | "longtext" | "alphanum" | "shorttext" | "bytes" | "bytea" | "long" | "raw" | "long raw" | "bfile" | "clob" | "nclob" | "image" | "timetz" | "timestamptz" | "timestamp with local time zone" | "smalldatetime" | "date" | "interval year to month" | "interval day to second" | "interval" | "year" | "seconddate" | "point" | "line" | "lseg" | "box" | "circle" | "path" | "polygon" | "geography" | "geometry" | "linestring" | "multipoint" | "multilinestring" | "multipolygon" | "geometrycollection" | "st_geometry" | "st_point" | "int4range" | "int8range" | "numrange" | "tsrange" | "tstzrange" | "daterange" | "enum" | "set" | "cidr" | "inet" | "inet4" | "inet6" | "macaddr" | "bit" | "bit varying" | "varbit" | "tsvector" | "tsquery" | "uuid" | "xml" | "json" | "jsonb" | "varbinary" | "hierarchyid" | "sql_variant" | "rowid" | "urowid" | "uniqueidentifier" | "rowversion" | "array" | "cube" | "ltree";
/**
 * Any column type column can be.
 */
export type ColumnType = WithPrecisionColumnType | WithLengthColumnType | WithWidthColumnType | SpatialColumnType | SimpleColumnType | BooleanConstructor | DateConstructor | NumberConstructor | StringConstructor;
  • name:列名称,可以不指定,默认使用变量名
  • length:列长度,通常数据类型为varchar时可用,如varchar(100)
  • width:类型占用字节,如int(4)
  • nullable:字段是否可为空,默认为false(不可空)
  • update:字段只读,通常指是否支持save操作,默认false
  • select:是否支持执行QueryBuilder操作,默认为true
  • insert:是否支持insert操作,默认为true
  • comment:列备注,不是所有类型都支持
  • precision: number类型小数点后的位数
  • transformer:数据库和实体类之前数据转换配置
  • 。。。等等

主键定义

  • @PrimaryColumn():创建一个指定任何类型的数据为主键
import { Entity, PrimaryColumn } from "typeorm"

@Entity()
export class User {
    @PrimaryColumn()
    id: number
}
  • @PrimaryGeneratedColumn():创建一个自增类型的主键,一般为number类型数据,我们不需要对其进行赋值,其会自动生成。
import { Entity, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;
}
  • @PrimaryGeneratedColumn(“uuid”):创建一个由uuid自动生成的主键列,Uuid 是一个独特的字符串 id。不需要我们手动分配数据,其会自动生成。
import { Entity, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class User {
    @PrimaryGeneratedColumn("uuid")
    id: string;
}

特殊列

  • @CreateDateColumn :自动为实体插入日期。无需设置此列,该值将自动设置。
  • @UpdateDateColumn:自动更新实体日期。无需设置此列,该值将自动设置。
  • @VersionColumn:每次调用实体管理器或存储库的save时自动增长实体版本(增量编号)。无需设置此列,该值将自动设置。

Nextjs配合nextui框架进行页面开发

amiko阅读(323)

当我们使用nextjs进行前端SSR相关开发的时候,想要选用一个UI框架进行页面渲染的时候,就会发现服务端渲染和客户端页面渲染是如此的不通,我们国内常用的elemet-ui, ant-design这些框架都会出现或这样或那样的问题,究其原因主要是目前国内主流的UI框架的页面渲染采用了大量的DOM操作,而服务端是没有document对象的。

查阅了网络上的各类UI框架推荐,总结的服务端渲染框架推荐如下:

如上几个UI框架我有测试使用的目前也就MUINextUI这两个框架,有关服务端渲染UI框架的推荐介绍之后会在使用了解后单独做一个推荐分享,本文主要是来介绍如何在nextjs中安装nextui框架。

安装

NextUI提供了两种安装方式供我们选择:一种是全量安装,一种是增量安装。可以根据需要选择更适合自己的安装方式。

环境要求

  • NextJs 12或更高
  • React 18或更高
  • TailWind CSS 3或更高
  • Framer Motion 4或更高

准备工作

在安装之前需要了解的是NextUI的构建是基于Tailwind CSS(一种原子类框) 框架的,所以想要能够正常使用NextUI的话,我们需要提前先安装 Tailwind CSS, 有关tailwindcss的安装可参考:Tailwindcss 配置使用指南

全量安装

1、全量安装是直接安装@nextui-org/reactframer-motion 两个库

# 使用npm
$ npm i @nextui-org/react framer-motion
# 使用yarn 
$ yarn add @nextui-org/react framer-motion
# 使用pnpm 
$ pnpm add @nextui-org/react framer-motion

2. 配置Tailwind CSS

// tailwind.config.js
const {nextui} = require("@nextui-org/react");

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
    "./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}",   // nextui配置
  ],
  theme: {
    extend: {},
  },
  darkMode: "class",
  plugins: [nextui()],
};

3、全局应用

安装后配置样式后,我们需要在nextjs的根节点添加一个全局组件NextUIProvider来嵌套所有需要使用NextUI的页面

import * as React from "react";


import {NextUIProvider} from "@nextui-org/react";

function Layout({ children }: { children: ReactElement }) {
  // 需要在顶层嵌套
  return (
    <NextUIProvider>
      { children  }
    </NextUIProvider>
  );
}

可以是在pages/_app.tsx中使用一个外层的layout来嵌套所有待设计的页面。

增量安装

也许你只是需要使用到个别的NextUI组件,并不想要全局导入,这时我们就可以通过NextUI的增量安装来使用组件。

1、下载安装核心库:@nextui-org/theme @nextui-org/systemframer-motion

# npm
$ npm i @nextui-org/theme @nextui-org/system framer-motion
# yarn
$ yarn add @nextui-org/theme @nextui-org/system framer-motion
# pnpm
$ pnpm add @nextui-org/theme @nextui-org/system framer-motion

2、指定组件安装

既然是增量安装,那我们只需要安装我们需要的几个组件就可以了,如我们可以只安装一个Button组件:

# npm
$ npm i @nextui-org/button
# yarn
$ yarn add @nextui-org/button
# pnpm
$ pnpm add @nextui-org/button

3、Tailwind CSS 配置

这里我们同样的只需要配置指定组件的位置就可以了:

// tailwind.config.js
const {nextui} = require("@nextui-org/theme");

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    // 单个组件样式
    "./node_modules/@nextui-org/theme/dist/components/button.js", 
    // 也可以使用glob的匹配规则实现多个组件的样式匹配
    './node_modules/@nextui-org/theme/dist/components/(button|snippet|code|input).js'
  ],
  theme: {
    extend: {},
  },
  darkMode: "class",
  plugins: [nextui()],
};

4、全局应用

使用方式同上:

import * as React from "react";


import {NextUIProvider} from "@nextui-org/react";

function Layout({ children }: { children: ReactElement }) {
  // 需要在顶层嵌套
  return (
    <NextUIProvider>
      { children  }
    </NextUIProvider>
  );
}

注意

需要注意的是当我们使用pnpm 工具来进行库的依赖管理的话,我们需要在我们的项目根目录下的.npmrc文件添加如下配置:

public-hoist-pattern[]=*@nextui-org/*

文件修改完成后需要使用pnpm install重写安装依赖,确保依赖的安装正常。

使用

安装完成NextUI后,我们就可以在页面中使用NextUI的组件了,代码如下:

// page.tsx
import {Button} from '@nextui-org/button';
import clsx from 'clsx';
import styles from './index.module.css';

const ToolkitPage = (props: any) => {
  return (
    <div className={clsx(styles.document, 'bg-white p-[30px]')}>
      <Button color='primary'>按钮</Button>
      <Button disabled className='ml-20'>禁用</Button>
    </div>
  );
};
export default ToolkitPage;

自定义样式

实现NextUI的自定义样式,我们可以通过如下设置来实现。

组件自定义

在开始使用前我们需要通过NextUIProvider组件来进行嵌套包裹,这里可以

import * as React from "react";


import {NextUIProvider} from "@nextui-org/react";

function Layout({ children }: { children: ReactElement }) {
  // 需要在顶层嵌套
  return (
    <NextUIProvider className="dark text-foreground bg-background">
      { children  }
    </NextUIProvider>
  );
}

Tailwind CSS配置

需要在tailwind.config.js中配置对应模式的样式格式,在如上的插件nextui():

module.exports = {
  plugins: [
    nextui({
      prefix: "nextui", // prefix for themes variables
      addCommonColors: false, // override common colors (e.g. "blue", "green", "pink").
      defaultTheme: "light", // default theme from the themes object
      defaultExtendTheme: "light", // default theme to extend on custom themes
      layout: {}, // common layout tokens (applied to all themes)
      themes: {
        light: {
          layout: {}, // light theme layout tokens
          colors: {}, // light theme colors
        },
        dark: {
          layout: {}, // dark theme layout tokens
          colors: {}, // dark theme colors
        },
        // ... custom themes
      },
    }),
  ],
};

样式属性支持

themes配置样式:

type ConfigTheme = {
  extend?: "light" | "dark"; // base theme to extend
  layout?: LayoutTheme; // see LayoutTheme
  colors?: ThemeColors; // see ThemeColors
};

type ConfigThemes = Record<string, ConfigTheme>;

tailwindcss支持配置支持属性

type BaseThemeUnit = {
  small?: string;
  medium?: string;
  large?: string;
};

type FontThemeUnit = {
  small?: string;
  medium?: string;
  large?: string;
  tiny?: string;
};

interface LayoutTheme {
  /**
   * Base unit token that defines a consistent spacing scale across
   * the components.
   */
  spacingUnit?: number;
  /**
   * The default font size applied across the components.
   */
  fontSize?: FontThemeUnit;
  /**
   * The default line height applied across the components.
   */
  lineHeight?: FontThemeUnit;
  /**
   * The default radius applied across the components.
   * we recommend to use `rem` units.
   */
  radius?: BaseThemeUnit;
  /**
   * A number between 0 and 1 that is applied as opacity-[value] when
   * the component is disabled.
   */
  disabledOpacity?: string | number;
  /**
   * The default height applied to the divider component.
   * we recommend to use `px` units.
   */
  dividerWeight?: string;
  /**
   * The border width applied across the components.
   */
  borderWidth?: BaseThemeUnit;
  /**
   * The box shadow applied across the components.
   */
  boxShadow?: BaseThemeUnit;
}

支持颜色配置:

type ColorScale = {
  50: string;
  100: string;
  200: string;
  300: string;
  400: string;
  500: string;
  600: string;
  700: string;
  800: string;
  900: string;
  foreground: string; // contrast color
  DEFAULT: string;
};

type BaseColors = {
  background: ColorScale; // the page background color
  foreground: ColorScale; // the page text color
  divider: ColorScale; // used for divider and single line border
  overlay: ColorScale; // used for modal, popover, etc.
  focus: ColorScale; // used for focus state outline
  content1: ColorScale; // used for card, modal, popover, etc.
  content2: ColorScale;
  content3: ColorScale;
  content4: ColorScale;
};

// brand colors
type ThemeColors = BaseColors & {
  default: ColorScale;
  primary: ColorScale;
  secondary: ColorScale;
  success: ColorScale;
  warning: ColorScale;
  danger: ColorScale;
};

Tailwindcss 配置使用指南

amiko阅读(310)

介绍

Tailwind CSS 是一个利用公用程序类(Utilize Class,下文皆称Utilize Class)的 CSS 框架 。 但被我们称之为”原子类”, 其工作原理是扫描所有 HTML 文件、JavaScript 组件以及任何 模板中的 CSS 类(class)名,然后生成相应的样式代码并写入 到一个静态 CSS 文件中。

使用 Tailwind CSS,你可以使用Utilize Class轻松设置响应式设计,因此您无需设置媒体查询。它快速、灵活、可靠,没有运行时负担。

快速上手

依赖环境

Tialwindcss 支持多种环境安装实现 。

安装

使用tailwind cli

从零开始使用Tailwind CSS最简单、最快的方法是使用Tailwind CLI工具。如果您想在不安装Node.js的情况下使用CLICLI也可以作为独立的可执行文件使用(使用cli可执行文件不支持windows系统安装,这里不做介绍,默认需要安装nodejs环境, 配置使用参考在没有nodejs情况下如何使用Tailwind CSS)。

1. 安装 Tailwind  CSS

$ npm install -D tailwindcss
$ npx tailwindcss init

2. 配置适用文件地址

tailwind.config.js 配置文件中添加所有模板文件的路径。

/** @type {import('tailwindcss').Config} */

module.exports = {
  content: ["./src/**/*.{html,js}"],
  theme: {
    extend: {},
  },
  plugins: [],
}

3. 全局加载 Tailwind指令

在我们的入口文件中导入一个全局css文件global.css , 如下:

// global.css
@tailwind base;
@tailwind components;
@tailwind utilities;

4. 开启Tailwind cli构建

运行命令行(CLI)工具扫描模板文件中的所有出现的 CSS 类(class)并编译 CSS 代码。

$ npx tailwindcss -i ./src/global.css -o ./dist/output.css --watch

5. 在html中使用tailwind

<head> 标签内引入编译好的 CSS 文件,然后就可以开始使用 Tailwind 的工具类 来为你的内容设置样式了。

// index.html
<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link href="/dist/output.css" rel="stylesheet">
</head>

<body>
  <h1 class="text-3xl font-bold underline">
    Hello world!
  </h1>
</body>
</html>

使用PostCss

Tailwind CSS安装为PostCSS插件是将其与webpack、Rollup、ViteParcel等构建工具集成的最无缝的方式。

1. 安装 tailwindcss 并创建 tailwind.config.js

$npm install -D tailwindcss postcss autoprefixer # 安装tailwindcss、postcss
$ npx tailwindcss init # 创建tailwind.config.js文件

2. 将tailwindcss添加到postcss配置中

//  postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  }
}

3. 配置适用文件

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{html,js}"],
  theme: {
    extend: {},
  },
  plugins: [],
}

4. 添加tailwind指令到入口的css文件中

// global.css
@tailwind base;
@tailwind components;
@tailwind utilities;

使用CDN

使用CDN在浏览器中直接尝试Tailwind,无需任何构建步骤。CDN仅用于开发目的,不是生产的最佳选择。

1. 添加CDN脚本到目标html文件中

// index.html
<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://cdn.tailwindcss.com"></script>   <!--tailwindcss 脚本地址-->
</head>

<body>
  <h1 class="text-3xl font-bold underline">
    Hello world!
  </h1>
</body>
</html>

2. 定制客制化配置

编辑tailwind.config对象以使用自己的自定义配置。

// index.html
<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://cdn.tailwindcss.com"></script>
  <script>
  // 自定义配置
    tailwind.config = {
      theme: {
        extend: {
          colors: {
            clifford: '#da373d',
          }
        }
      }
    }
  </script>
</head>
<body>
  <h1 class="text-3xl font-bold underline **text-clifford**">
    Hello world!
  </h1>
</body>
</html>

3. 添加自定义tailwindcss样式

在html中通过适用 type="text/tailwindcss" 添加自定义的tailwind  css样式

// index.html
<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://cdn.tailwindcss.com"></script>
  <style type="text/tailwindcss">
    @layer utilities {
      .content-auto {
        content-visibility: auto;
      }
    }
  </style>
</head>
<body>
  <div class="lg:content-auto">
    <!-- ... -->
  </div>
</body>
</html>

4. 使用插件

通过在cdn地址中添加plugins参数开启插件使用,如 formstypography

// index.html
<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,line-clamp"></script>
</head>
<body>
  <div class="prose">
    <!-- ... -->
  </div>
</body>
</html>

在没有nodejs情况下如何使用Tailwind CSS

amiko阅读(269)

Tailwind CSS是用JavaScript编写的,并以npm包的形式分发,这意味着您必须始终安装Node.jsnpm才能使用它。

这使得集成到使用npm并不总是常见的项目中变得更加困难,而且随着RailsPhoenix等工具默认都不再使用npm,我们需要找到一种方法,让人们在这些项目中使用Tailwind,而不必强迫他们采用一个完全独立的工具生态系统。

今天,我们宣布了一个新的独立CLI构建,它在一个独立的可执行文件中为您提供了Tailwind CLI的全部功能——不需要Node.jsnpm

开始

要安装它,请从GitHub上的最新版本中获取您平台的可执行文件,确保授予其可执行权限:

# Example for macOS arm64
curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-macos-arm64
chmod +x tailwindcss-macos-arm64
mv tailwindcss-macos-arm64 tailwindcss

现在,您可以像使用我们的npm分布式CLI工具一样使用它:

# 在根目录创建一个tailwind.config.js 文件
$ ./tailwindcss init
# 开始监听
$ ./tailwindcss -i input.css -o output.css --watch

# 为你的生产环境编译和压缩裁剪css文件
$ ./tailwindcss -i input.css -o output.css --minify

我们甚至捆绑了所有第一方插件的最新版本,所以如果你想在项目中使用它们,只需像在基于节点的项目中一样,在tailwind.config.js文件中使用“require”来导入它们:

//  tailwind.config.js
module.exports = {
  // ...
  plugins: [
    require('@tailwindcss/forms'),
    require('@tailwindcss/typography'),
  ]
}

您可以在一个方便、可移植的软件包中获得我们标准的npm分布式CLI的所有功能—不需要依赖项。

如何工作?

我们没有在Rust中重写Tailwind或其他任何东西(还…)——我们实际上使用的是pkg,这是Vercel的一个非常酷的项目,通过将项目所需的所有部分捆绑到可执行文件中,您可以将Node.js项目变成一个无需安装Node.js即可运行的可执行文件。

这使得您仍然可以使用具有JavaScript全部功能的tailwind.config.js文件,而不是像JSON这样的静态格式。

如何选择CLI?

如果您已经在项目中使用了npm,请使用我们一直提供的CLInpm分布式版本。更新更简单,文件大小更小,而且无论如何你已经在生态系统中了——使用独立构建根本没有好处。

另一方面,如果您正在处理一个不需要Node.jsnpm的项目,那么独立构建可能是一个不错的选择。如果Tailwind是您拥有package.json文件的唯一原因,那么这可能是一个更好的解决方案。

Vite使用unplugin-auto-import实现vue3中的自动导入

amiko阅读(271)

unplugin-auto-import 是基于 unplugin 写的,支持 Vite、Webpack、Rollup、esbuild 多个打包工具。我们可以使用unplugin-auto-import实现依赖的自动导入,不用再频繁导入依赖包,从而提交我们的开发效率。如下,以vue3+vite中使用改插件为例。

安装

使用前我们需要先安装该依赖:

$ pnpm i unplugin-auto-import -D

配置

安装好依赖后,我们就可以在vite中配置改插件了,其在vite中的配置方法如下:

// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  plugins: [
    AutoImport({
     imports: [
        'vue',
        'vue-router',
        'vue-i18n',
        '@vueuse/core',
        'pinia',
      ],
      dts: 'types/auto-imports.d.ts', // 使用typescript,需要指定生成对应的d.ts文件或者设置为true,生成默认导入d.ts文件
      dirs: ['src/stores', 'src/composables', 'src/hooks'],
    }),
  ],
})

如上就配置好了一个自动导入的插件,我们通过AutoImport插件实现了初始化导入vue、vue-router等依赖库,设置本地项目自动导入路径。其生成的声明文件如下:

/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const computed: typeof import('vue')['computed']
const computedAsync: typeof import('@vueuse/core')['computedAsync']
const computedEager: typeof import('@vueuse/core')['computedEager']
const computedInject: typeof import('@vueuse/core')['computedInject']
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
const controlledRef: typeof import('@vueuse/core')['controlledRef']
const createApp: typeof import('vue')['createApp']
const createEventHook: typeof import('@vueuse/core')['createEventHook']
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
const createPinia: typeof import('pinia')['createPinia']
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
const customRef: typeof import('vue')['customRef']
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const defineStore: typeof import('pinia')['defineStore']
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
const effectScope: typeof import('vue')['effectScope']
const extendRef: typeof import('@vueuse/core')['extendRef']
const getActivePinia: typeof import('pinia')['getActivePinia']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
const inject: typeof import('vue')['inject']
const isDark: typeof import('../src/composables/theme')['isDark']
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
const mapActions: typeof import('pinia')['mapActions']
const mapGetters: typeof import('pinia')['mapGetters']
const mapState: typeof import('pinia')['mapState']
const mapStores: typeof import('pinia')['mapStores']
const mapWritableState: typeof import('pinia')['mapWritableState']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
const onLongPress: typeof import('@vueuse/core')['onLongPress']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
const preferredLanguages: typeof import('../src/composables/i18n-locale')['preferredLanguages']
const provide: typeof import('vue')['provide']
const reactify: typeof import('@vueuse/core')['reactify']
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
const reactive: typeof import('vue')['reactive']
const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed']
const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit']
const reactivePick: typeof import('@vueuse/core')['reactivePick']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
const refDebounced: typeof import('@vueuse/core')['refDebounced']
const refDefault: typeof import('@vueuse/core')['refDefault']
const refThrottled: typeof import('@vueuse/core')['refThrottled']
const refWithControl: typeof import('@vueuse/core')['refWithControl']
const resolveComponent: typeof import('vue')['resolveComponent']
const resolveRef: typeof import('@vueuse/core')['resolveRef']
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
const setActivePinia: typeof import('pinia')['setActivePinia']
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const storeToRefs: typeof import('pinia')['storeToRefs']
const syncRef: typeof import('@vueuse/core')['syncRef']
const syncRefs: typeof import('@vueuse/core')['syncRefs']
const templateRef: typeof import('@vueuse/core')['templateRef']
const throttledRef: typeof import('@vueuse/core')['throttledRef']
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
const toRaw: typeof import('vue')['toRaw']
const toReactive: typeof import('@vueuse/core')['toReactive']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const toggleDark: typeof import('../src/composables/theme')['toggleDark']
const triggerRef: typeof import('vue')['triggerRef']
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
const unref: typeof import('vue')['unref']
const unrefElement: typeof import('@vueuse/core')['unrefElement']
const until: typeof import('@vueuse/core')['until']
const useAccess: typeof import('../src/composables/access')['useAccess']
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
const useAnimate: typeof import('@vueus
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
const useAttrs: typeof import('vue')['useAttrs']
const useAuthorization: typeof import('../src/composables/authorization')['useAuthorization']
const useBase64: typeof import('@vueuse/core')['useBase64']
const useBattery: typeof import('@vueuse/core')['useBattery']
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
const useCached: typeof import('@vueuse/core')['useCached']
const useClipboard: typeof import('@vueuse/core')['useClipboard']
const useCloned: typeof import('@vueuse/core')['useCloned']
const useColorMode: typeof import('@vueuse/core')['useColorMode']
const useCompConsumer: typeof import('../src/composables/comp-consumer')['useCompConsumer']
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
const useCounter: typeof import('@vueuse/core')['useCounter']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVar: typeof import('@vueuse/core')['useCssVar']
const useCssVars: typeof import('vue')['useCssVars']
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
const useCurrentRoute: typeof import('../src/composables/current-route')['useCurrentRoute']
const useCycleList: typeof import('@vueuse/core')['useCycleList']
const useDark: typeof import('@vueuse/core')['useDark']
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
const useDebounce: typeof import('@vueuse/core')['useDebounce']
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
const useEmployeeSeletor: typeof import('../src/hooks/employee')['useEmployeeSeletor']
const useGlobalConfig: typeof import('../src/composables/global-config')['useGlobalConfig']
const useI18n: typeof import('vue-i18n')['useI18n']
const useI18nLocale: typeof import('../src/composables/i18n-locale')['useI18nLocale']
const useIdle: typeof import('@vueuse/core')['useIdle']
const useImage: typeof import('@vueuse/core')['useImage']
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
const useInterval: typeof import('@vueuse/core')['useInterval']
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
const useLayoutMenu: typeof import('../src/stores/layout-menu')['useLayoutMenu']
const useLink: typeof import('vue-router')['useLink']
const useLoading: typeof import('../src/composables/base-loading')['useLoading']
const useLoadingCheck: typeof import('../src/composables/loading')['useLoadingCheck']
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
const useMemoize: typeof import('@vueuse/core')['useMemoize']
const useMemory: typeof import('@vueuse/core')['useMemory']
const useMessage: typeof import('../src/composables/global-config')['useMessage']
const useMetaTitle: typeof import('../src/composables/meta-title')['useMetaTitle']
const useModal: typeof import('../src/composables/global-config')['useModal']
const useMounted: typeof import('@vueuse/core')['useMounted']
const useMouse: typeof import('@vueuse/core')['useMouse']
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
const useMultiTab: typeof import('../src/stores/multi-tab')['useMultiTab']
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
const useNetwork: typeof import('@vueuse/core')['useNetwork']
const useNotification: typeof import('../src/composables/global-config')['useNotification']
const useNow: typeof import('@vueuse/core')['useNow']
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
const useOnline: typeof import('@vueuse/core')['useOnline']
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
const useParallax: typeof import('@vueuse/core')['useParallax']
const useParentElement: typeof import('@vueuse/core')['useParentElement']
const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']
const usePermission: typeof import('@vueuse/core')['usePermission']
const usePointer: typeof import('@vueuse/core')['usePointer']
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
const usePost: typeof import('../src/composables/api')['usePost']
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
const useScroll: typeof import('@vueuse/core')['useScroll']
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
const useSetGlobalConfig: typeof import('../src/composables/global-config')['useSetGlobalConfig']
const useShare: typeof import('@vueuse/core')['useShare']
const useSlots: typeof import('vue')['useSlots']
const useSorted: typeof import('@vueuse/core')['useSorted']
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
const useStepper: typeof import('@vueuse/core')['useStepper']
const useStorage: typeof import('@vueuse/core')['useStorage']
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
const useSupported: typeof import('@vueuse/core')['useSupported']
const useSwipe: typeof import('@vueuse/core')['useSwipe']
const useTable: typeof import('../src/hooks/pro-table')['useTable']
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
const useThrottle: typeof import('@vueuse/core')['useThrottle']
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
const useTimeout: typeof import('@vueuse/core')['useTimeout']
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
const useTitle: typeof import('@vueuse/core')['useTitle']
const useToNumber: typeof import('@vueuse/core')['useToNumber']
const useToString: typeof import('@vueuse/core')['useToString']
const useToggle: typeof import('@vueuse/core')['useToggle']
const useTools: typeof import('../src/hooks/self-service')['useTools']
const useTransition: typeof import('@vueuse/core')['useTransition']
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
const useUserManager: typeof import('../src/hooks/user')['useUserManager']
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
const useUserStore: typeof import('../src/stores/user')['useUserStore']
const useVModel: typeof import('@vueuse/core')['useVModel']
const useVModels: typeof import('@vueuse/core')['useVModels']
const useVibrate: typeof import('@vueuse/core')['useVibrate']
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
const watch: typeof import('vue')['watch']
const watchArray: typeof import('@vueuse/core')['watchArray']
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
const watchDeep: typeof import('@vueuse/core')['watchDeep']
const watchEffect: typeof import('vue')['watchEffect']
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
const watchImmediate: typeof import('@vueuse/core')['watchImmediate']
const watchOnce: typeof import('@vueuse/core')['watchOnce']
const watchPausable: typeof import('@vueuse/core')['watchPausable']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
const whenever: typeof import('@vueuse/core')['whenever']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
}

使用

通过如上步骤配置好了之后,我们接下来就可以直接使用这些自动导入的依赖库了,代码如下:

// test.vue

const count = ref(0)
const doubled = computed(() => count.value * 2)

如上,我们就不需要再重新导入ref了。

如何使用preact开始一个前端项目?

amiko阅读(292)

preactreact的最小化实现,其包大小仅仅只有3kb(有点过去夸大,但真的很小),如下是它贴出的一些相关信息的图:

本篇文章对于preact不做过深介绍,仅仅介绍其基础的使用方法。使用Preact,我们可以通过组装组件和元素的树来创建用户界面。组件是返回其树应该输出的内容的描述的函数或类。这些描述通常是用JSX(如下所示)或HTML编写的,它利用了标准的JavaScript标记模板。这两种语法都可以表示具有“props”(类似于HTML属性)和子元素的树。

preact支持通过多种方式引入使用。

直接使用

Preact被打包为直接在浏览器中使用,不需要任何构建或工具:

<script type="module">
import { h, render } from 'https://esm.sh/preact';
// Create your app
const app = h('h1', null, 'Hello World!');
render(app, document.body);
</script>

需要注意的是使用这种方式来使用preact是不支持jsx语法格式的。

使用Vite

在过去的几年里,Vite已经成为一种非常流行的工具,用于跨许多框架构建应用程序,Preact也不例外。它建立在流行的工具之上,如ES模块、RollupESBuildVite,通过我们的初始化器或他们的Preact模板,不需要任何配置或先验知识就可以开始,这种简单性使它成为一种非常流行的使用Preact的方式。

创建项目

为了快速启动并运行Vite,您可以使用我们的初始化器create-precast。这是一个交互式命令行界面(CLI)应用程序,可以在您机器上的终端中运行。使用它,您可以通过运行以下程序来创建新的应用程序:

$ npm install -g create-preact
$ npm init preact

这将引导您创建一个新的Preact应用程序,并为您提供一些选项,如TypeScript、路由(通过Preact iso)和ESLint支持。

配置JSX

要转换JSX,您需要一个Babel插件将其转换为有效的JavaScript代码。我们都使用的是@babel/plugin-transform-react-jsx。安装后,需要为JSX指定应使用的函数:

{
"plugins": [
["@babel/plugin-transform-react-jsx", {
"pragma": "h",
"pragmaFrag": "Fragment",
}]
]
}

无缝衔接React

在某个时刻,您可能会想利用庞大的React生态系统。最初为React编写的库和组件与我们的兼容层无缝工作。为了利用它,我们需要将所有的react和react dom导入指向Preact。此步骤称为混叠。

在webpack中使用

要对Webpack中的任何包进行别名,您需要在配置中添加resolve.alias部分。根据您使用的配置,此部分可能已经存在,但缺少Preact的别名。

const config = {
//...snip
"resolve": {
"alias": {
"react": "preact/compat",
"react-dom/test-utils": "preact/test-utils",
"react-dom": "preact/compat",     // Must be below test-utils
"react/jsx-runtime": "preact/jsx-runtime"
},
}
}

在Node中使用

Node中运行时,bundler别名(WebpackRollup等)将不起作用,如NextJS中所示。要解决此问题,我们可以在package.json中直接使用别名::

{
"dependencies": {
"react": "npm:@preact/compat",
"react-dom": "npm:@preact/compat",
}
}

在Parcel中使用

Parcel使用标准的package.json文件读取别名键下的配置选项。

{
"alias": {
"react": "preact/compat",
"react-dom/test-utils": "preact/test-utils",
"react-dom": "preact/compat",
"react/jsx-runtime": "preact/jsx-runtime"
},
}

在Rollup中使用

要在Rollup中使用别名,需要安装@Rollup/plugin-alias。在您的@rollup/plugin节点解析之前,需要放置插件

import alias from '@rollup/plugin-alias';
module.exports = {
plugins: [
alias({
entries: [
{ find: 'react', replacement: 'preact/compat' },
{ find: 'react-dom/test-utils', replacement: 'preact/test-utils' },
{ find: 'react-dom', replacement: 'preact/compat' },
{ find: 'react/jsx-runtime', replacement: 'preact/jsx-runtime' }
]
})
]
};

在TypeScript中使用

TypeScript,即使与bundler一起使用,也有自己的解析类型的过程。为了确保使用Preact的类型来代替React的类型,您需要将以下配置添加到tsconfig.json(或jsconfig.json)中:

{
"compilerOptions": {
...
"skipLibCheck": true,
"baseUrl": "./",
"paths": {
"react": ["./node_modules/preact/compat/"],
"react-dom": ["./node_modules/preact/compat/"]
}
}
}

使用Preact创建页面

import { h, render } from 'preact';
// Tells babel to use h for JSX. It's better to configure this globally.
// See https://babeljs.io/docs/en/babel-plugin-transform-react-jsx#usage
// In tsconfig you can specify this with the jsxFactory
/** @jsx h */
// create our tree and append it to document.body:
render(
<main>
<h1>Hello</h1>
</main>,
document.body
);
// update the tree in-place:
render(
<main>
<h1>Hello World!</h1>
</main>,
document.body
);
// ^ this second invocation of render(...) will use a single DOM call to update the text of the <h1>

创建组件

import { render, h } from 'preact';
import { useState } from 'preact/hooks';
/** @jsx h */
const App = () => {
const [input, setInput] = useState('');
return (
<div>
<p>Do you agree to the statement: "Preact is awesome"?</p>
<input value={input} onInput={e => setInput(e.target.value)} />
</div>
);
};
render(<App />, document.body);

如上示例,基本可以看出,和react的使用差异不大,但更小,对于一些轻量级的项目不妨尝试使用看看效果如何。

TypeScript中文手册 类型兼容性

amiko阅读(274)

介绍

TypeScript里的类型兼容性是基于结构子类型的。 结构类型是一种只使用其成员来描述类型的方式。 它正好与名义(nominal)类型形成对比。(译者注:在基于名义类型的类型系统中,数据类型的兼容性或等价性是通过明确的声明和/或类型的名称来决定的。这与结构性类型系统不同,它是基于类型的组成结构,且不要求明确地声明。) 看下面的例子:

interface Named {
name: string;
}
class Person {
name: string;
}
let p: Named;
// OK, because of structural typing
p = new Person();

在使用基于名义类型的语言,比如C#或Java中,这段代码会报错,因为Person类没有明确说明其实现了Named接口。

TypeScript的结构性子类型是根据JavaScript代码的典型写法来设计的。 因为JavaScript里广泛地使用匿名对象,例如函数表达式和对象字面量,所以使用结构类型系统来描述这些类型比使用名义类型系统更好。

关于可靠性的注意事项

TypeScript的类型系统允许某些在编译阶段无法确认其安全性的操作。当一个类型系统具此属性时,被当做是“不可靠”的。TypeScript允许这种不可靠行为的发生是经过仔细考虑的。通过这篇文章,我们会解释什么时候会发生这种情况和其有利的一面。

开始

TypeScript结构化类型系统的基本规则是,如果x要兼容y,那么y至少具有与x相同的属性。比如:

interface Named {
name: string;
}
let x: Named;
// y's inferred type is { name: string; location: string; }
let y = { name: 'Alice', location: 'Seattle' };
x = y;

这里要检查y是否能赋值给x,编译器检查x中的每个属性,看是否能在y中也找到对应属性。 在这个例子中,y必须包含名字是namestring类型成员。y满足条件,因此赋值正确。

检查函数参数时使用相同的规则:

function greet(n: Named) {
console.log('Hello, ' + n.name);
}
greet(y); // OK

注意,y有个额外的location属性,但这不会引发错误。 只有目标类型(这里是Named)的成员会被一一检查是否兼容。

这个比较过程是递归进行的,检查每个成员及子成员。

比较两个函数

相对来讲,在比较原始类型和对象类型的时候是比较容易理解的,问题是如何判断两个函数是兼容的。 下面我们从两个简单的函数入手,它们仅是参数列表略有不同:

let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // OK
x = y; // Error

要查看x是否能赋值给y,首先看它们的参数列表。 x的每个参数必须能在y里找到对应类型的参数。 注意的是参数的名字相同与否无所谓,只看它们的类型。 这里,x的每个参数在y中都能找到对应的参数,所以允许赋值。

第二个赋值错误,因为y有个必需的第二个参数,但是x并没有,所以不允许赋值。

你可能会疑惑为什么允许忽略参数,像例子y = x中那样。 原因是忽略额外的参数在JavaScript里是很常见的。 例如,Array#forEach给回调函数传3个参数:数组元素,索引和整个数组。 尽管如此,传入一个只使用第一个参数的回调函数也是很有用的:

let items = [1, 2, 3];
// Don't force these extra arguments
items.forEach((item, index, array) => console.log(item));
// Should be OK!
items.forEach((item) => console.log(item));

下面来看看如何处理返回值类型,创建两个仅是返回值类型不同的函数:

let x = () => ({name: 'Alice'});
let y = () => ({name: 'Alice', location: 'Seattle'});
x = y; // OK
y = x; // Error, because x() lacks a location property

类型系统强制源函数的返回值类型必须是目标函数返回值类型的子类型。

函数参数双向协变

当比较函数参数类型时,只有当源函数参数能够赋值给目标函数或者反过来时才能赋值成功。 这是不稳定的,因为调用者可能传入了一个具有更精确类型信息的函数,但是调用这个传入的函数的时候却使用了不是那么精确的类型信息。 实际上,这极少会发生错误,并且能够实现很多JavaScript里的常见模式。例如:

enum EventType { Mouse, Keyboard }
interface Event { timestamp: number; }
interface MouseEvent extends Event { x: number; y: number }
interface KeyEvent extends Event { keyCode: number }
function listenEvent(eventType: EventType, handler: (n: Event) => void) {
/* ... */
}
// Unsound, but useful and common
listenEvent(EventType.Mouse, (e: MouseEvent) => console.log(e.x + ',' + e.y));
// Undesirable alternatives in presence of soundness
listenEvent(EventType.Mouse, (e: Event) => console.log((e as MouseEvent).x + "," + (e as MouseEvent).y));
listenEvent(EventType.Mouse, ((e: MouseEvent) => console.log(e.x + "," + e.y)) as (e: Event) => void);
// Still disallowed (clear error). Type safety enforced for wholly incompatible types
listenEvent(EventType.Mouse, (e: number) => console.log(e));

你可以使用strictFunctionTypes编译选项,使TypeScript在这种情况下报错。

可选参数及剩余参数

比较函数兼容性的时候,可选参数与必须参数是可互换的。 源类型上有额外的可选参数不是错误,目标类型的可选参数在源类型里没有对应的参数也不是错误。

当一个函数有剩余参数时,它被当做无限个可选参数。

这对于类型系统来说是不稳定的,但从运行时的角度来看,可选参数一般来说是不强制的,因为对于大多数函数来说相当于传递了一些undefinded

有一个好的例子,常见的函数接收一个回调函数并用对于程序员来说是可预知的参数但对类型系统来说是不确定的参数来调用:

function invokeLater(args: any[], callback: (...args: any[]) => void) {
/* ... Invoke callback with 'args' ... */
}
// Unsound - invokeLater "might" provide any number of arguments
invokeLater([1, 2], (x, y) => console.log(x + ', ' + y));
// Confusing (x and y are actually required) and undiscoverable
invokeLater([1, 2], (x?, y?) => console.log(x + ', ' + y));

函数重载

对于有重载的函数,源函数的每个重载都要在目标函数上找到对应的函数签名。 这确保了目标函数可以在所有源函数可调用的地方调用。

枚举

枚举类型与数字类型兼容,并且数字类型与枚举类型兼容。不同枚举类型之间是不兼容的。比如,

enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };
let status = Status.Ready;
status = Color.Green;  // Error

类与对象字面量和接口差不多,但有一点不同:类有静态部分和实例部分的类型。 比较两个类类型的对象时,只有实例的成员会被比较。 静态成员和构造函数不在比较的范围内。

class Animal {
feet: number;
constructor(name: string, numFeet: number) { }
}
class Size {
feet: number;
constructor(numFeet: number) { }
}
let a: Animal;
let s: Size;
a = s;  // OK
s = a;  // OK

类的私有成员和受保护成员

类的私有成员和受保护成员会影响兼容性。 当检查类实例的兼容时,如果目标类型包含一个私有成员,那么源类型必须包含来自同一个类的这个私有成员。 同样地,这条规则也适用于包含受保护成员实例的类型检查。 这允许子类赋值给父类,但是不能赋值给其它有同样类型的类。

泛型

因为TypeScript是结构性的类型系统,类型参数只影响使用其做为类型一部分的结果类型。比如,

interface Empty<T> {
}
let x: Empty<number>;
let y: Empty<string>;
x = y;  // OK, because y matches structure of x

上面代码里,xy是兼容的,因为它们的结构使用类型参数时并没有什么不同。 把这个例子改变一下,增加一个成员,就能看出是如何工作的了:

interface NotEmpty<T> {
data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
x = y;  // Error, because x and y are not compatible

在这里,泛型类型在使用时就好比不是一个泛型类型。

对于没指定泛型类型的泛型参数时,会把所有泛型参数当成any比较。 然后用结果类型进行比较,就像上面第一个例子。

比如,

let identity = function<T>(x: T): T {
// ...
}
let reverse = function<U>(y: U): U {
// ...
}
identity = reverse;  // OK, because (x: any) => any matches (y: any) => any

高级主题

子类型与赋值

目前为止,我们使用了“兼容性”,它在语言规范里没有定义。 在TypeScript里,有两种兼容性:子类型和赋值。 它们的不同点在于,赋值扩展了子类型兼容性,增加了一些规则,允许和any来回赋值,以及enum和对应数字值之间的来回赋值。

语言里的不同地方分别使用了它们之中的机制。 实际上,类型兼容性是由赋值兼容性来控制的,即使在implementsextends语句也不例外。

更多信息,请参阅TypeScript语言规范.