「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」
开始
两种方式
vue-cli
npm i @vue/cli@next -g
Vite
# npm 6.x
npm init vite@latest my-vue-app --template vue
# npm 7+, 需要额外的双横线:
npm init vite@latest my-vue-app -- --template vue
新特性
Composition API
reactive
对于引用类型的对象,可以使用reactive来实现响应式
<script>
import { computed, reactive } from 'vue'
export default {
setup() {
const data = reactive({
counter: 1,
doubleCounter: computed(() => data.counter * 2)
})
return {data} // 任何在模板中使用的属性或方法,都需要return
}
}
</script>
<template>
<div>
<p>reactive</p>
<p>{{data.counter}}</p>
<p>{{data.doubleCounter}}</p>
</div>
</template>
reactive中的对象不可解构,会失去响应式的能力.如果需要解构要使用toRefs,使得对象中的字段变为响应式
<script>
import { computed, onMounted, onUnmounted, reactive, ref, toRefs } from 'vue'
export default {
setup() {
const data = reactive({
counter: 1,
doubleCounter: computed(() => data.counter * 2)
})
let timer
onMounted(() => {
timer = setInterval(() => {
data.counter++
}, 1000);
})
onUnmounted(() => {
clearInterval(timer)
})
const {counter, doubleCounter} = toRefs(data)
return {counter, doubleCounter}
}
}
</script>
<template>
<div>
<p>reactive</p>
<p>{{counter}}</p>
<p>{{doubleCounter}}</p>
</div>
</template>
ref
对于值类型可以使用ref,如果要修改这个ref的值,要通过.value修改,那么为什么模板直接通过变量名就可以显示呢?这个是因为Vue帮助我们把ref类型的值显示出来了
import { ref } from 'vue'
export default {
setup() {
const number = ref(1)
const increase = () => {
number.value++
}
return {
number,
increase
}
}
}
</script>
<template>
<p>{{ number }}</p>
<p @click="increase">plus</p>
</template>
setup
我们可以看到,我们上面大量用到了setup,那么setup是什么?setup是一个组合式API,组合式API是帮助我们将之前data,method这些分散在各个位置的逻辑可以统一在一起,随着复杂性越来越大,我们更倾向于这种将逻辑统一去管理的写法形式
setup接受两个参数
- props 传入的属性
- context 提供了三个常用属性
attrs,slot,emit
// 一段伪代码
setup(props, context) {
// goodsList
data...
methods...
// user
data...
methods...
// notice
data...
methods...
}
<script setup>
在Vue3.2中,还增加了组合式API的语法糖,通过在script标签写setup标识,方便我们更好的去书写setup,不需要return,只要声明了变量和方法,就可以直接使用,甚至引入组件也不需要注册,简直不能太方便
<script setup lang="ts">
import { ref } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
const number = ref(1)
const increase = () => {
number.value++
}
</script>
<template>
<p>{{ number }}</p>
<p @click="increase">plus</p>
<HelloWorld msg="Hello Vue 3 + TypeScript + Vite" />
</template>
computed
通过引入computed来实现
<script setup lang="ts">
import { computed, ref } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
const number = ref(1)
const increase = () => {
number.value++
}
const double = computed(() => number.value * 2)
</script>
<template>
<p>{{ number }}</p>
<p>{{ double }}</p>
<p @click="increase">plus</p>
<HelloWorld msg="Hello Vue 3 + TypeScript + Vite1" />
</template>
<style></style>
经过上面的例子,我们发现,Vue3.0的响应式和以前写法上有些许不同.Vue2.0是通过Object.defineProperty实现响应式的,Vue3.0则是通过Proxy.那么新的方案解决了什么问题?
在Vue2.0中,如果我们声明后的对象添加属性,则不会响应式,通过索引添加的数组也不会有响应式,而Proxy就是解决了这个问题
watch
依旧是引入一个方法watch,监听响应式数据变化
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
const number = ref(1)
const increase = () => {
number.value++
}
watch(number, (newVal, oldVal) => {
document.title = number.value.toString()
})
</script>
watch的第一个参数也可以是一个数组,监听多个响应式数据,而newVal与oldVal也将以数组形式返回.这里面的第一个参数是一个值类型,如果我们想监听一个通过reactive的对象中某一个值,则需要一个函数
<script setup lang="ts">
import { ref, watch, reactive } from 'vue'
const number = ref(1)
const increase = () => {
number.value++
}
const double = computed(() => number.value * 2)
const data = reactive({
name: 'Kun',
score: 1,
increase: () => {
data.score++
}
})
watch([number, () => data.score], () => { // 通过回调函数监听
document.title = number.value.toString() + data.score.toString()
})
</script>
或者通过toRef将某个属性转化为响应式
watch([number, toRef(data, 'score')], () => {
document.title = number.value.toString() + data.score.toString()
})
父子组件传值
父 -> 子
// 父组件
<template>
<Children name="Kun" />
</template>
<script setup lang="ts">
import Children from './components/Children.vue'
</script>
// 子组件
<template>
<h1>{{ props.name }}</h1>
<h1>{{ name }}</h1>
</template>
<script setup lang="ts">
const props = defineProps({
name: {
type: String,
default: ''
}
})
</script>
TIPS: defineProps默认在setup模式下引入,但是又由于eslint的提示,我们需要配置一下.eslintrc.js
// .eslintrc.js
...
globals: {
defineProps: true
}
...
子 -> 父
// 子组件 Increase.vue
<template>
<h1>count: {{ props.account }}</h1>
<button @click="changeAccount">click +1</button>
</template>
<script setup lang="ts">
const props = defineProps({
account: {
type: Number,
default: 0
}
})
const emit = defineEmits(['updateAccount']) // 同理,自动引入,可配置`eslint globals`
const changeAccount = () => {
emit('updateAccount')
}
</script>
// 父组件
<script setup>
import Increase from './components/Increase.vue'
const account = ref(0)
const updateAccount = () => {
account.value++
}
</script>
<template>
<Increase :account="account" @updateAccount="updateAccount" />
</template>
结构
在Vue3.0中template不需要强制要求有一个根节点
<template>
<h1>{{ count }}</h1>
<h1>{{ count }}</h1>
</template>
v-model
自定义组件的v-model在Vue3.0中可这样定义:
// 父组件
<script setup>
import Model from './components/Model.vue'
const num = ref(0)
</script>
<template>
<Model v-model="num" />
</template>
// 子组件
<script setup lang="ts">
const props = defineProps({
modelValue: Number
})
const emits = defineEmits(['update:modelValue'])
const changeValue = () => {
emits('update:modelValue', props.modelValue + 1)
}
</script>
<template>
<h1>{{ modelValue }}</h1>
<button @click="changeValue">click +1</button>
</template>
可以看到子组件props的modelValue,modelValue是一个默认值,它相当于:
<Model v-model:modelValue="num" />
也就是说如果省略了,那么默认的props就是modelValue
emit与props是相对应的,正因为有这样的对应关系,我们可以有多个v-model
// 父组件
<script setup>
import Model from './components/Model.vue'
const num = ref(0)
const name = ref('')
</script>
<template>
<Model v-model="num" v-model:name="name" />
</template>
// 子组件
<script setup lang="ts">
const props = defineProps({
modelValue: Number,
name: String
})
const emits = defineEmits(['update:modelValue', 'update:name'])
const changeValue = () => {
emits('update:modelValue', props.modelValue + 1)
}
const changeName = () => {
emits('update:name', `${props.name}!`)
}
</script>
<template>
<h1>{{ modelValue }}</h1>
<h1>{{ name }}</h1>
<button @click="changeValue">click +1</button>
<button @click="changeName">change name</button>
</template>
CSS v-bind
<script setup lang="ts">
const color = ref('red')
</script>
<style scoped>
h1 {
color: v-bind('color');
}
</style>
生命周期
生命周期的区别
| 2.0 | 3.0 |
|---|---|
| beforeCreate | setup |
| created | setup |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeDestroy | onBeforeUnmount |
| destroyed | onUnmounted |
| activated | onActivated |
| deactivated | onDeactivated |
| errorCaptured | onErrorCaptured |
Teleport
Teleport是一个传送门,它可以将我们的一段html传送到其他地方,主要场景就是我在项目中用到的弹窗组件Dialog,Dialog我们经常要嵌套在其他Vue文件中,这样就造成了css的污染,而传送门就可以很好的解决这个问题
// App.vue
<template>
<Modal />
<p>App</p>
</template>
to指的是将teleport挂载到哪个节点上,与index.html的#modal对应
// Modal.vue
<template>
<teleport to="#modal">
<div>
<h2>modal</h2>
</div>
</teleport>
</template>
<script lang="ts"></script>
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<div id="modal"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Suspense
Suspense是一个异步组件的解决方案,通过具名插槽来实现,异步组件执行完毕前,显示fallback内容,而异步执行完毕,显示default的内容.但是这个组件要返回一个Promise
在Vue 3.2中,可以看到,没有async,但是await可以正常使用,是因为<script setup>会被编译成async setup()作为顶层async
// App.vue
<Suspense>
<template #default>
<AsyncComp />
</template>
<template #fallback> Suspense loading... </template>
</Suspense>
// AsyncComp.vue
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
const data = await new Promise((resolve) => {
setTimeout(() => {
resolve(123)
}, 2000)
})
count.value = data
</script>
<template>
<h1>{{ count }}</h1>
</template>
总结
Vue3.0相比于老版本,真的是越来越像React了,虽然一开始用的时候,可能有那么一点奇怪,但是真正使用了就真香系列了😄