vue3学习——必会知识点(1)

553 阅读9分钟

前言

vue2.x向vue3.x过渡,都改变了什么呢?本文从创建vue3项目起步,一起来看看vue3有什么样的改变,以及可以带给我们什么好处吧~


一 、为什么学Vue3

1. 现状

  • 2020.09.18发布,周边生态不支持,大多数开发者处于观望。
  • 主流组件库element-plusvant ant-design-vue等已经发布支持Vue3.0的版本,这是趋势。

2. Vue3优点

  • 国内最火前端框架之一
  • 性能提升
  • 体积更小
  • 类型推断:更好的支持TS(js的超集)
  • 高级给予,暴露了更底层的API和提供更先进的内置组件
  • 选项API----->组合API(composition api),能够更好的组织逻辑,封装逻辑,复用逻辑(编程风格改变)

3. Vue3展望

  • 趋势
  • 大型项目,由于对Ts的友好,越来越多的大型项目可以使用vue3.0

二、 如何创建vue3应用

1. 基于Vue脚手架创建项目

# 全局安装vue脚手架
npm i @vue/cli -g

# 创建Vue项目,选择v3版本
vue create 项目名称

#--------创建完成后--------

# 切换路径
cd 项目名称

# 运行项目
npm run serve

操作步骤:

  • 找到要创建项目的根目录,地址栏输入cmd打开终端

  • 输入vue create 项目名称,项目名称自己起哦~

  • 选择手动创建(如图)

在这里插入图片描述

  • 选择需要的配置(空格选择,选择完毕后回车即可)

在这里插入图片描述

  • 各项配置按照下图选择即可

在这里插入图片描述

2. 入口文件分析

vue3的API典型风格:按需导入

链式操作

在这里插入图片描述

作用:把App根组件渲染到index.html页面中

总结:

  1. Vue的实例化方式发生变化:基于createApp方法进行实例化
  2. router和store采用use方法进行配置
  3. 按需导入,为了提升打包的性能

3. App.vue根组件结构分析

Vue2中的组件模板必须有唯一的根节点

Vue3中组件的模板可以没有根节点

总结:Vue3中组件的模板可以没有根节点(与Vue2进行对比)

4. router文件分析

Vue3创建实例对象

在这里插入图片描述

结论:

  1. 创建路由实例对象,采用createRouter方式,Vue3典型风格
  2. 采用hash和history的方式有变化
    • Vue2采用mode选项:hash / history
    • Vue3采用方法:createWebHashHistory()/createWebHistory()

5. vuex文件分析

结论:创建store对象采用createStore方法,而不是new

三、 选项API 和 组合API

目标:理解什么是选项API写法,什么是组合API

在这里插入图片描述

1. 选项API

每个代码写到哪个位置

在这里插入图片描述

2. 组合API

按照功能划分

总结:

  1. Vue2项目中使用的选项API
    • 代码风格:一个功能逻辑代码分散
    • 优点:易于学习 和使用,写代码的位置已经约定好
    • 缺点:代码组织性差,相似的逻辑(功能)代码不便于复用,逻辑复杂
  2. 组合API(hook)
    • 以功能为单位阻止代码结构,后续重用功能更加方便

四、 组合API相关方法

1. setup函数

使用细节:

  • 新的组件选项,作为组件中使用组合API的起点

  • 从组件生命周期来看,它的执行顺序在beforeCreate之前(Vue3中beforeCreate/created已经被废弃了,其实已经被setup替代了)

  • setup函数中this还没有创建,此时无法访问this,this是undefined

  • setup中返回的数据用于模板使用:类似于之前的data中提供的数据

  • Vue3中依然可以使用data中的数据,但是不建议使用(这是选项API的写法)

  • 方法在setup中定义后,也需要return出去

语法格式:

export default {
    setup() {
       // return ... 
    }
}

结论:

  1. setup选项是实现组合API的基础
  2. 触发的时机:
  3. this问题
  4. setup的返回值

2. 生命周期

vue2.x生命周期钩子函数:

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeDestory
  • destroyed

认识vue3.0生命周期钩子函数(相同的生命周期函数可以触发多次)

  • setup创建实例前
  • onMounted挂载DOM后
  • onBeforeUpdate更新组件前
  • onUpdated更新组件后
  • onBeforeUnmount卸载销毁前
  • onUnmounted:卸载销毁后

语法格式:

import { mounted } from 'vue'

export default {
    setup () {
    	mounted(() => {
        	console.log('xxx')
    	})
	}
}

结论:

  1. vue3生命周期函数发生了变化
  2. 去掉两个: beforeCreatecreated,添加了setup
  3. 方法名发生变化: 方法名前面多了个on ,中间是驼峰
  4. 卸载组件的生命周期变化:onBeforeUnmountonUnmounted
  5. 特点:同一个生命周期可以触发多次

五、数据响应式

数据的响应式:数据的变化导致视图自动变化。

1. reactive函数

可以定义一个复杂数据类型

语法格式:

模板中使用obj.msg

import { reactive } from 'vue'

export default{
	setup(){
		const obj = reactive({
    		msg: 'Hello'
		})
	}
}

:reactive中的对象属性如果重新赋值会失去响应式能力

2. toRef函数

需求:模板中不添加obj前缀,直接获取属性

把对象中的单个属性取出来,保证数据的响应式

语法格式:

import { toRef } from 'vue'

export default {
    const obj = {
    	msg: 'hello'
	}
    const msg = toRef(obj,'msg')
    
    setup() {
        return {msg}
    }
}

代码演示:

<template>
  <div>
    <div>{{ msg }}</div>
    <div>{{ info }}</div>
    <button @click="handleClick">点击</button>
  </div>
</template>

<script>
import { reactive, toRef } from 'vue'
export default {
  name: 'App',
  setup() {
    const obj = reactive({
      msg: 'hello',
      info: 'Leo'
    })

    const msg = toRef(obj, 'msg')
    const info = toRef(obj, 'info')

    const handleClick = () => {
      obj.msg = 'hi'
    }

    return { msg, info, handleClick }
  }
}
</script>

<style lang="less"></style>

结论: toRef方法可以把对象中的单个属性取出,并且保证响应式能力

3. toRefs函数

语法格式:

import { toRefs } from 'vue'

export default {
    setup() {
        const obj = reactive({
      		msg: 'hello',
      		info: 'Leo'
    	})
        // 把obj中的属性解构出来
        const {msg,info} = toRefs(obj)
        
        return {msg, info}
    }
}

结论:toRefs这个方法可以批量转换对象中的属性为独立的响应式数据

4. ref函数

主要用于定义普通(基本类型)的数据并保证响应式能力

语法格式:

import {ref} from 'vue'
export default {
    setup () {
        const count = ref(0)

		const handleClick = () => {
   		 	// ref定义的数据,在js中操作时需要通过value属性进行
   		 	count.value += 1
		}
        
    }
}

代码演示:

<template>
  <div>
    <div>{{ count }}</div>
    <button @click="handleClick">点击</button>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  name: 'App',
  setup() {
    const count = ref(0)

    const handleClick = () => {
      // ref定义的数据,在js中操作时需要通过value属性进行
      count.value += 1
    }

    return { count, handleClick }
  }
}
</script>

<style lang="less"></style>

结论:

  1. 如果是基本类型数据,可以使用ref进行定义
  2. ref其实也可以定义对象,但是访问时需要value属性

总结

  • setup中直接返回的普通数据不是响应式的
  • 通过reactive包裹对象可以称为响应式数据
  • 为了简化对象的访问(去掉前缀),可以使用toRef进行优化
  • 为了获取对象中多个属性,可以使用toRefs进一步简化
  • 如果是简单数据类型,其实使用ref定义更加合适

5. computed计算属性

注意:vue3中计算属性也是组合API风格

  1. 回调函数必须return,结果就是计算结果
  2. 如果计算属性依赖的数据发生变化,那么会重新计算
  3. 不要在计算属性中进行异步操作
  • 修改计算属性的值

示例代码:

<template>
  <div>
    今年:{{ age }}岁了<br />
    明年:{{ nextAge }}岁了<br />
    <button @click="nextAge = 28">点击</button>
  </div>
</template>

<script>
import { ref, computed } from 'vue'
export default {
  name: 'App',
  setup() {
    const age = ref(0)
    
    // 只读的写法
    // const nextAge = computed(() => {
    //   return age.value + 1
    // })

    // 可读可写的写法,需要写成对象格式
    const nextAge = computed({
      get() {
        return age.value + 1
      },
      set(v) {
        age.value = v - 1
      }
    })
    return { age, nextAge }
  }
}
</script>

<style lang="less"></style>

总结:

  1. 计算属性可以直接读取或者修改
  2. 如果要实现计算属性的修改操作,那么computed的实参应该是对象
    • 读取数据触发get方法
    • 修改数据触发set方法,set函数的形参就是赋给的值

6. watch 侦听器

被侦听的信息变化时,触发侦听器的回调

典型场景:路由参数发生变化后,重新调用接口,获取组件的数据

  • 监听ref定义的响应式数据

语法格式:

import {ref, watch} from 'vue'

export default {
    setup() {
        const count = ref(0)
        
        // 基于侦听器监听数据的变化
        // 被侦听的审计局必须是响应式的
        watch (count, (newVal,oldVal) => {
            // newVal表示修改后的值
            // oldVal表示修改前的值
            console.log(newVal,oldVal)
        })
        
        return {count}
    }
}

总结:侦听普通数据可以获取修改前后的值,被侦听的数据必须是响应式的

  • 监听reactive定义的响应式数据

语法格式:

import {reactive, watch} from 'vue'

export default {
    setup () {
        const obj = reactive({
            msg: 'tom'
        })
        
        watch(obj,() => {
            // 直接获取到的就是最新值
            console.log(obj)
        })
    }
}

总结:如果侦听对象,那么侦听器的回调函数的两个参数是一样的结果,表示最新的对象数据,此时,也可以直接读取被侦听的对象,那么得到的值也是最新的。

  • 监听多个响应式数据

语法格式:

import {ref, watch} from 'vue'

export default {
    setup () {
        const n1 = ref(1)
        const n2 = ref(2)
        
        watch ([n1, n2], (v) => {
            // v表示被侦听的所有数据的最新值,类型是数组
            console.log(v)
        })
        
        const handleClick = () => {
            n1.value = 3
            n2.value = 4
        }
        
    
        return {n1, n2}
    }
}

总结:

  1. 可以得到更新前后的值
  2. 侦听到的结果也是数组,数据顺序一致
  • 监听reactive定义的响应式数据的某一个属性

语法格式:

<template>
  <div>
    姓名:{{ user.name }}<br />
    今年:{{ user.age }}岁了<br />
    <button @click="user.age = 13">点击</button>
  </div>
</template>

<script>
import { reactive, watch } from 'vue'
export default {
  name: 'App',
  setup() {
    const user = reactive({
      name: '小明',
      age: 12
    })
    watch(
      () => user.age,
      v => {
        console.log(v)
      }
    )
    return { user }
  }
}
</script>

<style lang="less"></style>

结论:

  1. 如果侦听对象中某个属性,需要用到函数方式
  2. 侦听更少的数据有利于提高性能
  • watch的配置项

语法格式:

import {} from 'vue'

export default {
    setup () {
        // immediate: true 表示,组件渲染后,立即触发一次
        watch(() => stuInfo.friend, () => {
            console.log('stuInfo')
        },{
            immediate: true,
            // 被侦听的内容需要是函数的写法
            deep: true // 深度侦听
        })
    }
}

总结:

  1. immediate: true,表示组件渲染时历史调用
  2. deep: true,表示深度侦听对象里面的子属性(被侦听的内容需要是函数的写法)

7. ref属性

获取DOM或者组件实例可以使用ref属性,写法和Vue2.0需要区分开

vue2中规则

结论:

  1. vue2中可以通过ref直接操作单个DOM和组件
  2. vue2 中可以批量通过ref操作DOM和组件

vue3 中规则

  • ref操作单个DOM元素

    代码演示:

    <template>
      <div>
        <div ref="info">hello</div>
        <button @click="handleClick">点击</button>
      </div>
    </template>
    
    <script>
    import { ref } from 'vue'
    export default {
      name: 'App',
      setup() {
        const info = ref(null)
    
        const handleClick = () => {
          // 打印出来是一个对象,value里是DOM元素
          // 可以使用info.value.innerHTML打印出来DOM中的内容
          console.log(info.value.innerHTML)
        }
    
        return { info, handleClick }
      }
    }
    </script>
    
    <style lang="less"></style>
    
    

    总结:操作单个DOM或者组件的流程

    1. 定义一个响应式变量
    2. 把变量返回给模板使用
    3. 模板中绑定上述返回数据
    4. 可以通过变量操作DOM 或者组件
  • 批量操作

    代码演示:

    <template>
      <div>
        <ul>
          <li :ref="setFruits" v-for="item in fruits" :key="item.id">{{ item.name }}</li>
        </ul>
        <button @click="handleClick">点击</button>
      </div>
    </template>
    
    <script>
    import { ref } from 'vue'
    export default {
      name: 'App',
      setup() {
        const fruits = ref([
          { id: 1, name: 'apple' },
          { id: 2, name: 'banana' },
          { id: 3, name: 'pear' }
        ])
        const arr = []
        const setFruits = el => {
          // el 代表单个DOM元素
          arr.push(el.innerHTML)
        }
    
        const handleClick = () => {
          console.log(arr)
        }
    
        return { fruits, handleClick, setFruits }
      }
    }
    </script>
    
    <style lang="less"></style>
    
    

    总结:ref批量操作元素的流程

    1. 定义一个函数
    2. 把该函数绑定到ref上(必须动态绑定)
    3. 在函数中可以通过参数得到单个元素,这个元素一般可以放到数组中
点击
 总结:ref批量操作元素的流程
 1. 定义一个函数
 2. 把该函数绑定到ref上(**必须动态绑定**)
 3. 在函数中可以通过参数得到单个元素,这个元素一般可以放到数组中
 4. 通过上述数组即可操作批量元素

8. 父子通讯

vue2写法

  1. 父组件向子组件传递数据: 自定义属性props
  2. 子组件向父组件传递数据:自定义事件 $emit()

vue3写法

父向子传值

步骤

  • setup函数return
  • 子组件props接收

代码演示

子组件中setup想拿到,setup传入形参即可

:props 值是只读的,不可以直接在子组件中修改

  • 父组件中
<template>
 <div>
   父组件
   <hr>
   <son :money="money"></son>
 </div>
</template>

<script>
import Son from './Son.vue'
import { ref } from 'vue'
export default {
  name: 'App',
  setup () {
    const money = ref(100)

    return { money }
  },
  components: {
    Son
  }
}
</script>

<style lang="less">

</style>

  • 子组件中 模板中可以直接使用自定义属性
<template>
  <div>子组件-----{{ money }}</div>
</template>

<script>
export default {
  name: 'Son',
  props: {
    money: {
      type: Number,
      default: 0
    }
  },
  setup (props) {
    console.log(props.money)
  }

}
</script>

<style>

</style>

  • js中使用需要写如下代码
setup(props) {
    const getMoney = () => {
        console.log(props.xxx)
    }
    
    return { getMoney }
}

总结:

  1. 子组件模板中直接可以获取props中的属性值
  2. js代码中需要通过setup中的第一个形参来获取

子向父传值

步骤

  • 子组件中使用setup传入的第二个形参,向父组件抛出自定义事件
  • 父组件中定义自定义事件,完成后续操作

代码演示

  • 子组件中 setup还有第二个参数,形参名可以自定义

emits必须写,不写vue会有如下警告。如果还有其他自定义事件,可以继续向数组中添加。

在这里插入图片描述

<template>
  <div>子组件-----{{ money }}</div>
+  <button @click="handleClick"></button>
</template>

<script>
export default {
  name: 'Son',
+  emits: ['sendToPar']
  props: {
    money: {
      type: Number,
      default: 0
    }
  },
+  setup (props, context) {
    console.log(props.money)

+    const handleClick = () => {
+      context.emit('sendToPar', 50)
+   }

+    return { handleClick }
  }

}
</script>

<style>

</style>

总结:

  1. 通过setup函数的参数二 context.emit方法 触发自定义事件context.emit('xxx',xx)
  2. 子组件触发的自定义事件需要在emits选项中进行声明,直观的看到本组件中触发了哪些自定义事件
  • 父组件中
<template>
 <div>
   父组件
   <hr>
+   <son :money="money" @sendToPar="getMoney"></son>
 </div>
</template>

<script>
import Son from './Son.vue'
import { ref } from 'vue'
export default {
  name: 'App',
  setup () {
    const money = ref(100)

+    const getMoney = (val) => {
      console.log(val)
+      money.value = money.value - val
+    }

+    return { money, getMoney }
  },
  components: {
    Son
  }
}
</script>

<style lang="less">

</style>

9. 依赖注入

使用场景:有一个父组件,里头有子组件,孙组件,有很多后代组件,共享父组件数据

父组件向子孙组件传递数据

步骤

  • 父组件中使用provide向外传递数据
  • 子孙组件使用inject接收来自祖先组件的数据

代码演示

  • 父组件 provide ---> 提供
<template>
 <div>
   父组件
   <hr>
   <son :money="money" @sendToPar="getMoney"></son>
 </div>
</template>

<script>
import Son from './Son.vue'
+import { ref, provide } from 'vue'
export default {
  name: 'App',
  setup () {
    const money = ref(100)
    // 向子孙组件传递数据
+    provide('moneyInfo', 1000)

    // 子组件自定义事件函数
    const getMoney = (val) => {
      console.log(val)
      money.value = money.value - val
    }

    return { money, getMoney }
  },
  components: {
    Son
  }
}
</script>

<style lang="less">

</style>
  • 子组件中注册子孙组件
<template>
  <div>子组件-----{{ money }}</div>
  <button @click="handleClick">点击</button>
  <hr>
+  <grand-son></grand-son>
</template>

<script>
+import GrandSon from './GrandSon.vue'

export default {
  name: 'Son',
  emits: ['sendToPar'],
  props: {
    money: {
      type: Number,
      default: 0
    }
  },
  setup (props, context) {
    console.log(props.money)

    const handleClick = () => {
      context.emit('sendToPar', 50)
    }

    return { handleClick }
  },
+  components: {
+   GrandSon
+ }

}
</script>

<style>

</style>
  • 子孙组件 inject ---> 注入
<template>
  <div>孙子组件-----{{ money }}</div>
</template>

<script>
import { inject } from 'vue'
export default {
  name: 'GrandSon',
  setup () {
    const money = inject('moneyInfo')

    return { money }
  }

}
</script>

<style>

</style>

总结:

  1. 父传子孙数据:使用provide
  2. 子孙得到数据:使用inject

子孙组件传递给父组件数据

步骤

  • 祖先组件向子孙组件传递一个函数
  • 子孙组件接收,并给祖先组件传递一个实参

代码演示

  • 父组件 把一个函数传递给子孙组件
<template>
 <div>
   父组件
   <hr>
   <son :money="money" @sendToPar="getMoney"></son>
 </div>
</template>

<script>
import Son from './Son.vue'
import { ref, provide } from 'vue'
export default {
  name: 'App',
  setup () {
    const money = ref(100)
    // 向子孙组件传递一个函数
    
+    const handleMoney = (val) => {
+     console.log('子孙组件传递回来的数据', val)
+    }
+    provide('handleMoney', handleMoney)

    // 子组件自定义事件函数
    const getMoney = (val) => {
      console.log(val)
      money.value = money.value - val
    }

    return { money, getMoney }
  },
  components: {
    Son
  }
}
</script>

<style lang="less">

</style>
  • 子孙组件
<template>
  <div>
    孙子组件
+    <button @click="handleSend">点击</button>
  </div>
</template>

<script>
import { inject } from 'vue'
export default {
  name: 'GrandSon',
  setup () {
+    const handleMoney = inject('handleMoney')

+    const handleSend = () => {
+      handleMoney(200)
+    }

+    return { handleSend }
  }

}
</script>

<style>

</style>

总结: 子组件传递数据给爷爷组件,需要通过provide 一个函数的方式实现

  1. 爷爷组件传递一个函数,后续通过函数的形参获取数据
  2. 孙子组件获取并调用该函数传递数据

10. v-model语法糖

vue2使用回顾

v-model是value和@input的语法糖

本质是属性绑定和事件绑定的结合

<input type="text" :value="uname" @input="uname=$event.target.value">

  • v-model也可以用到组件上
<my-com v-model="info"></my-com>

<my-com :value="info" @input="info=$event"></my-com>
  1. 如果是原始DOM的事件,那么$event表示js的原生事件对象
  2. 如果是组件的自定义事件,那么$event是#emit传递的数据

总结:vue2中v-model的应用场景

  1. 用到表单元素上:$event表示事件对象
  2. 用到组件上:$event表示子组件传递的数据

vue3中v-model新特性

v-model的本质是:modelValue和 @update:modelValue两者的绑定

代码演示

  • 父组件
<template>
 <div>
   父组件
   {{info}}
   {{msg}}
   <hr>
   
   <text-event v-model:modelValue="info" v-model:msg="msg"></text-event>
   <!-- 等效写法 -->
   <!-- <text-event :modelValue="info" @update:modelValue="info=$event" /> -->
 </div>
</template>

<script>
import { ref } from 'vue'
// 引入子组件
import TextEvent from './TextEvent.vue'
export default {
  name: 'App',
  components: {
  	// 注册组件
    TextEvent
  },
  setup () {
    const info = ref('hi~')
    const msg = ref('Leo')
    
    return { info, msg }
  }

}
</script>
  • 子组件 获取父组件传递的值
<template>
  <div>
    子组件
    {{modelValue}}
    {{msg}}
    
    <button @click="handleClick">点击</button>
  </div>
</template>

<script>
export default {
  name: 'TextEvent',
  props: {
    modelValue: {
      type: String,
      default: ''
    },
    msg: {
      type: String,
      default: ''
    }
  }
}
</script>

修改父组件传递的值

<template>
  <div>
    子组件
    {{modelValue}}
    {{msg}}
      
    <button @click="handleClick">点击</button>
  </div>
</template>

<script>
export default {
  name: 'TextEvent',
  props: {
    modelValue: {
      type: String,
      default: ''
    },
    msg: {
      type: String,
      default: ''
    }
  },
+  setup (props, context) {
+    const handleClick = () => {
+      context.emit('update:modelValue', 'hello')
+      context.emit('update:msg', 'leo')
+   }
+    return { handleClick }
+  }

}
</script>

<style>

</style>

总结:

  1. v-model可以通过绑定多个属性的方式,向子组件传递多个值并且保证双向绑定
  2. 可以替代vue2中.sync修饰符(sync修饰符在vue3中已经被废弃)

总结

本系列会持续更新,有帮到小伙伴儿的话,记得点赞+收藏哦~