一、vue3 项目搭建
npm 6.x
npm init vite@latest myvue3 --template vuevue 3.2.26
使用 element plus ui 框架
npm i -S element plus
//全部引入
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
const Vue = createApp(App);
Vue.use(ElementPlus);
使用 scss
npm install --save-dev sass-loader
npm install --save-dev node-sass
npm install --save-dev sass
vite.config
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';// https://vitejs.dev/config/
export default defineConfig({publicPath: './', //打包路径css: {//配置scss全局变量和方法preprocessorOptions: {scss: {additionalData: "@use './src/assets/scss/style.scss' as *;"}}},plugins: [vue()]
});
二、vu3 语法部分
生命周期
vue2 | vue3 |
---|---|
beforeCreate | setup |
created | setup |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestory | onBeforeUnmount |
destoryed | onUnmounted |
路由
import { useRoute,useRouter } from 'vue-router';const router = useRouter();
const route = useRoute();
状态管理
import {useStore} from 'vuex';const store = useStore();
reactive
返回对象的响应式副本
注意:
reactive 返回的对象,重新赋值丢失响应式
reactive 返回的对象不可以解构
const form = reactive({name: 'aa',age: 20
});const { name, age } = form; //通过解构 此时name,age会丢失响应 要想让它具有响应式 需要通过toRefs处理
const { name, age } = toRefs(form); //此时name,age具有响应
ref
所有通过 ref 创建的数据 需要通过 xxx.value 取它的值, 在模板中无需通过.value。
可以简单地把 ref(obj) 理解为这个样子 reactive({value: obj})
import { ref } from "vue";
const count = ref(1);console.log(cout.value); //1
- ref获取单个dom元素
<div ref="root"></div>
import { ref } from "vue";
const root = ref(null);
onMounted(() => {console.log(root.value) //dom元素
}),
- ref获取v-for中dom元素
<ul><li :ref="setLiDom" v-for="(item, index) in state.list" :key="index">{{ item }}</li>
</ul>
import { ref, reactive } from "vue";
const state = reactive({list: [1, 2, 3, 4]
})const refList = ref([]);const setLiDom = (el) => {if(el){liItem.value.push(el);}
};onMounted(() => {console.log(root.value) //Proxy {0: li, 1: li, 2: li, 3: li}
}),
toRef 和 toRefs
- toRef 用来为源响应式对象上的某个 property 新创建一个 ref。
const state = reactive({foo: 1,bar: 2
});//目的使state对象中的foo属性保持响应式连接,修改其中一个都会同步修改那个属性
const fooRef = toRef(state, 'foo');
- toRefs 将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref。
//toRefs常用于 es6 的解构赋值操作,保持每个属性的都为响应式
setup(){let data = reactive({name: '张三',age: 18});return {...toRefs(data)}
}
toRaw 、markRaw
通过toRaw获取到原始对象,改变原始对象的值会同时改变响应式对象的值,但不会更新视图
const foo = {}
const reactiveFoo = reactive(foo)//reactiveFoo 和 foo 是引用的同一个地址console.log(toRaw(reactiveFoo) === foo) // true
通过markRaw包裹的原始对象,使其永远不会转换为响应式对象,也就是说转换之后修改值并不会更新视图
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false
unref
如果参数是一个 ref,则返回内部值,否则返回参数本身
let obj = ref({ a: 1, b: 2 });let reult = isRef(obj) ? obj.value : obj;
//等价于
let reult = unref(obj);
shallowRef 和 triggerRef
setup() {//shallowRef创建一个比ref更浅的响应式对象,改变其属性值,不会触发监听const foo = shallowRef({a: 1111,b: 2222});setTimeout(() => {foo.value.a = 3333;triggerRef(foo); //需要手动触发才能改变a的值}, 2000);watchEffect(() => {console.log(foo.value.a); //1111 3333});return {foo};
}
defineProps 和 defineEmits
在 <script setup> 中必须使用 defineProps 和 defineEmits API 来声明 props 和 emits
<script setup>
const props = defineProps({foo: String
})const emit = defineEmits(['change', 'delete'])
</script>立即触发回调函数
defineExpose
使用 <script setup> 的组件是默认关闭的,也就是说通过 ref 和$parent 是无法访问到的
解决办法:
import { ref } from 'vue';
const a = 1;
const b = ref(2);
defineExpose({ a, b });
computed
vue3中不再支持filter过滤器,vue2 中的filter 使用计算属性computed或者方法代替
import { computed, ref } from "vue";const count = ref(1);
const total = computed(()=>{//需要根据传递的参数进行计算// return (val)=>{// return val + count.value;// }return count.vallue;
})
setInterval(() => {count.value ++;
}, 1000);
watch、watchEffect
与选项式 API 中的 watch 完全等效
语法:watch(source, callback, [options])
options: deep(深度监听) 、immediate(立即触发回调函数)
// 侦听一个 getter
const state = reactive({ count: 0 });
watch(() => state.count,(count, prevCount) => {/* ... */}
);// 直接侦听一个 ref
const count = ref(0);
watch(count, (count, prevCount) => {/* ... */
});//侦听多个数据
//注意多个同步更改只会触发一次侦听器。
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {/* ... */
});
与watch的区别:
1、不需要指定监听的值,回调仅在侦听源发生变化时被调用
2、初始化会执行一次
3、watchEffect只能拿到变化后的值
import { watchEffect } from 'vue';watchEffect(() => {/* ... */})
watchEffect 与 watch 的区别:
provide、inject
依赖注入,用来实现深层次组件之间的数据传递
//parent
import { reactive, readonly, provide } from 'vue';
const updateInfo = () => {baseInfo.name = '李四';baseInfo.age = 20;
};const baseInfo = reactive({name: '张三',age: 12,updateInfo: updateInfo
}); //此时的baseInfo是响应式的, 添加readonly是为了防止孙子组件直接修改值
provide('baseInfo', readonly(baseInfo));//son
import { inject } from 'vue';const info = inject('baseInfo');
console.log(info); //{ name:"张三", age:12, updateInfo: function }const changeValue = () => {info.updateInfo();
};
slot (vue 内置组件)
属性:
- name - string, 用于具名插槽
用法:
1 、基本使用和默认值
//父组件
<div><child-com>只有子组件有插槽我就显示</child-com>
</div>//子组件child-com
<div><slot>我是插槽默认值</slot>
</div>//页面显示
只有子组件有插槽我就显示
2 、具名插槽
v-slot:插槽名 可以简写为 #插槽名
动态插槽名: v-slot:[dynamicSlotName]
//父组件
<div><child-com><template v-slot:header>头部1111</template><template #center>中间222</template><template #footer>底部333</template></child-com>
</div>//子组件child-com
<div><div class="top">上边显示:<slot name="header"></slot></div><div class="center">中间显示:<slot name="center"></slot></div><div class="bottom">底部显示:<slot name="footer"></slot></div>
</div>//页面显示
上边显示:头部1111
中间显示:中间222
底部显示:底部333
3 、作用域插槽
子组件的数据可以在父组件中使用
使用场景:
当一个组件被用来渲染一个数组元素时,我们使用插槽,并且希望插槽中没有显示每项的内容;
//父组件
<div><child-com><template #title="scope"><h1>{{ scope.msg }}</h1></template><template v-slot:default="{ row, index }"><span>{{ index }}、</span><span>{{ row.name }}</span>-<span>{{ row.age }}</span></template></child-com>
</div>//子组件child-com
<div><slot :msg="data.msg" name="title"></slot><div v-for="(item, index) in data.list" :key="index"><slot :row="item" :index="index"></slot></div>
</div>
import { reactive } from 'vue';
const data = reactive({msg: 'hello world',list: [{name: 'jane',age: 12},{name: 'xiaoming',age: 18}]
});//页面显示
hello world
0、jane - 12
1、xiaoming - 18
teleport (vue 内置组件)
功能:能够将我们的模板或者组件移动到相应的 DOM 元素中
属性:
- to - string, 必须是有效的目标元素,例如:body、#some-id、.some-class
- disabled - boolean, 为 true 表示移动到 to 指定的目标元素, false 表示不移动,
用法:
<teleport to="body"><div class="model"></div>
</teleport><teleport to="#app"><child-component name="hero"></child-component>
</teleport>
注意事项:
- 被移动的组件不会影响传值
- 在同一目标上使用多个 teleport,实际上是一个追加的过程
- 如果在组件上动态切换 disabled 的值,并不会导致其销毁和重新创建
transition (vue 内置组件)
复习 css:
缩写: transition: property duration timing-function delay;
- transition-property
- transition-duration
- transition-timing-function: linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n);
- transition-delay
属性
-
name - string 用于自动生成 CSS 过渡类名
-
appear - boolean 是否在初始渲染时使用过渡。默认为 false。
-
css - boolean 是否使用 CSS 过渡类。默认为 true。如果设置为 false,将只通过组件事件触发注册的 JavaScript 钩子
-
type - string 指定过渡事件类型。可选值 "transition" 和 "animation"。默认自动检测出持续时间长的为过渡事件类型。
-
mode - string 控制离开/进入过渡的时间序列。有效的模式有 "out-in" 和 "in-out"
- 为了解决两个元素相互显示时,会同时执行过渡
css 过渡 和 css 动画
//html
<div id="demo"><button @click="show = !show">Toggle</button><transition name="fade"><p v-if="show">hello</p></transition>
</div>//css过渡
.fade-enter-active,
.fade-leave-active {transition: all 1s ease;
}.fade-enter-active,
.fade-leave-active {transition: all 1s;
}.fade-enter-from {transform: translateY(200px);opacity: 0;
}
.fade-leave-to {transform: translateX(200px);opacity: 0;
}//css动画
.fade-enter-active {animation: bounce-in 1s;
}.fade-leave-active {animation: bounce-in 1s reverse;
}@keyframes bounce-in {0% {transform: scale(0);}50% {transform: scale(1.25);}100% {transform: scale(1);}
}
使用 三方库 animate.css
//安装 版本记录 4.1.1
npm install animate.css --save
//引入
import 'animate.css';
自定义过渡类名:
enter-from-class
enter-active-class
enter-to-class
leave-from-class
leave-active-class
leave-to-class
使用:
<transitionenter-active-class="animate__animated animate__tada"leave-active-class="animate__animated animate__bounce"
><div v-show="flag">hello world</div>
</transition>
transition 的 JavaScript 钩子
<transition@before-enter="beforeEnter"@enter="enter"@after-enter="afterEnter"@enter-cancelled="enterCancelled"@before-leave="beforeLeave"@leave="leave"@after-leave="afterLeave"@leave-cancelled="leaveCancelled":css="false"
><!-- ... -->
</transition>
Setup() 函数特性
setup() 函数接收两个参数:props、context(包含attrs、slots、emit)
setup()函数处于生命周期 beforeCreated 两个钩子函数之前
执行setup时,组件实例尚未被创建(在setup()内部,this不会是该活跃实例的引用,即不指向vue 实例,Vue 为了避免我们错误的使用,直接将setup 函数中的this 修改成了undefined)
与模版一起使用时,需要返回一个对象
因为setup函数中,props时响应式得,当传入新的prop时,它就会被更新,所以不能使用es6解构,因为它会消除 prop得响应性,如果需要解构prop,可以通过使用setup函数中torefs 来完成此操作。
在setup()内使用响应式数据时,需要通过.value 获取
从setup()中返回得对象上得property 返回并且可以在模版中被访问时,它将自动展开为内部值。不需要在模版中追加.value.
Setup 函数只能是同步的不能是异步的。
vue2和vue3的不同之处
1.双向数据绑定原理不同
Vue2 的双向数据绑定是利用ES5的一个APIObject.definePropert() 对数据进行劫持,结合发布订阅模式的方式来实现的。
Vue3 中使用ES6的Proxy API对数据代理。
Vue3 使用数据代理的优势有以下几点:1)definePropert 只能监听某个属性,不能对整个对象进行监听 2)可以省去for in,闭包等内容来提升效率(直接绑定整个对象即可)3)可以监听数组,不用再单独的对数组做特异性操作,Vue3可以检测到数组内部数据的变化
2.是否支持碎片
Vue2 不支持碎片。Vue3 支持碎片,就是说可以拥有多个根节点
3.API 类型不同
Vue2 使用选项类型api,选项型api 在代码里分割了不同的属性:data,computed,method等。
Vue3 使用合成型api,新的合成型api 能让我们使用方法来分割,相比于旧的api 使用属性来分组,这样代码会更加简便和整洁。
4定义数据变量和方法不同
Vue2是把数据放到了data 中,在 Vue2中 定义数据变量是data(){},创建的方法要在method:{}
Vue3 就需要使用一个新的setup()方法,此方法在组件初始化构造的时候触发。使用以下三个步骤来建立反应性数据:1)从vue 引入 reactive;2)使用 reactive ()方法来声明数据为响应性数据;3) 使用setup()方法来返回我们的响应性数据,从而template 可以获取这些响应性数据。
5.生命周期钩子函数不同
Vue2 中的生命周期:beforeCreate 组件创建之前;created 组建创建之后;beforeMount 组件挂载到页面之前执行;Mounted 组件挂载到页面之后执行,beforeUpdate 组件更新之前;updated组件更新之后
Vue3 中的生命周期:setup 开始创建组件;onBeforeMount 组件挂载到页面之前执行;onMounted 组件挂载到页面之后执行;onBeforeUpdate 组件更新之前;onUpdated 组件更新之后;
而且 Vue3 生命周期在调用前需要先进行引入。除了这些钩子函数外,Vue3 还增加了 onRenderTracked 和onRenderTriggered 函数。
6.父子传参不同
Vue2 父传子,用props ;子传父用事件Emitting Events。在Vue2 中,会调用this$emit 然后传入事件名和对象。
Vue3 父传子,用props;子传父用Emitting Events 。在Vue3 中的setup()中的第一参数content 对象中就有 emit,那么我们只要在setup()接收第二个参数中使用分解对象法取出emit 就可以在setup 方法中随意使用了。
7.指令与插槽不同
Vue2 中使用slot 可以直接使用slot ;v-for 与v-if 在Vue2中优先级高的是v-for 指令,而且不建议一起使用。
Vue3 中必须是使用v-slot的形式;vue 3中v-for 与v-if ,只会把当前v-if 当作v-for 的一个判断语句,不会相互冲突;
Vue3 中移除keyCode 作为v-on 的修饰符,当然也不支持config.keyCodes,取而代之的是使用键名来作为事件的修饰符来使用,于是Vue.config.keyCodes 也被弃用了-vue2我们在监听按键事件的时候,是可以通过在事件后面加上按键码来实现监听某一个按键的
8.Main.js 文件不同
Vue2中我们可以使用pototype(原型)的形式去进行操作,引入的是构造函数。
Vue3 中需要使用结构的形式进行操作,引入的是工厂函数;
Vue3中app组件中可以没有根标签。
Vue3中获取组件实例的方法
1、使用$ref获取实例
Vue3中使用$ref获取组件实例跟Vue2的用法基本一致,只不过需要使用Vue3提供的ref函数来进行实例的绑定。下面我们将通过示例来演示如何使用$ref获取组件实例。
const app = Vue.createApp({setup() {const myComponentRef = Vue.ref(null);return {myComponentRef};}});app.component('my-component', {template: `{{ message }}`,data() {return {message: 'Hello World!'};}});const vm = app.mount('#app');// 获取组件实例const myComponentInstance = vm.$refs.myComponentRef;console.log(myComponentInstance.message); // Hello World!
2、使用provide/inject获取实例
Vue3中提供了provide/inject来进行组件之间的依赖注入。我们可以在父组件中provide某个实例,在子组件中使用inject来获取这个实例。下面我们将通过示例来演示如何使用provide/inject获取组件实例。
// 父组件
const app = Vue.createApp({provide() {return {myService: Vue.ref(null)};},mounted() {this.$refs.myComponentRef.initService(this.myService);}
});app.component('my-component', {template: `{{ message }}`,props: ['initService'],mounted() {this.initService(this.myService);},data() {return {message: 'Hello World!'};},setup() {const myService = Vue.ref(null);return {myService};}
});// 子组件
app.component('my-other-component', {template: `{{ myService.message }}`,inject: ['myService']
});const vm = app.mount('#app');
3、使用v-for获取多个实例
如果我们需要获取多个组件实例,我们可以使用v-for来遍历一个组件数组,然后在每个组件上使用v-slot来获取实例。下面我们将通过示例来演示如何使用v-for获取多个组件实例。
const app = Vue.createApp({setup() {const myComponents = Vue.ref([{message: 'Hello World!'}, {message: 'Hello Vue3!'}]);return {myComponents};}
});app.component('my-component', {template: `{{ message }}`,props: ['myComponent'],data() {return {message: this.myComponent.message};}
});const vm = app.mount('#app');// 遍历组件数组
<template v-for="(myComponent, index) in myComponents"><my-component :key="index" :my-component="myComponent" v-slot="{ $ }"><button @click="$refs[index].message = 'Hello AI!'">Change Message</button></my-component>
</template>// 获取组件实例
const myComponentInstance1 = vm.$refs[0];
const myComponentInstance2 = vm.$refs[1];
console.log(myComponentInstance1.message); // Hello World!
console.log(myComponentInstance2.message); // Hello Vue3!
4、使用async/await获取动态组件实例
如果我们需要获取一个动态创建的组件实例,我们可以使用async/await来等待组件创建完成,然后使用$refs获取组件实例。下面我们将通过示例来演示如何使用async/await获取动态组件实例。
const app = Vue.createApp({setup() {const compName = Vue.ref('my-component');const loadComponent = async () => {const { default: dynamicComponent } = await import('./DynamicComponent.vue');app.component('dynamic-component', dynamicComponent);};return {compName,loadComponent};}
});app.component('my-component', {template: `{{ message }}`,data() {return {message: 'Hello World!'};}
});const vm = app.mount('#app');// 使用async/await等待组件创建完成
vm.loadComponent().then(() => {const dynamicComponentInstance = vm.$refs.dynamicComponent.$refs['my-dynamic-component'];console.log(dynamicComponentInstance.message); // Hello Dynamic Component!
});// 动态组件
<dynamic-component><my-component ref="my-dynamic-component"></my-component>
</dynamic-component>