一、双向绑定
Vue2.0版本是通过ES5的object.defineProperty
1、 不能监听数组的变化,除了push/pop/shift/unshift/splice/reverse/sort,其他都不行
2、.无法检测到对象属性的新增或删除
3、Object.defineProperty只能遍历对象属性直接修改(需要深拷贝进行修改)
Vue3.0版本是通过ES6的Proxy
1、 proxy有兼容性问题,不能用polyfill来兼容(polyfill主要抚平不同浏览器之间对js实现的差异)
2、直接监听对象而非属性
3、直接监听数组的变化
4、拦截的方式有很多种(有13种)
5、Proxy返回一个新对象,可以操作新对象达到目的
二、ref全家桶
1. ref:基本类型的响应式数据(源码本质上还是reactive)
ref本质也是reactive,ref(obj)等价于reactive({value: obj})
let name = ref("字符串")
let age = ref(数字)
js中操作是xxx.value,但模板中不需要.value
name不是响应式的,但是name.value是响应式的
使用ref获取dom节点的属性
1.const V1= ref(null)
1.template中定义ref="V1"
2.onMounted中获取V1.vuele
3. 将V1 return出去;
代码如下:
<!-- videocontainer -->
<template>
<div class="video-container">
<video :src="props.src" ref="V1" style='top:0px' loop playsinline muted></video>
</div>
</template>
<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
import {onMounted,ref} from 'vue';
export default {
props:['src'],
setup(props,context){
const V1=ref(null);
onMounted(()=>{
console.log(V1.value);
})
return{
props,V1,
}
},
}
</script>
2.isRef:是否为ref对象
3.shallowRef:创建一个跟踪自身 .value 变化的 ref,只有第一层为响应式,其值不会为响应式
只有第一层为响应式,triggerRef(),ref可改为响应式数据,页面更新
4. triggerRef:强制更新dom
5. customRef:自定义ref,可以控制数据的追踪跟触发更新
三、reactive全家桶
1. reactive
- reactive 参数必须是对象 (json / arr),否则不具有响应式
- 如果给 reactive 传递了其它对象(如Date对象),需要重新赋值
2. readonly(只读)
3. shallowReactive
只有第一层为响应式,所以只能对浅层的数据 如果是深层的数据只会改变值 不会改变视图
四、to全家桶
1. roRef(创建一个响应式数据)
- toRef的本质是引用关系,修改响应式数据会影响原始数据
- toRef当数据发生改变是,界面不会自动更新
- toRef接受两个参数,第一个是对象,第二个是对象的属性
- toRef一次仅能设置一个数据
2. toRefs(创建多个响应式数据)
批量设置多个数据为响应式,并且和原始数据关联,不再更新视图
toRefs接收一个对象作为参数,它会遍历对象身上的所有属性,然后挨个调用toRef执行
2.toRaw
转为普通对象,更新数据,不更新试图
五、computed
const $total = ref(0);
$total = computed(() => {
return ...
})
六、watch
watch(监听的数据源(可以是ref,也可以是[ref1, ref2]),(oldVal,newVal)=> {},{ immediate:true, deep: true})
七、watchEffect高级监听器
import { watchEffect, ref } from 'vue'
let message = ref<string>('')
let message2 = ref<string>('')
watchEffect((oninvalidate) => {
oninvalidate(()=>{
//触发监听之前会调用一个函数可以处理你的逻辑例如防抖
})
},{
flush:"post",
//有三个参数:pre组件更新前执行,sync强制效果始终同步触发,post组件更新后执行
onTrigger () {
debugger
//调试 watchEffect
}
})
八、vue2.x和3.x生命周期对比
Vue2.x的生命周期 | Vue3.x的生命周期 |
---|---|
delete | |
delete | |
beforeMount | onbeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
errorCaptured | onErrorCaptured |
增加了两个新的调试钩子函数,两个钩子函数都接收一个DebuggerEvent:
- onRenderTracked
- onRenderTriggered
export default {
onRenderTriggered(e) {
debugger
// 检查哪个依赖性导致组件重新渲染
},
}
九、父子组件传值
1. 父传子
1.1父组件属性传,子组件defineProps,withDefaults接受
父组件:字符串不需要加:,其他的类型属性传
子组件:defineProps,withDefaults(props函数,对象默认值)
- defineProps的写法
<template>
<div class="menu">
菜单区域 {{ title }}
<div>{{ data }}</div>
</div>
</template>
<script setup lang="ts">
defineProps<{
title:string,
data:number[]
}>()
</script>
- withDefaults的写法
type Props = {
title?: string,
data?: number[]
}
withDefaults(defineProps<Props>(), {
title: "张三",
data: () => [1, 2, 3]
})
1.2 Provide / Inject
父组件
<template>
<div class="App">
<button>我是App</button>
<A></A>
</div>
</template>
<script setup lang='ts'>
import { provide, ref } from 'vue'
import A from './components/A.vue'
let flag = ref<number>(1)
provide('flag', flag)
</script>
<style>
.App {
background: blue;
color: #fff;
}
</style>
子组件
<template>
<div style="background-color: green;">
我是B
<button @click="change">change falg</button>
<div>{{ flag }}</div>
</div>
</template>
<script setup lang='ts'>
import { inject, Ref, ref } from 'vue'
const flag = inject<Ref<number>>('flag', ref(1))
const change = () => {
flag.value = 2
}
</script>
<style>
</style>
2.子传父
2.1 父组件通过事件获取子组件的值
子组件:defineEmits(‘事件名’), emit(‘事件名’, 要传递的数据)
父组件:@事件名= “getList” 点击callBack函数获取值
子组件
<template>
<div class="menu">
<button @click="clickTap">派发给父组件</button>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
const list = reactive<number[]>([4, 5, 6])
const emit = defineEmits(['on-click'])
const clickTap = () => {
emit('on-click', list)
}
</script>
父组件
<template>
<div class="layout">
<Menu @on-click="getList"></Menu>
<div class="layout-right">
<Header></Header>
<Content></Content>
</div>
</div>
</template>
<script setup lang="ts">
import Menu from './Menu/index.vue'
import Header from './Header/index.vue'
import Content from './Content/index.vue'
import { reactive } from 'vue';
const data = reactive<number[]>([1, 2, 3])
const getList = (list: number[]) => {
console.log(list,'父组件接受子组件');
}
</script>
2.2 父组件直接获取子组件的值
父组件:通过ref获取
子组件: defineExport({ data }) 暴露
子组件
<template>
<div>我是demo组件</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const isFlag = ref(false)
const data = reactive({
name: 'tom',
age: 18
})
//把数据导出
defineExpose({
data,
isFlag
})
</script>
父组件
<template>
//给组件绑定ref,从ref上获取
<Mode ref="refMode"></Mode>
</template>
<script setup>
import { ref ,reactive,onMounted} from 'vue'
import Mode from './view/mode.vue'
//ref名称必须与绑定的名称相同
let refMode = ref(null)
//setup ## beforeCreate、create生命周期的集合这里是获取不到的
console.log('refMode',refMode.value)
onMounted(() => {
//只能在在里面才能获取到完整的组件ref的信息
console.log('refMode2',refMode.value)
//通过解构获取
const {data,isFlag} = refMode.value
console.log('refMode3',data,isFlag)
})
</script>
<style>
#app {
text-align: center;
}
</style>
3. 兄弟组件传值
3.1 父组件充当桥梁
<template>
<div>
<A @on-click="getFalg"></A>
<B :flag="Flag"></B>
</div>
</template>
<script setup lang='ts'>
import A from './components/A.vue'
import B from './components/B.vue'
import { ref } from 'vue'
let Flag = ref<boolean>(false)
const getFalg = (flag: boolean) => {
Flag.value = flag;
}
</script>
<style>
</style>
3.2 bus
写一个bus.ts
type BusClass<T> = {
emit: (name: T) => void
on: (name: T, callback: Function) => void
}
type BusParams = string | number | symbol
type List = {
[key: BusParams]: Array<Function>
}
class Bus<T extends BusParams> implements BusClass<T> {
list: List
constructor() {
this.list = {}
}
emit(name: T, ...args: Array<any>) {
let eventName: Array<Function> = this.list[name]
eventName.forEach(ev => {
ev.apply(this, args)
})
}
on(name: T, callback: Function) {
let fn: Array<Function> = this.list[name] || [];
fn.push(callback)
this.list[name] = fn
}
}
export default new Bus<number>()
挂载到全局Vue config
或者单独的页面引入 import Bus from ‘bus.ts’
Bus.$ emit 触发,Bus.$ on监听
3.4 mitt
和vue2的bus差不多,略过
十、全局组件,局部组件,递归组件
10.1全局组件
和vue2的写法差不多,全局注册的时候如下
createApp(App).component('组件名',组件实例).mount('#app')
10.2局部组件
和vue2差不多局部引入
10.3 递归组件
暂时不想写,略过
十一、动态组件
动态切换
<component :is="A"></component>
const tab = reactive<Com[]>([{
name: "A组件",
comName: markRaw(A)
}, {
name: "B组件",
comName: markRaw(B)
}])
十二、slot插槽
12.1匿名插槽
和vue2差不多,略过
12.2 具名插槽
和vue2差不多,略过
12.3作用于插槽
子组件属性传
父组件取值
子组件
<div>
<slot name="header"></slot>
<div>
<div v-for="item in 100">
<slot :data="item"></slot>
</div>
</div>
<slot name="footer"></slot>
</div>
父组件
<Dialog>
<template #header>
<div>1</div>
</template>
<template #default="{ data }">
<div>{{ data }}</div>
</template>
<template #footer>
<div>3</div>
</template>
</Dialog>
12.4动态插槽
<Dialog>
<template #[name]>
<div>
23 (这里会变成header)
</div>
</template>
</Dialog>
const name = ref('header')
十三、异步组件&代码分包&suspense
异步组件
<script setup>
const post = await fetch(`/api/post/1`).then(r => r.json())
</script>
代码分包
defineAsyncComponent + import 组件
suspense
<Suspense>
<template #default>
<Dialog>
<template #default>
<延迟加载的组件/>
</template>
</Dialog>
</template>
<template #fallback>
<div>loading...</div>
</template>
</Suspense>
十四、新增Fragment、Teleport、Suspense组件(和react类似)
1.Fragment(不用加根节点)
vue2.0只能有一个根节点,会增加很多没有用的节点
vue3.0有虚拟的Fragment
2.Teleport
将子节点渲染到存在于父组件以外的 DOM 节点
Teleport其实就是React中的Portal。Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优良的计划。
一个 portal 的典型用例是当父组件有 overflow: hidden 或 z-index 款式时,但你须要子组件可能在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框。
- 没有Teleport
- 有Teleport
Telepost的用法:
3. Suspense
Suspense组件可以等待某个异步组件解析完成的前后的显示情况
使用方式
这个组件有两个具名插槽:
default - 默认的插槽。可以放置异步的组件
fallback - 可以放置组件未加载前的样式 比如 loading
用法:
<Suspense>
<template #default>
<User />
</template>
<template #fallback>
Loading...
</template>
</Suspense>
十五、keep-alive缓存
十六、transition动画组件
十七、transition-group过度列表
十八、TSX
十九、v-model
二十、directive自定义指令
二十一、自定义hooks
二十二、vue3定义全局函数和变量
二十三、编写vue3插件
二十四、pinia
二十五、Router
二十六、Filter
3.x 不再支持filter,可以用method 或者 computed来代替。
二十七、Better TypeScript support
使用 TypeScript 可以增加代码的可读性和可维护性
二十八、Composition API复用
2.0版本用的是mixins
3.0如下
<template>
<div>X: {{ x }}</div>
<div>Y: {{ y }}</div>
</template>
<script>
import { defineComponent, onMounted, onUnmounted, ref } from "vue";
const useMouseMove = () => {
const x = ref(0);
const y = ref(0);
function move(e) {
x.value = e.clientX;
y.value = e.clientY;
}
onMounted(() => {
window.addEventListener("mousemove", move);
});
onUnmounted(() => {
window.removeEventListener("mousemove", move);
});
return { x, y };
};
export default defineComponent({
setup() {
const { x, y } = useMouseMove();
return { x, y };
}
});
</script>
下面暂时不更改
八、默认项目目录结构的不同
vue3移除了配置文件目录,config 和 build 文件夹,移除了 static 文件夹,新增 public 文件夹,并且 index.html 移动到 public 中,在 src 文件夹中新增了 views 文件夹,用于分类视图组件和公共组件
九、v-if和v-for的权重
2.0版本中是v-for的权重大于v-if
3.0版本中是v-if的权重大于v-for
十、安装vue3.x版本
1.全局安装create-vite-app
npm i -g create-vite-app
- 创建项目目录
create-vite-app 项目名
- 运行
cd 项目名
npm install
npm run dev
- 升级2.x版本
vue add vue-next
- 3.x比2.x版本代码更精简,3.x引入了一个新的函数名createApp,会把容器挂载到它上面来。
//vue2.0
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
//vue3.0
import { createApp } from 'vue';
import App from './App.vue'
createApp(App).mount('#app')