目录
vue3.0
vue2和vue3的区别
1vue2 和vue3双向数据绑定原理不同
2.0的响应式基于Object.defineProperty中的set和get方法实现兼容主流浏览器和ie9以上的ie浏览器,能够监听数据对象的变化,但是监听不到对象属性的增删、数组元素和长度的变化,同时会在vue初始化的时候把所有的Observer(观察者)都建立好,才能观察到数据对象属性的变化。
3.0的响应式采用了ES2015的Proxy来代替Object.defineProperty,可以做到监听对象属性的增删和数组元素和长度的修改,同时还实现了惰性的监听(不会在初始化的时候创建所有的Observer,而是会在用到的时候才去监听)但是,虽然主流的浏览器都支持Proxy,ie系列却还是不兼容,所以针对ie11,vue3.0决定做单独的适配,暴露出来的api一样,但是底层实现还是Object.defineProperty
2vue3默认进行懒观察(lazy observation)
vue2.0中数据一开始就创建了观察者,数据很大的时候,就会出现问题,vue3中进行了优化 只有用于渲染初始化可见部分的数据,才会创建观察者,效率更高。
3Vue3 对ts的支持更好
4项目目录结构发生了变化
2.x中 移除了配置文件目录,config 和 build 文件夹 3.x中 相关的配置需要在根目录中手动创建vue.config.js
5更精准的变更通知
2.x 版本中,你使用 Vue.set 来给对象新增一个属性时,这个对象的所有 watcher 都会重新运行; 3.x 版本中,只有依赖那个属性的 watcher 才会重新运行
6.vue2组件属性方式变成CompositionAPI(组合式)函数式风格。
vue/cli 5x
在vue3开发中 脚手架vue/cli版本必须最新 cnpm install -g @vue/cli
创建项目同 之前方式 使用 vue create 项目名
注意 在vue/cli 5x中对我们的文件名 有约束必须大驼峰
注意 在项目根目录找到一个vue.config.js 文件,没有就在根目录创建一个,写上下面标注的代码,保存,在重新编译。项目就可以正常运行了。
const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, lintOnSave: false//关闭eslint的校验 })
Vue3 新特性函数 ---- setup 启动器
1.setup函数是处于 生命周期函数 beforeCreate 和 Created 两个钩子函数之间的函数 也就说在 setup函数中是无法 使用 data 和 methods 中的数据和方法的 2、setup函数是 Composition API(组合API)的入口 3、在setup函数中定义的变量和方法最后都是需要 return 出去的 不然无法再模板中使用
注意:vue3和vue2不同的地方就是不必写 data、methods、等代码块了所有的东西都可以在 setup 中返回
setup 可以返回两种值:
返回对象
1、返回对象,对象中的属性、方法都可以直接在模板中使用--稍后马上学到
返回渲染函数(很少用)
2、返回渲染函数,可以自定义渲染内容(其实也就是 render 函数)注意:渲染函数优先级还高,他会替换掉模板中的其他内容(用的比较少记住就好)
VUE3中h()函数和createVNode()函数的使用
h()函数和createVNode()函数都是创建dom节点,他们的作用是一样的,但是在VUE3中createVNode()函数的功能比h()函数要多且做了性能优化,渲染节点的速度也更快。
语法:
h(标签, {属性},内容)
<template> <div> </div> </template> <script> // 1.要使用先引用 import {h} from "vue" export default { setup() { return () => h('div',{class:"demodiv"}, ['Hello,Vue3']) } } </script> <style> </style>
h(标签, {属性},[可以继续嵌套h()])
<template> <div> </div> </template> <script> // 1.要使用先引用 import {h} from "vue" export default { setup() { return () => h('div',{class:"demodiv"}, [ h("h1",{class:"demoh"},["我是标题标签"]) ]) } } </script> <style> </style>
createVNode(标签, {属性},[内容])
<template> <div> </div> </template> <script> // 1.要使用先引用 import {createVNode} from "vue" export default { setup() { return () => createVNode('div',{class:"demodiv"}, ['Hello,Vue3']) } } </script> <style> </style>
createVNode(标签, {属性},[可以继续嵌套createVNode()])
<template> <div> </div> </template> <script> // 1.要使用先引用 import {createVNode} from "vue" export default { setup() { return () => createVNode('div',{class:"demodiv"}, [ createVNode("h1",{class:"demoh"},["我是标题标签"]) ]) } } </script> <style> </style>
setup参数
setup有两个形参
参数1第一个参数props
props是一个对象,包含父组件传递给子组件的所有数据。
父组件
<template> <div> 我是父组件 <Zc title="我是正向传值的数据"/> </div> </template> <script> import { defineComponent } from 'vue' import Zc from "../components/ZiCom.vue" //defineComponent最重要的是:在TypeScript下,给予了组件 正确的参数类型推断 //defineComponent最重要的是:在TypeScript下,给予了组件 正确的参数类型推断 export default defineComponent({ components:{ Zc }, setup() { return { } }, }) </script> <style> </style>
子组件
<template> <div> 我是子组件--{{title}} </div> </template> <script> import { defineComponent } from 'vue' export default defineComponent({ props:["title"],//子组件设置props setup(props) {//setup第一个参数是props console.log(props)//如果想在setup中读取props数据必须设置第一个形参 return { } }, }) </script> <style> </style>
参数2第一个参数context
context是setup的第二个参数里面包含了以下三个属性 attrs emit slots
attrs
attrs 获取当前标签上面的所有属性的对象
注意 attrs是接收props没有声明的属性
注意 如果子组件用props已经声明 就不能使用attrs 否则会返回undefind
父组件
<template> <div> 我是父组件 <Zc title="我是正向传值的数据"/> </div> </template> <script> import { defineComponent } from 'vue' import Zc from "../components/ZiCom.vue" export default defineComponent({ components:{ Zc }, setup() { return { } }, }) </script> <style> </style>
子组件
<template> <div> 我是子组件--{{title}} </div> </template> <script> import { defineComponent } from 'vue' export default defineComponent({ // props:["title"],//如果使用attrs接收就不能声明props否则接收不到 setup(props,context) {//setup第2个参数是context console.log(context.attrs) return { } }, }) </script> <style> </style>
总结:
什么时候用props来接收数据 什么时候使用context的attrs来接受数据
1.如果你接收来的数据想在页面展示的时候你使用props来接收(使用props接受的时候 必须定义props这个属性)
2.如果父组件传递过来的数据你不想在页面展示 只是想使用这个数据进行一些逻辑处理 那么你可以使用context.attrs来进行接受(如果想使用 不能定义props)
emit自定义事件
emit事件分发 用于子传父 如果子组件的数据想传递给父组件 就是用emit(逆向传值)
子组件
<template> <div> 我是子组件 <button @click="zifun">点我抛出事件</button> </div> </template> <script> import { defineComponent } from 'vue' export default defineComponent({ setup(props,context) { // 子组件通过context的emit方法进行数据的抛出 let zifun=()=>{ context.emit("zipao",'我是子组件的数据') } return {//不要忘了return zifun } }, }) </script> <style> </style>
父组件
<template> <div> 我是父组件 <!-- 接收子组件的数据 注意不加()不加()--> <Zc @zipao="fufun"/> </div> </template> <script> import { defineComponent } from 'vue' import Zc from "../components/ZiCom.vue" export default defineComponent({ components:{ Zc }, setup() { let fufun=(val)=>{ console.log("子组件的数据",val) } return { fufun } }, }) </script> <style> </style>
slots
插槽 带有dom的属性 (用来接收父组件传递的html内容)
子组件
<template> <div> 我是子组件 <!-- 定义槽口 --> <slot name="com"></slot> </div> </template> <script> import { defineComponent } from 'vue' export default defineComponent({ setup(props,context) { console.log(context.slots) return { } }, }) </script> <style> </style>
父组件
<template> <div> 我是父组件 <Zc> <!-- 两个语法不能同时使用 <template v-slot:com> <h1>我是插入的内容</h1> </template> --> <template #com> <h1>我是插入的内容</h1> </template> </Zc> </div> </template> <script> import { defineComponent } from 'vue' import Zc from "../components/ZiCom.vue" export default defineComponent({ components:{ Zc }, setup() { return { } }, }) </script> <style> </style>
总结setup
setup是vue3中的CompositionAPI的启动器 他是会在beforecreate 与created之间去执行的 setup有2个形参 2中返回值
2个形参分别是 props和context 其中props就是接收父组件传递过来的属性 context其中包含的内容就比叫多了 他有三个属性 attrs emit slots
attrs获取当前标签上面的所有属性的对象 但是不能和props属性连用 如果连用了 那么会返回什么都没有
emit自定义事件
slots 插槽 带有dom的属性 (用来接收父组件传递的html内容)
2中返回值 分别是 对象 和 返回渲染函数
对象 就是要把定义在setup中的属性与方法必须返回出去 要不然模板不能使用
渲染函数 返回渲染函数的优先级比 写在模板中的内容优先级高 所以写了渲染函数 会覆盖原有的模板内容 如果要返回渲染函数 还要配合vue中提供的 h函数 或者是createVNode函数 createVNode()函数的功能比h()函数要多且做了性能优化,渲染节点的速度也更快。
新语法
Setup新方式3.2:它相对于之前vue3的写法 进行了语法升的升级 可以理解为是之前语法的 语法糖(全称 糖衣语法 他其实就是用简单的语法来描述之前复杂的写法)
-
不需要return任何东西,所有定义的变量和方法会自动导出,template模板可以直接使用
-
import引入的组件也会自动导出,模板里可以直接引用。
-
引入的组件命名需要首字母大写。
-
确切的说是:所有的变量方法和import导入都可以在模板中直接使用。
<script setup> </script>
ref
ref:的主要作用是定义数据和进行dom操作 ref 它接收一个参数作为值,然后返回一个响应式对象 要是想改变 ref 的值 必须改变它里面有个属性 value
ref创建基本类型数据
<template> <div> 我是一个组件-{{text}} </div> </template> // <script> // export default { // // 旧方式 // setup(){ // let text=ref("你好我是数据") // return { // text // } // } // } // </script> <script setup> import {ref} from "vue" let text=ref("你好我是数据") </script> <style> </style>
要是想改变 ref 的值 必须改变它里面有个属性 value
<template> <div> 我是一个组件-{{ text }} <button @click="fun()">点我修改</button> </div> </template> // <script> // export default { // // 旧方式 // setup() { // let text = ref("你好我是数据"); // let fun = () => { // text.value = "我变了"; // }; // return { // text, // fun, // }; // }, // }; // </script> <script setup> import { ref } from "vue"; let text = ref("你好我是数据"); let fun = () => { text.value = "我变了"; }; </script> <style> </style>
获取dom的ref
<template> <div> <!-- 2.绑定 --> <h1 ref="demoh">我是一个dom</h1> <button @click="fun()">点我获取</button> </div> </template> <script setup> import { ref } from "vue"; // 1.创建 let demoh = ref(null); let fun = () => { // 3使用 这个.value是ref获取数据的时候必要的 console.log(demoh.value) }; </script> <style> </style>
ref总结
在vue3中 ref可以创建基本数据类型的响应式变量 ref在创建出基本数据类型之外 想要修改的话 必须.value来进行修改
ref 如果传递null 那么久变成了页面dom操作的一员 在查找dom的时候 不要忘了也要.value 才能获取到dom数据
reactive:定义对象、数组
<template> <div> <h1>{{obj.name}}</h1> <button @click="fun()">修改</button> </div> </template> <script setup> import { reactive } from "vue"; let obj = reactive({ name:"xixi", age:18 }); let fun = () => { obj.name="haha" }; </script> <style> </style>
ref和reactive区别
使用方式:ref修改数据需要使用这样:数据.value=xxx的形式,而reactive只需要数据.key=值 这样来使用
计算属性computed
计算属性computed是用对数据进行逻辑处理操作,实现数据包装。计算属性通常依赖于当前vue对象中的普通属性
当依赖的依赖的普通属性发生变化的时候,计算属性也会发生变化。
基本使用
<template> <div> <input type="text" v-model="data"> <h1>默认的:{{data}}</h1> <h1>变大写的:{{newdata}}</h1> </div> </template> <script setup> import {ref,computed} from "vue" let data=ref("abcdefg"); // 计算属性 let newdata=computed(()=>{ return data.value.toUpperCase() }) </script> <style> </style>
watch监听
监听单个数据
<template> <div> <input type="text" v-model="data" /> <h1>{{ data }}</h1> </div> </template> <script setup> import { ref, watch } from "vue"; let data = ref("abcdefg"); // 第一个参数是你要监听的数据 // 第二个参数是数据改变之后触发的函数 watch(data, (newVal, oldVal) => { console.log("newVal", newVal); console.log("oldVal", oldVal); }); </script> <style> </style>
监听多个数据
将需要监听的数据添加到数组
<template> <div> <input type="text" v-model="data" /> <h1>{{ data }}</h1> <input type="text" v-model="datab" /> <h1>{{ datab }}</h1> </div> </template> <script setup> import { ref, watch } from "vue"; let data = ref("第1个数据"); let datab = ref("第2个数据"); // 第一个参数是你要监听的数据多个数据使用数组来表示 // 第二个参数是数据改变之后触发的函数 watch([data,datab], (newVal, oldVal) => { console.log("newVal", newVal); console.log("oldVal", oldVal); }); </script> <style> </style>
watch监听对象
在vue2中watch默认是无法监听到对象中的属性 必须手动开启deep深度监听
在vue3中强制开启deep深度监听(但是注意如果使用默认的watch语法当 监听值为响应式对象时,oldValue值将出现异常,此时与newValue相同)
观察下面得代码会发现 newval和oldval的值是一样的
<template> <div> <input type="text" v-model="obj.name" /> <h1>{{ obj.name }}</h1> </div> </template> <script setup> // 监听对象 // 在watch监听对象的时候vue3中会开启强制deep深度监听 import { reactive, watch } from "vue"; let obj=reactive({ name:"xixi", age:18 }) // 第一个参数是你要监听的数据多个数据使用数组来表示 // 第二个参数是数据改变之后触发的函数 watch(obj, (newVal, oldVal) => { console.log("newVal", newVal); console.log("oldVal", oldVal); }); </script> <style> </style>
监听对象属性正确写法
在监听对象的时候我们可以把第一个参数设置成函数并且返回你要监听的数据 这样new和oldval正常了
<template> <div> <input type="text" v-model="obj.name" /> <h1>{{ obj.name }}</h1> </div> </template> <script setup> import { reactive, watch } from "vue"; let obj=reactive({ name:"xixi", age:18 }) // 在监听对象的时候我们可以把第一个参数设置成函数并且返回你要监听的数据 watch(()=>{return obj.name}, (newVal, oldVal) => { console.log("newVal", newVal); console.log("oldVal", oldVal); }); </script> <style> </style>
监听多个对象属性
监听多个对象属性的时候使用数组来进行设置
<template> <div> <input type="text" v-model="obj.name" /> <input type="text" v-model="obj.age" /> <h1>{{ obj.name }}</h1> <h1>{{ obj.age }}</h1> </div> </template> <script setup> import { reactive, watch } from "vue"; let obj=reactive({ name:"xixi", age:18 }) // 监听多个对象属性的时候使用数组来进行设置 watch([()=>{return obj.name},()=>{return obj.age}], (newVal, oldVal) => { console.log("newVal", newVal); console.log("oldVal", oldVal); }); </script> <style> </style>
手动开启deep深度监听
当监听响应式对象的属性为复杂数据类型时,需要开启deep深度监听
当监听proxy对象的属性为复杂数据类型时,需要开启deep深度监听
当监听proxy对象的属性为复杂数据类型时,需要开启deep深度监听
比如下面得数据 我们监听obj下的user vue3也没有办法了
let obj = reactive({ user: { name: "xixi", age: 18, }, });
<template> <div> <input type="text" v-model="obj.user.name" /> <input type="text" v-model="obj.user.age" /> <h1>{{ obj.user.name }}</h1> <h1>{{ obj.user.age }}</h1> </div> </template> <script setup> import { reactive, watch } from "vue"; let obj = reactive({ user: { name: "xixi", age: 18, }, }); // 监听多个对象属性的时候使用数组来进行设置 watch( () => {return obj.user;}, (newVal, oldVal) => { console.log("newVal", newVal); console.log("oldVal", oldVal); }, // 第三个参数就是设置deep { deep:true } ); </script> <style> </style>
但是newval和oldval还是有问题 但是数据修改函数可以触发
watch总结
vue3中的watch作用和vue2是一样的都是对数据进行监听当数据改变的时候watch就会知道从而执行一个函数
在vue3中watch他在监听基本类型和复杂类型的时候语法有所不同
在监听基本类型的时候watch的第一个参数就是你要监听的内容 第二个参数就是监听内容改变之后触发的函数
在监听负载类型的时候 vue3watch默认是开启deep的 如果直接监听复杂数据 可能会出现 数据改变了 但是监听函数中的newval和oldval相同的问题 解决方式就是在监听的时候把第一个参数变成一个函数return 我们要监听的内容 即可解决
但是在监听数据的时候 如果监听的数据的属性也是复杂类型那么默认就不行了 我们必须在watch上设置第三个参数 是一个对象 写入deep为true开启深度监听
生命周期
<template> <div> </div> </template> <script setup> import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, } from "vue"; // 其他的生命周期 onBeforeMount(() => { console.log("App ===> 相当于 vue2.x 中 beforeMount"); }); onMounted(() => { console.log("App ===> 相当于 vue2.x 中 mounted"); }); // 注意,onBeforeUpdate 和 onUpdated 里面不要修改值 onBeforeUpdate(() => { console.log("App ===> 相当于 vue2.x 中 beforeUpdate"); }); onUpdated(() => { console.log("App ===> 相当于 vue2.x 中 updated"); }); onBeforeUnmount(() => { console.log("App ===> 相当于 vue2.x 中 beforeDestroy"); }); onUnmounted(() => { console.log("App ===> 相当于 vue2.x 中 destroyed"); }); </script> <style> </style>
过滤器
vue中 在vue1x中过滤器有内置过滤器和自定义过滤器 但是到了2x中取消了内置过滤器 在3x中啥都没有了
vue3取消了vue2中的自定义过滤器,但是变相一下,在vue3中可以把数据当成函数的实参传递给一个函数 这样一来就可以使用一个函数来模拟原来的vue2过滤器
比如我们要过滤下 一个数据
<template> <div> <h1>正常展示:{{data}}</h1> <!-- 创建一个函数模拟过滤器 --> <h1>只显示年:{{setdata(data)}}</h1> </div> </template> <script setup> import {ref} from "vue" let data=ref("2022-10-1") let setdata=(val)=>{ return val.substr(0,4) } </script> <style> </style>
插槽--slot
就是给组件设置了一个开口(组件默认是一个完整地独立地个体 外部内容不能插入 如果你想插入数据可以直接使用props)槽口/插槽是吧一个dom元素传入到组件中 传递了dom就可以提高组件的复用性
默认插槽
定义slot接受
<template> <div> <h1>我是子组件</h1> <slot></slot> </div> </template> <script setup> </script> <style> </style>
插入的时候需要使用#default来设置
<template> <div> <h1>我是父组件</h1> <ZiView> <template #default> <h1>我是内容</h1> <h1>我是内容</h1> <h1>我是内容</h1> </template> </ZiView> </div> </template> <script setup> import ZiView from "../components/ZiView.vue" </script> <style> </style>
具名插槽
使用name起名
<template> <div> <h1>我是子组件</h1> <slot name="main"></slot> </div> </template> <script setup> </script> <style> </style>
使用#slot的名字 代替原来2.0的slot属性
<template> <div> <h1>我是父组件</h1> <ZiView> <!-- 使用#slot的名字 代替原来2.0的slot属性 --> <template #main> <h1>我是内容</h1> <h1>我是内容</h1> <h1>我是内容</h1> </template> </ZiView> </div> </template> <script setup> import ZiView from "../components/ZiView.vue" </script> <style> </style>
作用域插槽
关于插槽就是无非就是在子组件中挖个坑,坑里面放什么东西由父组件决定。而作用域插槽就是父组件可以直接拿到子组件的值
定义子组件
<template> <div> <h1>我是子组件</h1> <!-- 向父组件 传递数据 以data接受 --> <slot :data="item"></slot> </div> </template> <script setup> import {ref} from "vue" let item=ref("我是数据") </script> <style> </style>
父组件
<template> <div> <h1>我是父组件</h1> <ZiView> <!-- 使用#slot的名字 代替原来2.0的slot属性 --> <template #default="{ data }"> {{ data}} </template> </ZiView> </div> </template> <script setup> import ZiView from "../components/ZiView.vue" </script> <style> </style>
组件
全局组件---component
1.在main.js中引用所需要的组件
2.使用component调用
3.在任意组件可以直接使用
import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' // 1.引用 import SunView from "./components/SunView.vue"; // 2.使用component调用 createApp(App).component('SunView',SunView).use(store).use(router).mount('#app')
局部组件
在vue3.x中,引入的组件是需要在components
中声明的(和vue2x一样写法),但是在vue3.2中,你可以直接使用。
旧方式
创建子组件
<template> <div> 我是一个子组件 </div> </template> <script> import {defineComponent} from "vue" export default defineComponent({ }) </script> <style> </style>
在父组件中使用子组件
<template> <div> <h1>我是父组件</h1> <!-- 3使用 --> <Zc/> </div> </template> <script> import { defineComponent } from 'vue' // 1.引用 import Zc from "../components/ZiCom.vue" export default defineComponent({ // 2.调用 components:{ Zc }, setup() { return { } }, }) </script> <style> </style>
新语法
新方式3.2:
-
不需要return任何东西,所有定义的变量和方法会自动导出,template模板可以直接使用
-
import引入的组件也会自动导出,模板里可以直接引用。
-
引入的组件命名需要首字母大写。
-
确切的说是:所有的变量方法和import导入都可以在模板中直接使用。
<template> <div> <!--2使用--> <ZiView/> </div> </template> <script setup> // 1.要使用先引用 import ZiView from "@/components/ZiView.vue" </script> <style> </style>
旧方式
<template> <div> <ZiView/> </div> </template> <script> // 1.要使用先引用 import ZiView from "@/components/ZiView.vue" export default { // 2.调用 components:{ ZiView } } </script> <style> </style>
组件传值
正向传值--defineProps
旧语法
子组件接收正向传值的语法同vue2x
<script> import { defineComponent } from 'vue' export default defineComponent({ // 旧语法和vue2x一样使用props props:["title","num"], setup() { }, }) </script>
新语法
子组件
<template> <div> 我是一个子组件--{{text}} </div> </template> <script setup> import {defineProps} from "vue" // 定义接受参数 defineProps({ text:{ type:String } }) </script> <style> </style>
父组件
<template> <div> <ZiView text="我是正向传值"/> </div> </template> <script setup> // 1.要使用先引用 import ZiView from "@/components/ZiView.vue" </script> <style> </style>
逆向传值
逆向传值--defineEmits
在vue2x中逆向传值使用this.$emit()自定义事件来完成
子组件--使用emits抛出自定义事件
<template> <div> 我是一个子组件 <button @click="fun()">点我逆向传值</button> </div> </template> <script setup> import {defineEmits} from "vue" const emits = defineEmits(['xiaoming']) let fun=()=>{ emits('xiaoming',"我是子组件数据") } </script> <style> </style>
父组件--接受自定义事件
<template> <div> <ZiView @xiaoming="fufun"/> </div> </template> <script setup> // 1.要使用先引用 import ZiView from "@/components/ZiView.vue" let fufun=(val)=>{ console.log("父组件",val) } </script> <style> </style>
逆向传值--ref---defineExpose
子组件---千万不要忘了 ref方式传值 需要在子组件使用defineExpose(子组件暴露自己的属性)
<template> <div> <h1>我是子组件--{{ obj.name }}</h1> </div> </template> <script setup> import { reactive ,defineExpose} from "vue"; let obj = reactive({ name: "xixi", age: 18, }); // 千万不要忘了 ref方式传值 需要在子组件使用defineExpose(暴露自己的属性) 暴露自己的属性 defineExpose({ obj }) </script> <style> </style>
父组件
<template> <div> <!-- 2.绑定到组件之上 --> <ZiView ref="com"/> <button @click="fun()">点我得到子组件的内容</button> </div> </template> <script setup> import ZiView from "@/components/ZiView.vue" import {ref} from "vue" // 1.创建ref let com=ref(null) let fun=()=>{ console.log(com.value.obj) } </script> <style> </style>
跨组件provide/inject
provide和inject是Vue中提供的一对API,该API可以实现父组件向子组件传递数据,无论层级有多深,都可以通过这对API实现
祖先组件--使用provide传递
<template> <div> <h1>爷组件</h1> <ZiView ref="com"/> </div> </template> <script setup> import ZiView from "@/components/ZiView.vue" import {ref,provide} from "vue" let yeText=ref("我是爷爷组件的数据") // 向子组件提供数据 provide('list', yeText.value) </script> <style> </style>
后代组件--inject使用数据
<template> <div> 我是孙组件--{{yeText}} </div> </template> <script setup> import { inject } from 'vue' // 接受父组件提供的数据 const yeText = inject('list') </script> <style> </style>
跨组件传值--vuex 4x
vuex4 是 vue3的兼容版本,提供了和vuex3 的相同API。因此我们可以在 vue3 中复用之前已存在的 vuex 代码。
state
1.定义状态
import { createStore } from 'vuex' export default createStore({ state: { text:"我是vuex中的数据"//定义状态 }, getters: { }, mutations: { }, actions: { }, modules: { } })
2.组件中使用
在组件的模板中直接使用,与之前的api保持一致 没有this
<template> <div> <h1>{{$store.state.text}}</h1> </div> </template> <script setup> </script>
3.在js中使用 通过 useStore 把store 引入组件内,然后操作 store 。
<template> <div> <!-- 在组件的模板中直接使用,与之前的api保持一致 没有this--> <h1>{{ $store.state.text }}</h1> </div> </template> <script setup> import { onMounted } from "vue"; // 1.引用useStore import { useStore } from "vuex"; // 2.得到store对象 const store = useStore(); onMounted(() => { // 3.使用 console.log(store.state.text); }); </script>
module
1.模块拆分--同之前
export default { state: { text: "我是vuex中的数据"//定义状态 }, getters: { }, mutations: { }, actions: { }, }
2.在文件中配置
import { createStore } from 'vuex' // 1.引用 import demom from "./demom" export default createStore({ modules: { demom//调用 } })
3.使用
$store.state.模块名.数据名
<template> <div> <h1>{{ $store.state.demom.text }}</h1> </div> </template> <script setup> import { onMounted } from "vue"; // 1.引用useStore import { useStore } from "vuex"; // 2.得到store对象 const store = useStore(); onMounted(() => { // 3.使用 console.log(store.state.demom.text); }); </script>
mutations
同之前
actions
同之前
getters
同之前
toRef与toRefs
引子
下面代码就是修改一个对象的值
<template> <div> <h2>传统方式:{{ user.name }}--{{user.age}}</h2> <button @click="hello">点我修改</button> </div> </template> <script setup> import { reactive } from "vue"; let user = reactive({ name: "xixi", age:18 }); let hello=()=>{ user.name = "小白"; user.age = 19; } </script>
但是页面中出现太多的user. xxx 这样就显得很难受
在这个时候我们想 可以修改下代码变成如下代码
<template> <div> <h2>传统方式:{{ name }}--{{age}}</h2> <button @click="hello">点我修改</button> </div> </template> <script setup> import { reactive } from "vue"; let user = reactive({ name: "xixi", age:18 }); // 添加如下代码 // let name=user.name // let age=user.age //技术好点的同学可能会想到用解构 let {name,age}=user let hello=()=>{ name = "小白"; age = 19; } </script>
因为使用reactive或者是ref创建出来的变量在vue3x中是一个响应式数据
但是发现数据不改变了 因为我们创建的name 和 age的两个变量不是vue的响应式数据 所以没有办法改变
所以我们把name和age变成响应式
<template> <div> <h2>传统方式:{{ name }}--{{age}}</h2> <button @click="hello">点我修改</button> </div> </template> <script setup> import { reactive,ref } from "vue"; let user = reactive({ name: "xixi", age:18 }); // 添加如下代码 let name=ref(user.name) let age=ref(user.age) let hello=()=>{ // 不要忘了ref创建数据需要.value name.value = "小白"; age.value = 19; } </script>
发现数据正常改变了 但是上面这种方式太麻烦了 我们只是为了简化语法 有点丢了西瓜捡了芝麻的感觉
toRef
因为使用解构这种快速取值的方式 会把数据的响应式丢失所以(数据变了页面不会改变) 我们在vue3中为了想对对象的属性快速取值 所以我们使用下面得方式
toRef作用:将对象某一个属性,作为引用返回为响应式ref数据。
所以这个使用toRef就派上了作用
<template> <div> <h2>传统方式:{{ name }}--{{age}}</h2> <button @click="hello">点我修改</button> </div> </template> <script setup> import { reactive,toRef } from "vue"; let user = reactive({ name: "xixi", age:18 }); // 添加如下代码 //参数一为一个响应对象, //参数二为参数一这个对象中的某个属性。 let name=toRef(user,"name") let age=toRef(user,"age") let hello=()=>{ //注意要使用.value取值 name.value = "小白"; age.value = 19; } </script>
toRefs
toRefs 返回对象中所有属性都响应式,相比之下比toRef写法跟简单,但是肯定会牺牲耗性能(因为会把数据中所有内容都进行操作)
<template> <div> <h2>传统方式:{{ name }}--{{age}}</h2> <button @click="hello">点我修改</button> </div> </template> <script setup> import { reactive,toRefs } from "vue"; let user = reactive({ name: "xixi", age:18 }); // 添加如下代码 let {name,age}=toRefs(user) let hello=()=>{ //注意要使用.value取值 name.value = "小白"; age.value = 19; } </script>
区别
toRefs一次性全部将对象的属性变成ref对象,而toRef单个的
自定义HOOK
HOOK是什么?
本质上HOOK是一个函数 只是把原来setup中我们写的组合式API进行了封装(就是把原来需要重复的逻辑封装起来方便复用)
类似于vue2的mixins 方便的把多个组件需要重复使用的逻辑封装起来
例子:
我们需要得到当前鼠标的点击坐标 传统方式
<template> <div> <h1>x:{{num.x}}---y:{{num.y}}</h1> </div> </template> <script setup> import { reactive,onMounted } from "vue"; let num=reactive({ x:0, y:0 }) let clickPint=()=>{ window.addEventListener("click",(event)=>{ num.x=event.pageX num.y=event.pageY }) } // 初始化调用函数 onMounted(()=>{ clickPint() }) </script>
但是多个组件都想用怎么办?
封装自定义HOOK
1.新建一个hooks的文件夹用来容纳 把原来的逻辑复制过来 但是不要忘了return数据
import { reactive, onMounted } from "vue"; export default function () { let num = reactive({ x: 0, y: 0 }) let clickPint = () => { window.addEventListener("click", (event) => { num.x = event.pageX num.y = event.pageY }) } // 初始化调用函数 onMounted(() => { clickPint() }) // 不要忘了return 数据 // 不要忘了return 数据 // 不要忘了return 数据 return num }
2.在想使用的组件中引用调用
<template> <div> <!-- 3.使用 --> <h1>x:{{useclick.x}}---y:{{useclick.y}}</h1> </div> </template> <script setup> // 1。引用 import useClick from "../hooks/index.js" // 2.调用 let useclick=useClick() </script>
Vue Router4路由
Vue3支持最新版本由于Vue 3 引入了createApp API,该API更改了将插件添加到Vue实例的方式。 因此,以前版本的Vue Router将与Vue3不兼容。Vue Router 4 引入了createRouter API,该API创建了一个可以在Vue3中安装 router 实例。
路由配置
同传统路由
路由模式
History模式
History选项在Vue Router 4中,这些模式已被抽象到模块中,可以将其导入并分配给新的history
选项。
import { createRouter, createWebHistory } from "vue-router"; export default createRouter({ history: createWebHistory(),//定义history模式url不带# routes: [], });
hash模式
import { createRouter, createWebHashHistory } from "vue-router"; export default createRouter({ history: createWebHashHistory(),//定义hash模式 routes: [], });
总结: vue的路由模式大概我知道又两大类 第一大类是router3x 第二道类是 router4x 答题没啥区别 都是hash history 区别也就是在逮捕带#号呀 刷新丢失不丢失上面 兼容性这方面 我个人觉的写法上差别挺大 3x直接就修改mode即可 4x中要使用createWebHistory 或者createWebHashHistory 这两个方法来切换
路由导航
声明式
同传统路由---router-link
编程式--useRouter()
使用useRouter() 来替代 this.$router
<template> <div> <button @click="fun">dianwo</button> </div> </template> <script setup> import { useRouter, useRoute } from "vue-router"; // useRouter赋值给一个变量 let router = useRouter(); let fun=()=>{ router.push("/") } </script>
动态路由匹配--useRoute()
动态路由匹配-路由传参 主要就是吧数据从一个页面传递到另外一个页面 在其中他又两种模式 params和query 当然他们也有比较大的区别 首先就是语法上面有区别 一个是用name继续宁跳转 一个使用path或name来进行的 同时他们在url上展示也不太相同 一个代key=val 一个只有数据 还有就是刷新之后会造成丢失
params
1.路由规则配置接受参数
2.发送数据--同之前
3.接受数据
<template> <div class="home"> </div> </template> <script lang="ts" setup> import {onMounted } from 'vue'; import {useRoute} from "vue-router"; let route=useRoute() onMounted(()=>{ // 接收params console.log(route.params.xiaoming) }) </script>
query
1.发送
2.接受
<template> <div class="home"> </div> </template> <script lang="ts" setup> import {onMounted } from 'vue'; import {useRoute} from "vue-router"; let route=useRoute() onMounted(()=>{ // 接收query console.log(route.query.xiaoming) }) </script>
路由守卫
同之前
vue3新组件
Fragment
在vue2中组件必须有一个根组件包裹多行内容 (但是往往这个根组件仅仅是包裹的作用 但是会在页面生成多余的冗余标签)
在vue3中 组件是可以不需要写根标签的 因为我们如果写了多行标签vue3会创建一个虚拟的Fragment根标签自动帮助我们包裹
优点:较少了不必要的标签渲染 减少过多的内存损耗
Teleport
Teleport 是什么
Teleport 可以把我们组件中的dom内容 移动到当前项目的任意dom节点中
观察下面得代码 会发现 当我们点击显示隐藏的时候 demob这个div会根据内容的显示隐藏变大变小
<template> <div> <div class="demoa"> <h1>我是demoa</h1> </div> <div class="demob"> <h1>我是demob</h1> <button @click="bool=!bool">点我显示隐藏</button> <div v-if="bool"> <p>我是占位的</p> <p>我是占位的</p> <p>我是占位的</p>å <p>我是占位的</p> <p>我是占位的</p> <p>我是占位的</p> <p>我是占位的</p> </div> </div> </div> </template> <script setup> import {ref} from "vue" let bool=ref(true) </script> <style scoped> .demoa{ background-color: pink; } .demob{ background-color: goldenrod; } </style>
使用Teleport 其中to属性就是传送到那个dom上
<template> <div> <div class="demoa"> <h1>我是demoa</h1> </div> <div class="demob"> <h1>我是demob</h1> <button @click="bool = !bool">点我显示隐藏</button> <teleport to="body"> <div v-if="bool"> <p>我是占位的</p> <p>我是占位的</p> <p>我是占位的</p> <p>我是占位的</p> <p>我是占位的</p> <p>我是占位的</p> <p>我是占位的</p> </div> </teleport> </div> </div> </template> <script setup> import { ref } from "vue"; let bool = ref(true); </script> <style scoped> .demoa { background-color: pink; } .demob { background-color: goldenrod; } </style>
使用场景 : 比如在某个组件中我们需要有一个弹框 弹出在页面中 那么就可以使用teleport 把这个组件内部的dom挂载到组件之外的地方 方便我们设置层级