我们把vue3 和 vue2 的项目进行对比来去说明
main.js
通过 按需导入的 createApp 方法来来构建 vue 实例
通过 vue实例.use 方法来挂载插件(router、vuex)
没有了 Vue 构造方法,无法再挂载原型
App.vue
组件内部结构无变化,依然是
tempalte
script
style
<template> 标签中支持多个根标签
store/index.js
通过 按需导入的 createStore 方法来来构建 store 实例
无需再通过 Vue.use(Vuex) 的形式进行挂载
router/index.js
通过 按需导入的 createRouter 方法来构建 router 实例
通过 按需导入的 createWebHashHistory 方法来创建 hash 模式对象,进行路由模式指定
无需再通过 Vue.use(VueRouter) 的形式进行挂载
routes 路由表的定义无差别
综上所述,在 vue3 的初始化项目中,与 vue2 对比的最大差异其实就是两点:
1、vue3 使用 按需导入的形式 进行初始化操作
2、<template> 标签中支持多个根标签
vue3新语法
setup 与 reactive
setup 函数也是 Composition API 的入口函数,我们的变量、方法都是在该函数里定义的,不再使用vue2中的data而是setup。
reactive 方法是用来创建一个响应式的数据对象,该API也很好地解决了Vue2通过 defineProperty 实现数据响应式的缺陷
//app.vue
<template>
<div id="app">
<p>{{ state.name }}</p>
</div>
</template>
<script lang="ts">
import {
defineComponent,
reactive,
} from 'vue'
export default defineComponent({
name: 'App',
setup(props, { slots, attrs, emit }) {
//let name = 'abc' //return的name 是abc,因为setInterval()是异步的
//reactive的name就会是aaa1111,每2秒变化一次
const state = reactive({
name: 'aaa',
})
setInterval(() => {
state.name += '1'
}, 2000)
//不能...state,因为...是提取值,那显示的就不是reactive对象了
return {
state
}
},
})
</script>
上面使用name必须state.name,不方便,另一个解决办法如下
export default {
setup(props, { attrs, slots, emit }) {
...
}
}
访问组件的 property
执行 setup
时,组件实例尚未被创建。因此,你只能访问以下 property: props
attrs
slots
emit
换句话说,你将无法访问以下组件选项:
data
computed
methods
生命周期
在Vue3中,这些生命周期部分有所变化,并且调用的方式也有所改变
Vue2 | Vue3 |
---|---|
beforeCreate | setup |
created | setup |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestory | onBeforeUnmount |
destoryed | onUnmounted |
Props的不同处
Ts可指定类型,而不是以往所有对象参数都是object的any类型
另外Props可以提出来
//app.vue
<template>
<div id="app">
<HelloWorld msg="v3 + TS" :age="age" :config="cfg" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
export default defineComponent({
name: 'App',
components: {
HelloWorld,
},
data() {
return {
age: 24,
cfg:{
name:"abc"
}
}
},
})
</script>
//helloword组件
<template>
<div class="hello">
name: {{config.name}}
age:{{ age }}
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
interface Config{
name:string
}
//把props提出来外面
const PropsType = {
msg: String,
config:{
type:Object as PropType<Config>,
required:true
},
age: {
type: Number,
required: true,
},
} as const //多个as const才能让props为readonly只读状态
export default defineComponent({
name: 'HelloWorld',
props: PropsType,
mounted(){
this.age
//有上面的 as PropType<Config>这里就是config接口类型,没有就是Record<string,any>类型
this.config //所有定义一个接口类型是有规范意义的
this.config.name //如果required不是true,那这里就会报错,因为有可能config不存在,那config.name就是未知数
}
})
</script>
vue文件转译为js是怎样的,h函数与createVNode
import { createApp, defineComponent, h } from 'vue'
//import App from './App.vue'
import router from './router'
import HelloWorld from './components/HelloWorld.vue'
//由于eslink选择的是TS语法效验,所以出现require是会报错的;手动屏蔽一行效验
const img = require('./assets/logo.png') //eslint-disable-line
/*
创建一个组件
const App = defineComponent({
render() {
return 'ABC' //此时页面就仅显示abc
},
})
*/
/*
//app.vue默认代码
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Welcome to Vue.js" />
</div>
</template>
*/
// h函数(标签,属性,子类)
// 以下就是转为vue文件转为JS的样式
const App = defineComponent({
render() {
return (
h('div', { id: 'app' }),
[
h('img', {
alt: 'Vue logo',
src: img,
}),
h(HelloWorld, {
msg: 'Welcome to Vue.js"',
}),
]
)
},
})
createApp(App).use(router).mount('#app')
h函数只是createVNode的简单封装,所以把h函数替换为createVNode也是一样的
import { createApp, defineComponent, h, createVNode } from 'vue'
ref 与 computed 与 watchEffect
ref 就是通过 reactive 包装了一个对象 ,然后是将值传给该对象中的 value 属性,这也就解释了为什么每次访问时我们都需要加上 .value。
我们可以简单地把 ref(obj) 理解为这个样子 reactive({value: obj})
watch 和 watchEffect 都是用来监视某项数据变化从而执行指定的操作的,但用法上还是有所区别
watchEffect:
1、不需要手动传入依赖
2、每次初始化时会执行一次回调函数来自动获取依赖
3、无法获取到原值,只能得到变化后的值
<template>
<div id="app">
<p>{{ name }}</p>
<p>{{ name2 }}</p>
<p>{{ ref2 }}</p>
</div>
</template>
<script lang="ts">
import {
defineComponent,
ref,
computed,
watchEffect,
} from 'vue'
export default defineComponent({
name: 'App',
setup(props, { slots, attrs, emit }) {
//不用reactive,而用ref
const nameRef = ref('ax:')
const ref2 = ref("bx:")
setInterval(() => {
nameRef.value += 'a'
}, 2000)
setInterval(() => {
ref2.value += 'b'
}, 2000)
//插一脚,演示下计算属性
const computedNameRef = computed(() => {
return nameRef.value + '2'
})
//只会监听内部用到的ref,如ref2没用到,所以就不会打印ref2
watchEffect(() => {
console.log("watch:",nameRef.value)
})
//这样就可以直接name了
return {
name: nameRef,
name2: computedNameRef,
ref2
}
},
created(){
console.log("created:",this.name) //这里的this.name仍然是ref,而不是值。因为编译时自动name.value了
}
})
</script>