普通视图

发现新文章,点击刷新页面。
昨天 — 2025年10月15日首页

🔥开源零配置!10 分钟上手:create-uni + uView Pro 快速搭建企业级 uni-app 项目

2025年10月14日 13:30

推荐阅读:

🔥 uView Pro 正式开源!70+ Vue3 组件重构完成,uni-app 组件库新晋之星

本文面向希望快速搭建 uni-app 项目的开发者与团队,介绍如何使用 create-uni 脚手架一键创建项目,如何在项目中引入并配置 uView Pro 组件库,以及如何利用 uni-helper 系列插件(vite-plugin、unocss 等)提高开发效率。

一、为什么选择 create-uni + uView Pro?

在 uniapp 构建的多端工程中,速度与一致性至关重要。

create-uni 提供一键生成、模板丰富的项目引导能力,而 uView Pro 则是基于 Vue3 + TypeScript 全面重构的高质量 uni-app 组件库。两者结合,能带来:

  • 快速上手:一行命令生成标准化项目结构;
  • 现代开发体验:Vite + Vue3 + TS,热更新快、类型友好;
  • 丰富组件:70+ 高质量组件覆盖主流业务场景;
  • 高度可扩展:uni-helper 插件体系支持文件路由、按需组件、布局系统等;
  • 企业友好:模板、样式、规范一致,便于团队协作与维护。

6.png

0.png

二、准备工作(环境与工具)

在开始之前,建议准备以下环境:

  • Node.js(建议 LTS 版本,如 18.x 或 20.x)
  • pnpm / npm / yarn(推荐 pnpm,速度更快且适合 monorepo)
  • VS Code + Volar(强烈推荐,Vue3 + TypeScript 最佳搭配,禁用 Vetur)
  • HBuilderX(如果需要使用 HBuilderX 工具链或插件市场,非必要不使用)

确保全局工具可用:

# 建议使用 pnpm
npm install -g pnpm
# 若需要全局安装脚手架(可选)
npm install -g @dcloudio/uni-app

三、使用 create-uni 快速创建项目(一步到位)

create-uni 是一套现代化脚手架,支持选择模板、快速集成 uView Pro 组件库等,下面给出用 pnpm create 的推荐流程:

# 使用 create-uni(交互式选择项目模板)
pnpm create uni@latest
cd my-uni-project
pnpm install

# 启动开发(以 H5 为例)
pnpm run dev:h5

在交互式选择时,选择需要的插件和库、选择需要的组件库 uView Pro ,可以让项目开箱即用:根据您的选择可以帮助您自动集成 uView Pro、UnoCSS、uni-helper 等插件,省去大量配置时间。

示例:

  • 选择需要的 vite 插件时勾选必要的插件:

    • vite-plugin-uni-pages(提供基于文件系统的路由)
    • vite-plugin-uni-components(按需自动引入组件)
    • vite-plugin-uni-layouts(提供类 nuxt 的 layouts 系统)
    • vite-plugin-uni-manifest(自动生成 manifest.json 文件)
  • 选择需要的库时勾选必要的库:

    • Pinia
    • Unocss
  • 选择 UI 组件库时勾选 uView Pro

通过以上选择完成后,脚手架会自动创建包含以下内容的项目:

  • Vite + uni-app 项目骨架
  • uview-pro 依赖与全局样式引入(index.scss / theme.scss)
  • 推荐的 tsconfig.jsonvite.config.ts 配置
  • UnoCSS 与 uni-helper 插件预配置

1.png

四、手动在已存在项目中安装 uView Pro(npm 或 uni_modules)

如果你已用其它方式创建项目,并不是使用 create-uni,下面是两种常见安装方式,分别适用于 CLI 项目(npm)与 HBuilderX 项目(uni_modules)。

1. CLI(npm / pnpm)方式(推荐团队/CLI 项目)

pnpm add uview-pro
# 或者 npm install uview-pro --save

在 Vue3 项目中,全局引入并注册:

// main.ts
import { createSSRApp } from "vue";
import uViewPro from "uview-pro";

export function createApp() {
  const app = createSSRApp(App);
  app.use(uViewPro);
  return {
    app,
  };
}

uni.scss 中引入主题:

@import "uview-pro/theme.scss";

在  App.vue  首行引入基础样式:

<style lang="scss">
  @import "uview-pro/index.scss";
</style>

pages.json / vite 的 easycom 配置中添加:

"easycom": {
  "autoscan": true,
  "custom": {
    "^u-(.*)": "uview-pro/components/u-$1/u-$1.vue"
  }
}

也可以使用@uni-helper/vite-plugin-uni-components(基于文件的按需组件引入)插件来替换 easycom 的方式,详细使用方式见下述介绍。

注:CLI npm 方式更易管理版本、配合 TypeScript 与 Volar 获得更好类型提示体验。

2. HBuilderX(uni_modules)方式(推荐 HBuilderX 项目)

uview-pro 目录放入项目 uni_modules 下(或通过插件市场安装);

DCloud 插件市场:ext.dcloud.net.cn/plugin?id=2…

main.ts全局引入并注册

// main.ts
import { createSSRApp } from 'vue'
import uViewPro from "@/uni_modules/uview-pro";

export function createApp() {
  const app = createSSRApp(App)
  app.use(uViewPro)
  return {
    app
  }
}

pages.json 中配置 easycom:

"easycom": {
  "autoscan": true,
  "custom": {
    "^u-(.*)": "@/uni_modules/uview-pro/components/u-$1/u-$1.vue"
  }
}

uni.scss 中引入主题:

@import "@/uni_modules/uview-pro/theme.scss";

在  App.vue  首行引入基础样式:

<style lang="scss">
  @import "@/uni_modules/uview-pro/index.scss";
</style>

HBuilderX 下,uni_modules 更符合编辑器和打包器的约定,部分原生插件或小程序构建会更兼容。

因此:建议 CLI 项目使用 npm/pnpm 方式,HBuilderX 项目使用 uni_modules 方式

五、结合 uni-helper 插件提升开发效率

uni-helper 系列插件在 vite + uni-app 生态下提供了大量现代化的便利能力。下面按插件逐一介绍它们的作用、安装、配置示例、与 uView Pro 的配合要点以及常见注意事项。

2.png

更多用法及插件请访问 uni-helper 官网文档:uni-helper.js.org/

1. @uni-helper/vite-plugin-uni-pages(文件系统路由)

作用:

  • 自动扫描 src/pagespages 目录,基于文件系统生成路由配置,替代手动维护 pages.json 的繁琐流程;
  • 支持页面元数据、分组、全局样式定义和路由扩展;
  • 提供 virtual:uni-pages 等虚拟模块用于在代码中读取页面信息,便于构建菜单、统计或自动化文档。

安装:

pnpm add -D @uni-helper/vite-plugin-uni-pages

基本配置(vite.config.ts):

import { defineConfig } from "vite";
import Uni from "@uni-helper/plugin-uni";
import UniPages from "@uni-helper/vite-plugin-uni-pages";

export default defineConfig({
  plugins: [UniPages(), Uni()],
});

pages 配置示例(pages.config.ts):

import { defineUniPages } from "@uni-helper/vite-plugin-uni-pages";

export default defineUniPages({
  pages: [],
  globalStyle: {
    navigationBarTextStyle: "black",
    navigationBarTitleText: "MyApp",
  },
  subPackages: [],
});

在代码中获取页面元数据:

/// <reference types="@uni-helper/vite-plugin-uni-pages/client" />
import { pages } from "virtual:uni-pages";
console.log(pages);

与 uView Pro 的配合要点:

  • 结合 uView Pro Starter,路由自动化能让示例页面、文档 demo 与项目页面保持一致;
  • 当需要在页面自动注入组件演示或 demo 链接时,pages 元数据非常方便。

注意事项:

  • 如果同时存在手动维护的 pages.json,请确认插件优先级与覆盖规则;
  • 某些小程序平台对动态生成的路由有特殊限制,发布前务必在目标平台做真机测试。

2. @uni-helper/vite-plugin-uni-components(基于文件的按需组件引入)

作用:

  • 基于文件系统实现组件按需自动引入,类似于 Vue 的 unplugin-vue-components,但针对 uni-app 场景优化;
  • 可以替代 easycom 的全局扫描,减少启动扫描成本并提升按需加载精度;
  • 支持自定义规则、扩展第三方组件库的映射。

安装:

pnpm add -D @uni-helper/vite-plugin-uni-components

配置示例,已经支持 uView Pro Resolver:

import { defineConfig } from "vite";
import Uni from "@uni-helper/plugin-uni";
import UniComponents from "@uni-helper/vite-plugin-uni-components";
import { uViewProResolver } from "@uni-helper/vite-plugin-uni-components/resolvers";

export default defineConfig({
  plugins: [
    UniComponents({
      dts: true,
      resolvers: [uViewProResolver()],
    }),
    Uni(),
  ],
});

与 uView Pro 的配合要点:

  • 使用此插件可避免在 pages.json 中重复写 easycom 规则;
  • 当配合 uview-pro 时,需要引入 uViewProResolver 使用;
  • 有助于实现按需打包,减小 H5 与小程序包体积。

注意事项:

  • 部分平台(例如 HBuilderX 的旧版本)可能仍需要 pages.json 的支持,务必在迁移前做兼容性验证;
  • 对于同名组件(不同来源)要明确命名或使用手动 import 以避免歧义。

3. @uni-helper/vite-plugin-uni-layouts(布局系统)

作用:

  • 在 uni-app 中实现类似 Nuxt 的布局机制(layouts),支持多个 layout 组件、slot、以及按页面应用布局;
  • 自动扫描 src/layouts 并将页面包裹在指定布局下,简化头部/尾部/侧边栏等公共区域维护。

安装:

pnpm add -D @uni-helper/vite-plugin-uni-layouts

配置示例:

import { defineConfig } from "vite";
import Uni from "@uni-helper/plugin-uni";
import UniLayouts from "@uni-helper/vite-plugin-uni-layouts";

export default defineConfig({
  plugins: [UniLayouts(), Uni()],
});

使用示例:

  • src/layouts/default.vue 中定义布局:
<template>
  <div class="layout">
    <slot name="header">默认头部</slot>
    <slot>主内容</slot>
    <slot name="footer">默认底部</slot>
  </div>
</template>
  • 在页面中指定布局(definePage):
<script setup>
definePage({ layout: "default" });
</script>

与 uView Pro 的配合要点:

  • 布局中可直接使用 uView Pro 的导航栏、Tabbar、Footer 等组件,保证风格统一;
  • 结合 uView Pro Starter,布局示例通常已经内置,直接复用即可。

注意事项:

  • 在微信小程序中如果页面使用 web-view,布局插件的包裹机制可能不生效;
  • 动态切换布局时注意保持页面状态。

4. @uni-helper/vite-plugin-uni-manifest(用 TypeScript 管理 manifest)

作用:

  • 允许使用 TypeScript 编写 manifest.json(如 manifest.config.ts),享受类型提示与可组合的配置方式;
  • 在构建时自动生成标准 manifest.json,并支持按平台差异化配置。

安装:

pnpm add -D @uni-helper/vite-plugin-uni-manifest

配置示例:

import Uni from "@uni-helper/plugin-uni";
import UniManifest from "@uni-helper/vite-plugin-uni-manifest";

export default defineConfig({
  plugins: [UniManifest(), Uni()],
});

示例 manifest.config.ts

import { defineManifestConfig } from "@uni-helper/vite-plugin-uni-manifest";

export default defineManifestConfig({
  appid: "your-appid",
  name: "MyApp",
  versionName: "1.0.0",
  h5: {
    devServer: {
      port: 8080,
    },
  },
});

与 uView Pro 的配合要点:

  • 将 theme 或构建相关的配置以类型化方式管理,便于在不同环境(dev/staging/prod)间切换;
  • 在企业项目中能更方便地实现 CI 自动化生成不同渠道包的 manifest 配置。

注意事项:

  • 生成的 manifest.json 应在真机或云打包平台上验证,避免配置项平台不兼容。

5. @uni-helper/vite-plugin-uni-platform(按平台文件替换)

作用:

  • 支持基于文件名的按平台编译,例如 index.h5.vueindex.mp-weixin.vueindex.app.vue 等,构建时自动替换为对应平台文件;
  • 便于按平台做差异化实现,同时保持统一的项目结构与代码管理。

安装:

pnpm add -D @uni-helper/vite-plugin-uni-platform

配置示例:

import Uni from "@uni-helper/plugin-uni";
import UniPlatform from "@uni-helper/vite-plugin-uni-platform";

export default defineConfig({
  plugins: [UniPlatform(), Uni()],
});

使用说明:

  • 在项目中创建文件如 pages/index.h5.vue 针对 H5 的实现,pages/index.mp-weixin.vue 针对微信小程序的实现;
  • 在编译目标为 H5 时,会优先使用 index.h5.vue,否则退回 index.vue

与 uView Pro 的配合要点:

  • 当使用 uView Pro 的某些平台相关适配(例如原生 SDK 或特定 API)时,可以在平台特定文件中做针对性封装;
  • 结合 uni-pages,能更方便地管理平台差异化页面列表。

注意事项:

  • 使用大量平台特异化文件会增加维护成本,建议仅在必要场景使用。

6. @uni-helper/unocss-preset-uni(UnoCSS 预设)

作用:

  • 为 uni-app 定制的 UnoCSS 预设,开箱即用的原子类工具集,支持属性化写法与按平台样式差异;
  • 极大减少重复样式、提高开发速度,同时配合 Uno 的即时编译,开发体验流畅。

安装:

pnpm add -D @uni-helper/unocss-preset-uni unocss unocss-applet

vite 配置示例:

import { defineConfig } from "vite";
import Uni from "@uni-helper/plugin-uni";
import UnoCSS from "unocss/vite";

export default defineConfig({
  plugins: [Uni(), UnoCSS()],
});

uno.config.ts 配置

import { presetUni } from "@uni-helper/unocss-preset-uni";

import {
  defineConfig,
  presetIcons,
  transformerDirectives,
  transformerVariantGroup,
} from "unocss";

export default defineConfig({
  presets: [
    presetUni({
      attributify: {
        // UnoCSS的解析规则可与uView Pro组件库内置样式冲突
        ignoreAttributes: ["size"],
      },
    }),
  ],
  transformers: [transformerDirectives(), transformerVariantGroup()],
});

与 uView Pro 的配合要点:

  • UnoCSS 非侵入式,可与 uView Pro 的 SCSS 主题变量共存;
  • 在快速原型或设计系统中,Uno 的原子类能极大提升迭代速度;
  • 推荐将设计变量(颜色、间距)同步到 uView Pro 的 theme.scss,并在 Uno 配置中复用。
  • 注意 UnoCSS 的解析规则可能会与 uView Pro 组件库内置样式冲突

注意事项:

  • UnoCSS 从 v0.59 起只提供 ESM 支持,某些老旧构建环境需降级或额外配置;
  • 在使用 apis 或小程序特性时,注意属性名与平台限制。

7. 插件组合示例(完整 vite.config.ts)

下面给出一个常见的 vite.config.ts 组合示例,展示如何把上面插件整合到同一个工程中:

import { fileURLToPath, URL } from "node:url";

import Uni from "@uni-helper/plugin-uni";
import Components from "@uni-helper/vite-plugin-uni-components";
import { uViewProResolver } from "@uni-helper/vite-plugin-uni-components/resolvers";
import UniLayouts from "@uni-helper/vite-plugin-uni-layouts";
import UniManifest from "@uni-helper/vite-plugin-uni-manifest";
import UniMiddleware from "@uni-helper/vite-plugin-uni-middleware";
import UniPages from "@uni-helper/vite-plugin-uni-pages";
import UniPlatform from "@uni-helper/vite-plugin-uni-platform";
import UniPlatformModifier from "@uni-helper/vite-plugin-uni-platform-modifier";
import UniRoot from "@uni-ku/root";
import UnoCSS from "unocss/vite";
import { defineConfig } from "vite";

export default defineConfig({
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
  plugins: [
    Components({
      dts: true,
      resolvers: [uViewProResolver()],
    }),
    UniPages(),
    UniLayouts(),
    UniManifest(),
    UniPlatform(),
    UniPlatformModifier(),
    UniMiddleware(),
    UniRoot(),
    Uni(),
    UnoCSS(),
  ],
});

8. 常见故障排查(针对插件集成)

  • uni-pages 未识别页面:确认目录结构、文件后缀以及 pages.config.* 是否存在语法错误;
  • uni-components 未按需引入:检查插件 dirs 配置与组件命名是否匹配,或手动添加 resolver;
  • layouts 无效:确认页面是否使用 definePage({ layout: 'xxx' }) 或 pages.json 的 layout 配置被覆盖;
  • manifest 生成错误:在本地构建时查看生成的 manifest.json,并在真机或云打包平台验证;
  • UnoCSS 样式不生效:确认 UnoCSS 是否在 plugins 列表中且 preset 已正确加载;
  • uView Pro 组件样式错乱:确认 UnoCss 解析规则是否与组件库存在冲突问题;

六、uView Pro Starter:开箱即用的项目模板

uView Pro Starter 是官方维护的快速启动模板,目前集成了 create-uni、uView Pro、UnoCSS 与 uni-helper 常用插件,适合作为企业或个人项目的起点。核心优势包括:

  • 规范的项目结构与开发脚本;
  • 预配置的 linter、格式化、TypeScript 与 Volar 支持;
  • UnoCSS 和主题变量已集成,支持快速定制风格;
  • 常用页面、布局、示例组件齐全,便于二次开发。

快速使用:

# 直接 clone
git clone https://github.com/anyup/uView-Pro-Starter.git
cd uView-Pro-Starter
pnpm install
pnpm run dev:h5

后面可以通过 create-uni 直接选择 uView Pro Starter 模板,目前还没建设完成。

Starter 的目的是把工程化、规范、常见实践都“开箱即用”,让团队把精力集中在业务实现上,而不是基础设施搭建。

3.png

5.png

4.png

七、uView Pro 与 uni-helper 的协同最佳实践(总结)

  • 使用 uView Pro Starter 作为项目模板,默认预集成了大部分插件配置,能让团队开箱即用;
  • 对于页面与组件的自动化引入,优先考虑 uni-pages + uni-components,降低重复维护成本;
  • uni-components 中为 u- 前缀做显式 resolver,避免与其他库冲突;
  • 将 uView Pro 的主题变量与 UnoCSS 的设计 tokens 做映射,保证样式统一且可维护;
  • 在 CI 中加入 pnpm install --frozen-lockfile、lint、typecheck 步骤,保证团队一致性;
  • 做好平台差异化管理(合理使用 uni-platform)但尽量减少全平台分支,以降低维护成本。

八、注意事项

1. 样式、sass 与版本兼容建议

在实际项目中,sass 与 sass-loader 的版本兼容性常会引发样式构建问题。建议在团队内统一并锁定版本,减少“本地能跑、CI 失败”的尴尬。

推荐版本(uView Pro 社区实践验证):

"sass": "1.63.2",
"sass-loader": "10.4.1"

同时,注意 uView Pro 的内部样式及主题文件采用 @import 形式引入。所以一定要注意 sass 的版本,

如使用 @use / @forward 语法引入 uView Pro 的样式文件,可能会导致样式丢失,报错,所以请使用 @import 引入。

2. TypeScript、Volar 与类型提示体验

uView Pro 自带 TypeScript 类型声明文件,结合 Volar 能获得良好的组件属性、事件、插槽的代码补全与类型校验。以下为推荐配置:

  1. 确保 VS Code 安装 Volar,并禁用 Vetur
  2. tsconfig.json 中添加:
{
  "compilerOptions": {
    "types": ["uview-pro/types"],
    "skipLibCheck": true
  }
}
  1. 在团队中统一 tsconfig 与 VS Code 推荐扩展配置(.vscode/extensions.json),减少“我的能提示你的不能提示”的现象。

3. 按需加载、tree-shaking 与打包优化

为减小包体积,建议:

  • 优先按需导入工具函数与业务组件(避免全局引入全部组件),
  • 使用 uni-helper 的 uni-components 或配合 Vite 的按需加载插件实现自动 tree-shaking,
  • 对大型列表使用虚拟滚动、分页或懒加载,
  • 在生产构建时开启压缩、静态资源缓存以及 CDN/边缘分发。

示例:按需引入工具函数

import { deepClone } from "uview-pro";
const copy = deepClone(obj);

4. 与其他组件库共存的注意事项

项目中若存在 uview-plusuView 1.xuView 2.x 或其他同类库,可能会出现 easycom 冲突、样式覆盖或工具命名冲突。解决建议:

  • 在迁移期避免自动扫描多个组件库的同名规则;
  • 调整 easycom.custom 规则,只指向 uview-pro 或具体库路径;
  • 团队层面统一组件库选型,减少冲突成本。

5. 常见问题与排查清单

  • 组件没有样式?→ 检查 theme.scssindex.scss 是否正确引入;
  • easycom 无效?→ 检查 pages.jsoncustom 配置与路径;
  • Volar 无补全?→ 禁用 Vetur、重启 VS Code、确认 tsconfig.json 设置;
  • Sass 语法报错?→ 检查 sasssass-loader 版本并统一锁定;
  • 依赖冲突?→ 清理 node_modules / pnpm install --frozen-lockfile 并统一依赖来源。

更多常见问题请参考社区网站,实时更新:uviewpro.cn/zh/guide/fa…

九、uView Pro(为开源而生)

uView Pro 是一款免费、开源、面向个人和企业的组件库。希望通过 uView Pro Startercreate-uni 的结合,降低团队上手成本,提高项目启动速度。

同时欢迎企业与开发者在 GitHub / Gitee 提交 PR、Issue,参与组件优化、示例补全与文档改进。

项目地址

十、结语:把时间交给业务,把基础交给 uView Pro

通过 create-uni + uView Pro + uni-helper 插件体系,你可以在极短的时间内搭建一个现代化、可维护、类型安全的 uni-app 项目。无论是单人项目、快速原型,还是企业级多团队协作,这套组合都能显著降低启动成本、提高开发效率。

所以,强烈建议你:

  • 使用 uView Pro Starter,将其作为项目起点;或者使用 create-uni 创建新项目时选择包含 uView Pro 的模板;
  • 合理使用 uni-helper 插件系统,减少重复工作;
  • 在团队内推广统一模板与依赖锁定策略;

欢迎访问与关注:

昨天以前首页

一个前端工程师的年度作品:从零开发媲美商业级应用的后台管理系统!

2025年10月14日 16:04

@2x封面.png

过去一年,我花了无数个夜晚,在一次次打磨与推翻中,完成了自己最满意的作品 —— Art Design Pro。

这不是一个普通的后台模板。
它是一场关于 「设计美学」与「工程化开发」 的融合实验——
希望让后台系统不再冰冷枯燥,而是像一件作品:优雅、流畅、有温度。

为什么要做这个项目?

在日常开发中,我几乎体验过所有主流后台模板。
它们的功能确实完整,但更多时候给人的感觉是——「工具」而非「产品」。

我希望做一个后台系统,
让人打开的第一眼就觉得舒服,
用起来像是在和它对话。

许多后台系统普遍存在这些问题 👇

视觉疲劳:灰白配色、生硬布局,难以长时间使用;

体验割裂:逻辑不统一、入口分散,操作效率低;

复用困难:组件风格不一致,二次开发成本高。

于是我决定从零开始,花一年的时间去打造一个真正属于自己的系统——
👉 一款 “既好看、又好用,还能直接用于商业项目” 的后台管理模板。

项目简介:Art Design Pro

Art Design Pro 是一款基于 Vue 3 + TypeScript + Vite + Element Plus 打造的现代化后台管理系统模板。

它的核心理念是:

让后台系统兼具设计美学与开发效率。

这个项目有什么特别的呢?

界面设计:现代化 UI 设计,流畅交互,以用户体验与视觉设计为核心

极速上手:简洁架构 + 完整文档,后端开发者也能轻松使用

丰富组件:内置数据展示、表单等多种高质量组件,满足不同业务场景的需求

丝滑交互:按钮点击、主题切换、页面过渡、图表动画,体验媲美商业产品

高效开发:内置 useTable、ArtForm 等实用 API,显著提升开发效率

精简脚本:内置一键清理脚本,可快速清理演示数据,立即得到可开发的基础项目

技术栈

开发框架:Vue3、TypeScript、Vite、Element-Plus

代码规范:Eslint、Prettier、Stylelint、Husky、Lint-staged、cz-git

预览

主页仪表盘

电子商务仪表盘

卡片

横幅

图表

系统图标库

富文本编辑器

礼花效果

全局搜索

系统设置

表格

暗黑模式


快速访问

GitHub: github.com/Daymychen/a…

演示地址: www.artd.pro

官方文档: www.artd.pro/docs

高效开发

在后台管理系统开发中,表格页面占据了 80% 的工作量。每次都要写分页、搜索、刷新、列配置...这些重复的代码让人头疼。今天分享一套我们团队正在使用的表格开发方案,让你的开发效率提升 10 倍!

痛点分析

在开发后台管理系统时,你是否遇到过这些问题:

  • 每个表格页面都要写一遍分页逻辑
  • 搜索、重置、刷新等功能重复实现
  • 表格列配置、显示隐藏、拖拽排序需要手动处理
  • 数据请求、loading 状态、错误处理代码冗余
  • 缓存策略难以统一管理
  • 移动端适配需要额外处理

如果你的答案是"是",那这篇文章就是为你准备的。

解决方案概览

我们的方案包含以下核心部分:

  • useTable - 强大的表格数据管理 Hook
  • ArtTable - 增强的表格组件
  • ArtTableHeader - 表格工具栏组件
  • ArtSearchBar - 智能搜索栏组件
  • ArtForm - 通用表单组件

一、useTable:表格数据管理的核心

1.1 基础用法

先看一个最简单的例子,只需要几行代码就能实现一个完整的表格:

const {
  data,
  columns,
  columnChecks,
  loading,
  pagination,
  refreshData,
  handleSizeChange,
  handleCurrentChange
} = useTable({
  core: {
    apiFn: fetchGetUserList,
    apiParams: {
      current: 1,
      size: 20
    },
    columnsFactory: () => [
      { prop: 'id', label: 'ID' },
      { prop: 'userName', label: '用户名' },
      { prop: 'userPhone', label: '手机号' }
    ]
  }
})

就这么简单!你已经拥有了:

  • ✅ 自动的数据请求
  • ✅ 分页功能
  • ✅ Loading 状态
  • ✅ 列配置管理

1.2 核心特性

🚀 智能缓存机制

useTable({
  core: {
    /* ... */
  },
  performance: {
    enableCache: true, // 启用缓存
    cacheTime: 5 * 60 * 1000, // 缓存 5 分钟
    debounceTime: 300, // 防抖 300ms
    maxCacheSize: 50 // 最多缓存 50 条
  }
})

缓存带来的好处:

  • 相同参数的请求直接从缓存读取,秒开
  • 减少服务器压力
  • 提升用户体验

🎯 多种刷新策略

不同的业务场景需要不同的刷新策略:

// 新增数据后:回到第一页,清空分页缓存
await refreshCreate()

// 编辑数据后:保持当前页,只清空当前搜索缓存
await refreshUpdate()

// 删除数据后:智能处理页码,避免空页面
await refreshRemove()

// 手动刷新:清空所有缓存
await refreshData()

// 定时刷新:轻量刷新,保持分页状态
await refreshSoft()

这些方法让你的代码更语义化,不用再纠结什么时候该清缓存。

🔄 数据转换器

有时候接口返回的数据需要处理一下才能用:

useTable({
  core: {
    /* ... */
  },
  transform: {
    dataTransformer: (records) => {
      return records.map((item, index) => ({
        ...item,
        // 替换头像
        avatar: localAvatars[index % localAvatars.length].avatar,
        // 格式化日期
        createTime: dayjs(item.createTime).format('YYYY-MM-DD')
      }))
    }
  }
})

📊 生命周期钩子

useTable({
  core: {
    /* ... */
  },
  hooks: {
    onSuccess: (data, response) => {
      console.log('数据加载成功', data)
    },
    onError: (error) => {
      ElMessage.error('加载失败:' + error.message)
    },
    onCacheHit: (data) => {
      console.log('从缓存读取', data)
    }
  }
})

1.3 搜索功能

搜索是表格的核心功能,useTable 提供了完善的搜索支持:

// 定义搜索参数
const searchParams = reactive({
  userName: '',
  userPhone: '',
  status: '1'
})

const { getData, resetSearchParams } = useTable({
  core: {
    apiFn: fetchGetUserList,
    apiParams: searchParams
  }
})

// 搜索
const handleSearch = (params) => {
  Object.assign(searchParams, params)
  getData() // 自动回到第一页
}

// 重置
const handleReset = () => {
  resetSearchParams() // 清空搜索条件并重新加载
}

二、ArtTable:增强的表格组件

2.1 核心特性

ArtTable 基于 Element Plus 的 ElTable 封装,完全兼容原有 API,同时提供了更多增强功能:

<ArtTable
  :loading="loading"
  :data="data"
  :columns="columns"
  :pagination="pagination"
  @selection-change="handleSelectionChange"
  @pagination:size-change="handleSizeChange"
  @pagination:current-change="handleCurrentChange"
/>

✨ 自动高度计算

不用再手动计算表格高度了!ArtTable 会自动计算剩余空间:

<div class="art-full-height">
  <UserSearch />
  <ElCard class="art-table-card">
    <ArtTableHeader />
    <ArtTable /> <!-- 自动占满剩余高度 -->
  </ElCard>

</div>

🎨 列配置灵活

支持多种列类型和自定义渲染:

columnsFactory: () => [
  { type: 'selection' }, // 勾选列
  { type: 'index', width: 60, label: '序号' }, // 序号列
  { type: 'globalIndex' }, // 全局序号(跨页)

  // 自定义渲染
  {
    prop: 'avatar',
    label: '用户',
    formatter: (row) => {
      return h('div', { class: 'user-info' }, [
        h(ElImage, { src: row.avatar }),
        h('span', row.userName)
      ])
    }
  },

  // 使用插槽
  {
    prop: 'status',
    label: '状态',
    useSlot: true // 在模板中使用 #status 插槽
  },

  // 操作列
  {
    prop: 'operation',
    label: '操作',
    fixed: 'right',
    formatter: (row) =>
      h('div', [
        h(ArtButtonTable, {
          type: 'edit',
          onClick: () => handleEdit(row)
        }),
        h(ArtButtonTable, {
          type: 'delete',
          onClick: () => handleDelete(row)
        })
      ])
  }
]

📱 响应式分页

自动适配移动端、平板、桌面端:

// 移动端:prev, pager, next, sizes, jumper, total
// 平板:prev, pager, next, jumper, total
// 桌面端:total, prev, pager, next, sizes, jumper

三、ArtTableHeader:强大的工具栏

3.1 开箱即用的功能

<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
  <template #left>
    <ElButton @click="handleAdd">新增用户</ElButton>

  </template>

</ArtTableHeader>

一行代码,你就拥有了:

  • 🔍 搜索栏切换
  • 🔄 刷新按钮(带加载动画)
  • 📏 表格尺寸切换(大、中、小)
  • 🖥️ 全屏模式
  • 📋 列显示/隐藏配置
  • 🎨 斑马纹、边框、表头背景切换

3.2 列配置功能

用户可以:

  • ✅ 勾选显示/隐藏列
  • ✅ 拖拽调整列顺序
  • ✅ 固定列不可拖动

这些配置会自动同步到表格显示。

3.3 自定义布局

<ArtTableHeader layout="refresh,size,fullscreen,columns" :show-zebra="false" :show-border="false" />

通过 layout 属性控制显示哪些功能按钮。

四、ArtSearchBar:智能搜索栏

4.1 配置化搜索表单

不用再手写一堆 ElFormItem 了,用配置就能搞定:

<template>
  <ArtSearchBar
    ref="searchBarRef"
    v-model="formData"
    :items="formItems"
    :rules="rules"
    @reset="handleReset"
    @search="handleSearch"
  />
</template>

<script setup lang="ts">
  const formData = ref({
    userName: undefined,
    userPhone: undefined,
    status: '1'
  })

  const formItems = computed(() => [
    {
      label: '用户名',
      key: 'userName',
      type: 'input',
      placeholder: '请输入用户名',
      clearable: true
    },
    {
      label: '手机号',
      key: 'userPhone',
      type: 'input',
      props: { placeholder: '请输入手机号', maxlength: '11' }
    },
    {
      label: '状态',
      key: 'status',
      type: 'select',
      props: {
        placeholder: '请选择状态',
        options: [
          { label: '在线', value: '1' },
          { label: '离线', value: '2' }
        ]
      }
    },
    {
      label: '性别',
      key: 'userGender',
      type: 'radiogroup',
      props: {
        options: [
          { label: '男', value: '1' },
          { label: '女', value: '2' }
        ]
      }
    }
  ])

  const handleSearch = async () => {
    await searchBarRef.value.validate()
    emit('search', formData.value)
  }

  const handleReset = () => {
    emit('reset')
  }
</script>

4.2 核心特性

🎯 支持多种表单组件

开箱即用的组件类型:

  • input - 输入框
  • select - 下拉选择
  • date / datetime / daterange - 日期选择
  • radiogroup / checkboxgroup - 单选/多选
  • cascader - 级联选择
  • treeselect - 树选择
  • 自定义组件 - 支持任意 Vue 组件

📦 自动展开/收起

当搜索项过多时,自动显示展开/收起按钮:

<ArtSearchBar :items="formItems" :show-expand="true" :default-expanded="false" :span="6" />
  • 默认只显示一行
  • 超出部分自动隐藏
  • 点击"展开"查看全部搜索项

🔄 动态选项加载

支持异步加载选项数据:

const statusOptions = ref([])

onMounted(async () => {
  // 模拟接口请求
  statusOptions.value = await fetchStatusOptions()
})

const formItems = computed(() => [
  {
    label: '状态',
    key: 'status',
    type: 'select',
    props: {
      options: statusOptions.value // 动态选项
    }
  }
])

🎨 响应式布局

自动适配不同屏幕尺寸:

// 通过 span 控制每行显示的表单项数量
// span=6: 一行显示 4 个(24/6=4)
// span=8: 一行显示 3 个(24/8=3)
// span=12: 一行显示 2 个(24/12=2)

移动端自动调整为单列布局。

4.3 表单校验

支持完整的表单校验:

const rules = {
  userName: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
  userPhone: [
    { required: true, message: '请输入手机号', trigger: 'blur' },
    { pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
  ]
}

const handleSearch = async () => {
  // 校验通过才执行搜索
  await searchBarRef.value.validate()
  emit('search', formData.value)
}

五、ArtForm:通用表单组件

5.1 配置化表单

ArtForm 和 ArtSearchBar 使用相同的配置方式,但更适合弹窗、详情页等场景:

<template>
  <ArtForm
    ref="formRef"
    v-model="formData"
    :items="formItems"
    :rules="formRules"
    :label-width="100"
    :span="12"
    @reset="handleReset"
    @submit="handleSubmit"
  />
</template>

<script setup lang="ts">
  const formData = ref({
    userName: '',
    userPhone: '',
    userEmail: '',
    userGender: '1',
    status: true
  })

  const formItems = [
    {
      label: '用户名',
      key: 'userName',
      type: 'input',
      placeholder: '请输入用户名'
    },
    {
      label: '手机号',
      key: 'userPhone',
      type: 'input',
      props: { maxlength: 11 }
    },
    {
      label: '邮箱',
      key: 'userEmail',
      type: 'input',
      placeholder: '请输入邮箱'
    },
    {
      label: '性别',
      key: 'userGender',
      type: 'radiogroup',
      props: {
        options: [
          { label: '男', value: '1' },
          { label: '女', value: '2' }
        ]
      }
    },
    {
      label: '是否启用',
      key: 'status',
      type: 'switch'
    },
    {
      label: '备注',
      key: 'remark',
      type: 'input',
      span: 24, // 占满整行
      props: {
        type: 'textarea',
        rows: 4
      }
    }
  ]

  const formRules = {
    userName: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
    userEmail: [{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }]
  }

  const handleSubmit = async () => {
    await formRef.value.validate()
    // 提交表单
    console.log('表单数据:', formData.value)
  }
</script>

5.2 高级特性

🎨 自定义组件渲染

支持使用 h 函数渲染任意组件:

import ArtIconSelector from '@/components/core/base/art-icon-selector/index.vue'

const formItems = [
  {
    label: '图标选择',
    key: 'icon',
    type: () =>
      h(ArtIconSelector, {
        iconType: IconTypeEnum.UNICODE,
        width: '100%'
      })
  },
  {
    label: '文件上传',
    key: 'files',
    type: () =>
      h(
        ElUpload,
        {
          action: '#',
          multiple: true,
          limit: 5,
          onChange: (file, fileList) => {
            formData.value.files = fileList
          }
        },
        {
          default: () => h(ElButton, { type: 'primary' }, () => '点击上传')
        }
      )
  }
]

🔌 插槽支持

支持为表单项添加插槽:

<ArtForm v-model="formData" :items="formItems">
  <template #customField>
    <div class="custom-content">
      <!-- 自定义内容 -->
    </div>

  </template>

</ArtForm>

或者在配置中使用插槽:

const formItems = [
  {
    label: '网站',
    key: 'website',
    type: 'input',
    slots: {
      prepend: () => h('span', 'https://'),
      append: () => h('span', '.com')
    }
  }
]

🎯 条件显示

根据条件动态显示/隐藏表单项:

const formItems = computed(() => [
  {
    label: '用户类型',
    key: 'userType',
    type: 'select',
    props: {
      options: [
        { label: '个人', value: 'personal' },
        { label: '企业', value: 'enterprise' }
      ]
    }
  },
  {
    label: '企业名称',
    key: 'companyName',
    type: 'input',
    // 只有选择企业类型时才显示
    hidden: formData.value.userType !== 'enterprise'
  }
])

📏 灵活布局

通过 span 控制表单项宽度:

const formItems = [
  {
    label: '用户名',
    key: 'userName',
    type: 'input',
    span: 12 // 占半行
  },
  {
    label: '手机号',
    key: 'userPhone',
    type: 'input',
    span: 12 // 占半行
  },
  {
    label: '地址',
    key: 'address',
    type: 'input',
    span: 24 // 占整行
  }
]

5.3 与 ArtSearchBar 的区别

特性 ArtSearchBar ArtForm
使用场景 表格搜索 表单提交、弹窗
展开/收起 ✅ 支持 ❌ 不支持
按钮文案 搜索/重置 提交/重置
样式 卡片样式 无背景
默认布局 横向排列 横向排列

六、完整示例

让我们看一个完整的用户管理页面:

<template>
  <div class="user-page art-full-height">
    <!-- 搜索栏 -->
    <ArtSearchBar
      v-model="searchForm"
      :items="searchItems"
      @search="handleSearch"
      @reset="resetSearchParams"
    />

    <ElCard class="art-table-card" shadow="never">
      <!-- 工具栏 -->
      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
        <template #left>
          <ElButton @click="handleAdd">新增用户</ElButton>

        </template>

      </ArtTableHeader>

      <!-- 表格 -->
      <ArtTable
        :loading="loading"
        :data="data"
        :columns="columns"
        :pagination="pagination"
        @selection-change="handleSelectionChange"
        @pagination:size-change="handleSizeChange"
        @pagination:current-change="handleCurrentChange"
      />
    </ElCard>

    <!-- 弹窗 -->
    <UserDialog
      v-model:visible="dialogVisible"
      :type="dialogType"
      :user-data="currentUserData"
      @submit="handleDialogSubmit"
    />
  </div>

</template>

<script setup lang="ts">
  import { useTable } from '@/composables/useTable'
  import { fetchGetUserList } from '@/api/system-manage'
  import UserDialog from './modules/user-dialog.vue'

  // 搜索表单
  const searchForm = ref({
    userName: undefined,
    userPhone: undefined,
    status: '1'
  })

  // 搜索项配置
  const searchItems = [
    {
      label: '用户名',
      key: 'userName',
      type: 'input',
      placeholder: '请输入用户名',
      clearable: true
    },
    {
      label: '手机号',
      key: 'userPhone',
      type: 'input',
      props: { placeholder: '请输入手机号', maxlength: '11' }
    },
    {
      label: '状态',
      key: 'status',
      type: 'select',
      props: {
        placeholder: '请选择状态',
        options: [
          { label: '在线', value: '1' },
          { label: '离线', value: '2' },
          { label: '异常', value: '3' }
        ]
      }
    }
  ]

  // 表格配置
  const {
    data,
    columns,
    columnChecks,
    loading,
    pagination,
    getData,
    searchParams,
    resetSearchParams,
    handleSizeChange,
    handleCurrentChange,
    refreshData
  } = useTable({
    core: {
      apiFn: fetchGetUserList,
      apiParams: {
        current: 1,
        size: 20,
        ...searchForm.value
      },
      columnsFactory: () => [
        { type: 'selection' },
        { type: 'index', width: 60, label: '序号' },
        {
          prop: 'avatar',
          label: '用户名',
          width: 280,
          formatter: (row) => {
            return h('div', { class: 'user-info' }, [
              h(ElImage, { src: row.avatar }),
              h('div', [h('p', row.userName), h('p', { class: 'email' }, row.userEmail)])
            ])
          }
        },
        { prop: 'userGender', label: '性别' },
        { prop: 'userPhone', label: '手机号' },
        {
          prop: 'status',
          label: '状态',
          formatter: (row) => {
            const config = getStatusConfig(row.status)
            return h(ElTag, { type: config.type }, () => config.text)
          }
        },
        {
          prop: 'operation',
          label: '操作',
          width: 120,
          fixed: 'right',
          formatter: (row) =>
            h('div', [
              h(ArtButtonTable, {
                type: 'edit',
                onClick: () => handleEdit(row)
              }),
              h(ArtButtonTable, {
                type: 'delete',
                onClick: () => handleDelete(row)
              })
            ])
        }
      ]
    }
  })

  // 搜索处理
  const handleSearch = (params) => {
    Object.assign(searchParams, params)
    getData()
  }

  // 新增
  const handleAdd = () => {
    dialogType.value = 'add'
    dialogVisible.value = true
  }

  // 编辑
  const handleEdit = (row) => {
    dialogType.value = 'edit'
    currentUserData.value = row
    dialogVisible.value = true
  }

  // 删除
  const handleDelete = (row) => {
    ElMessageBox.confirm('确定要删除该用户吗?', '提示', {
      type: 'warning'
    }).then(() => {
      // 调用删除接口
      // await deleteUser(row.id)
      ElMessage.success('删除成功')
      refreshData()
    })
  }
</script>

七、性能优化

7.1 智能防抖

搜索时自动防抖,避免频繁请求:

const { getDataDebounced } = useTable({
  performance: {
    debounceTime: 300 // 300ms 防抖
  }
})

// 使用防抖搜索
const handleSearch = () => {
  getDataDebounced()
}

7.2 请求取消

切换页面或快速切换搜索条件时,自动取消上一个请求:

// useTable 内部实现
let abortController = new AbortController()

const fetchData = async () => {
  // 取消上一个请求
  if (abortController) {
    abortController.abort()
  }

  // 创建新的控制器
  abortController = new AbortController()

  // 发起请求
  await apiFn(params)
}

7.3 缓存统计

实时查看缓存状态:

const { cacheInfo } = useTable({
  performance: { enableCache: true }
})

console.log(cacheInfo.value)
// { total: 10, size: '45KB', hitRate: '8 avg hits' }

八、最佳实践

8.1 目录结构

views/
  system/
    user/
      index.vue           # 主页面
      modules/
        user-search.vue   # 搜索组件
        user-dialog.vue   # 弹窗组件

8.2 搜索组件封装

使用 ArtSearchBar 封装搜索组件:

<!-- user-search.vue -->
<template>
  <ArtSearchBar
    ref="searchBarRef"
    v-model="formData"
    :items="formItems"
    @reset="handleReset"
    @search="handleSearch"
  />
</template>

<script setup lang="ts">
  const formData = defineModel({ required: true })
  const emit = defineEmits(['search', 'reset'])

  const formItems = [
    {
      label: '用户名',
      key: 'userName',
      type: 'input',
      placeholder: '请输入用户名',
      clearable: true
    },
    {
      label: '手机号',
      key: 'userPhone',
      type: 'input',
      props: { placeholder: '请输入手机号', maxlength: '11' }
    }
  ]

  const handleSearch = () => {
    emit('search', formData.value)
  }

  const handleReset = () => {
    emit('reset')
  }
</script>

8.3 类型安全

充分利用 TypeScript 的类型推导:

// API 类型定义
declare namespace Api.SystemManage {
  interface UserListItem {
    id: number
    userName: string
    userPhone: string
    userEmail: string
    status: string
    avatar: string
  }

  interface UserSearchParams {
    userName?: string
    userPhone?: string
    status?: string
  }
}

// useTable 会自动推导类型
const { data } = useTable({
  core: {
    apiFn: fetchGetUserList // 返回 Promise<UserListItem[]>
    // data 的类型自动推导为 UserListItem[]
  }
})

九、对比传统方案

传统方案(约 200 行代码)

// 需要手动管理的状态
const loading = ref(false)
const data = ref([])
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(20)
const searchForm = ref({})

// 需要手动实现的方法
const fetchData = async () => {
  loading.value = true
  try {
    const res = await api.getUserList({
      current: currentPage.value,
      size: pageSize.value,
      ...searchForm.value
    })
    data.value = res.records
    total.value = res.total
  } catch (error) {
    console.error(error)
  } finally {
    loading.value = false
  }
}

const handleSizeChange = (val) => {
  pageSize.value = val
  currentPage.value = 1
  fetchData()
}

const handleCurrentChange = (val) => {
  currentPage.value = val
  fetchData()
}

const handleSearch = () => {
  currentPage.value = 1
  fetchData()
}

const handleReset = () => {
  searchForm.value = {}
  currentPage.value = 1
  fetchData()
}

// ... 还有更多代码

使用 useTable(约 30 行代码)

const {
  data,
  columns,
  loading,
  pagination,
  searchParams,
  getData,
  resetSearchParams,
  handleSizeChange,
  handleCurrentChange
} = useTable({
  core: {
    apiFn: fetchGetUserList,
    apiParams: { current: 1, size: 20 },
    columnsFactory: () => [
      /* 列配置 */
    ]
  }
})

const handleSearch = (params) => {
  Object.assign(searchParams, params)
  getData()
}

代码量减少 85%,功能更强大!

十、总结

这套表格开发方案的核心优势:

  1. 开发效率提升 10 倍 - 从 200 行代码减少到 30 行
  2. 功能更强大 - 缓存、防抖、多种刷新策略、列配置等
  3. 类型安全 - 完整的 TypeScript 支持
  4. 易于维护 - 统一的代码风格和最佳实践
  5. 用户体验好 - 响应式设计、智能缓存、流畅交互

如果你也在开发后台管理系统,强烈建议尝试这套方案。它不仅能让你的代码更简洁,还能让你有更多时间专注于业务逻辑,而不是重复造轮子。

如果这篇文章对你有帮助,欢迎点赞收藏!有任何问题也欢迎在评论区讨论 💬

Vue 组件通信的两种世界观:`.sync` 与普通 `props` 到底有什么不同?

2025年10月11日 18:57
.sync 则通过约定事件 update:propName,让子组件可上抛修改意图,由父组件执行更新,从而在不破坏单向数据流的前提下实现“受控同步”。它统一了父子通信模式,简化代码,保持数据流向清晰。

2025WebAssembly详解

2025年10月11日 12:00

WebAssembly详解

引言

WebAssembly(简称Wasm)是一项革命性的Web技术,它为Web平台带来了接近原生的性能。作为继JavaScript之后的第四种Web语言(HTML、CSS、JavaScript之后),WebAssembly正在改变我们对Web应用性能和功能的认知。

什么是WebAssembly

WebAssembly是一种低级类汇编语言,具有紧凑的二进制格式,可以在现代Web浏览器中以接近原生的性能运行。它被设计为一种编译目标,允许C、C++、Rust等语言编写的代码在Web环境中运行。

WebAssembly的历史背景

WebAssembly的发展历程可以追溯到2015年,当时Mozilla、Google、Microsoft和Apple等主要浏览器厂商开始合作开发这一技术。2017年,WebAssembly正式成为W3C推荐标准,标志着它成为了Web平台的正式组成部分。

WebAssembly核心概念

字节码格式

WebAssembly的核心是其二进制格式,这种格式具有以下特点:

  • 紧凑性:相比文本格式,二进制格式更小,加载更快
  • 可读性:提供文本格式(.wat)用于调试和学习
  • 高效解析:浏览器可以快速解析和编译
  • 确定性:严格的规范确保跨平台一致性

虚拟机模型

WebAssembly运行在一个沙箱化的虚拟机中,具有以下特性:

  • 线性内存模型:使用单一的连续内存块
  • 栈式架构:基于栈的执行模型
  • 静态类型系统:所有类型在编译时确定
  • 确定性执行:相同输入总是产生相同输出

模块系统

WebAssembly程序以模块(Module)为单位组织,每个模块包含:

(module
  (func $add (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    i32.add)
  (export "add" (func $add))
)

WebAssembly与JavaScript的互操作

导入和导出

WebAssembly模块可以导入JavaScript函数,也可以导出函数供JavaScript调用:

// JavaScript中使用WebAssembly
const wasmModule = await WebAssembly.instantiateStreaming(
  fetch('math.wasm'),
  {
    // 导入对象
    env: {
      consoleLog: (value) => console.log(value)
    }
  }
);

// 调用导出的函数
const result = wasmModule.instance.exports.add(5, 3);

内存共享

WebAssembly和JavaScript可以共享内存:

// 创建共享内存
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 });

// 传递给WebAssembly模块
const wasmModule = await WebAssembly.instantiateStreaming(
  fetch('program.wasm'),
  { env: { memory } }
);

// 在JavaScript中访问WebAssembly内存
const buffer = new Uint8Array(memory.buffer);

开发工具链

Emscripten

Emscripten是最流行的C/C++到WebAssembly编译器:

# 安装Emscripten
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest

# 编译C代码到WebAssembly
emcc hello.c -o hello.html

Rust和wasm-pack

Rust语言对WebAssembly有很好的支持:

// lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}
# 使用wasm-pack构建
wasm-pack build --target web

AssemblyScript

AssemblyScript是一种类似TypeScript的语言,专门用于编译到WebAssembly:

// assembly/index.ts
export function add(a: i32, b: i32): i32 {
  return a + b;
}

性能优化

编译优化

WebAssembly的性能优势主要体现在:

  • 快速启动:二进制格式解析速度快
  • 高效执行:接近原生代码性能
  • 内存安全:沙箱环境保证安全性
  • 并行编译:支持多线程编译

内存管理优化

// 避免频繁内存分配
const memory = new WebAssembly.Memory({ initial: 256 });
const buffer = new Uint8Array(memory.buffer);

// 重用内存缓冲区
function processData(data) {
  // 将数据写入共享内存
  buffer.set(data);
  // 调用WebAssembly函数处理
  return wasmModule.instance.exports.process();
}

函数调用优化

减少JavaScript和WebAssembly之间的调用开销:

// 批量处理数据,减少调用次数
function batchProcess(items) {
  // 将所有数据写入内存
  writeDataToMemory(items);
  // 一次调用处理所有数据
  return wasmModule.instance.exports.batchProcess(items.length);
}

实际应用场景

图像处理

WebAssembly在图像处理方面表现出色:

// 使用WebAssembly进行图像滤镜处理
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const result = wasmFilters.applyBlur(imageData.data, radius);

游戏开发

许多高性能Web游戏使用WebAssembly:

// Unity WebGL导出使用WebAssembly
const unityInstance = UnityLoader.instantiate(
  "gameContainer",
  "Build/game.json",
  { onProgress: unityProgress }
);

科学计算

WebAssembly适合进行复杂的数学计算:

// 使用WebAssembly进行矩阵运算
const matrixA = new Float32Array([1, 2, 3, 4]);
const matrixB = new Float32Array([5, 6, 7, 8]);
const result = wasmMath.matrixMultiply(matrixA, matrixB);

加密算法

WebAssembly可以高效执行加密操作:

// 使用WebAssembly进行哈希计算
const data = new TextEncoder().encode("Hello World");
const hash = wasmCrypto.sha256(data);

调试和测试

开发工具

现代浏览器提供了强大的WebAssembly调试工具:

  • Chrome DevTools:可以查看WebAssembly源码和调试信息
  • Firefox Developer Tools:支持WebAssembly调试和性能分析
  • WebAssembly Studio:在线IDE,支持实时编译和调试

性能分析

使用浏览器的性能分析工具:

// 使用Performance API分析WebAssembly性能
performance.mark('wasm-start');
wasmModule.exports.complexCalculation();
performance.mark('wasm-end');
performance.measure('wasm-execution', 'wasm-start', 'wasm-end');

安全考虑

沙箱安全

WebAssembly运行在严格的沙箱环境中:

  • 内存隔离:无法直接访问系统内存
  • API限制:只能通过导入的函数访问外部资源
  • 类型安全:防止缓冲区溢出等内存错误

输入验证

在调用WebAssembly函数前验证输入:

function safeWasmCall(input) {
  // 验证输入参数
  if (typeof input !== 'number' || input < 0) {
    throw new Error('Invalid input');
  }
  
  // 调用WebAssembly函数
  return wasmModule.instance.exports.process(input);
}

未来发展趋势

接口类型(Interface Types)

WebAssembly Interface Types将允许模块之间更丰富的交互:

(module
  (import "env" "log" (func $log (param string)))
  (export "greet" (func $greet (param string) (result string)))
)

多线程支持

WebAssembly正在增加对多线程的支持:

// 使用Web Workers和SharedArrayBuffer
const worker = new Worker('wasm-worker.js');
const sharedMemory = new WebAssembly.Memory({
  initial: 256,
  maximum: 256,
  shared: true
});

组件模型

WebAssembly组件模型将提供更好的模块化和可组合性:

(component
  (import "logger" (func (param string)))
  (export "process" (func (param string) (result string)))
)

最佳实践

模块设计

设计WebAssembly模块时应考虑:

  1. 单一职责:每个模块专注于特定功能
  2. 接口清晰:明确导入和导出的函数
  3. 内存管理:合理规划内存使用
  4. 错误处理:提供清晰的错误信息

性能优化建议

  1. 减少JS-WASM互操作:批量处理数据
  2. 合理使用内存:避免频繁分配和释放
  3. 利用SIMD:使用单指令多数据操作
  4. 缓存编译结果:避免重复编译

兼容性处理

// 检测WebAssembly支持
if (!WebAssembly) {
  console.error('WebAssembly is not supported');
  // 提供降级方案
}

// 异步加载WebAssembly
async function loadWasm() {
  try {
    const wasmModule = await WebAssembly.instantiateStreaming(
      fetch('module.wasm')
    );
    return wasmModule.instance.exports;
  } catch (error) {
    console.error('Failed to load WebAssembly module:', error);
    return null;
  }
}

总结

WebAssembly作为现代Web平台的重要组成部分,为开发者提供了前所未有的性能和功能。通过将C、C++、Rust等语言编译为WebAssembly,我们可以在浏览器中运行接近原生性能的代码。

随着技术的不断发展,WebAssembly将在更多领域发挥作用,包括边缘计算、物联网、区块链等。掌握WebAssembly不仅能够提升现有Web应用的性能,还能为未来的Web开发开辟新的可能性。

对于前端开发者来说,学习WebAssembly是顺应技术发展趋势的明智选择。通过合理运用WebAssembly,我们可以构建出性能更优、功能更强的Web应用,为用户提供更好的体验。

从最简单的 icon组件开始了解Element-Plus 源码

作者 Nayana
2025年10月9日 15:57

从基础的组件看看Element-plus实现组建的基本流程

目录 packages\components\icon

icon目录.png

因为我们分析源码的目的是学习如何构建组件库已经封装复用性强的组件所以跳过.tsx文件从src目录开始看。

icon.ts
import { buildProps, definePropType } from '@element-plus/utils'

buildProps,definePropType(export const definePropType = <T>(val: any): PropType<T> => val)。definePropType定义了泛型的props类型,buildProps是对 element 组件开发过程中,大家编码时的 Props 的设置,进行了统一格式化。

buildProps.webp

import type { ExtractPropTypes } from 'vue'
import type Icon from './icon.vue'

使用 import 的话,TypeScript 是无法判断你是想导出类型还是一个 JavaScript 的方法或者变量。
所以 TypeScript 提供了 import type or export type,用来明确表示我引入/导出的是一个类型,而不是一个变量或者方法。

定义组件需要的参数
export const iconProps = buildProps({

  /**
   * @description SVG icon size, size x size
   */
  size: {
    type: definePropType<number | string>([Number, String]),
  },
  /**
   * @description SVG tag's fill attribute
   */
  color: {
    type: String,
  },
} as const)

as const 则可以快速将一个对象变成只读类型,常量断言可以把一个值标记为一个不可篡改的常量,从而让 TS 以最严格的策略来进行类型推断。

export type IconProps = ExtractPropTypes<typeof iconProps>

vue3 string构造函数 在TS类型中是他的构造函数类型:StringConstructor 并不是我们想要的,我们希望string构造函数返回的是字符串类型string. vue3提供了prop类型申明ExtractPropTypes 用于接收一个类型用yu把接收的prop类型返回出来;也可以把构造函数类型转换为对应的类型如:StringConstructor 转换成 string

export type IconInstance = InstanceType<typeof Icon> & unknown

返回SFC 实例类型,InstanceType 函数:该函数返回(构造) 由某个构造函数构造出来的实例类型组成的类型

icon/index.ts
import { withInstall } from '@element-plus/utils'

import Icon from './src/icon.vue'

import type { SFCWithInstall } from '@element-plus/utils'
//组件类型引入
export const ElIcon: SFCWithInstall<typeof Icon> = withInstall(Icon)
// 通过 withInstall 方法给 Icon 添加了一个 install 方法
export default ElIcon
// 导出 iCON 组件

export * from './src/icon'

至此,对icon组件的分析就结束了。涉及的withInstall是组件库注册的公共方法。

具体目录是packages\utils\vue\install.ts


export const withInstall = <T, E extends Record<string, any>>(
  main: T,
  extra?: E
) => {
  ;(main as SFCWithInstall<T>).install = (app): void => {
    for (const comp of [main, ...Object.values(extra ?? {})]) {
      app.component(comp.name, comp)
    }
  }

  if (extra) {
    for (const [key, comp] of Object.entries(extra)) {
      ;(main as any)[key] = comp
    }
  }
  return main as SFCWithInstall<T> & E
}
// 组件注册的公共方法
 传递两个参数,main类型为泛型T,extra是一个对象,通过Object.values 将 extra 中的属性值提取为一个数组,并进行遍历进行 组件的注册.
 
 如果extra不为空则通过 迭代器遍历 Object.entries 转换后的 二维数组, 将extra所有属性和值 挂载到 main 对象下
 
 SFCWithInstall 通过泛型 将最后返回的 main 的类型 定义为 T & Plugin & E的交叉类型,为并且关系

vue提高技术 高级语法相关

2025年10月9日 15:30

1. 组合式 API (Composition API)

Vue 3 引入了 Composition API,它是对 Options API 的补充,提供了更好的逻辑复用和组织方式。

示例:

<script setup>
import { ref, computed, watch } from 'vue'

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

watch(count, (newVal) => {
  console.log(`Count changed to ${newVal}`)
})

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

使用 <script setup> 可以简化组件定义,自动暴露顶层绑定给模板使用。


2. 响应式系统的深入理解

  • reactive() 创建深层响应式对象。
  • shallowReactive() 创建浅层响应式对象。
  • readonly() 和 shallowReadonly() 提供只读代理。
  • 自定义 ref 实现细粒度控制。

示例:自定义 ref

js
import { customRef } from 'vue'

function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}

3. Teleport 组件

用于将子节点渲染到 DOM 中的不同位置,常用于模态框、通知等全局组件。

示例:

vue
<Teleport to="body">
  <div class="modal">This is a modal</div>
</Teleport>

4. Suspense 组件(实验性)

允许你在等待异步组件加载时显示后备内容。

示例:

vue
<Suspense>
  <template #default>
    <AsyncComponent />
  </template>
  <template #fallback>
    Loading...
  </template>
</Suspense>

5. 动态组件与 keep-alive 缓存

使用 <component :is="currentComponent"> 动态切换组件,并配合 <keep-alive> 缓存未活动的组件实例。

示例:

vue
<keep-alive>
  <component :is="currentTab"></component>
</keep-alive>

6. 插槽 (Slots) 的高级用法

包括具名插槽、作用域插槽以及动态插槽名称。

示例:作用域插槽

vue
<!-- 子组件 -->
<slot :user="currentUser"></slot>

<!-- 父组件 -->
<ChildComponent v-slot="{ user }">
  Hello, {{ user.name }}
</ChildComponent>

7. Provide / Inject 跨层级通信

用于祖先组件向其所有子孙后代注入数据,避免逐层传递 props。

示例:

js
// 祖先组件
provide('theme', 'dark')

// 后代组件
const theme = inject('theme')

8. 自定义指令

创建具有特定行为的自定义 DOM 指令。

示例:

js
const myDirective = {
  mounted(el, binding) {
    el.style.color = binding.value
  }
}

app.directive('color', myDirective)

9. Transition 和 TransitionGroup 动画

为进入/离开 DOM 的元素添加过渡效果。

示例:

vue
<Transition name="fade">
  <p v-if="show">Hello</p>
</Transition>

CSS 类:

css
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

10. 性能优化技巧

  • 使用 v-memo(Vue 3.2+)缓存复杂计算结果。
  • 列表虚拟滚动减少 DOM 渲染压力。
  • 合理拆分组件防止不必要的重渲染。
  • 使用 markRaw() 避免某些对象被转为响应式。

这些高级特性和语法能够帮助你在开发大型应用时更加得心应手。是否需要针对某个具体功能展开详细说明?

❌
❌