目录
依赖预构建的内容
依赖扫描
html 类型的模块处理
源码解析
JS 模块
其他 JS 无关的模块
bare import
html 类型模块
总结
首页 web前端 Vue.js Vite学习之深度解析“依赖扫描”

Vite学习之深度解析“依赖扫描”

Sep 05, 2022 pm 06:13 PM
vue vite vite3

本篇文章带大家深入地讲解Vite中的依赖扫描的实现细节,最终的扫描结果是一个包含多个模块的名字的对象,不涉及预构建的过程、预构建产物如何是使用的。

Vite学习之深度解析“依赖扫描”

当我们首次运行 Vite 的时候,Vite 会执行依赖预构建,目的是为了兼容 CommonJS 和 UMD,以及提升性能。【相关推荐:vuejs视频教程

要对依赖进行预构建,首先要搞清楚这两个问题:

  • 预构建的内容是什么?/ 哪些模块需要进行预构建?

  • 如何找到需要预构建的模块?

这两个问题,其实就是依赖扫描的内容以及实现方式

本文会深入地讲解依赖扫描的实现细节,最终的扫描结果是一个包含多个模块的名字的对象,不涉及预构建的过程、预构建产物如何是使用的。如果对该部分内容感兴趣,可以关注我,等待后续文章。

依赖预构建的内容

一个项目中,存在非常多的模块,并不是所有模块都会被预构建。只有 bare import(裸依赖)会执行依赖预构建

什么是 bare import ?

直接看下面这个例子

// vue 是 bare import
import xxx from "vue"
import xxx from "vue/xxx"

// 以下不是裸依赖
import xxx from "./foo.ts" 
import xxx from "/foo.ts"
登录后复制

可以简单的划分一下:

  • 用名称去访问的模块是裸模块
  • 用路径去访问的模块,不是 bare import

实际上 Vite 也是这么判断的。

下面是一个常见的 Vue 项目的模块依赖树

1.png

依赖扫描的结果如下:

[ "vue", "axios" ]
登录后复制

为什么只对 bare import 进行预构建?

Node.js 定义了 bare import 的寻址机制 —— 在当前目录下的 node_modules 下寻找,找不到则往上一级目录的 node_modules,直到目录为根路径,不能再往上。

bare import 一般是 npm 安装的模块,是第三方的模块,不是我们自己写的代码,一般情况下是不会被修改的,因此对这部分的模块提前执行构建,有利于提升性能。

相反,如果对开发者写的代码执行预构建,将项目打包成 chunk 文件,当开发者修改代码时,就需要重新执行构建,再打包成 chunk 文件,这个过程反而会影响性能。

monorepo 下的模块也会被预构建吗?

不会。因为 monorepo 的情况下,部分模块虽然是 bare import,但这些模块也是开发者自己写的,不是第三方模块,因此 Vite 没有对该部分的模块执行预构建。

实际上,Vite 会判断模块的实际路径,是否在 node_modules 中

  • 实际路径在 node_modules 的模块会被预构建,这是第三方模块
  • 实际路径不在 node_modules 的模块,证明该模块是通过文件链接,链接到 node_modules 内的(monorepo 的实现方式),是开发者自己写的代码,不执行预构建

依赖扫描

实现思路

我们再来看看这棵模块依赖树:

2.png

要扫描出所有的 bare import,就需要遍历整个依赖树,这就涉及到了树的深度遍历

当我们在讨论树的遍历时,一般会关注这两点:

  • 什么时候停止深入?
  • 如何处理叶子节点?

3.png

当前叶子节点不需要继续深入遍历的情况:

  • 当遇到 bare import 节点时,记录下该依赖,就不需要继续深入遍历
  • 遇到其他 JS 无关的模块,如 CSS、SVG 等,因为不是 JS 代码,因此也不需要继续深入遍历

当所有的叶子节点遍历完成后,记录的 bare import 对象,就是依赖扫描的结果

依赖扫描的实现思路其实非常容易理解,但实际的处理就不简单了。

我们来看看叶子节点的处理:

  • bare import

        可以通过模块 id 判断,模块 id 不为路径的模块,就是 bare import。遇到这些模块则记录依赖,不再深入遍历

  • 其他 JS 无关的模块

        可以通过模块的后缀名判断,例如遇到 *.css 的模块,无需任何处理,不再深入遍历

  • JS 模块

        要获取 JS 代码中依赖的子模块,就需要将代码转成 AST,获取其中 import 语句引入的模块,或者正则匹配出所有 import 的模块,然后继续深入遍历这些模块

  • HTML 类型模块

        这类模块比较复杂,例如 HTML 或 Vue,里面有一部分是 JS,需要把这部分 JS 代码提取出来,然后按 JS 模块进行分析处理,继续深入遍历这些模块。这里只需要关心 JS 部分,其他部分不会引入模块。

4.png

具体实现

我们已经知道了依赖扫描的实现思路,思路其实不复杂,复杂的是处理过程,尤其是 HTML、Vue 等模块的处理。

Vite 这里用了一种比较巧妙的办法 —— 用 esbuild 工具打包

为什么可以用 esbuild 打包代替深度遍历的过程?

本质上打包过程也是个深度遍历模块的过程,其替代的方式如下:

深度遍历 esbuild 打包
叶子节点的处理 esbuild 可以对每个模块(叶子节点)进行解析和加载
可以通过插件对这两个过程进行扩展,加入一些特殊的逻辑
例如将 html 在加载过程中转换为 js
不深入处理模块 esbuild 可以在解析过程,指定当前解析的模块为 external
则 esbuild 不再深入解析和加载该模块
深入遍历模块 正常解析模块(什么都不做,esbuild 默认行为),返回模块的文件真实路径

这块暂时看不懂没有关系,后面会有例子

5.png

各类模块的处理


例子 处理
bare import vue 在解析过程中,将裸依赖保存到 deps 对象中,设置为 external
其他 JS 无关的模块 less文件 在解析过程中,设置为 external
JS 模块 ./mian.ts 正常解析和加载即可,esbuild 本身能处理 JS
html 类型模块 index.htmlapp.vue 在加载过程中,将这些模块加载成 JS

最后 dep 对象中收集到的依赖就是依赖扫描的结果,而这次 esbuild 的打包产物,其实是没有任何作用的,在依赖扫描过程中,我们只关心每个模块的处理过程,不关心构建产物

用 Rollup 处理可以吗?

其实也可以,打包工具基本上都会有解析和加载的流程,也能对模块进行 external

但是 esbuild 性能更好

html 类型的模块处理

这类文件有 htmlvue 等。之前我们提到了要将它们转换成 JS,那么到底要如何转换呢?

6.png

由于依赖扫描过程,只关注引入的 JS 模块,因此可以直接丢弃掉其他不需要的内容,直接取其中 JS

html 类型文件(包括 vue)的转换,有两种情况:

  • 每个外部 script,会直接转换为 import 语句,引入外部 script
  • 每个内联 script,其内容将会作为虚拟模块被引入。

什么是虚拟模块?

是模块的内容并非直接从磁盘中读取,而是编译时生成

举个例子,src/main.ts 是磁盘中实际存在的文件,而 virtual-module:D:/project/index.html?id=0 在磁盘中是不存在的,需要借助打包工具(如 esbuild),在编译过程生成。

为什么需要虚拟模块?

因为一个 html 类型文件中,允许有多个 script 标签,多个内联的 script 标签,其内容无法处理成一个 JS 文件 (因为可能会有命名冲突等原因)

既然无法将多个内联 script,就只能将它们分散成多个虚拟模块,然后分别引入了。

源码解析

依赖扫描的入口

下面是扫描依赖的入口函数(为了便于理解,有删减和修改):

import { build } from 'esbuild'
export async function scanImports(config: ResolvedConfig): Promise<{
  deps: Record<string, string>
  missing: Record<string, string>
}> {
    
  // 将项目中所有的 html 文件作为入口,会排除 node_modules
  let entries: string[] = await globEntries('**/*.html', config)

  // 扫描到的依赖,会放到该对象
  const deps: Record<string, string> = {}
  // 缺少的依赖,用于错误提示
  const missing: Record<string, string> = {}
  
  // esbuild 扫描插件,这个是重点!!!
  const plugin = esbuildScanPlugin(config, container, deps, missing, entries)

  // 获取用户配置的 esbuild 自定义配置,没有配置就是空的
  const { plugins = [], ...esbuildOptions } =
    config.optimizeDeps?.esbuildOptions ?? {}

  await Promise.all(
    // 入口可能不止一个,分别用 esbuid 打包
    entries.map((entry) =>
      // esbuild 打包
      build({
        absWorkingDir: process.cwd(),
        write: false,
        entryPoints: [entry],
        bundle: true,
        format: 'esm',
        // 使用插件
        plugins: [...plugins, plugin],
        ...esbuildOptions
      })
    )
  )

  return {
    deps,
    missing
  }
}
登录后复制

主要流程如下:

  • 将项目内所有的 html 作为入口文件(排除 node_modules)。

  • 将每个入口文件,用 esbuild 进行打包

这里的核心其实是 esbuildScanPlugin 插件的实现,它定义了各类模块(叶子节点)的处理方式。

function esbuildScanPlugin(config, container, deps, missing, entries){}
登录后复制
  • depmissing对象被当做入参传入,在函数中,这两个对象的内容会在打包(插件运行)过程中被修改

esbuild 插件

很多同学可能不知道 esbuild 插件是如何编写的,这里简单介绍一下:

每个模块都会经过解析(resolve)和加载(load)的过程:

  • 解析:将模块路径,解析成文件真实的路径。例如 vue,会解析到实际 node_modules 中的 vue 的入口 js 文件
  • 加载:根据解析的路径,读取文件的内容

7.png

插件可以定制化解析和加载的过程,下面是一些插件示例代码:

const plugin = {
    name: 'xxx',
    setup(build) {
        
        // 定制解析过程,所有的 http/https 的模块,都会被 external
        build.onResolve({ filter: /^(https?:)?\/\// }, ({ path }) => ({
            path,
            external: true
        }))
        
        // 定制解析过程,给所有 less 文件 namespace: less 标记
        build.onResolve({ filter: /.*\.less/ }, args => ({
            path: args.path,
            namespace: 'less',
        }))

        // 定义加载过程:只处理 namespace 为 less 的模块
        build.onLoad({ filter: /.*/, namespace: 'less' }, () => {
            const raw = fs.readFileSync(path, 'utf-8')
            const content = // 省略 less 处理,将 less 处理成 css
            return {
                contents,
                loader: 'css'
        	}
        })
    }
}
登录后复制
  • 通过 onResolveonLoad 定义解析和加载过程
  • onResolve 的第一个参数为过滤条件,第二个参数为回调函数,解析时调用,返回值可以给模块做标记,如 externalnamespace(用于过滤),还需要返回模块的路径
  • 每个模块, onResolve 会被依次调用,直到回调函数返回有效的值,后面的不再调用。如果都没有有效返回,则使用默认的解析方式
  • onLoad  的第一个参数为过滤条件,第二个参数为回调函数,加载时调用,可以读取文件的内容,然后进行处理,最后返回加载的内容
  • 每个模块,onLoad 会被依次调用,直到回调函数返回有效的值,后面的不再调用。如果都没有有效返回,则使用默认的加载方式。

扫描插件的实现

function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin
登录后复制

部分参数解析:

  • config:Vite 的解析好的用户配置

  • container:这里只会用到 container.resolveId 的方法,这个方法能将模块路径转成真实路径

    例如 vue 转成 xxx/node_modules/dist/vue.esm-bundler.js

  • depImports:用于存储扫描到的依赖对象,插件执行过程中会被修改

  • missing:用于存储缺少的依赖的对象,插件执行过程中会被修改

  • entries:存储所有入口文件的数组

esbuild 默认能将模块路径转成真实路径,为什么还要用 container.resolveId

因为 Vite/Rollup 的插件,也能扩展解析的流程,例如 alias 的能力,我们常常会在项目中用 @ 的别名代表项目的 src 路径。

因此不能用 esbuild 原生的解析流程进行解析。

container(插件容器)用于兼容 Rollup 插件生态,用于保证 dev 和 production 模式下,Vite 能有一致的表现。感兴趣的可查看《Vite 是如何兼容 Rollup 插件生态的》

这里  container.resolveId  会被再次包装一成 resolve 函数(多了缓存能力)

const seen = new Map<string, string | undefined>()
const resolve = async (
    id: string,
    importer?: string,
    options?: ResolveIdOptions
) => {
    const key = id + (importer && path.dirname(importer))
    
    // 如果有缓存,就直接使用缓存
    if (seen.has(key)) {
        return seen.get(key)
    }
    // 将模块路径转成真实路径
    const resolved = await container.resolveId(
        id,
        importer && normalizePath(importer),
        {
            ...options,
            scan: true
        }
    )
    // 缓存解析过的路径,之后可以直接获取
    const res = resolved?.id
    seen.set(key, res)
    return res
  }
登录后复制

那么接下来就是插件的实现了,先回顾一下之前写的各类模块的处理:


例子 处理
bare import vue 在解析过程中,将裸依赖保存到 deps 对象中,设置为 external
其他 JS 无关的模块 less文件 在解析过程中,设置为 external
JS 模块 ./mian.ts 正常解析和加载即可,esbuild 本身能处理 JS
html 类型模块 index.htmlapp.vue 在加载过程中,将这些模块加载成 JS

JS 模块

esbuild 本身就能处理 JS 语法,因此 JS 是不需要任何处理的,esbuild 能够分析出 JS 文件中的依赖,并进一步深入处理这些依赖。

其他 JS 无关的模块

// external urls
build.onResolve({ filter: /^(https?:)?\/\// }, ({ path }) => ({
    path,
    external: true
}))

// external css 等文件
build.onResolve(
    {
        filter: /\.(css|less|sass|scss|styl|stylus|pcss|postcss|json|wasm)$/
    },
    ({ path }) => ({
        path,
        external: true
    }
)
    
// 省略其他 JS 无关的模块
登录后复制

这部分处理非常简单,直接匹配,然后 external 就行了

bare import

build.onResolve(
  {
    // 第一个字符串为字母或 @,且第二个字符串不是 : 冒号。如 vite、@vite/plugin-vue
    // 目的是:避免匹配 window 路径,如 D:/xxx 
    filter: /^[\w@][^:]/
  },
  async ({ path: id, importer, pluginData }) => {
    // depImports 为
    if (depImports[id]) {
      return externalUnlessEntry({ path: id })
    }
    // 将模块路径转换成真实路径,实际上调用 container.resolveId
    const resolved = await resolve(id, importer, {
      custom: {
        depScan: { loader: pluginData?.htmlType?.loader }
      }
    })
    
    // 如果解析到路径,证明找得到依赖
    // 如果解析不到路径,则证明找不到依赖,要记录下来后面报错
    if (resolved) {
      if (shouldExternalizeDep(resolved, id)) {
        return externalUnlessEntry({ path: id })
      }
      // 如果模块在 node_modules 中,则记录 bare import
      if (resolved.includes('node_modules')) {
        // 记录 bare import
        depImports[id] = resolved

        return {
        	path,
        	external: true
   		}
      } 
      // isScannable 判断该文件是否可以扫描,可扫描的文件有 JS、html、vue 等
      // 因为有可能裸依赖的入口是 css 等非 JS 模块的文件
      else if (isScannable(resolved)) {
        // 真实路径不在 node_modules 中,则证明是 monorepo,实际上代码还是在用户的目录中
        // 是用户自己写的代码,不应该 external
        return {
          path: path.resolve(resolved)
        }
      } else {
        // 其他模块不可扫描,直接忽略,external
        return {
            path,
            external: true
        }
      }
    } else {
      // 解析不到依赖,则记录缺少的依赖
      missing[id] = normalizePath(importer)
    }
  }
)
登录后复制
  • 如果文件在 node_modules 中,才认为是 bare import,记录当前模块
  • 文件不在 node_modules 中,则是 monorepo,是用户自己写的代码
    • 如果这些代码 isScanable 可扫描(即含有 JS 代码),则继续深入处理
    • 其他非 JS 模块,external

html 类型模块

如: index.htmlapp.vue

const htmlTypesRE = /\.(html|vue|svelte|astro)$/

// html types: 提取 script 标签
build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => {
    // 将模块路径,转成文件的真实路径
    const resolved = await resolve(path, importer)
    if (!resolved) return
    
    // 不处理 node_modules 内的
    if (resolved.includes('node_modules'){
        return
   }

    return {
        path: resolved,
        // 标记 namespace 为 html 
        namespace: 'html'
    }
})
登录后复制

解析过程很简单,只是用于过滤掉一些不需要的模块,并且标记 namespace 为 html

真正的处理在加载阶段:

8.png

// 正则,匹配例子: <script type=module></script>
const scriptModuleRE = /(<script\b[^>]*type\s*=\s*(?:"module"|'module')[^>]*>)(.*?)<\/script>/gims
// 正则,匹配例子: <script></script>
export const scriptRE = /(<script\b(?:\s[^>]*>|>))(.*?)<\/script>/gims

build.onLoad(
    { filter: htmlTypesRE, namespace: 'html' },
    async ({ path }) => {
        // 读取源码
        let raw = fs.readFileSync(path, 'utf-8')
        // 去掉注释,避免后面匹配到注释
        raw = raw.replace(commentRE, '<!---->')

        const isHtml = path.endsWith('.html')
        // scriptModuleRE: <script type=module></script>
        // scriptRE: <script></script>
        // html 模块,需要匹配 module 类型的 script,因为只有 module 类型的 script 才能使用 import
        const regex = isHtml ? scriptModuleRE : scriptRE

        // 重置正则表达式的索引位置,因为同一个正则表达式对象,每次匹配后,lastIndex 都会改变
        // regex 会被重复使用,每次都需要重置为 0,代表从第 0 个字符开始正则匹配
        regex.lastIndex = 0
        // load 钩子返回值,表示加载后的 js 代码
        let js = ''
        let scriptId = 0
        let match: RegExpExecArray | null

        // 匹配源码的 script 标签,用 while 循环,因为 html 可能有多个 script 标签
        while ((match = regex.exec(raw))) {
            // openTag: 它的值的例子: <script type="module" src="xxx">
            // content: script 标签的内容
            const [, openTag, content] = match
            
            // 正则匹配出 openTag 中的 type 和 lang 属性
            const typeMatch = openTag.match(typeRE)
            const type =
                  typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3])
            const langMatch = openTag.match(langRE)
            const lang =
                  langMatch && (langMatch[1] || langMatch[2] || langMatch[3])
            
            // 跳过 type="application/ld+json" 和其他非 non-JS 类型
            if (
                type &&
                !(
                    type.includes('javascript') ||
                    type.includes('ecmascript') ||
                    type === 'module'
                )
            ) {
                continue
            }
            
            // esbuild load 钩子可以设置 应的 loader
            let loader: Loader = 'js'
            if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') {
                loader = lang
            } else if (path.endsWith('.astro')) {
                loader = 'ts'
            }
            
            // 正则匹配出 script src 属性
            const srcMatch = openTag.match(srcRE)
            // 有 src 属性,证明是外部 script
            if (srcMatch) {
                
                const src = srcMatch[1] || srcMatch[2] || srcMatch[3]
                // 外部 script,改为用 import 用引入外部 script
                js += `import ${JSON.stringify(src)}\n`
            } else if (content.trim()) {
                // 内联的 script,它的内容要做成虚拟模块

                // 缓存虚拟模块的内容
                // 一个 html 可能有多个 script,用 scriptId 区分
                const key = `${path}?id=${scriptId++}`
                scripts[key] = {
                    loader,
                    content,
                    pluginData: {
                        htmlType: { loader }
                    }
                }

                // 虚拟模块的路径,如 virtual-module:D:/project/index.html?id=0
                const virtualModulePath = virtualModulePrefix + key
                js += `export * from ${virtualModulePath}\n`
            }
        }

        return {
            loader: 'js',
            contents: js
        }
    }
)
登录后复制

加载阶段的主要做有以下流程:

  • 读取文件源码
  • 正则匹配出所有的 script 标签,并对每个 script 标签的内容进行处理
    • 外部 script,改为用 import 引入
    • 内联 script,改为引入虚拟模块,并将对应的虚拟模块的内容缓存到 script 对象。
  • 最后返回转换后的 js

srcMatch[1] || srcMatch[2] || srcMatch[3] 是干嘛?

我们来看看匹配的表达式:

const srcRE = /\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im
登录后复制

因为 src 可以有以下三种写法:

  • src="xxx"
  • src='xxx'
  • src=xxx

三种情况会出现其中一种,因此是三个捕获组

虚拟模块是如何加载成对应的 script 代码的?

export const virtualModuleRE = /^virtual-module:.*/

// 匹配所有的虚拟模块,namespace 标记为 script
build.onResolve({ filter: virtualModuleRE }, ({ path }) => {
  return {
    // 去掉 prefix
    // virtual-module:D:/project/index.html?id=0 => D:/project/index.html?id=0
    path: path.replace(virtualModulePrefix, ''),
    namespace: 'script'
  }
})

// 之前的内联 script 内容,保存到 script 对象,加载虚拟模块的时候取出来
build.onLoad({ filter: /.*/, namespace: 'script' }, ({ path }) => {
  return scripts[path]
})
登录后复制

虚拟模块的加载很简单,直接从 script 对象中,读取之前缓存起来的内容即可。

这样之后,我们就可以把 html 类型的模块,转换成 JS 了

扫描结果

下面是一个  depImport 对象的例子:

{
  "vue": "D:/app/vite/node_modules/.pnpm/vue@3.2.37/node_modules/vue/dist/vue.runtime.esm-bundler.js",
  "vue/dist/vue.d.ts": "D:/app/vite/node_modules/.pnpm/vue@3.2.37/node_modules/vue/dist/vue.d.ts",
  "lodash-es": "D:/app/vite/node_modules/.pnpm/lodash-es@4.17.21/node_modules/lodash-es/lodash.js"
}
登录后复制
  • key:模块名称
  • value:模块的真实路径

总结

依赖扫描是预构建前的一个非常重要的步骤,这决定了 Vite 需要对哪些依赖进行预构建。

本文介绍了 Vite 会对哪些内容进行依赖预构建,然后分析了实现依赖扫描的基本思路 —— 深度遍历依赖树,并对各种类型的模块进行处理。然后介绍了 Vite 如何巧妙的使用 esbuild 实现这一过程。最后对这部分的源码进行了解析:

  • 最复杂的就是 html 类型模块的处理,需要使用虚拟模块
  • 当遇到 bare import 时,需要判断是否在 node_modules 中,在的才记录依赖,然后  external。
  • 其他 JS 无关的模块就直接 external
  • JS 模块由于 esbuild 本身能处理,不需要做任何的特殊操作

最后获取到的 depImport 是一个记录依赖以及其真实路径的对象

(学习视频分享:web前端开发编程基础视频

以上是Vite学习之深度解析“依赖扫描”的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

vue中怎么用bootstrap vue中怎么用bootstrap Apr 07, 2025 pm 11:33 PM

在 Vue.js 中使用 Bootstrap 分为五个步骤:安装 Bootstrap。在 main.js 中导入 Bootstrap。直接在模板中使用 Bootstrap 组件。可选:自定义样式。可选:使用插件。

vue怎么给按钮添加函数 vue怎么给按钮添加函数 Apr 08, 2025 am 08:51 AM

可以通过以下步骤为 Vue 按钮添加函数:将 HTML 模板中的按钮绑定到一个方法。在 Vue 实例中定义该方法并编写函数逻辑。

vue中的watch怎么用 vue中的watch怎么用 Apr 07, 2025 pm 11:36 PM

Vue.js 中的 watch 选项允许开发者监听特定数据的变化。当数据发生变化时,watch 会触发一个回调函数,用于执行更新视图或其他任务。其配置选项包括 immediate,用于指定是否立即执行回调,以及 deep,用于指定是否递归监听对象或数组的更改。

vue多页面开发是啥意思 vue多页面开发是啥意思 Apr 07, 2025 pm 11:57 PM

Vue 多页面开发是一种使用 Vue.js 框架构建应用程序的方法,其中应用程序被划分为独立的页面:代码维护性:将应用程序拆分为多个页面可以使代码更易于管理和维护。模块化:每个页面都可以作为独立的模块,便于重用和替换。路由简单:页面之间的导航可以通过简单的路由配置来管理。SEO 优化:每个页面都有自己的 URL,这有助于搜索引擎优化。

vue.js怎么引用js文件 vue.js怎么引用js文件 Apr 07, 2025 pm 11:27 PM

在 Vue.js 中引用 JS 文件的方法有三种:直接使用 &lt;script&gt; 标签指定路径;利用 mounted() 生命周期钩子动态导入;通过 Vuex 状态管理库进行导入。

vue返回上一页的方法 vue返回上一页的方法 Apr 07, 2025 pm 11:30 PM

Vue.js 返回上一页有四种方法:$router.go(-1)$router.back()使用 &lt;router-link to=&quot;/&quot;&gt; 组件window.history.back(),方法选择取决于场景。

vue遍历怎么用 vue遍历怎么用 Apr 07, 2025 pm 11:48 PM

Vue.js 遍历数组和对象有三种常见方法:v-for 指令用于遍历每个元素并渲染模板;v-bind 指令可与 v-for 一起使用,为每个元素动态设置属性值;.map 方法可将数组元素转换为新数组。

vue的div怎么跳转 vue的div怎么跳转 Apr 08, 2025 am 09:18 AM

Vue 中 div 元素跳转的方法有两种:使用 Vue Router,添加 router-link 组件。添加 @click 事件监听器,调用 this.$router.push() 方法跳转。

See all articles