Vue3.0的优点
- 性能提升,运行速度是vue2.x的1.5倍左右
- 体积更小,按需编译体积比vue2.x要更小
- 类型推断,更好的支持ts
- 高级给与,暴露了更底层的API和提供更先进的内置组件
- 组合API(composition API),更好地组织、封装和复用逻辑
vite
- 概述
是一个更加轻量的vue项目脚手架工具,相对于
vue-cli
,它默认安装的插件更少
- 创建项目
npm init vite-app 项目名称 或者 yarn create vite-app 项目名称
- 启动项目
npm run dev 或者 yarn dev
创建vue应用
main.js
//1. 创建main.js,并从vue中导入createApp函数
import { createApp } from 'vue'
//2. 创建一个根组件App.vue,并导入到main.js中
import App from './App.vue'
//3. 使用createApp创建应用实例
const app = createApp(App)
//4. 应用示例挂载到#app容器中
app.mount("#app")
App.vue
<template>
<div class="container">
根组件
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
组合API
选项API与组合API
选项API(Options API)
- 说明
在Vue2.x中项目使用的就是选项api写法,这种写法的风格就是,在data中写数据,在methods中写函数,…,即一个功能逻辑的代码分散
- 优点
易于学习和使用,因为写代码的位置已经约定好了
- 缺点
代码组织性差,即相似的逻辑代码不便于复用,一旦逻辑复杂、代码多了不便阅读,虽然提供
mixins
用来封装逻辑,但是出现数据函数覆盖的概率很大,不好维护
组合API(Composition API)
- 说明
在vue3.0中将使用组合API,即一个功能逻辑的代码组织在一起(包含数据、函数等等)【也支持选项API用法】
- 优点
功能逻辑复杂的情况下,各个功能逻辑代码组织在一起,便于阅读与维护
- 缺点
需要良好的代码组织能力与拆分逻辑能力
setup
函数
说明
setup
是一个新的组件选项,作为组件中使用组合API的起点
从组件生命周期来看,它的执行在组件示例创建之前(即vue2.x的beforeCreate)执行,这意味着在setup中this
还不是组件实例,还是个undefined
在模板中需要使用的数据和函数,需要在setup
返回
示例
<template>
<div class="container">
<h1 @click="say">{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'App',
//1. 组合api的起点(组合api在此当中编写)
//2. 可以理解为在beforeCreate之前执行(组件实例创建之前,所以无法使用this)
//3. 模板的数据和函数,有setup提供,即需要在setup中返回
setup() {
const msg = 'hello vue3'
const say = () => { console.log(msg) }
return {msg, say} //返回的数据需要是个对象格式
}
}
</script>
钩子函数
setup: 创建实例的
onBeforeMount: 挂载DOM前
onMounted: 过载DOM后
onBeforeUpdate: 更新组件前
onUpdated: 更新组件后
onBeforeUnmount: 卸载销毁前
onUnmounted: 卸载销毁后
//可以注册多个相同的钩子(执行顺序:先注册先执行)
<template>
<div class="container">
根组件
</div>
</template>
<script>
/*注意:先要进行导入*/
import {onBeforeMount, onMounted} from "vue";
export default {
name: 'App',
setup () {
//1.DOM渲染前钩子
onBeforeMount(() => {
//null
console.log("DOM渲染前钩子", document.querySelector('.container'))
})
//2.DOM渲染后钩子
//功能A
onMounted(() => {
//<div class="container"> 根组件 </div>
console.log("DOM渲染后钩子1", document.querySelector('.container'))
})
//3.可以注册多个相同的钩子(先注册先执行)
//功能B
onMounted(() => {
console.log("DOM渲染后钩子2", document.querySelector('.container'))
})
}
}
</script>
reactive
函数
说明
reactive
函数用于定义复杂类型的响应式数据
示例
<template>
<div class="container">
根组件
<div>obj: < {{obj.name1}} -- {{obj.age}} ></div>
<div>obj2: < {{obj2.name}} -- {{obj2.age}} ></div>
<button @click="updateName">修改名字</button>
</div>
</template>
<script>
import {reactive} from 'vue'
export default {
name: 'App',
setup() {
//1.数据
const obj = {
name1: 'xiaoming',
age: 18
}
//2.修改名字 4.页面数据并未响应式显示地修改后的数据,因为setup中的数据非响应式的数据
//5.注意:除了obj2,里面的obj也会变成响应式,//7.且如果将obj2的修改注释了,又不会
const updateName = () => {
console.log("修改名字了") //3.会打印,即名字数据已被修改
obj.name1 = "小明"
obj2.name = "小明" //6.注释掉
}
//5.使用reactive
const obj2 = reactive({
name: 'xiaoming',
age: 18
})
return {obj, updateName, obj2}
}
}
</script>
toRef
函数
说明
toRef
函数用于转换响应式对象中某个属性为单独响应数据,并且值是关联的。
- 使用场景
如有一个响应式对象数据,但是模板中只需要使用其中一项数据
示例
<template>
<div class="container">
根组件
<div>obj: < {{name}} ></div>
<button @click="updateName">修改名字</button>
</div>
</template>
<script>
import {reactive, toRef} from "vue";
export default {
name: 'App',
setup() {
//1.响应式数据对象
const obj = reactive({
name: "xiaoming",
age: 18
})
//2.将对象中的具体的某个属性抽取、定义并包装成一个响应式对象数据
const name = toRef(obj, 'name')
const updateName = () => {
name.value = '小明' //具体数值存放在包装的响应式对象的value属性中,所以需要通过修改value的值来实现数据的修改
}
//3.返回出数据
return {name, updateName}
}
}
</script>
toRefs
函数
说明
toRef
函数用于转换响应式对象中所有属性为单独响应数据,并且值是关联的(相当于reactive省去了对象点属性 的 对象点
)。
示例
<template>
<div class="container">
根组件
<div>obj: < {{name}} {{age}}></div>
<button @click="updateName">修改名字</button>
</div>
</template>
<script>
import {reactive, toRefs} from "vue";
export default {
name: 'App',
setup() {
//1.响应式数据对象
const obj = reactive({
name: "xiaoming",
age: 18
})
//2.将对象中的所有属性抽取、定义并包装成一个响应式对象
const reactiveObj = toRefs(obj)
const updateName = () => {
reactiveObj.name.value = '小明'
reactiveObj.age.value = 19
}
//3.返回出数据
return {...reactiveObj, updateName}
}
}
</script>
ref
函数
说明
ref
函数用于定义(一般用于简单类型,也可以复杂类型)响应式数据。
示例
<template>
<div class="container">
根组件
<div>< {{name}} {{age}}></div>
<button @click="updateName">修改</button>
</div>
</template>
<script>
import {ref} from "vue";
export default {
name: 'App',
setup() {
/**
* 也可以用于定义复杂类型的响应式数据
* 通常在对数据的类型无法确定的时候会如此使用它
* let data = ref(null)
* data.value = 123
* data.value = [1, 2, 3]
* data.value = {"key": 123}
*/
const name = ref('xiaoming')
const age = ref(18)
const updateName = () => {
name.value = '小明'
age.value = 19
}
return {name, age, updateName}
}
}
</script>
computed
函数
说明
computed
函数用于定义计算属性
示例
<template>
<div class="container">
<div>{{name}} 今年:{{age}}岁</div>
<div>{{name}} 后年:{{newAge}}岁</div>
<button @click="yearAgo">明年</button>
</div>
</template>
<script>
import {reactive, computed, toRefs} from "vue";
export default {
name: 'App',
setup() {
const obj = reactive({
name: "小明",
age: 18
})
const newAge = computed(() => {
return obj.age + 2
})
const yearAgo = () => {
obj.age++
}
return {...toRefs(obj), newAge, yearAgo}
}
}
</script>
get & set
和v-model
<template>
<div class="container">
<div>{{name}} 今年:{{age}}岁</div>
<div>{{name}} 后年:{{newAge}}岁</div>
</div>
</template>
<script>
import {computed, ref} from "vue";
export default {
name: 'App',
setup() {
const age = ref(18)
let newAge = computed({
get () {
return age.value + 2
},
set (value) {
age.value = value - 2
}
})
setTimeout(() => newAge.value = 21, 2000)
return {age, newAge}
}
}
</script>
watch
函数
说明
watch
函数用于定义监听器。
示例
<template>
<div class="container">
<p>count value: {{count}}</p>
<button @click="add">modify - ref</button>
<p>obj: {{obj.desc}}, {{obj.num}}</p>
<button @click="updateObj">modify - reactive</button>
<p>subObj: {{obj.subObj.id}}, {{obj.subObj.name}}</p>
<button @click="updateSubObj">modify - reactive2</button>
</div>
</template>
<script>
import {ref, watch, reactive} from 'vue'
export default {
name: 'App',
setup() {
//1. 监听一个 ref 数据
const count = ref(0)
const add = () => {
count.value++
}
watch(count, (newValue, oldValue) => {
console.log(newValue, oldValue)
}, {
immediate: true //立即执行,即打开页面时就默认执行一次
})
//2. 监听一个 reactive 数据
const obj = reactive({
desc: "test",
num: 20,
subObj: {
id: 1,
name: "sub object"
}
})
const updateObj = () => {
obj.desc = "测试";
obj.num = 21;
}
watch(obj, (newValue, oldValue) => {
console.log(newValue, oldValue) //响应式数据,都是修改后的
})
//3. 同时监听 ref 和 reactive 数据(监听多个数据变化)
watch([count, obj], (newValue, oldValue) => {
console.log(newValue, oldValue)
})
//4. 监听 reactive 中对象的指定属性的数据变化
//使用函数-返回要监听的属性
watch(()=>obj.num, (newValue, oldValue) => {
console.log("监听 reactive 中对象的指定属性的数据变化", newValue, oldValue)
})
//5. 监听 reactive 中的指定复杂类型属性
//如果直接监听obj,则无需深度监听
const updateSubObj = () => {
obj.subObj.id = 2
}
watch(()=>obj.subObj, (newValue, oldValue)=>{
console.log(newValue, oldValue)
}, {
deep: true //需要深度监听
})
return {count, add, obj, updateObj, updateSubObj}
}
}
</script>
ref
属性
说明
ref
属性用于绑定并获取DOM或者组件实例。
示例
<template>
<div class="container">
<div ref="box">box</div>
<ul>
<li v-for="i in 4" :key="i" :ref="setDom">{{i}}</li>
</ul>
</div>
</template>
<script>
import {nextTick, onMounted, ref} from "vue";
export default {
name: 'App',
setup() {
/**
* 1.获取单个元素
* vue2.x
* 1.<div ref="box">box</div>
* 2.this.$refs.box
*/
//1.1 定义一个空的响应式数据,用于接收数据
const box = ref(null)
//1.3 验证
onMounted(() => {
console.log(box.value.innerText) //box
})
/**
* 2.获取v-for遍历的多个元素
* vue2.x
* 1.<ul><li v-for="i in 4" :key="i" ref="li">{{i}}</li></ul>
* 2.this.$refs.li
*/
//2.1 定义一个空的数组,用于接收数据
const lis = []
//2.2 定义一个函数,往空数组进行push DOM
const setDom = (el) => {
console.log("setDom start...")
console.log(el) //li标签
lis.push(el)
}
//2.4 验证
nextTick(() => {
console.log(lis)
})
//1.2 将box返回出去
//2.3 将setDom返回出去
return {box, setDom}
}
}
</script>
组件通信
父传子 - props
Son.vue
<template> <div class="container"> <h1>子组件</h1> <p>{{data}}</p> </div> </template> <script> export default { name: 'Son', //子组件使用props接收父组件数据 props: { data: { type: Number, default: 0 } }, setup(props) { //默认第一个参数就是props //vue3的新用法,在setup中获取父组件数据 console.log(props.data) } } </script>
App.vue
<template> <div class="container"> <h1>父组件</h1> <p>{{data}}</p> <hr> <Son :data="data"/> </div> </template> <script> import Son from './components/Son.vue' import {ref} from "vue"; export default { name: 'App', components: { Son }, setup() { const data = ref(1000000) return {data} } } </script>
子传父 - emit
Son.vue
<template> <div class="container"> <h1>子组件</h1> <button @click="changeData">change</button> </div> </template> <script> export default { name: 'Son', setup(props, {emit}) { //context中包装了emit对象 const data = 10000000 const changeData = () => { emit('change-data', data) } return {data, changeData} } } </script>
App.vue
<template> <div class="container"> <h1>父组件</h1> <p>{{data}}</p> <hr> <Son @change-data="getData"/> </div> </template> <script> import Son from './components/Son.vue' export default { name: 'App', components: { Son }, setup() { const getData = (data) => { console.log(data) } return {getData} } } </script>
依赖注入 - provide & inject
App.vue
<template> <div class="container"> <h1>父组件 {{money}}</h1> <button @click="money=1000">发红包</button> <hr> <Son/> </div> </template> <script> import Son from './Son.vue' import {provide, ref} from "vue"; export default { name: 'App', components: { Son }, setup() { const money = ref(100) //使用 provide 将 money 提供给 Son, GrandSon //注意:单向原则,即接受方组件的修改无法影响提供方组件 provide('m', money) //消费 const changeMoney = (count) => { money.value -= count } //使用 provide 将函数 changeMoney 提供给GrandSon provide('cm', changeMoney) return {money} } } </script>
Son.vue
<template> <div class="container"> <h2>子组件 {{money}}</h2> <hr> <GrandSon></GrandSon> </div> </template> <script> import GrandSon from './GrandSon.vue' import {inject} from "vue"; export default { name: 'Son', components: { GrandSon }, setup() { //使用 inject 接受父辈数据 const money = inject('m') return {money} } } </script>
GrandSon.vue
<template> <div class="container"> <h3>孙组件 {{money}}</h3> <button @click="fn">消费50元</button> </div> </template> <script> import {inject} from "vue"; export default { name: 'GrandSon', setup() { //使用 inject 接受父辈数据 const money = inject('m') /** * 需求:孙组件消费50,要求通知父组件修改 * 根据单向原则,数据是哪个组件定义的就由该组件修改,即父组件进行修改操作 */ //使用 inject 接受父辈函数 const changeMoney = inject('cm') //用于触发changeMoney函数 const fn = () => { changeMoney(50) } return {money, fn} } } </script>
双向绑定
说明
这里指不直接使用
v-model
,而是通过用v-model:属性
的方式来实现数据的双向绑定效果,要求是属性名和自定义事件名是一样的
v-model
语法糖
- App.vue
<template> <div class="container"> <h2>父组件 {{number}}</h2> <button @click="fn">改变</button> <hr> <!-- $event: 自定义事件的传值(50) / 系统事件的事件对象(@click="$event.target.style.color='red'") --> <Son :modelValue="number" @update:modelValue="number=$event"></Son> <!-- 语法糖(实质上内部就是以上的实现方式) --> <Son v-model="number"/> </div> </template> <script> import Son from './Son.vue' import {ref} from "vue"; export default { name: 'App', components: { Son }, setup() { const number = ref(100) const fn = () => { number.value = 1000000 } return {number, fn} } } </script>
- Son.vue
<template> <div class="container"> <h3>子组件 {{modelValue}}</h3> <button @click="fn">改变</button> </div> </template> <script> export default { name: 'Son', props: { modelValue: { type: Number, default: 0 } }, setup(props, {emit}) { const fn = () => { emit("update:modelValue", 50) } return {fn} } } </script>
示例
Son.vue
<template> <div class="container"> <h1>子组件</h1> <p>{{ data }}</p> <button @click="changeData">change</button> </div> </template> <script> export default { name: 'Son', props: { data: { type: Number, default: 0 } }, setup(props, {emit}) { //context中包装了emit对象 console.log(props) const changeData = () => { emit('update:data', 10000000) } return {changeData} } } </script>
App.vue
<template> <div class="container"> <h1>父组件</h1> <p>{{ data }}</p> <hr> <Son :data="data" @update:data="changeData"/> <!-- 属性名和自定义事件名相同时:简写(vue2.x:<Son :data.sync/>)--> <Son v-model:data="data"/> </div> </template> <script> import Son from './components/Son.vue' import {ref} from "vue"; export default { name: 'App', components: { Son }, setup() { const data = ref(1000000) const changeData = (newData) => { data.value = newData } return {data, changeData} } } </script>