vue2与vue3的区别
vue2和vue3双向数据绑定原理发生了改变
vue2的双向数据绑定是利用了es5 的一个API Object.definepropert() 对数据进行劫持 结合发布订阅模式来实现的。
vue3中使用了es6的proxyAPI对数据进行处理。
1. vue2和vue3双向数据绑定原理发生了改变
- 相比与vue2,使用proxy API 优势有:
- defineProperty只能监听某个属性,不能对全对象进行监听;
- 可以省去for in 、闭包等内容来提升效率(直接绑定整个对象即可);
- 可以监听数组,不用再去单独的对数组做特异性操作,vue3可以检测到数组内部数据的变化。
2.Vue3支持碎片(Fragments)
就是说可以拥有多个跟节点。
vue2
- <template>
- <div class='form-element'>
- <h2> {{ title }} </h2>
- </div>
- </template>
vue3
- <template>
- <div class='form-element'>
- </div>
- <h2> {{ title }} </h2>
- </template>
3. Composition API
Vue2 与vue3 最大的区别是vue2使用选项类型api,对比vue3合成型api。
- 旧得选项型api在代码里分割了不同得属性:data,computed,methods等;
- 新得合成型api能让我们使用方法来分割,相比于旧的API使用属性来分组,
- 这样代码会更加简便和整洁。
vue2
-
- export default {
- props: {
- title: String
- },
- data () {
- return {
- username: '',
- password: ''
- }
- },
- methods: {
- login () {
- // 登陆方法
- }
- },
- components:{
- "buttonComponent":btnComponent
- },
- computed:{
- fullName(){
- return this.firstName+" "+this.lastName;
- }
- }
-
- }
-
vue3
- export default {
- props: {
- title: String
- },
-
- setup () {
- const state = reactive({ //数据
- username: '',
- password: '',
- lowerCaseUsername: computed(() => state.username.toLowerCase()) //计算属性
- })
- //方法
- const login = () => {
- // 登陆方法
- }
- return {
- login,
- state
- }
- }
- }
4. 建立数据data
Vue2 - 这里把数据放入data属性中
-
- export default {
- props: {
- title: String
- },
- data () {
- return {
- username: '',
- password: ''
- }
- }
- }
-
vue2是把数据放入data中,vue3就需要使用一个新的setup()方法,此方法在组件初始化构造得时候触发。
使用一下三个步骤建立反应性数据:
1. 从vue引入reactive;
2.使用reactive() 方法来声明数据为响应性数据;
3. 使用setup()方法来返回我们得响应性数据,从而template可以获取这些响应性数据。
- import { reactive } from 'vue'
-
- export default {
- props: {
- title: String
- },
- setup () {
- const state = reactive({
- username: '',
- password: ''
- })
-
- return { state }
- }
- }
template使用,可以通过state.username和state.password获得数据的值。
- <template>
- <div>
- <h2> {{ state.username }} </h2>
- <h2> {{ state.password}} </h2>
- </div>
- </template>
-
5. 生命周期
- vue2 --------------- vue3
- beforeCreate setup()
- Created setup()
- beforeMount onBeforeMount
- mounted onMounted
- beforeUpdate onBeforeUpdate
- updated onUpdated
- beforeDestroyed onBeforeUnmount
- destroyed onUnmounted
- activated onActivated
- deactivated onDeactivated
- setup() :开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method
- onBeforeMount() : 组件挂载到节点上之前执行的函数。
- onMounted() : 组件挂载完成后执行的函数。
- onBeforeUpdate(): 组件更新之前执行的函数。
- onUpdated(): 组件更新完成之后执行的函数。
- onBeforeUnmount(): 组件卸载之前执行的函数。
- onUnmounted(): 组件卸载完成后执行的函数
- 若组件被<keep-alive>包含,则多出下面两个钩子函数。
- onActivated(): 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行 。
- onDeactivated(): 比如从 A组件,切换到 B 组件,A 组件消失时执行。
6. 父子传参不同,setup()函数特性
- setup()函数接收两个参数:props、context(包含attrs、slots、emit)
- setup函数是处于生命周期beforeCreated和created俩个钩子函数之前
- 执行setup时,组件实例尚未被创建(在setup()内部,this不会是该活跃实例得引用,即不指向vue实例,Vue为了避免我们错误得使用,直接将setup函数中得this修改成了undefined)
- 与模板一起使用时,需要返回一个对象(在setup函数中定义的变量和方法最后都是需要 return 出去的 不然无法再模板中使用)
- 因为setup函数中,props是响应式得,当传入新的prop时,它将会被更新,所以不能使用es6解构,因为它会消除prop的响应性,如需解构prop,可以通过使用setup函数中得toRefs来完成此操作。
- 父传子,用props,子传父用事件 Emitting Events。在vue2中,会调用this$emit然后传入事件名和对象;在vue3中得setup()中得第二个参数content对象中就有emit,那么我们只要在setup()接收第二个参数中使用分解对象法取出emit就可以在setup方法中随意使用了。父传子,props
- 在setup()内使用响应式数据时,需要通过 .value 获取
6.1、父传子
- import { toRefs } from 'vue'
-
- setup(props) {
- const { title } = toRefs(props)
-
- console.log(title.value)
- onMounted(() => {
- console.log('title: ' + props.title)
- })
-
- }
6.2、子传父,事件 - Emitting Events
举例,现在我们想在点击提交按钮时触发一个login的事件。
在 Vue2 中我们会调用到this.$emit然后传入事件名和参数对象。
- login () {
- this.$emit('login', {
- username: this.username,
- password: this.password
- })
- }
在setup()中的第二个参数content对象中就有emit,这个是和this.$emit是一样的。那么我们只要在setup()接收第二个参数中使用分解对象法取出emit就可以在setup方法中随意使用了。
然后我们在login方法中编写登陆事件
另外:context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context 使用 ES6 解构
-
- setup (props, { attrs, slots, emit }) {
- // ...
- const login = () => {
- emit('login', {
- username: state.username,
- password: state.password
- })
- }
-
- // ...
- }
6.3、attrs和listeners
子组件使用$attrs可以获得父组件除了props传递的属性和特性绑定属性 (class和 style)之外的所有属性。子组件使用$listeners可以获得父组件(不含.native修饰器的)所有v-on事件监听器,在Vue3中已经不再使用;但是Vue3中的attrs不仅可以获得父组件传来的属性也可以获得父组件v-on事件监听器
Vue 2
我们可以使用$attrs
和$listeners
来传递父组件的属性和事件到子组件中。例如,我们有一个名为ChildComponent
的子组件,它的模板如下:
- <template>
- <div>
- <h1>{{ title }}</h1>
- <button @click="onClick">{{ buttonText }}</button>
- </div>
- </template>
我们可以在父组件中使用v-bind
和v-on
来传递属性和事件:
- <template>
- <div>
- <ChildComponent v-bind="$attrs" v-on="$listeners" />
- </div>
- </template>
Vue 3
- <template>
- <div>
- <h1>{{ title }}</h1>
- <button @click="onClick">{{ buttonText }}</button>
- </div>
- </template>
$attrs
和$listeners
已经被移除了,如果需要使用它们,需要在子组件中手动声明它们。例如,在ChildComponent
中,我们可以使用以下方式声明$attrs
和$listeners
:
- import { defineComponent } from 'vue'
-
- export default defineComponent({
- inheritAttrs: false,
- props: {
- title: String,
- buttonText: String
- },
- methods: {
- onClick() {
- this.$emit('click')
- }
- },
- render() {
- return (
- <div>
- <h1>{this.title}</h1>
- <button onClick={this.onClick}>{this.buttonText}</button>
- {this.$slots.default}
- </div>
- )
- }
- })
inheritAttrs: false
表示不继承父组件的属性,需要手动声明$attrs
和$listeners
。在模板中,我们可以使用v-bind="$attrs"
和v-on="$listeners"
来传递属性和事件:
- <template>
- <div>
- <ChildComponent title="Hello World" buttonText="Click Me" v-bind="$attrs" v-on="$listeners" />
- </div>
- </template>
-
6.4、 setup()内使用响应式数据时,需要通过.value获取
- import { ref } from 'vue'
- const count = ref(0)
- console.log(count.value)复制
6.5、 从setup() 中返回得对象上得property 返回并可以在模板中被访问时,它将自动展开为内部值。不需要在模板中追加.value。
例如,如果您在setup()
中返回了一个名为person
的对象,并且该对象具有一个名为name
的属性,您可以在模板中使用{{ person.name }}
来访问该属性的值,而无需使用
{{ person.name.value }}
。
6.5、setup函数只能是同步的不能是异步的。
如果setup()
函数是异步的,则无法保证在开始运行应用程序之前完成初始化。如果您需要执行异步操作,请在setup()
函数之外执行它们
- import { reactive } from 'vue'
-
- export default {
- setup() {
- const state = reactive({
- data: null
- })
-
- async function loadData() {
- // 从服务器加载数据的异步操作
- const response = await fetch('/data')
- const data = await response.json()
- // 将数据存储在组件状态中
- state.data = data
- }
-
- loadData()
-
- return {
- state
- }
- }
- }
或者
- import { ref, onMounted } from 'vue'
-
- export default {
- setup() {
- const data = ref(null)
-
- onMounted(async () => {
- const response = await fetch('https://api.example.com/data')
- data.value = await response.json()
- })
-
- return {
- data
- }
- }
- }
我们在组件的 created 函数中使用异步函数来获取数据,并将数据保存到组件的状态中。然后,在 setup 函数中使用这个状态来显示数据。
注意,我们使用了 onMounted 函数来在组件挂载后执行异步操作。这是因为在组件挂载之前,组件的状态还没有被设置,无法使用组件的状态来保存异步操作的结果。
在 setup 函数中,我们可以使用异步函数,例如:
- import { ref } from 'vue'
-
- export default {
- setup() {
- const message = ref('Hello, world!')
-
- setTimeout(() => {
- message.value = 'Hello, Vue!'
- }, 1000)
-
- return {
- message
- }
- }
- }
虽然 setup 函数本身是同步的,但是我们可以在其中使用异步函数来进行一些异步操作。只要这些操作在组件实例创建之前就已经完成,就不会影响组件的正常运行。
7.vue3 Teleport瞬移组件
Teleport一般被翻译成瞬间移动组件,我把他理解成"独立组件", 他可以拿你写的组件挂载到任何你想挂载的DOM上,所以是很自由很独立的,
以一个例子来看: 编写一个弹窗组件
- <template>
- <teleport to="#modal">
- <div id="center" v-if="isOpen">
- <h2><slot>this is a modal</slot></h2>
- <button @click="buttonClick">Close</button>
- </div>
- </teleport>
- </template>
- <script lang="ts">
-
- export default {
- props: {
- isOpen: Boolean,
- },
- emits: {
- 'close-modal': null
- },
- setup(props, context) {
- const buttonClick = () => {
- context.emit('close-modal')
- }
- return {
- buttonClick
- }
- }
- }
- </script>
- <style>
- #center {
- width: 200px;
- height: 200px;
- border: 2px solid black;
- background: white;
- position: fixed;
- left: 50%;
- top: 50%;
- margin-left: -100px;
- margin-top: -100px;
- }
- </style>
-
在app.vue中使用的时候跟普通组件调用是一样的
- <template>
- <div id="app">
- <img alt="Vue logo" src="./assets/logo.png">
- <HelloWorld msg="Welcome to Your Vue.js App"/>
- <HooksDemo></HooksDemo>
- <button @click="openModal">Open Modal</button><br/>
- <modal :isOpen="modalIsOpen" @close-modal="onModalClose"> My Modal !!!!</modal>
- </div>
-
- </template>
- <script>
- import HelloWorld from './components/HelloWorld.vue'
- import HooksDemo from './components/HooksDemo.vue'
- import Modal from './components/Modal.vue'
- import{ref} from 'vue'
- export default {
- name: 'App',
- components: {
- HelloWorld,
- HooksDemo,
- Modal
- },
- setup() {
- const modalIsOpen = ref(false)
- const openModal = () => {
- modalIsOpen.value = true
- }
- const onModalClose = () => {
- modalIsOpen.value = false
- }
- return {
- modalIsOpen,
- openModal,
- onModalClose
- }
- }
- }
- </script>
-
- <style>
- #app {
- font-family: Avenir, Helvetica, Arial, sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- text-align: center;
- color: #2c3e50;
- margin-top: 60px;
- }
- </style>
-
-
8、路由
vue3和vue2路由常用功能只是写法上有些区别:
vue3的beforeRouteEnter
作为路由守卫的示例是因为它在setup
语法糖中是无法使用的;大家都知道setup
中组件实例已经创建,是能够获取到组件实例的。而beforeRouteEnter
是再进入路由前触发的,此时组件还未创建,所以是无法用在setup
中的;如果想在setup语法糖中使用则需要再写一个script
如下:
- <script>
- export default {
- beforeRouteEnter(to, from, next) {
- // 在渲染该组件的对应路由被 confirm 前调用
- next()
- },
- };
- </script>
vue2
- <script>
- export default {
- beforeRouteEnter (to, from, next) {
- // 在渲染该组件的对应路由被 confirm 前调用
- next()
- },
- beforeRouteEnter (to, from, next) {
- // 在渲染该组件的对应路由被 confirm 前调用
- next()
- },
- beforeRouteLeave ((to, from, next)=>{//离开当前的组件,触发
- next()
- }),
- beforeRouteLeave((to, from, next)=>{//离开当前的组件,触发
- next()
- }),
- methods:{
- toPage(){
- //路由跳转
- this.$router.push(xxx)
- }
- },
- created(){
- //获取params
- this.$route.params
- //获取query
- this.$route.query
- }
- }
- </script>
vue3 路由写法
- <script>
- import { defineComponent } from 'vue'
- import { useRoute, useRouter } from 'vue-router'
- export default defineComponent({
- beforeRouteEnter (to, from, next) {
- // 在渲染该组件的对应路由被 confirm 前调用
- next()
- },
- beforeRouteLeave ((to, from, next)=>{//离开当前的组件,触发
- next()
- }),
- beforeRouteLeave((to, from, next)=>{//离开当前的组件,触发
- next()
- }),
- setup() {
- const router = useRouter()
- const route = useRoute()
- const toPage = () => {
- router.push(xxx)
- }
-
- //获取params 注意是route
- route.params
- //获取query
- route.query
- return {
- toPage
- }
- },
- });
- </script>