环境搭建:
npm init vue\@latest
vue3 文件介绍和变化
组合式API
组合式API写法,在<script>标签上加上关键字setup
setup选项在beforeCreate之前自动执行,其特点是 定义数据+函数+以对象形式return,setup中this指向undefined
<template>
<div>{{msg}}</div>
</template>
<script setup>
//在script标签上加setup为语法糖,setup标签语法糖为我们做的事如下注释
// export default {
// name: "setupTest",
// //setup再beforeCreate之前调用
// //vue3中this指向undefined
// setup(){
// const msg = 'msg';
// const logMsg = ()=>{
// console.log(msg);
// };
// //必须return出才能使用
// return {
// msg,
// logMsg,
// }
// },
// beforeCreate(){
//
// }
// }
const msg = 'msg';
const logMsg = () => {
console.log(msg);
};
</script>
<style scoped>
</style>
响应式数据
reactive 针对对象类型数据
使用方法
1.从vue中导入reactive
2.执行reactive函数,并传入一个对象类型参数,用一个变量接收它
<script setup>
//导入
import {reactive} from 'vue';
//执行函数 传入一个变量参数 变量接受
const status = reactive({
count:0
});
const addCount = ()=>{
status.count++;
}
</script>
ref 支持所有数据类型
使用方法
1.从vue包中导入ref
2.在
<script setup>中执行ref函数并传入初始值,并用变量接收ref函数的返回值在脚本区更改ref产生的响应式对象数据,必须通过.value属性
<script setup>
//导入ref
import {ref} from 'vue';
//执行ref函数
const count = ref(0);
const addCount = ()=>{
//在脚本区更改ref产生的响应式对象数据,必须通过.value属性
count.value++
}
</script>
reactive vs ref
- 1.reactive不能处理简单类型的数据
- 2.ref参数类型支持更好但是必须通过.value访问修改
- 3.ref函数的内部实现依赖于reactive函数
在实际工作中推荐使用ref函数,更加灵活
computed计算属性
计算属性基本思想和Vue2的完全一致,组合式API下的计算属性只是修改了写法
使用方法:
1.导入computed函数
2.执行函数在回调参数中return基于响应式数据做计算的值,用变量接收
<template>
<div>原始数据:{{arr}}</div>
<div>计算属性数据:{{state}}</div>
<button @click="pushValue">push 9 </button>
</template>
<script setup>
//导入
import {computed,ref} from 'vue';
//响应式数组
const arr = ref([1,2,3,4,5,6,7,8]);
//执行computed函数
const state = computed(()=>{
return arr.value.filter((item)=>{
if(item>2) return true;
})
});
//验证
const pushValue = () =>{
arr.value.push(9);
}
</script>
计算属性中应只有计算,不应该有“副作用(如:异步请求、修改dom等)”,且避免直接修改计算属性的值,计算属性应该是只读的
watch函数
使用方法:
1.导入watch函数
2.执行watch函数传入要侦听的响应式数据(ref对象)和回调函数
两个属性immediate和deep介绍
- immediate : 在侦听器创建时立即触发回调,响应式数据变化之后继续执行回调,
- deep : 通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启deep选项, deep开启会有性能损耗,在工作中,绝大多数情况下,不会开启,尽量精准监听
监听单个数据源
<script setup>
import {watch,ref} from 'vue';
const state = ref(0);
const addState = () =>{
state.value ++ ;
};
//代用watch 单个数据源
watch(state,(newValue,oldValue)=>{
console.log('state从' + oldValue + '变为' + newValue);
},{
immediate:true,//在侦听器创建时立即触发回调,响应式数据变化之后继续执行回调,
//deep:true,
});
</script>
监听多个数据源,数据源与新老数据都以数组形式显示
<script setup>
import {watch,ref} from 'vue';
const name = ref(123);
const count = ref(0);
//使用watch 多个数据源
watch([name,count],([newName,newCount],[oldName,oldCount])=>{
console.log('name或count变化了')
})
</script>
精准监听,多应用于对象,第一个参数为要监听的属性,
<script setup>
//导入watch、
import {watch,ref} from 'vue';
const person = ref({
name:'lilei',
age:18,
});
const chageAge = ()=>{
person.value.age ++;
};
//精准监听某个具体属性
watch(
()=>person.value.age,//坑:不是对象,是对象属性,注意写法,是箭头函数!!!!!精准监听某个具体属性
(newValue,oldValue)=>{
console.log('age +1 了');
},
{
immediate:true,
// 通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启deep选项,
//deep开启会有性能损耗,绝大多数情况下,不会开启,尽量精准监听
//deep:true,
}
)
</script>
生命周期
使用方法:
- 1导入生命周期函数
- 2.执行生命周期函数 传入回调
- 3.生命周期函数是可以执行多次的,多次执行时传入的回调会在时机成熟时依次执行
这里以onMounted为例:
<script setup>
import {onMounted} from 'vue';
onMounted(()=>{
//doSomething
console.log('onMounted1');
});
onMounted(()=>{
//doSomething
console.log('onMounted2');
})
</script>
通信
父传子
与vue2不同的地方在于子组件需要通过defineProps ’编译器宏函数‘接收父组件传递的数据
//语法:
defineProps({
//属性名:属性类型
})
parent template
<template>
<!-- 给子组件绑定parentMsg-->
<children :count="count" parentMsg="这是parent传给children的值"></children>
</template>
<script setup>
//引入子组件,可以直接使用
import children from "./children";
import {ref} from 'vue';
const count = ref(0);
</script>
children template
<template>
<div>{{parentMsg}}</div>
<div>count:{{count}}</div>
</template>
<script setup>
//通过defineProps ’编译器宏‘接收父组件传递的数据
const prop = defineProps({
parentMsg:String,
count:Number,//响应式数据
});
</script>
子传父
与vue2不同在于,在子组件中需要通过defineEmits编译器宏函数生成emit
//语法:
defineEmits(
['事件名数组']
)
parent template
<template>
<!-- 绑定自定义事件getChildrenMsg-->
<children @getChildrenMsg="getChildrenMsg"></children>
</template>
<script setup>
//引入子组件,可以直接使用
import children from "./children";
import {ref} from 'vue';
const count = ref(0);
const getChildrenMsg = (childrenMsg)=>{
console.log(childrenMsg);
}
</script>
children template
<script setup>
//通过defineEmits编译器宏函数生成emit
const emit = defineEmits(['getChildrenMsg']);
const sendMsg = ()=>{
//触发自定义事件 并传递参数
emit('getChildrenMsg','我是子组件传给父组件的msg');
}
</script>
模板引用 ref
模板引用概念:通过ref标识获取真实的dom对象或者组件实例对象
使用方法:
- 调用ref函数生成一个ref对象
- 通过ref标识绑定ref对象到标签
默认情况下,组件实例在<script setup>语法糖下组件内部的属性和方法是不开放给父组件访问的,可以通过defineExpose编译宏指定哪些属性和方法允许访问
//语法
defineExpose(
{//属性或方法}
)
refText template
<template>
<!-- 2. 通过ref标识绑定ref对象-->
<div ref="refObj">我是div标签</div>
<children ref="childRef"></children>
</template>
<script setup>
import children from "./children";
import {ref,onMounted} from 'ref';
// 1.调用ref函数得到ref对象 用null占位置
const refObj = ref(null);//标签实例对象
const childRef = ref(null);//组件实例对象
//验证:组件挂载完毕后才可获取
onMounted(()=>{
console.log(refObj.value);
console.log(childRef.value)
})
</script>
children template
<script setup>
import {ref} from 'vue';
const name = ref('children');
const setName = ()=>{
name.value = 'lilei';
};
//子组件通过defineExpose公开可访问是属性和方法
defineExpose({
name,
setName
})
</script>
跨层组件通信——provide和inject
作用:顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信
第一层:通过provide提供数据或修改数据方法
<template>
<div>顶层</div>
<button @click="setCount">{{count}}</button>
<br>
<partition-tow></partition-tow>
</template>
<script setup>
import partitionTow from "./partitionTow.vue";
import {provide,ref} from 'vue';
const count = ref(0);
//1.顶层提供数据
provide('data-key','partitionFirstMsg');
provide('data-count',count);//坑:传的是count不是count.value
const setCount = ()=>{
count.value++;
};
provide('setCount',setCount);//坑2:如果使用var定义参数,注意provide放置位置,推荐使用let和const定义参数
第n层(不用管是第几层哈,都是同样的用法):使用inject接收使用provide传的数据和方法
<template>
<div>底层:{{msg}}</div>
<div>底层响应式数据:{{count}}</div>
<button @click="setCount">three +count</button>
</template>
<script setup>
import {inject} from 'vue';
//2.接收数据
const msg = inject('data-key');
const count = inject('data-count');
//接收方法,更改数据 时需要使用provide传递相应修改函数
//谁的数据谁修改
const setCount = inject('setCount');
</script>
自定义指令
钩子函数
一个指令的定义对象可以提供几种钩子函数 (都是可选的):
const vFocus = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode, prevVnode) {
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode, prevVnode) {}
}
指令的钩子会传递以下几种参数:
-
el:指令绑定到的元素。这可以用于直接操作 DOM。 -
binding:一个对象,包含以下属性。value:传递给指令的值。例如在v-my-directive="1 + 1"中,值是2。oldValue:之前的值,仅在beforeUpdate和updated中可用。无论值是否更改,它都可用。arg:传递给指令的参数 (如果有的话)。例如在v-my-directive:foo中,参数是"foo"。modifiers:一个包含修饰符的对象 (如果有的话)。例如在v-my-directive.foo.bar中,修饰符对象是{ foo: true, bar: true }。instance:使用该指令的组件实例。dir:指令的定义对象。
-
vnode:代表绑定元素的底层 VNode。 -
prevNode:之前的渲染中代表指令所绑定元素的 VNode。仅在beforeUpdate和updated钩子中可用。
局部指令
在 <script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令。如下面的例子中,vFocus 即可以在模板中以 v-focus 的形式使用。
<script setup>
// 在模板中启用 v-focus
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
全局指令
在main.ts中定义指令并注册指令
app.directive('img-lazy',{
mounted(el,binding){
//vueuse 实现
const {stop } = useIntersectionObserver(el,([{ isIntersecting }]) =>{
if(isIntersecting) {
el.src = binding.value;
stop();
}
})
}
})
注意再实际开发中不推荐在main.ts中定义指令,main.ts中应该只是初始化 可以在src中创建directives目录,在目录中添加指令,最后在main.ts中引入并注册 如下面代码和图中所示:
index.js代码:
import {useIntersectionObserver} from "@vueuse/core";
export const imgLazy = {
//坑:要在install中写指令哈
install(app){
//懒加载指令容器
app.directive('img-lazy',{
mounted(el,binding){
//vueuse 实现
const {stop } = useIntersectionObserver(el,([{ isIntersecting }]) =>{
if(isIntersecting) {
el.src = binding.value;
stop();
}
})
}
})
}
}
main.ts 代码:
//引入懒加载指令插件并且注册
import {imgLazy} from '@/directives/index.js'
//注册指令
app.use(imgLazy)
注册全局组件
可以在main.ts中使用app.component('组件名',组件配置对象)直接注册全局组件,这里不过多阐述,工作中常用的是下面这种形式进行全局注册。
- 在components目录中添加index.js,如下图所示:
index.js代码:
//把components中常用组件进行全局话注册
//引入组件
//坑:要把组件名写完整,包括后缀名!!!!!!
import refTest from '@/components/vueBase/refTest.vue'
export const componentPlugin = {
install(app){
//app.component('组件名','组件配置对象')
app.component('refTest',refTest);
}
}
- 在main.ts中引入并注册:
//引入全局组件
import {componentPlugin} from '@/components/index.js'
//以App作为一个参数,创建一个应用实例对象
const app = createApp(App);
//注册全局组件
app.use(componentPlugin);
//挂载到id为app的节点上
app.mount('#app');