Vue3

发布于 2025-04-02  64 次阅读


一种渐进式的JS框架,所谓渐进式,就是只提供了最基础和最核心的代码,后续我们需要什么可以再子行扩展。

官网:https://cn.vuejs.org/

Vue环境搭建

1.脚手架搭建(用命令搭建一个空的vue项目)

2.在html中引入vue.js(也就是CDN方式)

差值表达式

{{js表达式}}这样的式子就叫差值表达式(mustahce)

含有插值表达式的html叫模板(template)

最基础的vue代码模板

<script src="path/vue.global.js"></script>
<body>
  <div class="box">
    {{str}}
  </div>
</body>
let vm = Vue.createApp({

  data(){

     return {

         str: 'Hello World!'

     }

  }

}).mount('.box')

其中数据放到data中,并return,会返回一个Proxy(代理)对象,所谓代理,也就是对data中的数据进行了监听,能够实时知道对数据的操作,也就是成了一种响应式数据。

mount('.box')表示把创建的vm实例挂载到页面上对应选择器为.box的结点上

MVVM

M:model 模型---数据

V:view 视图---渲染数据

VM:viewmodel 视图模型---数据驱动

Vue的两大核心是数据驱动和组件系统

指令(directive)

组件(component)

路由(router)

vuex

第三方库

Vue常用的8个指令

v-text:语法----- <p v-text="str"></p>

作用:和{{插值表达式一样}}

v-html: 语法----- <p v-html="str"></p>

与v-text的区别在于v-html可以解析文本中的html标签

v-bind:语法----- <p v-bind:attribute="str"></p>

作用:用于把变量绑定到指定的属性上,属于单项绑定,数据变化,视图就会变化,视图变化,数据不会变化

该指令可以简写为 :attribute="str"

v-model:语法----- <input type="text" v-model="str"/>

作用:双向绑定,视图变化,数据也会变化,不需要指定特定属性,会自动找寻会变化的属性。

该指令有三个修饰符:

lazy:惰性,视图变化时,数据不会立马跟着变化,只有当失去焦点时数据才会变化

number:把输入的数据转化为数字

trim:去除输入字符串两边的空白

v-on:语法----- <button v-on:click="clickHandler($event, ...arg)"></button>

作用:给对应的标签添加事件,其中$event表示事件对象,...arg表示传递的参数,对应的事件处理函数放在methods里面。

methods: { clickHandler(e, ...arg){} }//methods是放在data的外面的。

该指令可以简写为@click="clickHandler($event, ...arg)"

v-if和v-show:语法——<p v-if="BlooeanVar"></p>,<p v-show="BlooeanVar"></p>

两个指令都是控制某一块视图是否可见。当BlooeanVar的值为true时,才会渲染p标签的内容

v-if是惰性指令,条件渲染(条件为true时会创建p标签,为false时会删除p标签)

v-show只是切换p标签的css样式(条件为true时为display="block",为false时为display)

v-for:语法——<p v-for="item in list" :key="item.id"></p>

其中key可以提高循环的执行效率

绑定样式

绑定style:

语法——<p v-bind:style="{属性:属性值}"></p>或者<p v-bind:style="[styleObj1, styleObj2]"></p>

绑定css:

语法——<p v-bind:class="{class1: isClass1}"></p>或者<p :class="[{class1: isClass1}, {class2: isClass2}]"></p>

v-on修饰符

stop: 用法——v-on:事件.stop,阻止冒泡行为

prevent: 用法——@事件.prevent,阻止默认行为

enter: 用法——@keyup.enter或者@keydown.enter,按下回车事件

self:用法——@事件.self,当前元素只触发它自己绑定的事件

capture:用法——@事件.capture,捕获事件

once:用法——@事件.once,这事件只触发一次

v-for遍历字符串,对象,数字

遍历数字:<p v-for="n in num">{{n}}</p>,遍历数字是从1开始遍历到num

遍历字符串:<p v-for="char in str">{{char}}</p>,从左往右依次遍历字符串中的每一个字符

遍历对象:<p v-for="(value, key, index) in obj"></p>依次遍历对象的每一个键值对,注意顺序(值,键,下标(从0开始))

计算属性:

computed: 用法——

computed: {

value(){

return 表达式(计算式)

}

}

作用:将表达式或者计算式的结果返回给value变量,value变量的值会跟随表达式的变化自动变化。

computed的高级用法:

computed: {

value: {

get(){

return 表达式

},

set(v){

这里可给data中的变量进行赋值操作,比如this.str='23'

}

}

},

案例:基于computed实现全选和全不选功能

computed: {

get(){

return this.arr.every(item => item.flag)//这里flag用于绑定渲染列表中的checkbox

},

set(v){

this.arr.forEach(item => item.flag = v)//这里的v接收的是全选的checkbox的checked值

}

}

组件(component)

组件就是指可以复用的页面的一部分(本身就是对DOM元素的一种封装)

组件分为全局组件和局部组件

全局组件定义实例代码:

let app = Vue.createApp({

data(){

return {}

}

}).mount('.box')

//app.component('全局组件名',全局组件的内容(obj)),

app.component('MyComponent', {

template: '<p>123</p>'

})

//接下来只需要在要需要使用组件的地方,插入组件即可,但是插入组件的时候需要注意,在html中插入标签时,需要用短横线连接两个单词,并且除第一个单词以外的单词需要小写

局部组件示例

const ChildComponent={

template=`<p>child</p>`,

data(){

return{}

},

methods: {}

}

在实例中注册子组件

Vue.create({

components: {

ChildComponent

},

data(){

return{}

}

})

//从语法定义上来说,全局组件不需要注册就可以使用,而局部组件需要先注册才能使用

//全局组件可以在整个实例(应用)的任何位置(包括其子组件)使用,而局部组件只能在这个实例中使用,不能在其它组件中使用。

//组件和一个实例一样,也可以有自己的data,methods等等属性

组件通讯

父组件给子组件传值,示例:

<Child :data="parentData"></Child>

在子组件内部,还需要用props接收一下父组件传递的值

props: ['data'],需要和自定义属性名一样,

也可以用对象来接收

props: {

data: {

type: Number, //指定了接收的数据的类型

default: 213, //如果没有传递值时,使用的默认值

validator(value){

return value满足的条件 //规定了传递的值必须满足的约束条件

},

required: true //是否是必须传递值(这个属性通常不予default连用)

}

}

子组件向父组件传值(通过this.$emit('evt', value))

示例:<Child @click="sendData"></Child>

sendData(){

this.$emit('evt', '123')

},

父组件通过子组件的自定义事件接收子组件传递的值

<Child @evt="receive($event)" @click="sendData"></Child>

在父组件的methods中,通过evt方法接收子组件传递的值

receive(value){

console.log(value)

}

//说明,自定义事件的名字是自己取的,只要保持一致即可,同理接收子组件传值的方法名也是自己取的,前后保持一致即可。组件之间的传值,通过$event进行传递

watch: 监听数据变化——语法,写在methods外面:

watch: {

data1: (newValue, oldValue){

//当监听的组件里的data1有变化时,就会执行下面的代码

console.log(newValue,oldValue)

//如果data1是一个对象类型数据(引用类型),并且改变的是该对象内的内容时

//需要特别声明deep: true,表示深度监听,并且通过hanlder监听对象数据变化

//示例

hanlder(newV){

console.log(newV)

}

deep: true,

immediate: true//表示运行就开始监听,(即运行就执行一次代码块)

//如果是直接对整个对象进行赋值,也就是改变了对象的地址,那么就和普通数据监听一样即可

}

}

computed与watch的区别:

computed可以缓存计算过的数据,而watch则不能

watch可以监听异步数据,但是computed不能

Vue中的8个钩子函数

beforecreate(){console.log('beforeCreate')}//即拿不到数据,也拿不到结点

//特别说明,在beforeCreate中,可以通过this.$nextTick(()=>{console.log(str)}),来拿到数据,nextTick表示在下论循环执行,在这里也就是整个DOM树构建好之后才执行。所以是可以拿到数据的。

created(){console.log('created')}//拿得到数据,但是拿不到结点

beforeMount(){consolo.log('beforeMount')}//可以拿到数据,但是拿不到结点

mounted(){console.log('mounted'), console.log(this.$refs.box)}//即可以拿到数据,又可以拿到结点

//特别说明,在Vue3中,可以通过在组件中绑定ref来获取结点,示例:

<My-component ref="box"></My-component>

在父组件中就可以通过this.$refs.box获取MyComponent组件结点

beforeUpdate(){console.log('beforeUpdate')}//数据更新之后,视频更新之前执行

updated(){console.log('updated')}//数据更新之后,视图更新之后执行

beforeUnmount(){console.log('beforeUnmount')}//组件卸载之前执行

unmounted(){console.log('unmounted')}组件卸载之后执行

动态组件

在Vue中有一个标签可以支持动态的替换组件,示例代码如下:

<div class="box">

<component :is="cname"></component>

<button @click="cname='One'">组件1</button>

<button @click="cname='Two'">组件2</button>

</box>

<script>

const One = {template: ''}

const Two = {template: ''}

let app = Vue.CreateAPP({

components: {One, Two},

data(){return {cname: 'One'}}

})

app.mount('.box')

</script>

特别说明:组件的切换是组件的挂载与卸载,所以组件之间的切换并不会保留之前组件的数据,但是可以通过把component放到keep-alive标签中保留之前组件的数据(即组件切换不再是卸载)。

插槽

插槽是组件预留的位置,可以在不改变现有组件的代码的前提下,进行扩展组件,Vue中的插槽有3种,分别是具名插槽,匿名插槽和作用域插槽。

匿名插槽:

const Com = {template: '<div></div><slot></slot>'}

<Com>234</Com>//此时234就会放在slot内,也就是放在组件的div后面

const Com = {template: '<slot></slot><div></div><slot></slot>'}

<Com><p>123</p><p>456</p></Com>//此时的效果是在div的两侧都会插入123,456。并不是左边123,右边456.

具名插槽:

const Com = {template: '<slot name="s1"></slot><div></div><slot name="s2"></slot>'}

<Com><p v-slot:s1>123</p><p v-slot:s2>456</p></Com>//此时的效果就是在div的左侧是123,右侧是456.

v-slot可以简写为#

作用域插槽:

const Com = {template: '<div></div><slot name="s1" :data="str"></slot>'}

<Com><p v-slot:s1="data"></p></Com>

此时插槽的str会通过以对象的形式传递给data

搭建Vue的脚手架环境流程

原来创建一个App实例的代码如下:

const App = Vue.create({

data(){

retutn {str: 'hello'}

}

}).mount('.box')

//在此基础上,引入ES module(ES6的模块化)

可以看到括号里的是一个对象,也就说我们可以把这个对象拿出来。即

const app = {data(){return {str: 'hello'}}}

在通过Vue的create方法创建一个示例。即

const App = Vue.create(app),App.mount('.box')。此时的我们只是做了一个类似变量替换,整体效果都是一样的。

到这里其实可以发现,根据ES module,我们可以把跟组件也就是app这个对象放到一个模块中去声明并导出,然后在需要的地方导入这个对象并通过create方法创建根组件即可。也就是可以写成下面的代码方式。

import App = from './path/App.js'

Vue.create(App).mount('.box'),其中在我们的App.js中的代码如下:

const App = {data(){return {str: 'hello'}}}

对于非根组件,写法都是一样,只不过我们需要在其父组件中进行注册一下。

到这里手动搭建脚手架就可以告一段落。下面介绍通过其它工具搭建脚手架。

Vite

Vite是一款前端构建工具。

首先需要先在项目中下载Vite,新建一个目录。然后在此目录下打开cmd,输入npm init -y,会生成一个package.json文件,路径不要包含中文!!!

创建好json文件后,即输入指令:npm i @vitejs/plugin-vue,只有安装了此插件,才能正常打开vue项目

在项目根目录下创建一个vite.config.js文件,内容如下:

import {defineConfig} from 'vite'

import vue from '@vitejs/plugin-vue'

export default defineConfig({

    plugins:[vue()],

})

在此目录下创建一个index.html,内容如下:

<body>

    <div id="box"></div>

    <script src="/js/index/js" type="module"></script>

</body>

通过指令搭建脚手架:

npm init vue@latest

cd 项目名

npm i

npm run dev

sass安装指令

npm i sass-embedded

代码格式化工具

Eslint:用于检查项目中的代码是否存在格式问题。

pritter:用于自动格式化代码

setup的使用:

组合式API

import {ref} from 'vue'

setup(){

console.log('setup', this)

const k = ref(0)//只有通过ref进行声明的数据才具有响应式。相当于data里返回的数据。

const obj = reactive({name: 'zs'})//reactive是针对应用类型的数据,访问通过reactive声明的变量时,可以不用加value。

const inc = () => {k.value++}//此时的inc相当于methods里面的方法,并且通过ref声明的变量的值位 于value中,所有需要通过.value来获取。

return {

k, inc//无论是定义的变量还是方法,都需要return才能模板中使用

}

}

ref可以声明引用类型和基本类型的数据,而reactive只能声明引用类型的数据。并且ref声明的数据需要通过value来获取,而reactive声明的数据直接获取即可。

ref的实现原理是vue2中的Object.defineProperty(代理对象的属性),reactive的实现原理是vue3中的proxy(代理对象)

对于这种组合式api的使用,当在setup里面写方法和数据的,方法和数据都需要return。

toRef和toRefs

toRef和toRefs可以用来复制reactive里面的属性并将其转化为响应式的数据,同时保留了引用,也就是当视图发生改变时,reactive的属性值也会发生变化。两者的区别在于toRef只是将单个属性变成响应式的,而toRefs是将所有的属性都变成响应式的。语法如下:

const {x} = toRef(position, 'x'),这里将position对象里面的x属性转化成响应式。使用的方式和正常的ref一样,需要加.value

const {x, y} = toRefs(position),这里将position对象里的所有属性都变成响应式的。

注意:在template中使用toRef时,不需要加.value,而使用toRefs时需要加.value,否则会出现引号。

vue3中父子组件的传值:

父传子:父组件通过自定义属性向子组件传值,子组件通过defineProps(['父组件的自定义属性名'])来接收。

子传父:子组件通过const emit = defineEmits(['自定义事件']),然后emit('自定义事件',数据),向父组件传值,父组件通过子组件自定义的事件进行接收。

父组件获取子组件的属性和方法:子组件通过defineExpose({要暴露的属性和方法}),父组件通过在子组件上绑定ref属性,再通过const ref属性对应的值(组件的标识)=ref(),或者const 变量名 = useTemplateRef('ref标识')来获取子组件以及子组件暴露的属性和方法。

hooks:(vue3中的自定义函数)

hooks就是一种自定义函数,只不过这种自定义函数可以使用vue3中生命周期的各个钩子函数以及可以使用ref和reactive来声明变量。

约定:hooks定义的函数都用use开头,比如useTemplateRef。

路由:(router)

作用:让组件浏览器中的地址栏的地址产生一一对应关系。

路由分为两种:createWebHashHistory和createWebHistory

两种路由方式最明显的区别就是Hash模式中,在域名的后面会跟随这/#,而history模式就是没有/#的。

对于hash模式,服务器端不需要做额外配置,部署即可使用。而history则需要服务器端单独做配置。

因为history模式是直接/路由地址,会让服务器误以为是后端接口,就会出现404的情况。

路由懒加载:语法表现

{

path: '/地址名',

name: '地址名',

conponent: ()=> import('组件路径')

}

路由懒加载就是只有当前需要加载此组件时才会加载,而不是一开始就全部加载。

router和route的区别:

在我们的vue-router中,我们一般把vue-router的实例化对象叫作router,而将router中的routes的某一个对象叫作route,一个route至少包含path和component属性,还包含name,alias,redicret,meta等常用属性,其中alias是给路由的别名,redicret表示重定向,meta用于存储一些变量。

我们可以通过在script中用useRoute()(内置hook),来获取当前的路由对象,也就是当前是路由表的哪一个对象,该对象也包含name,path,component,meta,children等属性。通过children属性,我们可以实现嵌套路由。

我们可以通过在script中用useRouter()(内置hook),来获取当前的路由实例,也就是vue-router的那个实例,该实例主要包含一些方法,该实例的方法可以帮助我们很好的改变当前的路由。常用的方法包括push:向历史记录栈中推入一条路由记录,replace:将当前的路由记录替换成指定的路由记录,go:去到那一条路由,back:回到上一条,forward:去到前一条,go(-1)等价于back,go(1)等价于forward。

Element-Plus

Element-Plus是一款第三方UI组件库,提供了许多第三方组件和模板,可以提高我们的开发效率。

常用的Element-Plus组件:

<el-button></el-button>

ElMessage:弹出一个消息对话框

<el-header></el-header>:头部组件

<el-main></el-main>:内容组件

<el-aside></el-aside>:侧边栏组件

<el-menu></el-menu>:菜单组件

<el-sub-menu></el-sub-menu>:菜单组件的第一级子菜单组件。

<el-meun-item></el-menu-item>:菜单组件中的子菜单组件。

<el-table :data="list"></el-table>:表格组件,我们只需要提供list数据,并指定列对应的属性,就能自动循环生成对应的数据。

<el-table-column prop="id"></el-table-column>:表格中的列组件,通过prop和list中的每一项的id属性对应。

<el-tabs></el-tabs>:选项卡组件,包含type属性(card/border-card)

<el-tab-pane></el-tab-pane>:选项卡中的每一项属性,包括label属性(选项卡的名称),每一项的内容放在标签内。

<el-form></el-form>:表单组件,常用属性包括:model(绑定的数据),rules(表单的验证规则)。

<el-form-item></el-form-item>:表单里的每一行,常用的属性为prop(对应rules里某一项的验证规则)。

在表单验证时,通过ref获取表单结点,例如ref="form",那么通过form.value.validate(res=>{if(res){...}})//其中res是一个布尔值,表示表单验证的结果。

导航守卫

避免用户直接访问所有组件。对路由跳转时,做了一定的规则判断再决定路由的跳转。

语法:

router.beforeEach(to, from, next){

if(to.path === '/login'){return true}

if(localStorage.getItem('token')){return true}

return {path: '/login'}

}

//上述导航拦截实现的就是,每次进行路由跳转之前,先判断是否符合跳转规则,如果符合则跳转,否则就返回指定的页面。

页面传值

vue3中页面传值的常用方法有两种:query和params

示例:router.push('/user?username='+username)

跳转到user页面时,把username传递过去。在user页面需要通过route.query.username来接收,示例如下:

onMounted(()=>{

const username = route.query.username

})

params是在配置路由时,添加需要传递的参数。示例如下:

{

path: "/user/:username"

component: ()=>import('../views/user/User.vue')

}

在传递时:

router.push(`/user/${username}`)

在接收时:route.params.username即可接收。

Axios

数据请求的方法,通过npm i axios下载。

import axios from 'axios'

//get请求获取数据

axios.get(url).then(res=>{console.log(res)})//请求的数据放在res.data里

或者

axios({

url,

method: 'get',

params: {}

}).then(res=>{console.log(res)})

post请求

axios.post(url, data).then(res=>{console.log(res)})

或者

axios({

url,

method: 'post',

headers: {

token,

'content-type': 'application.json'

}

data: {}

}).then(res=>console.log(res))

axios的基准地址baseURL,每次请求都会在请求地址前面加上的地址

axios.default.baseURL = '/api',

超时时间:timeout,请求超过该时间时则认为超时,单位毫秒

axios.default.timeout = 3000

拦截器: interceptors

const instance = axios.create({

baseURL: url,

timeout: 3000

})

请求拦截器:

instance.interceptors.request.use(config => {

const token = localStorage.getItem('token')

if(token){

config.headers.token = token

}

})

响应拦截器:

instance.interceptors.response.use(res => {return res.data})

axios封装:

import axios from 'axios'

const instance = axios.create({

baseUrl: '/api',

timeOut: 3000

})

//设置拦截器

//请求拦截器

instance.interceptors.request.use(config=>{

const token = localStorage.getItem('token')

if(token){

config.headers.token = token

}

return config

})

//响应拦截器

instance.interceptors.response.use(res=>{return res.data})

//自定义get请求方法

const get = (url, params, options)=>{

return instance.get(url, {params, ...options})

}

//自定义post请求方法

const post = (url, data, optoins)=>{

return instance.post(url, data, options)

}

export{get, post}

解决跨域问题

后端解决跨域问题:后端服务器在配置的时候通过设置cors来运行指定的源访问。比如:

res.header("Access-Control-Allow-Origin", "*")//表示允许所有的源访问

res.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")//表示允许的请求方式

res.header("Access-Control-Allow-Headers", "Content-type, Authorization")//表示允许的请求头

前端解决跨域问题:通过在vite.config.js中配置proxy解决。比如:
server: {

proxy: {

'/api': {

target: '需要代理的域名+端口',

changeOrigin: true,

rewrite: (path)=>path.replace(/^\/api/, 'replace')

}

}

}

在访问后端接口时,将接口的域名和端口号替换成/api即可。

pinia

pinia是vue专属的状态管理库,它允许跨组件或页面共享状态,和之前的vuex功能一样,可以把pinia立即为下一代的vuex工具。

pinia选项式语法:

import {defineStore} from 'pinia';

const userStore = defineStore('userStore', {

state: ()=>{//数据写在state里,并且return

return {

user: {

name: 'zs',

age: 20

},

n: 666,

}

},

actions: {//方法写在actions里

getUser=>{

return this.user

}

},

getter: {//类似于计算函数computed

doubleN: this.n * 2

}

persist: true//持久化存储

})

选项式语法:

import store from '@/stores/store'//在store.js脚本里对pinia进行一些初始化操作,包括持久化操作。

const useUserStore = define('userStore', ()=>{

const user = ref({})

const getUser = ()=>{

return user.value

}

const setUser = (newUser) => {

user.value = newUser

}

const removeUser = ()=>{

user.value = {}

}

return {

getUser,

setUser,

removeUser

}

})

export const userStore = useUserStore (store)

附:store.js

import {createPinia} from 'pinia';

import {createPersistedState} from 'pinia-persisted-plugin'//引入持久化插件

const store = createPinia();

store.use(createPersistedState())//使用持久化插件

export defalut store;


真正的英雄主义,是认清生活真相后依然热爱生活 ——罗曼.罗兰