Vue3和Electron实现桌面端应用详解

目录
  • Vue CLI 搭建Vue项目
  • Vue项目改造为markdown编辑器
    • Vue CLI Plugin Electron Builder
  • 优化功能
    • 启动全屏显示
    • 修改菜单栏
    • 编辑器打开markdonw文件的内容
    • markdonw的内容存入文件
    • 打包

为了方便记录一些个人随笔,我最近用LaravelVue 3.0撸了一个博客系统,其中使用到了一个基于markdown-itmarkdown 编辑器Vue组件v-md-editor。我感觉用它去编写markdown还是很方便的。后面就有了一个想法,基于此组件用Electron来实现一个markdown桌面端应用,自己平时拿来使用也是个不错的选择。

题外话:VS Code就是用Electron开发出来的桌面应用,我现在除了移动端的开发外,其他的都是使用VS Code来开发了,各种插件开发起来真的很方便。

接下来我就带大家来一步步来实现这个功能。

Vue CLI 搭建Vue项目

在选择的目录下执行vue create electron-vue3-mark-down

选择自定义的模板(可以选择默认的Vue 3 模板)

选择Vue3TypeScript, 其他的选项基于自身项目决定是否选择

执行npm run serve看看效果

Vue项目改造为markdown编辑器

执行npm i @kangc/v-md-editor@next -S安装v-md-editor

添加TypeScript类型定义文件

由于v-md-editor这个库没有TypeScript类型定义文件,我就直接在shims-vue.d.ts这个文件的后面添加的,当然也可以新建一个文件添加申明(tsconfig.json能找到这个文件就OK)。

declare module "*.vue" {
  import type { DefineComponent } from "vue";
  const component: DefineComponent<{}, {}, any>;
  export default component;
}

<!-- 添加的内容 -->
declare module "@kangc/v-md-editor/lib/theme/vuepress.js";
declare module "@kangc/v-md-editor/lib/plugins/copy-code/index";
declare module "@kangc/v-md-editor/lib/plugins/line-number/index";
declare module "@kangc/v-md-editor";
declare module "prismjs";

改造App.vue

<template>
  <div>
    <v-md-editor v-model="content" height="100vh"></v-md-editor>
  </div>
</template>

<script lang="ts">
// 编辑器
import VMdEditor from "@kangc/v-md-editor";
import "@kangc/v-md-editor/lib/style/base-editor.css";
import vuepress from "@kangc/v-md-editor/lib/theme/vuepress.js";
import "@kangc/v-md-editor/lib/theme/style/vuepress.css";
// 高亮显示
import Prism from "prismjs";
import "prismjs/components/prism-json";
import "prismjs/components/prism-dart";
import "prismjs/components/prism-c";
import "prismjs/components/prism-swift";
import "prismjs/components/prism-kotlin";
import "prismjs/components/prism-java";

// 快捷复制代码
import createCopyCodePlugin from "@kangc/v-md-editor/lib/plugins/copy-code/index";
import "@kangc/v-md-editor/lib/plugins/copy-code/copy-code.css";
// 行号
import createLineNumbertPlugin from "@kangc/v-md-editor/lib/plugins/line-number/index";
VMdEditor.use(vuepress, {
  Prism,
})
  .use(createCopyCodePlugin())
  .use(createLineNumbertPlugin());

import { defineComponent, ref } from "vue";
export default defineComponent({
  name: "App",
  components: { VMdEditor },
  setup() {
    const content = ref("");

    return { content };
  },
});
</script>

<style>
/* 去掉一些按钮 */
.v-md-icon-save,
.v-md-icon-fullscreen {
  display: none;
}
</style>

这个文件也很简单,整个页面就是一个编辑器<v-md-editor v-model="content" height="100vh"></v-md-editor>,这个markdown编辑器有高亮显示,代码显示行号,复制代码按钮等插件,当然更方便的是可以添加其他的插件丰富这个markdown编辑器的功能.

效果如下

Vue CLI Plugin Electron Builder

我尝试过用Vite 2.0去搭建Electron项目,但是没有找到类似的ViteElectron结合好使的工具,所以放弃了Vite 2.0的诱惑。如果有小伙伴有推荐可以分享下。

使用vue add electron-builder安装,我选择的是13.0.0Electron的最新版本。

我一般是选择最高的版本,其实这个版本有坑,我后面再想想要不要介绍下这个坑,哈哈。

我们看到新加了很多的依赖库,还添加了一个background.ts文件。简单介绍下,这个文件执行在主线程,其他的页面都是在渲染线程。渲染线程有很多限制的,有些功能只能在主线程执行,这里就不具体展开了。

执行npm run electron:serve看效果

至此,就可以看到桌面应用的效果了,并且边修改Vue的代码,桌面应用也能实时看到修改后的效果。

优化功能

启动全屏显示

引入screen

import { screen } from "electron";

创建窗口的时候设置为screen大小

<!-- background.ts -->
async function createWindow() {
  const { width, height } = screen.getPrimaryDisplay().workAreaSize;
  const win = new BrowserWindow({
    width,
    height,
    // 省略...
  });
    // 省略...
}

这样应用启动的时候就是全屏显示了。

修改菜单栏

定义菜单栏

<!-- background.ts -->

const template: Array<MenuItemConstructorOptions> = [
  {
    label: "MarkDown",
    submenu: [
      {
        label: "关于",
        accelerator: "CmdOrCtrl+W",
        role: "about",
      },
      {
        label: "退出程序",
        accelerator: "CmdOrCtrl+Q",
        role: "quit",
      },
    ],
  },
  {
    label: "文件",
    submenu: [
      {
        label: "打开文件",
        accelerator: "CmdOrCtrl+O",
        click: (
          item: MenuItem,
          focusedWindow: BrowserWindow | undefined,
          _event: KeyboardEvent
        ) => {
            // TODO: 打开文件
        },
      },
      {
        label: "存储",
        accelerator: "CmdOrCtrl+S",
        click: (
          item: MenuItem,
          focusedWindow: BrowserWindow | undefined,
          _event: KeyboardEvent
        ) => {
          // TODO: 存储内容
        },
      },
    ],
  },
  {
    label: "编辑",
    submenu: [
      {
        label: "撤销",
        accelerator: "CmdOrCtrl+Z",
        role: "undo",
      },
      {
        label: "重做",
        accelerator: "Shift+CmdOrCtrl+Z",
        role: "redo",
      },
      {
        type: "separator",
      },
      {
        label: "剪切",
        accelerator: "CmdOrCtrl+X",
        role: "cut",
      },
      {
        label: "复制",
        accelerator: "CmdOrCtrl+C",
        role: "copy",
      },
      {
        label: "粘贴",
        accelerator: "CmdOrCtrl+V",
        role: "paste",
      },
    ],
  },
  {
    label: "窗口",
    role: "window",
    submenu: [
      {
        label: "最小化",
        accelerator: "CmdOrCtrl+M",
        role: "minimize",
      },
      {
        label: "最大化",
        accelerator: "CmdOrCtrl+M",
        click: (
          item: MenuItem,
          focusedWindow: BrowserWindow | undefined,
          _event: KeyboardEvent
        ) => {
          if (focusedWindow) {
            focusedWindow.maximize();
          }
        },
      },
      {
        type: "separator",
      },
      {
        label: "切换全屏",
        accelerator: (function () {
          if (process.platform === "darwin") {
            return "Ctrl+Command+F";
          } else {
            return "F11";
          }
        })(),
        click: (
          item: MenuItem,
          focusedWindow: BrowserWindow | undefined,
          // eslint-disable-next-line @typescript-eslint/no-unused-vars
          _event: KeyboardEvent
        ) => {
          if (focusedWindow) {
            focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
          }
        },
      },
    ],
  },
  {
    label: "帮助",
    role: "help",
    submenu: [
      {
        label: "学习更多",
        click: function () {
          shell.openExternal("http://electron.atom.io");
        },
      },
    ],
  },
];

具体如何定义参阅Electron Menu

打开文件存储目前还没实现,后面实现。

设置菜单栏

import { Menu } from "electron";
app.on("ready", async () => {
  // 省略...
  // 创建菜单
  Menu.setApplicationMenu(Menu.buildFromTemplate(template));
});

ready钩子函数中进行设置Menu

效果

编辑器打开markdonw文件的内容

主线程选择文件,将文件路径传给渲染线程

<!-- background.ts -->
dialog
  .showOpenDialog({
    properties: ["openFile"],
    filters: [{ name: "Custom File Type", extensions: ["md"] }],
  })
  .then((res) => {
    if (res && res["filePaths"].length > 0) {
      const filePath = res["filePaths"][0];
      // 将文件传给渲染线程
      if (focusedWindow) {
        focusedWindow.webContents.send("open-file-path", filePath);
      }
    }
  })
  .catch((err) => {
    console.log(err);
  });

showOpenDialog是打开文件的方法,我们这里指定了只打开md文件;

获得文件路径后,通过focusedWindow.webContents.send("open-file-path", filePath);这个方法将文件路径传给渲染线程。

渲染线程取到文件路径,读取文件内容,赋值给markdown编辑器

<!-- App.vue -->
import { ipcRenderer } from "electron";
import { readFileSync } from "fs";

export default defineComponent({
  // 省略...
  setup() {
    const content = ref("");

    onMounted(() => {
      // 1.
      ipcRenderer.on("open-file-path", (e, filePath: string) => {
        if (filePath && filePath.length > 0) {
          // 2.
          content.value = readFileSync(filePath).toString();
        }
      });
    });

    return { content };
  },
});

vue添加node支持

<!-- vue.config.js -->
module.exports = {
  pluginOptions: {
    electronBuilder: {
      nodeIntegration: true,
    },
  },
};

效果

markdonw的内容存入文件

主线程发起向渲染线程获取编辑器内容的请求

<!-- background.js -->
if (focusedWindow) {
    focusedWindow.webContents.send("get-content", "");
}

渲染线程主线程向返回编辑器的内容

<!-- App.vue -->
onMounted(() => {
    ipcRenderer.on("get-content", () => {
        ipcRenderer.send("save-content", content.value);
    });
});

主线程收到内容然后存入文件

<!-- background.ts -->
// 存储文件
ipcMain.on("save-content", (event: unknown, content: string) => {
  if (openedFile.length > 0) {
    // 直接存储到文件中去
    try {
      writeFileSync(openedFile, content);
      console.log("保存成功");
    } catch (error) {
      console.log("保存失败");
    }
  } else {
    const options = {
      title: "保存文件",
      defaultPath: "new.md",
      filters: [{ name: "Custom File Type", extensions: ["md"] }],
    };
    const focusedWindow = BrowserWindow.getFocusedWindow();
    if (focusedWindow) {
      dialog
        .showSaveDialog(focusedWindow, options)
        .then((result: Electron.SaveDialogReturnValue) => {
          if (result.filePath) {
            try {
              writeFileSync(result.filePath, content);
              console.log("保存成功");
              openedFile = result.filePath;
            } catch (error) {
              console.log("保存失败");
            }
          }
        })
        .catch((error) => {
          console.log(error);
        });
    }
  }
});

效果

打包

设置应用的名字和图片

<!-- vue.config.js -->
module.exports = {
  pluginOptions: {
    electronBuilder: {
      nodeIntegration: true,
      // 添加的设置
      builderOptions: {
        appId: "com.johnny.markdown",
        productName: "JJMarkDown",  // 应用的名字
        copyright: "Copyright © 2021", //版权声明
        mac: {
          icon: "./public/icon.icns", // icon
        },
      },
    },
  },
};

icon.icns生成 准备一个1024*1024的图片,同级目录下创建一个为icons.iconset的文件夹;

创建各种不同尺寸要求的图片文件

sips -z 16 16 icon.png -o icons.iconset/icon_16x16.png
sips -z 32 32 icon.png -o icons.iconset/icon_16x16@2x.png
sips -z 32 32 icon.png -o icons.iconset/icon_32x32.png
sips -z 64 64 icon.png -o icons.iconset/icon_32x32@2x.png
sips -z 128 128 icon.png -o icons.iconset/icon_128x128.png
sips -z 256 256 icon.png -o icons.iconset/icon_128x128@2x.png
sips -z 256 256 icon.png -o icons.iconset/icon_256x256.png
sips -z 512 512 icon.png -o icons.iconset/icon_256x256@2x.png
sips -z 512 512 icon.png -o icons.iconset/icon_512x512.png
sips -z 1024 1024 icon.png -o icons.iconset/icon_512x512@2x.png

获得名为icon.icns的图标文件

iconutil -c icns icons.iconset -o icon.icns

打包

npm run electron:build

结果

获得的dmg文件就可以直接安装使用了。

代码

<!-- background.ts -->
"use strict";

import {
  app,
  protocol,
  BrowserWindow,
  screen,
  Menu,
  MenuItem,
  shell,
  dialog,
  ipcMain,
} from "electron";
import { KeyboardEvent, MenuItemConstructorOptions } from "electron/main";
import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
import installExtension, { VUEJS3_DEVTOOLS } from "electron-devtools-installer";
const isDevelopment = process.env.NODE_ENV !== "production";
import { writeFileSync } from "fs";

let openedFile = "";
// 存储文件
ipcMain.on("save-content", (event: unknown, content: string) => {
  if (openedFile.length > 0) {
    // 直接存储到文件中去
    try {
      writeFileSync(openedFile, content);
      console.log("保存成功");
    } catch (error) {
      console.log("保存失败");
    }
  } else {
    const options = {
      title: "保存文件",
      defaultPath: "new.md",
      filters: [{ name: "Custom File Type", extensions: ["md"] }],
    };
    const focusedWindow = BrowserWindow.getFocusedWindow();
    if (focusedWindow) {
      dialog
        .showSaveDialog(focusedWindow, options)
        .then((result: Electron.SaveDialogReturnValue) => {
          if (result.filePath) {
            try {
              writeFileSync(result.filePath, content);
              console.log("保存成功");
              openedFile = result.filePath;
            } catch (error) {
              console.log("保存失败");
            }
          }
        })
        .catch((error) => {
          console.log(error);
        });
    }
  }
});

const template: Array<MenuItemConstructorOptions> = [
  {
    label: "MarkDown",
    submenu: [
      {
        label: "关于",
        accelerator: "CmdOrCtrl+W",
        role: "about",
      },
      {
        label: "退出程序",
        accelerator: "CmdOrCtrl+Q",
        role: "quit",
      },
    ],
  },
  {
    label: "文件",
    submenu: [
      {
        label: "打开文件",
        accelerator: "CmdOrCtrl+O",
        click: (
          item: MenuItem,
          focusedWindow: BrowserWindow | undefined,
          // eslint-disable-next-line @typescript-eslint/no-unused-vars
          _event: KeyboardEvent
        ) => {
          dialog
            .showOpenDialog({
              properties: ["openFile"],
              filters: [{ name: "Custom File Type", extensions: ["md"] }],
            })
            .then((res) => {
              if (res && res["filePaths"].length > 0) {
                const filePath = res["filePaths"][0];
                // 将文件传给渲染线程
                if (focusedWindow) {
                  focusedWindow.webContents.send("open-file-path", filePath);
                  openedFile = filePath;
                }
              }
            })
            .catch((err) => {
              console.log(err);
            });
        },
      },
      {
        label: "存储",
        accelerator: "CmdOrCtrl+S",
        click: (
          item: MenuItem,
          focusedWindow: BrowserWindow | undefined,
          // eslint-disable-next-line @typescript-eslint/no-unused-vars
          _event: KeyboardEvent
        ) => {
          if (focusedWindow) {
            focusedWindow.webContents.send("get-content", "");
          }
        },
      },
    ],
  },
  {
    label: "编辑",
    submenu: [
      {
        label: "撤销",
        accelerator: "CmdOrCtrl+Z",
        role: "undo",
      },
      {
        label: "重做",
        accelerator: "Shift+CmdOrCtrl+Z",
        role: "redo",
      },
      {
        type: "separator",
      },
      {
        label: "剪切",
        accelerator: "CmdOrCtrl+X",
        role: "cut",
      },
      {
        label: "复制",
        accelerator: "CmdOrCtrl+C",
        role: "copy",
      },
      {
        label: "粘贴",
        accelerator: "CmdOrCtrl+V",
        role: "paste",
      },
    ],
  },
  {
    label: "窗口",
    role: "window",
    submenu: [
      {
        label: "最小化",
        accelerator: "CmdOrCtrl+M",
        role: "minimize",
      },
      {
        label: "最大化",
        accelerator: "CmdOrCtrl+M",
        click: (
          item: MenuItem,
          focusedWindow: BrowserWindow | undefined,
          // eslint-disable-next-line @typescript-eslint/no-unused-vars
          _event: KeyboardEvent
        ) => {
          if (focusedWindow) {
            focusedWindow.maximize();
          }
        },
      },
      {
        type: "separator",
      },
      {
        label: "切换全屏",
        accelerator: (function () {
          if (process.platform === "darwin") {
            return "Ctrl+Command+F";
          } else {
            return "F11";
          }
        })(),
        click: (
          item: MenuItem,
          focusedWindow: BrowserWindow | undefined,
          // eslint-disable-next-line @typescript-eslint/no-unused-vars
          _event: KeyboardEvent
        ) => {
          if (focusedWindow) {
            focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
          }
        },
      },
    ],
  },
  {
    label: "帮助",
    role: "help",
    submenu: [
      {
        label: "学习更多",
        click: function () {
          shell.openExternal("http://electron.atom.io");
        },
      },
    ],
  },
];

protocol.registerSchemesAsPrivileged([
  { scheme: "app", privileges: { secure: true, standard: true } },
]);

async function createWindow() {
  const { width, height } = screen.getPrimaryDisplay().workAreaSize;
  const win = new BrowserWindow({
    width,
    height,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
    },
  });

  if (process.env.WEBPACK_DEV_SERVER_URL) {
    // Load the url of the dev server if in development mode
    await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string);
    if (!process.env.IS_TEST) win.webContents.openDevTools();
  } else {
    createProtocol("app");
    // Load the index.html when not in development
    win.loadURL("app://./index.html");
  }
}

// Quit when all windows are closed.
app.on("window-all-closed", () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== "darwin") {
    app.quit();
  }
});

app.on("activate", () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (BrowserWindow.getAllWindows().length === 0) createWindow();
});

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", async () => {
  if (isDevelopment && !process.env.IS_TEST) {
    // Install Vue Devtools
    try {
      await installExtension(VUEJS3_DEVTOOLS);
    } catch (e) {
      console.error("Vue Devtools failed to install:", e.toString());
    }
  }
  createWindow();
  // 创建菜单
  Menu.setApplicationMenu(Menu.buildFromTemplate(template));
});

// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
  if (process.platform === "win32") {
    process.on("message", (data) => {
      if (data === "graceful-exit") {
        app.quit();
      }
    });
  } else {
    process.on("SIGTERM", () => {
      app.quit();
    });
  }
}

到此这篇关于Vue3和Electron实现桌面端应用详解的文章就介绍到这了,更多相关Vue3 Electron 桌面端应用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • electron踩坑之remote of undefined的解决

    之前的项目,引用electron的remote可以直接调用 electron.remote 来去使用,而近期使用electron却频繁报错???踩坑后我快速去查看了下官方文档,是不是electron进行了更新?果然不出所料,在electron 10中,修改了enableRemoteModule默认为false,我们需要手动将其修改为true. 此前版本中我们使用electron中的remote模块时,不需在主进程的窗口中加入 enableRemoteModule:true 参数才能够调用remo

  • vue-electron使用serialport时问题解决方案

    报错如下: Uncaught TypeError: Cannot read property 'modules' of undefined at Object.eval (webpack-internal:///./node_modules/bindings/bindings.js:29) at eval (webpack-internal:///./node_modules/bindings/bindings.js:223) at Object../node_modules/bindings/

  • 基于 Vue 的 Electron 项目搭建过程图文详解

    Electron 应用技术体系推荐 目录结构 demo(项目名称) ├─ .electron-vue(webpack配置文件) │ └─ build.js(生产环境构建代码) | └─ dev-client.js(热加载相关) │ └─ dev-runner.js(开发环境启动入口) │ └─ webpack.main.config.js(主进程配置文件) │ └─ webpack.renderer.config.js(渲染进程配置文件) │ └─ webpack.web.config.js ├

  • electron踩坑之dialog中的callback解决

    踩坑分析 之前版本使用dialog时选择文件时,可以加入callback,来获取被选择文件的路径,而electron10更新后发生了改动,采用了Promise对象来获取结果. electron 10之前我们获取文件路径,只需加入callback即可,也就是下述写法,且之前返回的data结果直接是文件的路径. openDialogDom.onclick = function(){ remote.dialog.showOpenDialog({ properties:['openFile'] },fu

  • vue3+electron12+dll开发客户端配置详解

    当前使用的版本为 @vue/cli4 创建的 vue3.0 + typescript + electron 12 版本创建,其他版本暂未测试.网上资料良莠不齐,因此花费时间依次试验,达到一个目前最优解. 修改仓库源 由于electron版本的未知性,可能存在serve可用而build之后打开白屏的情况,因此需要谨慎对待.最好在版本可用的情况下commit一个版本,方便代码回滚,如果谁有更好的资料希望共享. 在开始配置前,可以将yarn和npm的rc文件稍作修改,使用命令或者文件直接修改.npmr

  • Electron+vue从零开始打造一个本地播放器的方法示例

    为什么要做? 女朋友工作是音频后期,平常会收集一些音频音乐,需要看音频的频谱波形,每次用au这种大型软件播放音乐看波形,很不方便,看到她这么辛苦,身为程序猿的我痛心疾首,于是,就有了这么一个小软件,软件涉及到的技术主要为electron,vue,node,波形的展示主要通过wavesurfer生成. 从零开始-搭建项目 项目通过vue脚手架搭建的,所以需要安装cli工具,如果已经装了,可以跳过这一步. npm install -g @vue/cli # OR yarn global add @v

  • Vue3和Electron实现桌面端应用详解

    目录 Vue CLI 搭建Vue项目 Vue项目改造为markdown编辑器 Vue CLI Plugin Electron Builder 优化功能 启动全屏显示 修改菜单栏 编辑器打开markdonw文件的内容 markdonw的内容存入文件 打包 为了方便记录一些个人随笔,我最近用Laravel和Vue 3.0撸了一个博客系统,其中使用到了一个基于markdown-it的 markdown 编辑器Vue组件v-md-editor.我感觉用它去编写markdown还是很方便的.后面就有了一个

  • Electron打包React生成桌面应用方法详解

    目录 一.Electron简介 二.搭建准备 三.创建基本应用程序 四.打包项目 一.Electron简介 Electron是一个能让你使用JavaScript,HTML和CSS来创建桌面应用程序的框架.这些应用程序可以打包后在 macOS.Windows 和 Linux 上直接运行. 在目前浏览器和移动端盛行的互联网环境下,跨平台的桌面应用开发,也为前端提供了一个新分支方向. 二.搭建准备 1.检查git和node是否安装完成 git --versionnode -vnpm -v 2.搭建Re

  • Vue3.0版本强势升级点特性详解

    目录 一.Composition API: 组合API/注入API 二.自定义渲染API(Custom Renderer API) vue2.x架构问题 三.更先进的组件 Fragment组件 Suspense组件 四.更好的TS支持 五.更快的开发体验(vite开发构建工具) 六.按需编译,体积比Vue2.x更小(Tree shaking) 七.性能比2.x快1.2-2倍 diff算法的优化 render阶段的静态提升(render阶段指生成虚拟dom树的阶段) 事件侦听缓存 减少创建组件实例

  • Vue3 如何通过虚拟DOM更新页面详解

    目录 引言 Vue 虚拟 DOM 执行流程 DOM 的创建 patch 函数 patchElement 函数 节点自身属性的更新 子元素的更新 patchChildren 位运算 为什么位运算性能更好 如何运用位运算 最长递增子系列 贪心 + 二分 引言 上一讲我们主要介绍了 Vue 项目的首次渲染流程,在 mountComponent 中注册了effect 函数,这样,在组件数据有更新的时候,就会通知到组件的 update 方法进行更新 Vue 中组件更新的方式也是使用了响应式 + 虚拟 DO

  • Vue2和Vue3如何使用watch侦听器详解

    watch:侦听数据变化 (某个值的change事件) vue2.x data(){ return{ num:10 } }, watch:{ num:{ /* * newValue:当前值 * oldValue:修改上一刻的值 */ handler(newValue,oldValue){ // doSomething }, /* * deep:Boolean : 深度监听 * true: 监听堆的改变就 * false:只监听栈的改变(默认) */ deep:true/false, /* * i

  • Vue2.x与Vue3.x中路由钩子的区别详解

    目录 vue2.x 前置概念: 路由钩子分类 路由和组件的概念(方便理解钩子函数) 全局路由钩子 路由配置守卫钩子 组件内的守卫钩子 路由钩子执行顺序 eg: 从A组件跳转到B组件顺序 如果B路有更新, 每次都会执行以下三个钩子: vue3.x 对比变化图 区别补充: vue2.x 前置概念: 路由钩子分类 一共分3类, 7个钩子 路由和组件的概念(方便理解钩子函数) 路由和组件是2个概念, 可以粗犷的认为: 路由是浏览器网址 组件是显示在网页上的不同内容 全局路由钩子 router.befor

  • Vue3内置组件Teleport使用方法详解

    目录 1.Teleport用法 2.完成模态对话框组件 3.组件的渲染 前言: Vue 3.0 新增了一个内置组件 teleport ,主要是为了解决以下场景: 有时组件模板的一部分逻辑上属于该组件,而从技术角度来看,最好将模板的这一部分移动到 DOM 中 Vue app 之外的其他位置 场景举例:一个 Button ,点击后呼出模态对话框 这个模态对话框的业务逻辑位置肯定是属于这个 Button ,但是按照 DOM 结构来看,模态对话框的实际位置应该在整个应用的中间 这样就有了一个问题:组件的

  • 数据库初始化及数据库服务端操作详解

    目录 为什么要学习数据库? 数据库的好处: 数据库的概念: SQL语言的介绍 SQL的优点 数据库存储数据的原理 MySQL服务端的操作 1.服务端的登陆和退出 2.MYSQL常见命令 为什么要学习数据库? 数据库的好处: 实现持久化数据到本地 使用完整的管理系统统一管理,易于查询 数据库的概念: SQL语言的介绍 SQL的优点 1.不是某个特定数据库供应商专有的语言,几乎所有DBMS都支持SQL 2.简单易学 3.虽然简单,但实际上是一种强有力的语言,灵活使用其他语言元素,可以进行非常复杂和高

  • VUE3中watch和watchEffect的用法详解

    watch和watchEffect都是监听器,但在写法和使用上有所区别. watch在监听 ref 类型时和监听reactive类型时watch函数的写发有所不一样.watch在监听 ref 类型时: <script> import {ref, watch} from 'vue' export default {     setup() {          const state = ref(0)         watch(state, (newValue, oldValue) =>

  • Vue3使用hooks函数实现代码复用详解

    目录 前言 VUE2我们是怎么做的呢? VUE3中我们怎么处理复用代码逻辑的封装呢? 说那么多,不如直接上代码来看差异 前言 项目开发过程中,我们会遇到一些情况,就是多个组件都可以重复使用的一部分代码逻辑,功能函数,我们想要复用,这可怎么办呢? VUE2我们是怎么做的呢? 在vue2 中有一个东西:Mixins 可以实现这个功能 mixins就是将这些多个相同的逻辑抽离出来,各个组件只需要引入mixins,就能实现代码复用 弊端一: 会涉及到覆盖的问题 组件的data.methods.filte

随机推荐