目录
单文件组件
先看一下vue3单文件代码大体的样子。这是官网对vue3组件的简单示例:
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">Count is: {{ count }}</button>
</template>
<style scoped>
button {
font-weight: bold;
}
</style>
这是我们熟悉的template,script和style三部分,很明显,script的风格已经从面向对象的vue2变为了函数式和钩子的写法,有些类似于原生js和react hooks,官方的叫法是组合式API。
注意这是3.2版本开始的script setup语法糖,如果你看到这样的代码:
import { ref } from 'vue'
export default {
// `setup` 是一个特殊的钩子,专门用于组合式 API。
setup() {
const count = ref(0)
// 将 ref 暴露给模板
return {
count
}
}
}
这是在Vue 3.0发布初期的写法,在setup钩子中,所有的模板变量和方法都需要手动return。目前推荐使用第一种写法,更加简洁和方便,注意要在script标签上加上setup。
main.js实例化
挂载和插件的语法改成函数式,示例:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')
注意:虽然vue3向下兼容vue2语法,但main.js的写法是一定要改的。
生命周期
vue3全部生命周期钩子:
示例:
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
console.log(`the component is now mounted.`)
})
</script>
大部分钩子和之前的效果一样,命名改成了on+驼峰大写,比如mounted变成了onMounted,有几个例外是:
1.beforeCreate,created已删除,setup函数代替了Vue 2中的created钩子
2.destroyed,beforeDestroy分别改名为onUnmounted,onBeforeUnmount
声明响应式:ref,reactive
<template>
<button @click="increment">
{{ count }}
</button>
<button @click="increment2">
{{ state.count }}
</button>
</template>
<script setup>
import { ref, reactive } from 'vue'
const count = ref(0)
const state = reactive({ count: 0 })
function increment() {
count.value++
}
function increment2() {
state.count++
}
</script>
相比以往所有响应式变量都挂在一个data里的写法,现在你可以自由地声明。上图两种写法所实现的效果是相同的。
ref可以传入任何类型,但需要注意在script部分需要使用.value来读取,在模板部分则无需使用。reactive不需要在意.value的问题,但局限有1.只适用于对象类型 2.不能直接覆盖整个对象 3.解包属性会丢失响应性。
// 正确的用法
const state = reactive({ count: 0 })
state.count++
state.sum = 99
// 或者用ref
const count = ref(0)
const state2 = ref({count: 0})
state2.value = {count: 0, sum: 99}
state2.value = null
// 错误,只能用于对象数组等类型
let state = reactive(0)
// 错误,不能直接覆盖
let state = reactive({ count: 0 })
state = {count: 0, sum: 99}
// 这样也是错的,会丢失响应性
let state = reactive({ count: 0 })
state = reactive({count: 0, sum: 99})
// 错误!解包后count丢失响应性
let {count} = state
count++
总体来说reactive限制比较多,官方更推荐用ref。在定义对象并且确定只需要更改属性的时候可以用reactive,更简洁。
computed和方法
除了变成函数式风格以外,作用都和之前一致,computed有响应式依赖缓存,method没有,这点也还是一样。官网的例子:
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
// 方法
function calculateBooksMessage() {
return author.books.length > 0 ? 'Yes' : 'No'
}
watch
watch接收一个监听数据源,一个回调函数。有以下3种写法:
const x = ref(0)
const y = ref(0)
// 第一种 直接传入监听项
watch(x, (newX) => {
console.log(`x is ${newX}`)
})
// 第二种 getter 函数
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// 第三种 可以用数组来监听多个
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
第一种写法虽然最简单但是不能用来监听对象的单个属性,此时一般用第二种:
// 监听整个对象时是深度监听 属性变更时会触发
const obj = reactive({key1: 0, key2: 0})
watch(obj, (newObj) => {
console.log(`obj is ${newObj}`)
})
// 错误,因为 watch() 得到的参数是一个 number
const obj = reactive({ count: 0 })
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})
// 第二种 可以用来监听响应式对象的某个属性
watch(
() => obj.key1,
(key1) => {
console.log(`obj.key1: ${key1}`)
}
)
// 如果要深度监听整个对象需要加上deep
watch(
() => obj,
(obj) => {
console.log(`obj: ${obj}`)
}, {
deep: true
}
)
deep和immediate和之前的用法一样,使用immediate在监听源变化之前就初始执行一次回调。
watchEffect
这个新增的方法可以让你省去手动传递依赖项,在回调里自动跟踪。简单来说,会自动找到回调里用到的依赖,不用自己指定了。下面这个示例会自动跟踪todoId.value,每次变化时重新发送fetch请求。
watchEffect(async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
})
对于这种只有一个依赖项的例子来说,
watchEffect()
的好处相对较小。但是对于有多个依赖项的侦听器来说,使用watchEffect()
可以消除手动维护依赖列表的负担。此外,如果你需要侦听一个嵌套数据结构中的几个属性,watchEffect()
可能会比深度侦听器更有效,因为它将只跟踪回调中被使用到的属性,而不是递归地跟踪所有的属性。
watchEffect在多个依赖项监听时很好用。比如有个接口请求了多个参数,在参数变动时都需要重新查询接口,就可以直接用这个自动跟踪,比深度监听也更省性能。
watchEffect
仅会在其同步执行期间,才追踪依赖。在使用异步回调时,只有在第一个await
正常工作前访问到的属性才会被追踪。
注意只会跟踪回调里同步代码的依赖,有异步方法时只有第一个await里同步代码访问的属性才会被追踪,这个例子里就是await里的todoId,除此之外的属性都不会被跟踪。
模板引用ref
概念和之前的ref一样,不过需要手动用ref声明一个同名的来存放引用。注意这里只能用ref不能用reactive。
<script setup>
import { ref, onMounted } from 'vue'
// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)
onMounted(() => {
input.value.focus()
})
</script>
<template>
<input ref="input" />
</template>
v-for时的模板引用见我的这篇:vue3在循环列表里动态加上ref-CSDN博客
组件
单文件组件会默认导出,导入时也是直接可用,不需要手动注册。
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>
<template>
<h1>Here is a child component!</h1>
<ButtonCounter />
</template>
props和emit
和vue2的用法基本一致,不过需要用defineProps和defineEmits手动声明
<script setup>
const props = defineProps({
foo: String
})
const emit = defineEmits(['change', 'delete'])
// setup 代码
</script>
defineProps
和defineEmits
都是只能在<script setup>
中使用的编译器宏。他们不需要导入,且会随着<script setup>
的处理过程一同被编译掉。
defineProps
接收与props
选项相同的值,defineEmits
接收与emits
选项相同的值。
defineProps
和defineEmits
在选项传入后,会提供恰当的类型推导。传入到
defineProps
和defineEmits
的选项会从 setup 中提升到模块的作用域。因此,传入的选项不能引用在 setup 作用域中声明的局部变量。这样做会引起编译错误。但是,它可以引用导入的绑定,因为它们也在模块作用域内。
总结一下需要注意的:
1.defineProps和defineEmits是编译器宏,不用手动导入。
2.不能引用局部变量,比如下面这个例子是错误的。
const STRINGTYPE = {
type: String,
required: true
}
const props = defineProps({
foo: STRINGTYPE,
foo2: STRINGTYPE
})
但是可以使用导入的变量:
<template>
<h1>{{ greeting }}</h1>
</template>
<script setup>
import { someValue } from './someModule'; // 导入的绑定
const props = defineProps({
greeting: someValue // 正确使用导入的变量
});
</script>
暴露组件方法defineExpose
defineExpose和上面一样,不用手动导入,vue2里的调用组件方法是
this.$refs.someComponent.someFun()
现在则是要在组件里手动暴露
子组件ChildComponent :
<script setup >
const props = defineProps({
initialCount: Number
});
let count = props.initialCount;
const increaseCount = () => {
count++;
};
defineExpose({
increaseCount
});
</script>
父组件用ref来引用子组件,然后.value可以调用暴露出来的方法。
<template>
<div>
<ChildComponent ref="childComponentRef"></ChildComponent>
<button @click="onIncreaseChildCount">Increase Child Count</button>
</div>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';
import { ref } from 'vue';
const childComponentRef = ref(null);
const onIncreaseChildCount = () => {
childComponentRef.value.increaseCount();
};
</script>
依赖注入provide / inject
provide和inject可以用于在父组件与多层嵌套的子组件之间进行数据传递,不需要通过props来逐层传递。用法非常简单,只要provide和inject的参数名对应就可以传值。
提供provide的顶层组件:
<!-- ParentComponent.vue -->
<template>
<ChildComponent></ChildComponent>
</template>
<script setup>
import { provide } from 'vue';
import ChildComponent from './ChildComponent.vue';
provide('message', 'Hello from ParentComponent');
</script>
中间的一个示例组件:
<!-- ChildComponent.vue -->
<template>
<GrandChildComponent></GrandChildComponent>
</template>
<script setup>
import GrandChildComponent from './GrandChildComponent.vue';
</script>
最后在这个组件用inject接收到了provide的值:
<!-- GrandChildComponent.vue -->
<template>
<div>
<p>{{ message }}</p>
</div>
</template>
<script setup>
import { inject } from 'vue';
const message = inject('message');
</script>
现在你已经对Vue3和Vue2的常用语法差异有了一定了解,是不是比想象中更容易上手呢?后面我会更新vue-router4、element-plus等第三方库和它们在Vue2和Vue3中使用的差异。
【Vue3】element-plus按需自动导入的配置 以及icon不显示的解决方案_vite element plus icon 按需自动导入不显示-CSDN博客