Vue的模板语法:
v- 开头都是vue 的指令
v-text 用来显示文本
v-html 用来展示富文本
v-if 用来控制元素的显示隐藏(切换真假DOM)
v-else-if 表示 v-if 的“else if 块”。可以链式调用
v-else v-if条件收尾语句
v-show 用来控制元素的显示隐藏(display none block Css切换)
v-on 简写@ 用来给元素添加事件
v-bind 简写: 用来绑定元素的属性Attr
v-model 双向绑定
v-for 用来遍历元素
v-on修饰符 冒泡案例
//v-bind 绑定class 案例 1
<template>
<div :class="[flag ? 'active' : 'other', 'h']">12323</div>
</template>
<script setup lang="ts">
const flag: boolean = false;
</script>
<style>
.active {
color: red;
}
.other {
color: blue;
}
.h {
height: 300px;
border: 1px solid #ccc;
}
</style>
//v-bind 绑定class 案例 2
<template>
<div :class="flag">{{flag}}</div>
</template>
<script setup lang="ts">
type Cls = {
other: boolean,
h: boolean
}
const flag: Cls = {
other: false,
h: true
};
</script>
<style>
.active {
color: red;
}
.other {
color: blue;
}
.h {
height: 300px;
border: 1px solid #ccc;
}
</style>
//v-bind 绑定style案例
<template>
<div :style="style">2222</div>
</template>
<script setup lang="ts">
type Style = {
height: string,
color: string
}
const style: Style = {
height: "300px",
color: "blue"
}
</script>
<style>
</style>
//v-model 案例
<template>
<input v-model="message" type="text" />
<div>{{ message }}</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const message = ref("v-model")
</script>
<style>
.active {
color: red;
}
.other {
color: blue;
}
.h {
height: 300px;
border: 1px solid #ccc;
}
</style>
________________________________分段_______________________________________
Vue——ref
接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value
property,指向该内部值。
//我们这样操作是无法改变message 的值 应为message 不是响应式的无法被vue 跟踪要改成ref
<template>
<div>
<button @click="changeMsg">change</button>
<div>{{ message }}</div>
</div>
</template>
<script setup lang="ts">
let message: string = "我是message"
const changeMsg = () => {
message = "change msg"
}
</script>
<style>
</style>
改为ref
//Ref TS对应的接口
interface Ref<T> {
value: T
}
注意被ref包装之后需要.value 来进行赋值!!!!!
<template>
<div>
<button @click="changeMsg">change</button>
<div>{{ message }}</div>
</div>
</template>
<script setup lang="ts">
import {ref,Ref} from 'vue'
let message:Ref<string> = ref("我是message")
const changeMsg = () => {
message.value = "change msg"
}
</script>
<style>
</style>
//--------------------------------ts两种方式
<template>
<div>
<button @click="changeMsg">change</button>
<div>{{ message }}</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
let message = ref<string | number>("我是message")
const changeMsg = () => {
message.value = "change msg"
}
</script>
<style>
</style>
isRef
判断是不是一个ref对象
import { ref, Ref,isRef } from 'vue'
let message: Ref<string | number> = ref("我是message")
let notRef:number = 123
const changeMsg = () => {
message.value = "change msg"
console.log(isRef(message)); //true
console.log(isRef(notRef)); //false
}
shallowRef
创建一个跟踪自身 .value
变化的 ref,但不会使其值也变成响应式的
例子
修改其属性是非响应式的这样是不会改变的
<template>
<div>
<button @click="changeMsg">change</button>
<div>{{ message }}</div>
</div>
</template>
<script setup lang="ts">
import { Ref, shallowRef } from 'vue'
type Obj = {
name: string
}
let message: Ref<Obj> = shallowRef({
name: "小满"
})
const changeMsg = () => {
message.value.name = '大满'
}
</script>
<style>
</style>
例子2
这样是可以被监听到的修改value
import { Ref, shallowRef } from 'vue'
type Obj = {
name: string
}
let message: Ref<Obj> = shallowRef({
name: "小满"
})
const changeMsg = () => {
message.value = { name: "大满" }
}
triggerRef
强制更新页面DOM
这样也是可以改变值的
<template>
<div>
<button @click="changeMsg">change</button>
<div>{{ message }}</div>
</div>
</template>
<script setup lang="ts">
import { Ref, shallowRef,triggerRef } from 'vue'
type Obj = {
name: string
}
let message: Ref<Obj> = shallowRef({
name: "小满"
})
const changeMsg = () => {
message.value.name = '大满'
triggerRef(message)
}
</script>
<style>
</style>
customRef
自定义ref
customRef 是个工厂函数要求我们返回一个对象 并且实现 get 和 set
<script setup lang="ts">
import { Ref, shallowRef, triggerRef, customRef } from 'vue'
function Myref<T>(value: T) {
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newVal: T) {
console.log('set');
value = newVal
trigger()
}
}
})
}
let message = Myref('小满')
const changeMsg = () => {
message.value = '大满'
// triggerRef(message)
}
</script>
________________________________分段_______________________________________
diff算法:
关于我的个人理解,diff算法不是用暴力算法来解决DOM的相同或者不相同
特点 :
(1.) diff 算法就是将两个新旧的虚拟 DOM 进行对比并且返回一个 patch对象,用来存储两个节点不同的地方,最后用patch记录的消息去局部更新Dom。
diff的过程就是调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁
(2.) 比较只会在同层级别进行,不会跨层级比较。
(3.) 在 diff 比较 过程汇总,循环从两边向中间比较。
(4.) diff 算法的步骤:
1、用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文 档当中
2、当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较(diff),记录两棵树差异
3、把第二棵树所记录的差异应用到第一棵树所构建的真正的DOM树上(patch),视图就更新了
________________________________分段_______________________________________
组件基础
每一个.vue 文件呢都可以充当组件来使用
每一个组件都可以复用
例如 helloWorld 充当子组件
父组件使用
引入子组件 helloWorld 然后直接就可以去当标签去使用 (切记组件名称不能与html元素标签名称一样)
组件的生命周期
简单来说就是一个组件从创建 到 销毁的 过程 成为生命周期
在我们使用Vue3 组合式API 是没有 beforeCreate 和 created 这两个生命周期的
onBeforeMount()
在组件DOM实际渲染安装之前调用。在这一步中,根元素还不存在。
onMounted()
在组件的第一次渲染后调用,该元素现在可用,允许直接DOM访问。
onBeforeUpdate()
数据更新时调用,发生在虚拟 DOM 打补丁之前。
updated()
DOM更新后,updated
的方法即会调用。
onBeforeUnmount()
在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。
onUnmounted()
卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。
选项式 API | Hook inside setup |
---|---|
beforeCreate |
Not needed* |
created |
Not needed* |
beforeMount |
onBeforeMount |
mounted |
onMounted |
beforeUpdate |
onBeforeUpdate |
updated |
onUpdated |
beforeUnmount |
onBeforeUnmount |
unmounted |
onUnmounted |
errorCaptured |
onErrorCaptured |
renderTracked |
onRenderTracked |
renderTriggered |
onRenderTriggered |
activated |
onActivated |
deactivated |
onDeactivated |
配置全局组件
例如组件使用频率非常高(table,Input,button,等)这些组件 几乎每个页面都在使用便可以封装成全局组件
案例------我这儿封装一个Card组件想在任何地方去使用↓
<template>
<div class="card">
<div class="card-header">
<div>标题</div>
<div>副标题</div>
</div>
<div v-if='content' class="card-content">
{{content}}
</div>
</div>
</template>
<script setup lang="ts">
type Props = {
content:string
}
defineProps<Props>()
</script>
<style scoped lang='less'>
@border:#ccc;
.card{
width: 300px;
border: 1px solid @border;
border-radius: 3px;
&:hover{
box-shadow:0 0 10px @border;
}
&-content{
padding: 10px;
}
&-header{
display: flex;
justify-content: space-between;
padding: 10px;
border-bottom: 1px solid @border;
}
}
</style>
卡片效果:
使用方法:
在main.ts 引入我们的组件跟随在createApp(App) 后面 切记不能放到mount 后面这是一个链式调用用(使用的是vite脚手架)
其次调用 component 第一个参数组件名称 第二个参数组件实例
import { createApp } from 'vue'
import App from './App.vue'
import './assets/css/reset/index.less'
import Card from './components/Card/index.vue'
//(调用↓):component
createApp(App).component('Card',Card).mount('#app')
↑这里Card全局组件就已经装载完毕了
Card组件的使用方法(直接在其他vue页面 立即使用即可 无需引入)↓
<template>
<Card></Card>
</template>
配置局部组件:
<template>
<div class="wraps">
<layout-menu :flag="flag" @on-click="getMenu" @on-toogle="getMenuItem" :data="menuList" class="wraps-left"></layout-menu>
<div class="wraps-right">
<layout-header> </layout-header>
<layout-main class="wraps-right-main"></layout-main>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive,ref } from "vue";
import layoutHeader from "./Header.vue";
import layoutMenu from "./Menu.vue";
import layoutMain from "./Content.vue";
/*
这里分别使用到的组件(layoutHeader)(layoutMenu)(layoutMain)
*/
</script>
就是在一个组件内(A) 通过import 去引入别的组件(B) 称之为局部组件
应为B组件只能在A组件内使用 所以是局部组件
如果C组件想用B组件 就需要C组件也手动import 引入 B 组件
配置递归组件:
原理跟我们写js递归是一样的 自己调用自己 通过一个条件来结束递归 否则导致内存泄漏
案例递归树
在父组件配置数据结构 数组对象格式 传给子组件
type TreeList = {
name: string;
icon?: string;
children?: TreeList[] | [];
};
const data = reactive<TreeList[]>([
{
name: "no.1",
children: [
{
name: "no.1-1",
children: [
{
name: "no.1-1-1",
},
],
},
],
},
{
name: "no.2",
children: [
{
name: "no.2-1",
},
],
},
{
name: "no.3",
},
]);
子组件接收值 第一个script
type TreeList = {
name: string;
icon?: string;
children?: TreeList[] | [];
};
type Props<T> = {
data?: T[] | [];
};
defineProps<Props<TreeList>>();
const clickItem = (item: TreeList) => {
console.log(item)
}
子组件增加一个script 定义组件名称为了 递归用
<script lang="ts">
export default {
name:"TreeItem"
}
</script>
template
TreeItem 其实就是当前组件 通过import 把自身又引入了一遍 如果他没有children 了就结束
<div style="margin-left:10px;" class="tree">
<div :key="index" v-for="(item,index) in data">
<div @click='clickItem(item)'>{{item.name}}
</div>
<TreeItem @on-click='clickItem' v-if='item?.children?.length' :data="item.children"></TreeItem>
</div>
</div>
效果图如下↓
关于递归组件的使用完整说明:
(创建了[一个Index组件和一个Test组件还有一个View组件])
{
(1)Index组件是放需要递归的假数据和引入Test组件还有传值给Test数据(父子组件传值)
(2)Test组件是接收了父组件的值也就是Index组件的值,重点:(如果想要递归一个不确定层级的树结构,就需要再一次引入本身这个组件也就是再一次引入Test这个组件)
(3)View组件就只是一个视图而已
}
下面展示组件的详细代码:
Index组件:
<script lang="ts" setup>
import { reactive } from 'vue';
import Test1 from './test-1.vue'
/*
定义一个不确定层级的树结构
为了演示递归组件
因为它的层级是不固定的
*/
type TestList = {
name: string;
icon?: string;
children?: TestList[] | [];
};
const data = reactive<TestList[]>([
{
name: "no.1",
children: [
{
name: "no.1-1",
children: [
{
name: "no.1-1-1",
},
],
},
],
},
{
name: "no.2",
children: [
{
name: "no.2-1",
},
],
},
{
name: "no.3",
},
])
</script>
<template>
<Test1 :data="data"></Test1>
</template>
<style lang="less" scoped></style>
Test组件:
重点:这里引入了本身也就是Test这个组件我给它起名叫:QuoteItself 后来我再使用了QuoteItself 这个组件(注意下方代码要牢记)
<script lang="ts" setup>
import QuoteItself from './test-1.vue'
/*
接收index里组件的值
通过defineProps接收数据
*/
type TestList = {
name: string;
icon?: string;
children?: TestList[] | [];
};
type Props<T> = {
data?: T[] | [];
};
defineProps<Props<TestList>>();
const clickItem = (item: TestList) => {
console.log(item)
}
</script>
<template>
<!--(1) 这里循环了父组件传入的数据还有使用v-for进行循环这样 -->
<div v-for="(item,index) in data" :key="index">
<!--(2) 如果想要将数据展现出来需要用item里的name -->
{{item.name}}
<!--(3) 这里用v-if是确定index里的data下的children是否存在因为递归组件需要一个能结束它的条件 -->
<QuoteItself v-if="item?.children?.length" :data="item.children"></QuoteItself>
</div>
</template>
<style lang="less" scoped></style>
View组件:
<script setup lang='ts'>
</script>
<template>
<div>
<!-- 这里是我在main.ts里注册了一个全局组件 -->
<Test></Test>
</div>
</template>
<style lang='less' scoped>
</style>
异步组件:
如果我用yarn build进行打包
打包结果是这样的结构:
这样vite会把所有的组件打包到一个js里这样是很消耗性能的。所谓异步组件就是把请求后台的异步任务封装成一个组件众,所周知的是如果请求后台数据会有可能因各种因素让后台数据无法展示到页面上,这样就会使页面出现白屏现象这样是很影响用户的用户体验。所以就需要把异步组件打包成另一个js;而不是像上面把所有组件都打包到一个js文件里面。
下面我将实现封装一个异步组件:
首先我需要创建一个假的后台接口我将它命名为data.json
而后我要写一个接口去拿到这里的数据
type NameList = {
name:String
}
export function Axios(url:string):Promise<NameList[]> {
return new Promise((resolve)=>{
let xhr:XMLHttpRequest = new XMLHttpRequest()
xhr.open('GET',url)
xhr.onreadystatechange=function(){
if(xhr.readyState === 4 && xhr.status === 200){
setTimeout(() => {
resolve(JSON.parse(xhr.responseText))
}, 2000);
}
}
xhr.send(null)
})
}
在我的项目目录下创建一个用于测试使用的 test-4.vue 组件
随后引入刚刚写的接口
再去读取刚刚data.json里的数据
随后我们去浏览器观察才发现在网页里并没有展示出我们想要的数据
其实没展示出来也是正确的我们现在已经把这个组件变成异步组件了
所以我们需要用Vue的defineAsyncComponent()函数封装一下还需要用Vue里内置组件<Suspense></Suspense>来配合Test4的使用才能在页面上显示数据
<script setup lang='ts'>
import { defineAsyncComponent } from 'vue'
/*
import Test4 from '@/components/recursion&&Test/test-4.vue';
要删除上方这样的引入方式
需要采用的是下方的引入方式
*/
const Test4 = defineAsyncComponent(() => import('@/components/recursion&&Test/test-4.vue'))
</script>
<template>
<div class="Menu">
菜单区域
<Suspense>
<template #default>
<!--这里就是放入Test4的地方上方的template提供了两个插槽一个是#default
还有一个是#fallback; #fallback是数据在等待的时候显示的内容
-->
<Test4></Test4>
</template>
<template #fallback>
<div>
loading...
</div>
</template>
</Suspense>
</div>
</template>
页面大概就是以下的效果:
当我再次使用yarn build打包之后:
可以看到多了一个js文件这个文件就是我们分出来的异步组件
----------------------------------------------------------分段-----------------------------------------------------------------
watch侦听器
watch是能监听复杂的数据结构的,但是它会有个bug
现在我传入了一个复杂的数据结构
然后watch本身有第二个参数deep这是深度监听如果不传入第二个参数是不能监听复杂的数据结构
我没再来看看变化
页面效果:
如果watch监听的是reactive的数据那么就不需要写第二个参数“deep”
watch的几种语法写法:
(1)如果你想监听多个数据可以用数组的方式写列如:
let Test1 = ref<string>('');
let Test2 = ref<string>('');
watch([Test1,Test2 ], (newVal, oldVal) => {
console.log('新的值----', newVal);
console.log('旧的值----', oldVal);
})
(2)如果你想只监听一个数据结构可以用回调函数的方式写列如:
let data = reactive({
name1:'<>',
name2:'[]'
})
watch(()=>data.name1,(newVal, oldVal) => {
console.log('新的值----', newVal);
console.log('旧的值----', oldVal);
})
----------------------------------------------------------分段-----------------------------------------------------------------
认识watchEffect高级侦听器:
watchEffect
立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
如果用到message 就只会监听message 就是用到几个监听几个 而且是非惰性 会默认调用一次
let message = ref<string>('')
let message2 = ref<string>('')
watchEffect(() => {
//console.log('message', message.value);
console.log('message2', message2.value);
})
清除副作用
就是在触发监听之前会调用一个函数可以处理你的逻辑例如防抖
import { watchEffect, ref } from 'vue'
let message = ref<string>('')
let message2 = ref<string>('')
watchEffect((oninvalidate) => {
//console.log('message', message.value);
oninvalidate(()=>{
})
//oninvalidate()里会比watchEffect函数里所有东西都提前调用
console.log('message2', message2.value);
})
停止跟踪 watchEffect 返回一个函数 调用之后将停止更新
const stop = watchEffect((oninvalidate) => {
//console.log('message', message.value);
oninvalidate(()=>{
})
console.log('message2', message2.value);
},{
flush:"post",
onTrigger () {
}
})
stop()
更多的配置项
副作用刷新时机 flush 一般使用post
pre | sync | post | |
更新时机 | 组件更新前执行 | 强制效果始终同步触发 | 组件更新后执行 |
onTrigger 可以帮助我们调试 watchEffect
import { watchEffect, ref } from 'vue'
let message = ref<string>('')
let message2 = ref<string>('')
watchEffect((oninvalidate) => {
//console.log('message', message.value);
oninvalidate(()=>{
})
console.log('message2', message2.value);
},{
flush:"post",
onTrigger () {
}
})
----------------------------------------------------------分段-----------------------------------------------------------------