<template>
姓:<input v-model="username.surname"> <br>
名:<input v-model="username.name"> <br>
单向响应:<input v-model="username.nick"> <br>
</template>
<script setup lang="ts">
import { computed } from '@vue/reactivity';
import { reactive } from 'vue';
const username = reactive({
surname: "张",
name: "三"
})
username.nick = computed(()=>{
return username.surname + '*' + username.name
})
</script>
<template>
姓:<input v-model="username.surname"> <br>
名:<input v-model="username.name"> <br>
双向响应:<input v-model="username.nick"> <br>
</template>
<script setup lang="ts">
import { computed } from '@vue/reactivity';
import { reactive } from 'vue';
const username = reactive({
surname: "张",
name: "三"
})
username.nick = computed({
get(){
return username.surname + '*' + username.name
},
set(value){
const arr = value.split('*')
username.surname = arr[0]
username.name = arr[1]
}
})
</script>
# 监听整个对象,由于是浅拷贝,他们新旧指向的是同一个对象
<template>
<h2>年龄:{{ userInfo.age }}</h2>
<button @click="userInfo.age++"> 年龄+1</button>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
const userInfo = reactive({
name: '张三',
age: 20
})
watch(userInfo, (newVal, oldVal) => {
console.log('新:');
console.log(newVal);
console.log('************************');
console.log('旧:');
console.log(oldVal);
})
</script>
# 监听对象中单个属性可以检测到新旧值的变化
<template>
<h2>年龄:{{ userInfo.age }}</h2>
<button @click="userInfo.age++"> 年龄+1</button>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
const userInfo = reactive({
name: '张三',
age: 20
})
watch(()=>userInfo.age, (newVal, oldVal) => {
console.log('新:');
console.log(newVal);
console.log('************************');
console.log('旧:');
console.log(oldVal);
})
</script>
# 监听对象中多个属性
<template>
<h2>姓名:{{ userInfo.name }}</h2>
<button @click="userInfo.name+='是个傻逼'">改名</button>
<br>
<h2>年龄:{{ userInfo.age }}</h2>
<button @click="userInfo.age++"> 年龄+1 </button>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
const userInfo = reactive({
name: '张三',
age: 20
})
watch([()=>userInfo.name,()=>userInfo.age], (newVal, oldVal) => {
console.log('新:');
console.log(newVal);
console.log('************************');
console.log('旧:');
console.log(oldVal);
})
</script>
<template>
<h2>年龄:{{ userInfo.hobby.like }}</h2>
<button @click="userInfo.hobby.like += '很快乐'">点我</button>
<br>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
const userInfo = reactive({
name: '张三',
age: 20,
hobby: {
like: '打球'
}
})
watch(() => userInfo.hobby.like, (newVal, oldVal) => {
console.log('新:');
console.log(newVal);
console.log('************************');
console.log('旧:');
console.log(oldVal);
}, { deep: true })
</script>
# watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调
<template>
<h2>姓名:{{ userInfo.name }}</h2>
<button @click="userInfo.name += '111'">修改</button>
<br>
</template>
<script setup lang="ts">
import { reactive, watch, watchEffect } from 'vue';
const userInfo = reactive({
name: '张三'
})
watchEffect(()=>{
userInfo.name
console.log('姓名变了');
})
</script>
# oninvalidate 监听值修改前的回调
<template>
<h2>姓名:{{ userInfo.name }}</h2>
<button @click="userInfo.name += '111'">修改</button>
<br>
</template>
<script setup lang="ts">
import { reactive, watchEffect } from 'vue';
const userInfo = reactive({
name: '张三'
})
watchEffect((oninvalidate)=>{
oninvalidate(()=>{
console.log("before");
})
userInfo.name
console.log('姓名变了');
})
</script>
# 停止后不再监听值的变化
<template>
<h2>姓名:{{ userInfo.name }}</h2>
<button @click="userInfo.name += '111'">修改</button>
<button @click="stop">停止监听</button>
<br>
</template>
<script setup lang="ts">
import { reactive, watchEffect } from 'vue';
const userInfo = reactive({
name: '张三'
})
const stop = watchEffect((oninvalidate)=>{
oninvalidate(()=>{
console.log("before");
})
userInfo.name
console.log('姓名变了');
})
</script>
<template>
<h2>姓名:{{ AA }}</h2>
<button @click="userInfo.name += '加1'">修改</button>
</template>
<script setup lang="ts">
import { reactive, toRef } from 'vue';
const userInfo = reactive({
name: '张三'
})
const AA = toRef(userInfo,'name')
</script>
<template>
<h2>姓名:{{ name }}</h2>
<button @click="name += '加1'">修改姓名</button>
<br>
<h2>年龄:{{ age }}</h2>
<button @click="age++">修改年龄</button>
</template>
<script setup lang="ts">
import { reactive, toRef, toRefs } from 'vue';
const userInfo = reactive({
name: '张三',
age: 20
})
const {name,age} = toRefs(userInfo)
</script>
<script setup lang="ts">
import { onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated } from 'vue';
console.log('setup执行');
onBeforeMount(() => {
console.log('*** 挂载前 ***');
})
onMounted(() => {
console.log('*** 挂载后 ***');
})
onBeforeUpdate(() => {
console.log('*** 更新前 ***');
})
onUpdated(() => {
console.log('*** 更新后 ***');
})
onBeforeUnmount(() => {
console.log('*** 卸载前 ***');
})
onUnmounted(() => {
console.log('*** 卸载后 ***');
})
</script>
<template>
<div ref="box">我是box</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
let box = ref(null) // 本质是reactive({value:null})
// dom挂载后的生命周期
onMounted(()=>{
console.log(box.value);
})
// 这里会输出null,setup是最先执行,此时dom还没生成
console.log(box.value);
</script>
// 父组件
<template>
<SonVue tit="父组件传值1" title="父组件传值2"/>
</template>
// 子组价
<script setup lang="ts">
import { useAttrs } from 'vue';
let attr = useAttrs()
console.log(attr);
</script>
# convertImg.ts
import { ref, onMounted, onUnmounted } from 'vue'
// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
// 被组合式函数封装和管理的状态
const x = ref(0)
const y = ref(0)
// 组合式函数可以随时更改其状态。
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
// 一个组合式函数也可以挂靠在所属组件的生命周期上
// 来启动和卸载副作用
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// 通过返回值暴露所管理的状态
return { x, y }
}
# 在组件中的使用
<template>
<div>横轴:{{ x }}</div>
<div>纵轴:{{ y }}</div>
</template>
<script setup lang="ts">
import { useMouse } from '../../hooks/convertImg'
const { x, y } = useMouse()
</script>
安装依赖npm install @vueuse/core
简单使用
<template>
<div ref="el" :style="style" style="position: fixed;background-color: pink;">
请拖动我 {{ x }}, {{ y }}
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useDraggable } from '@vueuse/core'
const el = ref<HTMLElement | null>(null)
const { x, y, style } = useDraggable(el, {
initialValue: { x: 40, y: 40 }
})
</script>
<template>
<B>
这是XXX数据
这是YYY数据
</B>
</template>
<script setup lang="ts">
import B from '@/views/A/B.vue'
</script>
<template>
<div>
<header>
<div>头部</div>
<slot></slot>
</header>
<footer>
<div>底部</div>
<slot></slot>
</footer>
</div>
</template>
<template>
<B>
<!-- 标准写法 -->
<template v-slot:xxx>这是xxx数据</template>
<template v-slot:yyy>这是yyy数据</template>
<!-- 简写 -->
<template #xxx>这是yyy数据</template>
<template #yyy>这是yyy数据</template>
</B>
</template>
<script setup lang="ts">
import B from '@/views/A/B.vue'
</script>
<template>
<div>
<h1>
子:A组件
</h1>
<header>
<div>头部</div>
<slot name="xxx"></slot>
</header>
<footer>
<div>底部</div>
<slot name="yyy"></slot>
</footer>
</div>
</template>
A组件
<template>
<B>
<!-- 标准写法 -->
<template v-slot="{ data }">{{ data.name }}==>{{ data.age }}</template>
<!-- 简写 -->
<template #default="{ data }">{{ data.name }}==>{{ data.age }}</template>
</B>
</template>
<script setup lang="ts">
import B from '@/views/A/B.vue'
</script>
B组件
<template>
<div>
<section>
<div v-for="item in list" :key="item.id">
<slot :data="item"></slot>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const list = ref([
{id:1,name:'张三',age:15},
{id:2,name:'李四',age:18},
{id:3,name:'王二麻子',age:20},
{id:4,name:'刘五',age:28},
{id:5,name:'小六子',age:22},
])
</script>
A组件
<template>
<B>
<template #[yyy]>这是yyy数据</template>
</B>
</template>
<script setup lang="ts">
import B from '@/views/A/B.vue'
import { ref } from 'vue';
let yyy = ref('yyy')
</script>
B组件
<header>
<div>头部</div>
<slot name="xxx"></slot>
<div>底部</div>
<slot name="yyy"></slot>
</header>
是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。
<Teleport to=".main">这是传送1</Teleport>
<Teleport to="#home">这是传送2</Teleport>
<Teleport to="body">这是传送3</Teleport>
注意:必须dom存在才能传送
Vue 提供了 defineAsyncComponent
方法来实现异步组件
<template>
<A></A>
<B></B>
<div ref="target">
<AsyncComp v-if="targetIsShow"></AsyncComp>
</div>
</template>
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
import { useIntersectionObserver } from '@vueuse/core'; // npm插件
import A from './A.vue';
import B from './B.vue';
const AsyncComp = defineAsyncComponent(() =>
import('./C.vue')
)
const target = ref<any>(null);
const targetIsShow = ref(false);
useIntersectionObserver(
target,
([{ isIntersecting }]) => {
if (isIntersecting) {
targetIsShow.value = isIntersecting
}
}
)
</script>
defineAsyncComponent 打包时做分包处理,异步组件是一个单独的js文件。
含义:分发vue组件中可复用的功能。
注意:vue3中使用Hooks
// mixin.ts
import { ref } from "vue";
export default function() {
let num = ref(1);
let bel = ref(true)
const btn = () => {
num.value += 1
bel.value = false
setTimeout(() => {
bel.value = true
}, 3000);
}
return {
num,
bel,
btn
}
}
A组件使用
<template>
<div style="width:300px;height: 200px;background-color: #f60;">
<h1>A组件</h1>
{{ num }}
<button @click="btn">{{ bel }}</button>
</div>
</template>
<script setup lang="ts">
import mixin from '../mixin/mixin';
let { num, bel, btn } = mixin();
</script>
B组件使用
<template>
<div style="width:300px;height: 600px;background-color: blue;">
B组件
{{ num }}
<button @click="btn">{{ bel }}</button>
</div>
</template>
<script setup lang="ts">
import mixin from '../mixin/mixin';
let { num, bel, btn } = mixin();
</script>
在组合API中,我们需要将生命周期钩子导入到项目中,才能使用,这有助于保持项目的轻量性。
import { onMounted } from 'vue'
除了 beforecate 和 created (它们被 setup 方法本身所取代),我们可以在 setup 方法中访问的API生命周期钩子有9个选项
onBeforeMount – 在挂载开始之前被调用:相关的 render 函数首次被调用。
onMounted – 组件挂载时调用
onBeforeUpdate – 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
onUpdated – 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
onBeforeUnmount – 在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。
onUnmounted – 卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。
onActivated – 被 keep-alive 缓存的组件激活时调用。
onDeactivated – 被 keep-alive 缓存的组件停用时调用。
onErrorCaptured – 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。
<template>
<SonVue msg="父组件的值" />
</template>
<script setup lang="ts">
const props = defineProps(
{ msg: String }
)
console.log(props);
</script>
<template>
<SonVue @getuser="getuser" />
</template>
<script setup lang="ts">
const getuser = (val:string) => {
console.log(val)
}
</script>
<template>
<button @click="buttonClick">传输</button>
</template>
<script setup lang="ts">
const emit = defineEmits(['getuser'])
const buttonClick = ()=>{
emit('getuser','我是子组件传给父组件的值')
}
</script>
子组件
<template>
<slot></slot>
</template>
父组件
<template>
<SonVue>
插槽传递
</SonVue>
</template>
<template>
<SonVue>
<template #title>这是具名插槽</template>
</SonVue>
</template>
<template>
<slot name="title"></slot>
</template>
# 数据在子组件的自身,但根据数据生成的结构需要父组件决定
<template>
<!-- 父组件将信息传递给子组件 -->
<SonVue :person="person">
<!-- 子组件接收父组件的插槽中传的值 -->
<template #tab="scope">
<tr v-for="(item, index) in scope.person" :key="index">
<th>{{ item.name }}</th>
<th>{{ item.age }}</th>
<th>
<button>编辑</button>
</th>
</tr>
</template>
</SonVue>
</template>
<script setup lang="ts">
const person = [
{ name: '小明', age: 20 },
{ name: '小红', age: 18 }
]
</script>
<template>
<table border="1">
<tr>
<th>姓名</th>
<th>年龄</th>
<th>操作</th>
</tr>
<slot name="tab" :person="props.person"></slot>
</table>
</template>
<script setup lang="ts">
const props = defineProps<{ person: { name: string, age: number } }>()
</script>
官网称为依赖注入,Provide 为提供,Inject为注入
<template>
<h1>我是父组件</h1>
<SonVue></SonVue>
</template>
<script setup lang="ts">
import { provide, ref } from "vue";
let flag = ref<number>(10);
provide("flag", flag);
</script>
<template>
<button>子组件</button>
<div> {{ flag }} </div>
<button @click="flag++">+1</button>
</template>
<script setup lang="ts">
import { inject, ref } from 'vue';
const flag = inject('flag',ref(1))
</script>
注意:Provide / Inject是具有响应式的,而且是单向数据流。
npm install vue-router
// src/router/index.js
import { createRouter, createWebHistory } from "vue-router";
const routes = [
{
path: '/',
redirect: '/index'
},
{
path: '/index',
name: 'index',
component: () => import('../views/index/index.vue')
},
{
path: '/account',
name: 'account',
component: () => import('../views/account/index.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
});
export default router
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index.js'
createApp(App).use(router).mount('#app')
app.vue设置路由出口
// App.vue
<template>
<router-view></router-view>
</template>
index.vue 跳转到 account.vue
<template>
<div>
<h1>首页</h1>
<!-- 路由跳转方式 1 -->
<router-link to="/account">去个人中心</router-link>
<!-- 路由跳转方式 2 -->
<button @click="gotoAccount">去个人中心</button>
</div>
</template>
<script setup>
import { useRoute, useRouter } from 'vue-router';
// useRoute 类似于 vue2 的 this.$route
useRoute == this.$route
// useRouter 类似于 vue2 的 this.$router
useRouter == this.$router
const router = useRouter();
const gotoAccount = () => {
console.log(router);
router.push('/account')
}
</script>
<template>
{{ (x as number).toFixed(2) }} // 正确
{{ (<number>x).toFixed(2) }} // 正确
{{ x.toFixed(2) }} // IDE提示:类型“string | number”上不存在属性“toFixed”
</template>
<script setup lang="ts">
let x:string | number = 1
</script>
1.src下定义types文件夹命名xx.d.ts
2.建立全局接口global.d.ts
interface globalInterface{
name:string
age:string
}
3.组件中直接使用
<script setup lang="ts">
const props = defineProps<{person:globalInterface[]}>()
</script>
4.如果不是在src下则需要在tsconfig.json中配置全局目录
{
{
...
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], //配置全局目录
"references": [{ "path": "./tsconfig.node.json" }]
}
1.使用环境:全局定义的数据,函数在vue组件中直接访问报错
2.index.html中定义数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script>
const global=1
</script>
</body>
</html>
3.定义类型增强
// common.d.ts
declare const global:string;
4.组件中直接读取
<script setup lang="ts">
console.log(global)
</script>
<template>
<SonVue msg="父组件传过来的值"></SonVue>
</template>
<template>
{{ props.msg }}
</template>
<script setup lang="ts">
interface msgInterface{
msg:string
}
// withDefaults 帮助程序为默认值提供类型检查,并确保返回的 props 类型删除了已声明默认值的属性的可选标志。
const props = withDefaults(defineProps<msgInterface>(),{
msg:'dasdas'
})
console.log(props.msg);
</script>
<template>
<SonVue @getuser="getuser"></SonVue>
</template>
<script setup lang="ts">
const getuser = (a:number)=>{
console.log('我是父组件打印的值');
}
</script>
<template>
<button @click="buttonClick">传输</button>
</template>
<script setup lang="ts">
const emit = defineEmits<{(e:'getuser',id:number):void}>() // (e: 事件名, 键名:类型): void
const buttonClick = ()=>{
emit('getuser',1)
}
</script>
<template>
<button>我是父组件</button>
<SonVue></SonVue>
</template>
<script setup lang="ts">
import { provide, ref } from 'vue';
let flag = ref<number>(99)
provide('flag',flag)
</script>
<template>
<h2>我是子组件</h2>
<div>{{flag}}</div>
<button @click="(flag as number)++">+1</button>
</template>
<script setup lang="ts">
import { Ref, inject, ref } from 'vue';
const flag:Ref<number | string> = inject('flag',ref(1))
</script>
父组件
<template>
{{ num }}
<HelloWorld v-model="num" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
const num = ref(1)
</script>
子组件
<template>
<input type="text" v-model="value">
</template>
<script lang="ts" setup>
import { computed } from "vue";
const props = defineProps<{modelValue:number}>();
const emit = defineEmits<{(e:'update:modelValue',id:number):void}>();
const value = computed({
get(){
return + props.modelValue
},
set(value){
emit('update:modelValue',+value)
}
})
</script>
v-model原理
<template>
{{ num }}
<HelloWorld :modelValue="num" @update:modelValue="num = $event"/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
const num = ref(1)
</script>
// main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
const app = createApp(App)
app.directive('focus',{
mounted(el){
el.focus()
}
})
app.mount('#app')
<template>
<input type="text" v-model="value" v-focus />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
let value = ref('全局自定义指令')
</script>
<template>
<input type="text" v-focus />
</template>
<script setup lang="ts">
const vFocus = {
mounted: (el: any)=>{
el.focus()
}
}
</script>
<template>
<!-- 自定义指令,参数,修饰符 -->
<div v-move:a.x="{ background: 'red' }"></div>
</template>
<script setup lang="ts">
import type { Directive, DirectiveBinding } from 'vue';
type Dir = { background: string };
const vMove: Directive = {
// 元素初始化的时候
created() { },
// 指令绑定到元素后调用 只调用一次
beforeMount() { },
// 元素插入父级dom调用
mounted(el: HTMLElement, dir: DirectiveBinding<Dir>) {
console.log(dir.value.background);
el.style.background = dir.value.background
},
// 元素被更新之前调用
beforeUpdate() { },
// 元素被更新之后调用 改用updated
updated() { },
// 在元素被移除前调用
beforeUnmount() { },
// 指令被移除后调用 只调用一次
unmounted() { }
}
</script>
<template>
<!-- 自定义指令,参数,修饰符 -->
<div v-move:a.x="{ background: 'red' }">自定义指令生命周期简写</div>
</template>
<script setup lang="ts">
import type { Directive, DirectiveBinding } from 'vue';
type Dir = { background: string };
const vMove: Directive = (el: HTMLElement, dir: DirectiveBinding<Dir>)=>{
el.style.background = dir.value.background
}
</script>
<template>
<!-- 自定义指令,参数,修饰符 -->
<div v-move style="
background-color: red;
width: 200px;
height: 200px;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
">
<div style="background-color: black; width: 200px; color: white">
自定义指令
</div>
</div>
</template>
<script setup lang="ts">
import type { Directive } from 'vue';
const vMove: Directive = (el: HTMLElement) => {
const move = (e: MouseEvent) => {
console.log(e);
el.style.left = e.clientX + 'px'
el.style.top = e.clientY + 'px'
}
// 鼠标按下
el.addEventListener('mousedown', () => {
// 鼠标按下拖拽
document.addEventListener('mousemove', move);
// 鼠标松开
document.addEventListener('mouseup', () => {
// 清除事件
document.addEventListener('mousemove', move)
});
})
}
</script>
<script>
let obj = {
name: "小明",
age: 26,
};
let agent = new Proxy(obj, {
/*
target表示obj这个对象
property表示读取的属性的key
*/
get(target, property) {
console.log("执行了get方法");
return target[property];
},
});
console.log(agent.age);
</script>
let obj = {
name: "小明",
age: 26,
};
let agent = new Proxy(obj, {
/*
target表示obj这个对象
property表示读取的属性的key
newValue表示设置的值
*/
set(target, property, newValue) {
console.log('执行了set方法');
target[property] = newValue
return true
},
});
agent.age=30
console.log(agent.age);
注意:定义 Proxy 代理对象的 set 的时候,要返回 return true ,特别是在严格模式下,否则,
会报错 'set' on proxy: trap returned falsish for property 'age'
<script>
let person = {
name: "小明",
sex: '男',
};
// 为 person对象 传输了一个新属性 “age”,并且设定它的值为 18
Object.defineProperty(person,'age',{
value : 18
})
console.log(person)
</script>
<script>
let person = {
name: '张三',
sex: '男',
}
function Observer(obj) {
const keys = Object.keys(obj)
keys.forEach((key) => {
Object.defineProperty(this,key,{
get() {
return obj[key]
}
})
})
}
const obs = new Observer(person)
console.log(obs.sex);
</script>
<script>
let person = {
name: '张三',
sex: '男',
}
function Observer(obj) {
const keys = Object.keys(obj)
keys.forEach((key) => {
Object.defineProperty(this,key,{
set(val) {
console.log('set方法调用了')
obj[key] = val
}
})
})
}
const obs = new Observer(person)
obs.name=15
</script>