概述
Vue的两个核心功能
- 声明式渲染:Vue 基于标准 HTML 拓展了一套模板语法,使得我们可以声明式地描述最终输出的 HTML 和 JavaScript 状态之间的关系。
- 响应性:Vue 会自动跟踪 JavaScript 状态并在其发生变化时响应式地更新 DOM
Vite+Vue3项目的目录结构
- public/ 目录:用于存放一些公共资源,如 HTML 文件、图像、字体等,这些资源会被直接复制到构建出的目标目录中。
- src/ 目录:存放项目的源代码,包括 JavaScript、CSS、Vue 组件、图像和字体等资源。在开发过程中,这些文件会被 Vite 实时编译和处理,并在浏览器中进行实时预览和调试。以下是src内部划分建议:
assets/
目录:用于存放一些项目中用到的静态资源,如图片、字体、样式文件等。components/
目录:用于存放组件相关的文件。组件是代码复用的一种方式,用于抽象出一个可复用的 UI 部件,方便在不同的场景中进行重复使用。layouts/
目录:用于存放布局组件的文件。布局组件通常负责整个应用程序的整体布局,如头部、底部、导航菜单等。pages/
目录:用于存放页面级别的组件文件,通常是路由对应的组件文件。在这个目录下,可以创建对应的文件夹,用于存储不同的页面组件。plugins/
目录:用于存放 Vite 插件相关的文件,可以按需加载不同的插件来实现不同的功能,如自动化测试、代码压缩等。router/
目录:用于存放 Vue.js 的路由配置文件,负责管理视图和 URL 之间的映射关系,方便实现页面之间的跳转和数据传递。store/
目录:用于存放 Vuex 状态管理相关的文件,负责管理应用程序中的数据和状态,方便统一管理和共享数据,提高开发效率。utils/
目录:用于存放一些通用的工具函数,如日期处理函数、字符串操作函数等。- vite.config.js 文件:Vite 的配置文件,可以通过该文件配置项目的参数、插件、打包优化等。该文件可以使用 CommonJS 或 ES6 模块的语法进行配置。
- package.json 文件:标准的 Node.js 项目配置文件,包含了项目的基本信息和依赖关系。其中可以通过 scripts 字段定义几个命令,如 dev、build、serve 等,用于启动开发、构建和启动本地服务器等操作。
- Vite 项目的入口为 src/main.js 文件,这是 Vue.js 应用程序的启动文件,也是整个前端应用程序的入口文件。在该文件中,通常会引入 Vue.js 及其相关插件和组件,同时会创建 Vue 实例,挂载到 HTML 页面上指定的 DOM 元素中。
Vite+Vue3项目组件
- 一个页面作为整体,是由多个部分组成的,每个部分在这里就可以理解为一个组件
- 每个.vue文件就可以理解为一个组件,多个.vue文件可以构成一个整体页面
- 组件化给我们带来的另一个好处就是组件的复用和维护非常的方便
关于.vue文件
- 传统的页面有.html文件.css文件和.js文件三个文件组成(多文件组件)
- vue将这文件合并成一个.vue文件(Single-File Component,简称 SFC,单文件组件)
- .vue文件对js/css/html统一封装,这是VUE中的概念 该文件由三个部分组成
<script> <template> <style>
- template标签 代表组件的html部分代码 代替传统的.html文件
- script标签 代表组件的js代码 代替传统的.js文件
- style标签 代表组件的css样式代码 代替传统的.css文件
组件的组织
- index.html是项目的入口,其中
<div id ='app'></div>
是用于挂载所有组建的元素- index.html中的script标签引入了一个main.js文件,具体的挂载过程在main.js中执行
- main.js是vue工程中非常重要的文件,他决定这项目使用哪些依赖,导入的第一个组件
- App.vue是vue中的核心组件,所有的其他组件都要通过该组件进行导入,该组件通过路由可以控制页面的切换
关于样式的导入方式
可以直接将样式写在.vue文件中的style内
若要将样式都写在一个.css文件中,导入.css文件的方式如下:
全局引入main.js
import './style/reset.css' //书写引入的资源的相对路径即可!
vue文件script代码引入
import './style/reset.css'
Vue文件style代码引入
@import './style/reset.css'
响应式
响应式是指 : 数据模型发生变化时,自动更新DOM树内容,页面上显示的内容会进行同步变化,vue3的数据模型不是自动响应式的,需要做一些特殊的处理
vue2中,数据不做特殊处理,默认就是响应式的
vue3中,数据要经过ref/reactive函数处理才是响应式的
ref/reactive函数是vue框架中提供的函数,导入即可使用,导入格式:
import {ref,reactive} from 'vue'
使用其函数时,将数据传入参数即可,例:
let num = ref(10)
此时的num就是一个对象,这个对象的一个叫value属性的值为10
让一个数据转换为响应式数据有两种方式:
-
ref函数,更适合单个变量
格式如上
在script标签中,操作此value属性要通过操作num.value
在template标签中,操作此value属性不需要操作num.value,直接操作num即为操作num.value
-
reactive函数,更适合对象或数组
格式:
let obj = reactive({属性名:属性值,属性名:属性值,……})
或
let arr = reactive([1,2,3,4……])
在script和template中,都是通过obj.value来操作obj对象的value属性
toRef函数:将reactive响应式数据中的某个属性转换为ref响应式数据
格式:
let xxx = toRef(对象名,’属性名’)
toRefs函数:将reactive响应式数据中的多个数据转换为ref响应式数据
格式:
let {属性名,属性名,……} = toRefs(person)
视图渲染技术
插值表达式
插值表达式:最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 ,即双大括号{{}}
语法格式:{{数据}}
- 插值表达式是将数据渲染到元素的指定位置的手段之一
- 插值表达式不绝对依赖标签,其位置相对自由
- 插值表达式中支持javascript的运算表达式(比如)
- 插值表达式中也支持函数的调用
例:
<script setup type="module">let msg ="hello vue3"let getMsg= ()=>{return 'hello vue3 message'}let age = 19let bee = '蜜 蜂'// 购物车const carts = [{name:'可乐',price:3,number:10},{name:'薯片',price:6,number:8}];//计算购物车总金额function compute(){let count = 0;for(let index in carts){count += carts[index].price*carts[index].number;}return count;}
</script><template><div><h1>{{ msg }}</h1>msg的值为: {{ msg }} <br>getMsg返回的值为:{{ getMsg() }} <br>是否成年: {{ age>=18?'true':'false' }} <br>反转: {{ bee.split(' ').reverse().join('-') }} <br>购物车总金额: {{ compute() }} <br/>购物车总金额: {{carts[0].price*carts[0].number + carts[1].price*carts[1].number}} <br></div>
</template><style scoped></style>
文本渲染
为了渲染双标中的文本,我们也可以选择使用v-text和v-html命令
- v-*** 这种写法的方式使用的是vue的命令
- v-***的命令必须依赖元素,并且要写在元素的开始标签中
- v-***指令支持ES6中的字符串模板
- 插值表达式中支持javascript的运算表达式
- 插值表达式中也支持函数的调用
- v-text可以将数据渲染成双标签中间的文本,但是不识别html元素结构的文本
- v-html可以将数据渲染成双标签中间的文本,识别html元素结构的文本
<script setup type="module">let msg ='hello vue3'let getMsg= ()=>{return msg}let age = 19let bee = '蜜 蜂'let redMsg ='<font color=\\'red\\'>msg</font>'let greenMsg =`<font color=\\'green\\'>${msg}</font>`
</script><template><div><span v-text='msg'></span> <br><span v-text='redMsg'></span> <br><span v-text='getMsg()'></span> <br><span v-text='age>18?"成年":"未成年"'></span> <br><span v-text='bee.split(" ").reverse().join("-")'></span> <br><span v-html='msg'></span> <br><span v-html='redMsg'></span> <br><span v-html='greenMsg'></span> <br><span v-html="`<font color='green'>${msg}</font>`"></span> <br></div>
</template><style scoped></style>
属性渲染
想要渲染一个元素的 attribute,使用 v-bind指令
- 插值表达式不能直接放在标签的属性中,要渲染元素的属性使用v-bind
- v-bind可以用于渲染任何元素的属性,语法为
v-bind:属性名='数据名'
, 可以简写为:属性名='数据名'
<script setup type="module">const data = {name:'尚硅谷',url:"<http://www.atguigu.com>",logo:"<http://www.atguigu.com/images/index_new/logo.png>"}
</script><template><div><av-bind:href='data.url'target="_self"><img:src="data.logo":title="data.name"><br><input type="button":value="`点击访问${data.name}`"></a></div>
</template><style scoped>
</style>
事件渲染
使用 v-on 来监听 DOM 事件,并在事件触发时执行对应的 Vue的JavaScript代码。
- 用法:
v-on:click="handler"
或简写为@click="handler"
- vue中的事件名=原生事件名去掉
on
前缀 如:onClick --> click
- handler的值可以是方法事件处理器,也可以是内联事件处理器
- 绑定事件时,可以通过一些绑定的修饰符,常见的事件修饰符如下
.once:只触发一次事件
.prevent:阻止默认事件
- .stop:阻止事件冒泡。
- .capture:使用事件捕获模式而不是冒泡模式。
- .self:只在事件发送者自身触发时才触发事件。
<script setup type="module">import {ref} from 'vue'// 响应式数据 当发生变化时,会自动更新 dom树let count=ref(0)let addCount= ()=>{count.value++}let incrCount= (event)=>{count.value++// 通过事件对象阻止组件的默认行为event.preventDefault();}
</script><template><div><h1>count的值是:{{ count }}</h1><!-- 方法事件处理器 --><button v-on:click="addCount()">addCount</button> <br><!-- 内联事件处理器 --><button @click="count++">incrCount</button> <br><!-- 事件修饰符 once 只绑定事件一次 --><button @click.once="count++">addOnce</button> <br><!-- 事件修饰符 prevent 阻止组件的默认行为 --><a href="<http://www.atguigu.com>" target="_blank" @click.prevent="count++">prevent</a> <br><!-- 原生js方式阻止组件默认行为 (推荐) --><a href="<http://www.atguigu.com>" target="_blank" @click="incrCount($event)">prevent</a> <br></div>
</template><style scoped></style>
条件渲染
v-if:
v-if='表达式'
只会在指令的表达式返回真值(true)时才被渲染- 也可以使用
v-else
为v-if
添加一个“else 区块”。 - 一个
v-else
元素必须跟在一个v-if
元素后面,否则它将不会被识别。
<script type="module" setup>import {ref} from 'vue'let awesome = ref(true)
</script><template><div><h1 v-if="awesome">Vue is awesome!</h1><h1 v-else>Oh no 😢</h1><button @click="awesome = !awesome">Toggle</button></div>
</template><style scoped>
</style>
v-show:
- 另一个可以用来按条件显示一个元素的指令是
v-show
。其用法基本一样: - 不同之处在于
v-show
会在 DOM 渲染中保留该元素;v-show
仅切换了该元素上名为display
的 CSS 属性。 v-show
不支持在<template>
元素上使用,也不能和v-else
搭配使用。
<script type="module" setup>import {ref} from 'vue'let awesome = ref(true)
</script><template><div><h1 id="ha" v-show="awesome">Vue is awesome!</h1><h1 id="hb" v-if="awesome">Vue is awesome!</h1><h1 id="hc" v-else>Oh no 😢</h1><button @click="awesome = !awesome">Toggle</button></div>
</template><style scoped>
</style>
注
v-if
是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。v-if
是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。- 相比之下,
v-show
简单许多,元素无论初始条件如何,始终会被渲染,只有 CSSdisplay
属性会被切换。 - 总的来说,
v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此,如果需要频繁切换,则使用v-show
较好;如果在运行时绑定条件很少改变,则v-if
会更合适。
列表渲染
可以使用 v-for 指令基于一个数组来渲染一个列表。
v-for
指令的值需要使用item in items
形式的特殊语法,其中items
是源数据的数组,而item
是迭代项的别名:- 在
v-for
块中可以完整地访问父作用域内的属性和变量。v-for
也支持使用可选的第二个参数表示当前项的位置索引。
<script type="module" setup>import {ref,reactive} from 'vue'let parentMessage= ref('产品')let items =reactive([{id:'item1',message:"薯片"},{id:'item2',message:"可乐"}])
</script><template><div><ul><!-- :key不写也可以 --><li v-for='item in items' :key='item.id'>{{ item.message }}</li></ul><ul><!-- index表示索引,当然不是非得使用index这个单词 --><li v-for="(item, index) in items" :key="index">{{ parentMessage }} - {{ index }} - {{ item.message }}</li></ul></div>
</template><style scoped>
</style>
双向绑定
页面上的数据由于用户的操作造成了改变,也会同步修改对应的响应式数据,双向绑定一般用于表单标签。
格式:v-model:value=”数据”,一般省略:value,写为v-model=”数据”,此时的数据还是指value值
例:
<script type="module" setup>//引入模块import { reactive,ref} from 'vue'let hbs = ref([]); //装爱好的值let user = reactive({username:null,password:null,introduce:null,pro:null})function login(){alert(hbs.value);alert(JSON.stringify(user));}function clearx(){//user = {};// 这中写法会将数据变成非响应的,应该是user.username=""user.username=''user.password=''user.introduce=''user.pro=''hbs.value.splice(0,hbs.value.length);;}
</script><template><div>账号: <input type="text" placeholder="请输入账号!" v-model="user.username"> <br>密码: <input type="text" placeholder="请输入账号!" v-model="user.password"> <br>爱好:吃 <input type="checkbox" name="hbs" v-model="hbs" value="吃">喝 <input type="checkbox" name="hbs" v-model="hbs" value="喝">玩 <input type="checkbox" name="hbs" v-model="hbs" value="玩">乐 <input type="checkbox" name="hbs" v-model="hbs" value="乐"><br>简介:<textarea v-model="user.introduce"></textarea><br>籍贯:<select v-model="user.pro"><option value="1">黑</option><option value="2">吉</option><option value="3">辽</option><option value="4">京</option><option value="5">津</option><option value="6">冀</option></select><br><button @click="login()">登录</button><button @click="clearx()">重置</button><hr>显示爱好:{{ hbs }}<hr>显示用户信息:{{ user }}</div>
</template><style scoped>
</style>
计算属性
使用compute函数,用一个变量接受其结果值,此变量便是计算属性
例:
<script type="module" setup>//引入模块import { reactive,computed} from 'vue'const author = reactive({name: 'John Doe',books: ['Vue 2 - Advanced Guide','Vue 3 - Basic Guide','Vue 4 - The Mystery']})// 一个计算属性 refconst publishedBooksMessage = computed(() => {console.log("publishedBooksMessage")return author.books.length > 0 ? 'Yes' : 'No'})// 一个函数let hasBooks = ()=>{console.log("hasBooks")return author.books.length > 0?'Yes':'no'}</script><template><div><p>{{author.name}} Has published books?:</p><span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span><span>{{ hasBooks() }}</span><!-- 调用方法,每个标签都会调用一次 --><span>{{ hasBooks() }}</span><p>{{author.name}} Has published books?:</p><span>{{ publishedBooksMessage }}</span><!-- 属性计算,属性值不变时,多个个标签只会调用一次 --><span>{{ publishedBooksMessage }}</span></div>
</template><style scoped>
</style>
- 定义了一个计算属性
publishedBooksMessage
。computed()
方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似,可以通过publishedBooksMessage.value
访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加.value
- Vue 的计算属性会自动追踪响应式依赖。它会检测到
publishedBooksMessage
依赖于author.books
,所以当author.books
改变时,任何依赖于publishedBooksMessage
的绑定都会同时更新 - 若将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实是完全相同的,然而,不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要
author.books
不改变,无论多少次访问publishedBooksMessage
都会立即返回先前的计算结果
数据监听器
数据监听方式
watch函数:
ref数据响应式数据监听的格式:
watch(数据,(newValue,oldValue)=>{})
reactive响应式数据监听的格式(监听此数据的一个属性)
watch(()=>数据.属性,(newValue,oldValue)=>{})
reactive响应式数据监听的格式(监听此数据的所有属性)
watch(数据,(newValue,oldValue)=>{},{deep:true,immediate:true})
//deep:true 深度监视
//immediate:true 深度监视在进入页面时立即执行一次
deep:true 深度监视 immediate:true 深度监视在进入页面时立即执行一次
例:
<script type="module" setup>//引入模块import { ref,reactive,watch} from 'vue'let firstname=ref('')let lastname=reactive({name:''})let fullname=ref('')//监听一个ref响应式数据watch(firstname,(newValue,oldValue)=>{console.log(`${oldValue}变为${newValue}`)fullname.value=firstname.value+lastname.name})//监听reactive响应式数据的指定属性watch(()=>lastname.name,(newValue,oldValue)=>{console.log(`${oldValue}变为${newValue}`)fullname.value=firstname.value+lastname.name})//监听reactive响应式数据的所有属性(深度监视,一般不推荐)//deep:true 深度监视//immediate:true 深度监视在进入页面时立即执行一次watch(()=>lastname,(newValue,oldValue)=>{// 此时的newValue和oldValue一样,都是lastnameconsole.log(newValue)console.log(oldValue)fullname.value=firstname.value+lastname.name},{deep:true,immediate:false})
</script><template><div>全名:{{fullname}} <br>姓氏:<input type="text" v-model="firstname"> <br>名字:<input type="text" v-model="lastname.name" > <br></div>
</template><style scoped>
</style>
watchEffect函数
在watchEffect函数中,如果想监听数据,直接调用即可,无需将要监听的响应式数据传入参数。
在watchEffect函数中调用后的响应式数据才会被监视,否则不会被监视。
在watchEffect函数中,所有被调用的响应式数据指的都是newValue,不能使用oldValue。
生命周期
每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数
,让开发者有机会在特定阶段运行自己的代码
- 常见钩子函数
- onMounted() 注册一个回调函数,在组件挂载完成后执行。
- onUpdated() 注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用。
- onUnmounted() 注册一个回调函数,在组件实例被卸载之后调用。
- onBeforeMount() 注册一个钩子,在组件被挂载之前被调用。
- onBeforeUpdate() 注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。
- onBeforeUnmount() 注册一个钩子,在组件实例被卸载之前调用。
组件
要传入组件,导入相应的组件文件,并起别名,用别名当作标签名,将标签放入template标签中即可
例:
<script setup>import Header from './components/Header.vue'import Navigator from './components/Navigator.vue'import Content from './components/Content.vue'
</script><template><div><Header class="header"></Header><Navigator class="navigator"></Navigator><Content class="content"></Content></div>
</template><style scoped>.header{height: 80px;border: 1px solid red;}.navigator{width: 15%;height: 800px;display: inline-block;border: 1px blue solid;float: left;}.content{width: 83%;height: 800px;display: inline-block;border: 1px goldenrod solid;float: right;}</style>
组件传递参数
父传子
Vue3 中父组件向子组件传值可以通过 props 进行,具体操作如下:
- 首先,在父组件中定义需要传递给子组件的值,接着,在父组件的模板中引入子组件,同时在引入子组件的标签中添加 props 属性并为其设置需要传递的值。
- 在 Vue3 中,父组件通过 props 传递给子组件的值是响应式的。也就是说,如果在父组件中的传递的值发生了改变,子组件中的值也会相应地更新。
- 父组件代码:App.vue
<script setup>import Son from './components/Son.vue'import {ref,reactive,toRefs} from 'vue'let message = ref('parent data!')let title = ref(42)function changeMessage(){message.value = '修改数据!'title.value++}
</script><template><div><h2>{{ message }}</h2><hr><!-- 使用子组件,并且传递数据! --><Son :message="message" :title="title"></Son><hr><button @click="changeMessage">点击更新</button></div>
</template><style scoped>
</style>
- 子组件代码:Son.vue
<script setup type="module">import {ref,isRef,defineProps} from 'vue'//声明父组件传递属性值defineProps({message:String ,title:Number})
</script><template><div><div>{{ message }}</div><div>{{ title }}</div></div>
</template><style>
</style>
子传父
- 父组件: App.vue
<script setup>import Son from './components/Son.vue'import {ref} from 'vue'let pdata = ref('')const padd = (data) => {console.log('2222');pdata.value =data;}//自定义接收,子组件传递数据方法! 参数为数据!const psub = (data) => {console.log('11111');pdata.value = data;}
</script><template><div><!-- 声明@事件名应该等于子模块对应事件名!调用方法可以是当前自定义!--><Son @add="padd" @sub="psub"></Son><hr>{{ pdata }}</div>
</template><style>
</style>
- 子组件:Son.vue
<script setup>import {ref,defineEmits} from 'vue'//1.定义要发送给父组件的方法,可以1或者多个let emites = defineEmits(['add','sub']);let data = ref(1);function sendMsgToParent(){console.log('-------son--------');//2.出发父组件对应的方法,调用defineEmites对应的属性emites('add','add data!'+data.value)emites('sub','sub data!'+data.value)data.value ++;}
</script><template><div><button @click="sendMsgToParent">发送消息给父组件</button></div>
</template>