目录
vue3的特点
- 打包体积更小
- 新的代码编写方式 组合式API( Composition API)
- 提供了一些新的方法 比如setup()函数, computed
- vue3 使用 es6的 Proxy 实现数据的双向绑定Proxy链接描述
- vue3 使用 摇树(tree shaking)的机制点击摇树详细讲解
vue3与vue2区别和部分vue2代码不能在vue3项目运行情况
Vue CLI 4.5以下,对应的是Vue2;Vue CLI 4.5及以上,对应的是Vue3
查看vue版本
npm list vue
vue3的实例化创建
vue3抛弃了使用 new Vue()
创建实例化的过程,改用 Vue.createApp()
创建应用实例
// 每个 Vue 应用都是通过用 createApp 函数创建一个新的应用实例开始的
const app = Vue.createApp({...})
// 与大多数应用方法不同的是,mount 不返回应用本身。相反,它返回的是根组件实例。
const vm = app.mount('#app')
VueX下载
npm install vuex
store/index.js
// vue3中创建store实例对象的方法createStore()按需引入
import {
createStore
} from 'vuex'
export default createStore({
state: {
ac: 123
},
mutations: {},
actions: {},
getters: {},
modules: {}
})
main.js
import router from './router'
import store from "./store";
createApp(App).use(router).use(store).mount('#app')
vue3 的组件创建
vue3的组件支持多个根元素
<div id="app">
<com></com>
</div>
const app = Vue.createApp({ })
const com = {
template:` <div class="wrap">
<p>helo vue</p>
</div>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>`
}
app.component('com', com)
const vm = app.mount('#app')
组件通信
场景:父组件里面有a组件,a组件里面又有b组件,b组件需要使用父组件里面的值,以往做法,两次父子通信,比较麻烦
vue3中 可以使用一对 provide 和 inject。
特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。
<div id="app">
<child></child>
</div>
<script src="./js/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
a: '(数据1)',
}
},
/* (1)provide: {
success: '这是成功的消息',
msg: this.a// 不能直接传递父组件的data中的属性, 如果需要传递,需要使用函数形式
}*/
provide() {
return {
success: '这是成功的消息',
msg: this.a
}
}
})
app.component('child', {
template: `<div>
<com ></com>
</div>`,
})
app.component('com', {
template: `
<div>
<span>这是com组件={{success}}={{msg}}<span>
</div>
`,
//(1)inject: ['success','msg']//msg值接收不到
inject: ['success', 'msg']
})
const vm = app.mount('#app')
</script>
组合式API
<script>
const app = Vue.createApp({
data() {
return {
msg: 'hello'
}
},
beforeCreate() {
console.log('beforeCreate 钩子函数 === 1');
},
created() {
console.log('created 钩子函数 ==== 2');
},
setup() {
console.log("自定执行, 先于beforeCreate() 和 created() 执行");
}
})
const vm = app.mount("#app")
组合式API 的应用入口 是 setup()函数
组合式API含义:在setup()函数中可以处理几乎所有的业务逻辑, 比如数据响应式,计算属性,侦听器,方法,生命周期钩子函数的应用,路由,vuex等
生命周期
- setup() 函数 替换了 数据data 和方法methods
- setup() 函数 替换了 生命周期钩子函数 beforeCreate() 和 created() ,setup函数是比 生命周期函数 beforeCreate 和 Created 两个钩子函数还先执行的函数
- setup() 函数内使用的生命周期钩子函数: setup () 内部调用生命周期钩子 和选项式API生命周期钩子函数名字相同,只是需要添加前缀 ‘on’ :
import { defineComponent, ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue';
export default defineComponent({
setup() {
console.log("setup执行了");
const msg = ref('abc')
const update = () => {
msg.value += '==='
}
onBeforeMount(() => {
console.log("============3.0中的BeforeMount");
})
onMounted(() => {
console.log("============3.0中的onMounted");
})
onBeforeUpdate(() => {
console.log("============3.0中的BeforeUpdate");
})
onUpdated(() => {
console.log("============3.0中的Updated");
})
onBeforeUnmount(() => {
console.log("============3.0中的BeforeUnmount");
})
onUnmounted(() => {
console.log("============3.0中的Unmounted");
})
return {
msg, update
}
}});
setup函数
- 与模板一起使用:需要返回一个对象 (在setup函数中定义的变量和方法最后都是需要 return 出去的 ,否则无法在模板中使用)
- 执行 setup 时,组件实例尚未被创建(在 setup() 内部,this 不会是该活跃实例的引用,即不指向vue实例,Vue为了避免我们错误的使用,直接将 setup函数中的this修改成了 undefined)
setup 函数参数详解
- setup 函数时,它将接受两个参数:(props、context(包含attrs、slots、emit))
props参数,是一个对象,里面有父级组件向子级组件传递的数据,子级组件使用props接受,模板使用
export default defineComponent({
props: ["参数"],
setup(props) {
console.log(props);//涉及传值时,props: ["参数"] 是必须的,否则打印没有值
return {}
}
});
attrs: 获取当前组件标签上的属性,但是该属性是在props中没有声明接收的对象, 相当于 this.$attrs
slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
emit: 用来分发自定义事件的函数, 相当于 this.$emit
- 使用渲染函数:可以返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态
注意事项:
1、setup函数中不能使用this。不能通过this来访问data/computed/methods/props
2、setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新。但是,因为 props 是响应式的,你不能使用 ES6 解构,因为它会消除 prop 的响应性。
3、在vue3中尽量不要混合的使用data和setup及methods和setup,在 setup 方法中不能访问 data 和 methods
4、setup中对象的方法和methods对象中的方法会合并为组件对象的方法,如果有重命setup优先
5、setup 不能是一个 async 函数: 因为返回值不再是 return 的对象, 而是 promise, 模板看不到 return 对象中的属性数据,也就是说setup函数只能是同步的不能是异步的
如果需要解构 prop,可以通过使用 setup 函数中的 toRefs 来完成此操作:
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
另外:context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context 使用 ES6 解构
export default {
setup(props, { attrs, slots, emit }) {
...
}
}
setup函数中数据响应式
- 监听单一数据源 ref()函数
<!-- 在模板中使用ref() 返回的数据会自动解包 -->
<p>{{count}}</p>
<button @click="updata">更新数据</button>
<script>
import { ref } from "vue";
export default {
/* data() {原来的写法存放数据
return {
msg: 'hello'
}
},*/
setup() {
// 监听单一的数据, ref()函数的参数就是数据的初始值
const count = ref(100);
console.log(count.value); //100
// ref() 返回的数据 被包裹value属性中, 通过 value 属性访问获取数据,但是在vue的模板语法中可以直接访问, 不需要通过value
function updata(params) {
count.value++
} //这里不要写分号,会报error Unnecessary semicolon no-extra-semi
return {
// return 这里返回的任何内容都可以用于组件的其余部分
count,updata
};
},
};
</script>
- 监听多个数据 使用的是 reactive() 函数
<template>
<div>复杂数据:{{ reactiveVal.num }}</div>
</template>
<script>
import { reactive } from "vue";
export default {
setup() {
const reactiveVal = reactive({ num: 5 });
reactiveVal.num = 666;
//reactive的本身也是ref,只不过是通过进一步处理,省去了value属性。
return { reactiveVal };
},
//常用于复杂的数据类型。
};
</script>
setup() {
const obj={ num: 5 }
//返回的是一个Proxy的代理对象,被代理的目标对象就是obj对象
//reactiveVal现在是代理对象,obj是目标对象
//reactiveVal对象类型是Proxy
const reactiveVal = reactive(obj);
//reactive的本身也是ref,只不过是通过进一步处理,省去了value属性。
console.log("reactiveVal", reactiveVal);
function updataUser(){
// obj.num = 555;直接使用目标对象的方式来更新目标对象中的成员的值,是不可能的,只能使用代理对象的方式来更新数据(响应式数据)
reactiveVal.num = 666;
}
return { reactiveVal,updataUser };
},
ref中如果放入的是一个对象,那么是经过reactive的处理,形成了一个proxy类型的对象
ref 内部: 通过给 value 属性添加getter/setter 来实现对数据的劫持
reactive 内部: 通过使用 Proxy 来实现对对象内部所有数据的劫持, 并通过 Reflect 操作对象内部数据
const m2 = reactive({
name: "小明",
wife: {
name: "小红"
}
})
const m3 = ref({
name: "小明",
wife: {
name: "小红"
}
})
setup函数中应用计算属性和侦听器
计算属性
vue3 提供了一个 计算方法 Vue.computed() 在 setup()函数中使用计算属性
<template>
<div>计算属性:{{ maxNum }}</div>
</template>
<script>
import { ref, computed } from "vue";
export default {
setup() {
const count = ref([1, 3, 345, 456, 12]);
// 获取大于50 的数据
// vue3中计算属性的函数中如果只传入一个回调函数,表示的是get
const maxNum = computed(() => count.value.filter((item) => item > 50));
return {
maxNum,
};
},
};
</script>
计算属性的getter 和 setter
<template>
<div class="main">
<input type="text" v-model="changeTwoName" />
<h1>计算属性</h1>
<input type="text" v-model="userName.msg" />
</div>
</template>
<script>
import { computed, reactive } from "vue";
export default {
setup() {
const userName = reactive({
msg: "原始数据",
});
// vue3中计算属性的函数中如果传入一个对象,表示的是get和set
const changeTwoName = computed({
get() {
return userName.msg;
},
set(val) {
console.log("值",val);
userName.msg = val;
},
});
return {
userName,
changeTwoName,
};
},
};
</script>
侦听器
vue3 提供了一个 方法 watch() 实现数据的监听
- 监听单个数据源
<template>
<div>
{{ count }}
<button @click="setCount">单击加一并触发侦听器</button>
</div>
</template>
<script>
import { ref, watch } from "vue"
export default {
setup() {
const count = ref(10);
const setCount = () => {
count.value++
}
/* watch(() => count.value, (newValue, oldValue) => {
console.log("侦听器被触发", newValue, oldValue);
}) */
watch(count, (newValue, oldValue) => {
console.log("侦听器被触发", newValue, oldValue);
})
return {
count, setCount
}
}
}
</script>
- 侦听器还可以使用数组同时侦听多个源:
const firstName = ref('');
const lastName = ref('');
watch([firstName, lastName], (newValues, prevValues) => {
console.log(newValues, prevValues);
})
firstName.value = "John"; // logs: ["John",""] ["", ""]
lastName.value = "Smith"; // logs: ["John", "Smith"] ["John", ""]
- 侦听响应式对象
使用侦听器来比较一个数组或对象的值,这些值是响应式的,要求它有一个由值构成的副本。
const numbers = reactive([1, 2, 3, 4])
watch(
() => [...numbers],
(numbers, prevNumbers) => {
console.log(numbers, prevNumbers);
})
numbers.push(5) // logs: [1,2,3,4,5] [1,2,3,4]
尝试检查深度嵌套对象或数组中的 property 变化时,仍然需要 deep 选项设置为 true。
,{immediate:true }//默认执行一次watch
const state = reactive({
id: 1,
attributes: {
name: "",
},
});
watch(
() => state,
(state, prevState) => {
console.log(
"not deep ",
state.attributes.name,
prevState.attributes.name
);
}
);
watch(
() => state,
(state, prevState) => {
console.log(
"deep ",
state.attributes.name,
prevState.attributes.name
);
},
{ deep: true }
);
state.attributes.name = "Alex"; // 日志: "deep " "Alex" "Alex"
然而,侦听一个响应式对象或数组将始终返回该对象的当前值和上一个状态值的引用。为了完全侦听深度嵌套的对象和数组,可能需要对值进行深拷贝。这可以通过诸如 lodash.cloneDeep 这样的实用工具来实现。
import _ from 'lodash';
import {watch} from 'vue';
const state = reactive({
id: 1,
attributes: {
name: "",
},
});
watch(
() => _.cloneDeep(state),
(state, prevState) => {
console.log(
state.attributes.name,
prevState.attributes.name
);
}
);
state.attributes.name = "Alex"; // 日志: "Alex" ""
toRefs
把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
//场景:一秒改变一次数据,页面更新, return中解构后数据改变了,页面没有更新,用到了toRefs
<template>
<div>
<h2>toRef的使用</h2>
<h3>name:{{ name }}</h3>
<h3>age:{{ age }}</h3>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue';
export default defineComponent({
name: 'App',
setup() {
const state = reactive({
name: '测试数据',
age: 47
})
const { name, age } = toRefs(state)//(1) 解构出来的变量直接是就是响应式的ref
setInterval(() => {
//state.age += 1;
age.value += 1; //(return没有结构时不能使用这种方法)
console.log("定时器执行了");
}, 1000)
return {
//...state //解构后数据不能响应
name, age//(1)数据响应式
//...toRefs(state)// (2)数据响应式
}
}
});
</script>
setup语法糖
<template>
<div class="main">
{{ text }}
</div>
</template>
<script setup>
const text = "你好";
</script>
<script setup> 标签又称 setup 文件——是 setup 函数的语法糖之一。
setup 文件的特点:
对于在 setup 文件中定义的属性和方法,无需将它们 return 到最后的结果对象中去,直接在 template 模板中使用它们即可。
里面的代码会被编译成组件 setup() 函数的内容。
setup语法糖的Props
用setup语法糖,那么需要用defineProps
(defineProps是编译器宏,无需引入)定义props
<template>
<div>
{{ aa }}
<br>
传的值:{{ msg }}
</div>
</template>
<script setup>
const props = defineProps({
msg:String,//子接受父传的值
})
const aa="数据"
</script>
自定义hook函数
相当于vue2中的mixins(混入)
举例子
useMousePosition.ts
import { ref, onMounted, onBeforeUnmount } from 'vue';
export default function(){
const x = ref(-1)
const y = ref(-1)
//获取鼠标点击位置坐标
const clickHandler = (event: MouseEvent) => {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => {
window.addEventListener("click", clickHandler)
})
onBeforeUnmount(() => {
window.removeEventListener("click", clickHandler);
})
return {
x, y
}
}
调用
<script lang="ts">
import { defineComponent} from 'vue';
import useMousePosition from './hooks/useMousePosition'
export default defineComponent({
name: 'App',
setup() { //收集用户鼠标点击的页面坐标
const { x, y } = useMousePosition()
return {
x, y
}
}
});
</script>
其他
- shallowReactive和shallowRef
shallowReactive : 只处理了对象内最外层属性的响应式(也就是浅响应式)
shallowRef: 只处理了 value 的响应式, 不进行对象的 reactive 处理
<template>
<div>
<h2>shallowReactive和shallowRef</h2>
<h3>m1: {{ m1 }}</h3>
<h3>m2: {{ m2 }}</h3>
<h3>m3: {{ m3 }}</h3>
<h3>m4: {{ m4 }}</h3>
<hr>
<button @click="update">更新</button>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, shallowReactive, shallowRef } from 'vue'
export default defineComponent({
name: 'App',
setup() {
const m1 = reactive({ name: "鸣人", age: 20, car: { name: "奔驰", color: "red" } })
const m2 = shallowReactive({ name: "鸣人", age: 20, car: { name: "奔驰", color: "red" } })
const m3 = ref({ name: "鸣人", age: 20, car: { name: "奔驰", color: "red" } })
const m4 = shallowRef({ name: "鸣人", age: 20, car: { name: "奔驰", color: "red" } })
const update = () => {
/* m1.name += '==='
m1.car.name += '===' */
// m2.name += '==='
// m2.car.name += '==='
// m3.value.name += '==='
// m3.value.car.name += '==='
m4.value.name += '==='
}
return {
m1,
m2,
m3,
m4,
update
}
}
});
</script>
- readonly 与 shallowReadonly
readonly:深度只读数据(包含浅只读)
shallowReadonly 浅只读数据
<template>
<h2>readonly 深度只读数据与 shallowReadonly浅只读数据</h2>
<h3>state:{{ state }}</h3>
<hr>
<button @click="updata">更新数据</button>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, readonly, shallowReadonly } from 'vue';
export default defineComponent({
name: 'App',
setup() {
const state = reactive({
name: "伽罗",
age: 20,
car: {
name: "奔驰",
color: "yellow"
}
})
// const state2 = readonly(state)
const state2 = shallowReadonly(state)
const updata = () => {
// state2.name += '==='
state2.car.name += '==='
}
return {
state, state2, updata
}
}
});
</script>
- toRaw 与 markRaw
-
toRaw:
返回由 reactive 或 readonly 方法转换成响应式代理的普通对象。 这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
-
markRaw:
标记一个对象,使其永远不会转换为代理。返回对象本身 应用场景: 有些值不应被设置为响应式的,例如复杂的第三方类实例或 Vue 组件对象。 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。
<template>
<h2>{{ state }}</h2>
<button @click="testToRaw">测试toRaw</button>
<button @click="testMarkRaw">测试markRaw</button>
</template>
<script lang="ts">
import { markRaw, reactive, toRaw } from 'vue'
export default {
setup() {
const state = reactive<any>({
name: 'tom',
age: 25
})
const testToRaw = () => {
//把代理对象变成了普通对象,数据变化,界面不变化
const user = toRaw(state)
user.age++ // 界面不会更新
console.log("toRaw执行了");
}
const testMarkRaw = () => {
const likes = ['a', 'b']
// state.likes = likes
//markRaw标记的对象数据,从此以后都不能在成为代理对象了
state.likes = markRaw(likes) // likes数组就不再是响应式的了
setInterval(() => {
console.log("定时器执行了");
state.likes[0] += '--'
}, 1000)
}
return {
state,
testToRaw,
testMarkRaw
}
}
}
</script>
- toRef
为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
- 应用: 当要将某个 prop 的 ref 传递给复合函数时,toRef 很有用
App.vue
<template>
<h2>App</h2>
<p>state:{{ state }}</p>
<p>age:{{ age }}</p>
<p>money:{{ money }}</p>
<hr />
<button @click="update">点击更新</button>
<Child :age="age" /> <!--:age接收的是一个数据而不是一个对象 -->
</template>
<script lang="ts">
import { reactive, toRef, ref } from "vue";
import Child from "./components/child.vue";
export default {
setup() {
const state = reactive({
age: 5,
money: 100,
});
//把响应式数据state对象中的某个属性age变成了ref对象
const age = toRef(state, "age");
const money = ref(state.money); //是拷贝的对象
console.log("toRef", age);
console.log("ref", money);
const update = () => {
state.age += 2;
// age.value += 3;
//money.value++; //state中的数据不会更新
};
return {
state,
age,
money,
update,
};
},
components: {
Child,
},
};
</script>
child.vue
<template>
<h2>Child子级组件</h2>
<h3>age:{{ age }}</h3>
<h3>age的长度{{ length }}</h3>
</template>
<script lang="ts">
import { computed, defineComponent, Ref, toRef } from "vue";
function useFeatureX(age: Ref) {
// console.log("长度值", age.value.toString().length);
const lenth = computed(() => {
return age.value.toString().length;
});
return lenth;
}
const component = defineComponent({
props: {
age: {
type: Number,
require: true, //必须的
},
},
setup(props, context) {
// useFeatureX(age);理论这样拿值,这里拿不到,应该是一个ref的对象
const length = useFeatureX(toRef(props, "age"));
return {
length,
};
},
});
export default component;
</script>
CustomRef的使用
创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制(就像是给ref添加自己的方法)
<template>
<div>
<h2>CustomRef的使用</h2>
<input type="text" v-model="keyword" />
<p>{{ keyword }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent, customRef, ref } from "vue";
//自定义防抖函数
function useDebouncedRef<T>(value: T, delay = 200) {
let timeOutId: number
return customRef((track, trigger) => {
return {
get() {//返回数据的
track()//告诉vue追踪数据
return value
},
set(newValue: T) {//设置数据的
clearTimeout(timeOutId)//清理定时器
//开启定时器
timeOutId = setTimeout(() => {
value = newValue
trigger()//告诉vue界面
}, delay)
}
}
})
}
export default defineComponent({
name: "App",
setup() {
// const keyword = ref("a");
const keyword = useDebouncedRef("abc", 500);
return { keyword };
},
});
</script>
响应式数据的判断
isRef: 检查一个值是否为一个 ref 对象
isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理
例如: console.log(isRef(ref()));
Teleport(瞬移)
Teleport 提供了一种干净的方法, 让组件的 html 在父组件界面外的特定标签(很可能是 body)下插入显示
App.vue
<template>
<h2>App父级组件</h2>
<hr>
<ModelButton />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import ModelButton from "./ModelButton.vue"
export default defineComponent({
name: 'App',
components: {
ModelButton
},
});
</script>
ModelButton.vue
<template>
<button @click="ModelOpen = true">
打开一个对话框
</button>
<!-- 对话框代码 -->
<Teleport to="body">/(2)
<div v-if="ModelOpen">
<div>这是对话框</div>
<button @click="ModelOpen = false">关闭对话框</button>
</div>
</Teleport>
<!-- <div v-if="ModelOpen">/(1)
<div>这是对话框</div>
<button @click="ModelOpen = false">关闭对话框</button>
</div> -->
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'ModelButton',
setup() {//控制对话框显示或隐藏
const ModelOpen = ref<any>(false)
return {
ModelOpen
}
}
});
</script>
使用Teleport 包裹的情况
Suspense(不确定的)
它们允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验
app.vue
<template>
<div>
<h2>App父级组件:Suspense组件的使用</h2>
<Suspense>
<template #default><!--简写 v-slot:default -->
<AsynComponent /> <!-- 异步组件 -->
</template>
<template v-slot:fallback>
<h2>Loading</h2> <!-- Loading的内容 -->
</template>
</Suspense>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import AsynComponent from "./AsynComponent.vue"
export default defineComponent({
name: 'App',
components: {
AsynComponent
},
setup() {
return {
}
}
});
</script>
AsynComponent.vue
<template>
<div>
<h3>AsynComponent子级组件</h3>
<p>{{ msg }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'AsynComponent',
setup() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
msg: 'what are you no sha lei'
})
}, 2000)
})
}
});
</script>
效果:
VV3快捷键实现模板
文件–>首选项–>用户片段–>vue.json文件
"Print to console": {
"prefix": "vv3",
"body": [
"<template>",
"\t<div>",
"\t</div>",
"</template>",
"<script lang=\"ts\">",
"import { defineComponent } from 'vue';",
"export default defineComponent({",
"\tname:'App'",
"});",
"</script>"
],
"description": "Log output to console"
}