Vue3 基础语法学习记录

129 阅读10分钟

Vue3

1.创建Vue3工程

1.使用vue-cli创建

 # 1.查看版本,保证版本大于4.5.0
 vue --version
 # 安装或者升级
 npm install -g @vue/cli
 ​
 # 创建
 vue create vue_test
 # 启动
 cd vue_test
 npm run serve

2.使用vite创建

1.什么是vite

是一种新型的前端构建工具

优势:

  • 开发环境中,无需打包操作,可快速冷启动。
  • 轻量快速的热重载。
  • 真的的按需编译,不再等待整个应用编译完成。

2.创建工程

 # 创建工程
 npm init vite-app <project-name>
 # 进入
 cd <project-name>
 # 安装依赖
 npm install
 # 运行
 npm run dev

3.工程结构

1.main.js

new Vue() 不再使用

 //引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
 import { createApp } from 'vue'
 import App from "./App.vue'
 ​
 // 创建应用实例对象 - createApp(App) 比 new Vue() 更轻
 createApp(App).mount('#app')
 ​

2.常用组合API

1.setup函数

vue3中的新的配置项,值为一个函数。组件中所用到的数据、方法等,均配置在setup中。

返回值:

  1. 返回一个对象,则对象的属性,方法,在模板中均可直接使用。
  2. 返回一个渲染函数,则可以自定义渲染的内容。(了解)

注意:setup不能是一个async函数,因为async函数返回值不再是return的对象,而是promise,模板看不到return对象中的属性。

 <template>
   <h1>我是APP组件</h1>
   <h2>姓名:{{ name }}</h2>
   <h2>年龄:{{ age }}</h2>
   <button @click="sayHello">说话</button>
 </template>
 ​
 <script>
 // h 为渲染函数
 import { h } from 'vue'
 export default {
   name: 'App',
   setup(){
     let name = '张三'
     let age = 18
 ​
     function sayHello(){
       alert(`我叫${name},我${age}岁了,你好啊!`)
     }
 ​
     // return {
     //   name,
     //   age,
     //   sayHello
     // }
 ​
     //返回渲染函数,不管前面的内容,只渲染该函数的内容
     return ()=>h('h1', 'AnAn')
   }
 }
 </script>

注意点:

  1. setup执行的时机:在beforeCreate之前执行一次,this是undefined。

  2. setup的参数

    • props:值为对象,包含组件外部传递过来,且内部声明接收的属性。

    • context:上下文对象

      • attrs:值为对象,包含组件外部传递过略,但没有声明接受的属性。相当于this.$attrs
      • slots:收到的插槽内容。相当于this.$slots
      • emit:分发自定义事件的函数。相当于this.$emit
 <template>
   <Demo @hello="showMsg" name="zc" age="18">demo 组件</Demo>
 </template>
 ​
 <script>
 import { reactive } from 'vue'
 import Demo from './components/Demo.vue';
 ​
 export default {
   name: 'App',
   components: {Demo},
   setup(){
     function showMsg(value){
       alert(`触发了hello事件,收到的参数是${value}`)
     }
     return {
       showMsg
     }
   }
 }
 </script>
 <template>
     <h1>姓名:{{ person.name }}</h1>
     <h1>年龄:{{ person.age }}</h1>
     <button @click="test">测试hello事件</button>
 </template>
 ​
 <script>
 import { reactive } from 'vue';
 ​
 export default {
     name: 'Demo',
     props: ['name', 'age'],
     emits: ['hello'],
     setup(props, context){
         console.log(props)
         let person = reactive({
             name: props.name,
             age: props.age
         })
         function test(){
             context.emit('hello', 666)
         }
 ​
         return {
             person, test
         }
     }
 }
 </script>

2.ref函数

定义一个响应式的数据。

let name = '张三'定义的变量并不是响应式的变量,应使用let name = ref('张三'),值存储在ref对象的属性value中,使用get set进行获取设置,修改变量时,需要修改变量的属性value。

  • 创建的是包含响应式数据的引用对象。ref对象。
  • JS中操作数据,xxx.value
  • 模板中读取数据不需要.value

注意:

  • ref()接受的数据可以是:基本类型,也可以是对象类型/
  • 基本类型的数据:响应式依然是靠Object.defineProperty()的get 与 set 完成的
  • 对象类型的数据:内部是Vue3中的新函数,reactive函数,返回一个proxy对象。
 <template>
   <h1>我是APP组件</h1>
   <h2>姓名:{{ name }}</h2>
   <h2>年龄:{{ age }}</h2>
   <h2>工作类型:{{ job.type }}</h2>
   <h2>工作薪水:{{ job.salary }}</h2>
   <button @click="changeInfo">修改信息</button>
 </template>
 ​
 <script>
 import { ref } from 'vue'
 ​
 export default {
   name: 'App',
   setup(){
     let name = ref('张三')
     let age = ref(18)
     let job = ref({
       type: '前端工程师',
       salary: '30k'
     })
     function changeInfo(){
       name.value = '李四'
       age.value = 48
       job.value.type = '后端工程师'
       job.value.salary = '50k'
     }
 ​
     return {
       name, age, job, changeInfo, 
     }
 ​
   }
 }
 </script>

3.reactive函数

定义一个对象类型的响应式数据,不能使用基本类型。

const 代理对象 = reactive(源对象) 接收一个对象或数组,返回一个proxy对象。

reactive 定义的响应式数据是深层次的,基于ES6的Proxy实现,通过代理对象操作元对象内部数据,不需要.value。

 <template>
   <h1>我是APP组件</h1>
   <h2>姓名:{{ name }}</h2>
   <h2>年龄:{{ age }}</h2>
   <h2>工作类型:{{ job.type }}</h2>
   <h2>工作薪水:{{ job.salary }}</h2>
   <h2>爱好:{{ hobby }}</h2>
   <h2>b: {{ job.a.b }}</h2>
   <button @click="changeInfo">修改信息</button>
 </template>
 ​
 <script>
 import { ref,reactive } from 'vue'
 ​
 export default {
   name: 'App',
   setup(){
     let name = ref('张三')
     let age = ref(18)
     let job = reactive({
       type: '前端工程师',
       salary: '30k',
       a:{
         b: 666
       }
     })
     let hobby = reactive(['抽烟','喝酒','烫头'])
 ​
 ​
     function changeInfo(){
       name.value = '李四'
       age.value = 48
       job.type = '后端工程师'
       job.salary = '50k'
       hobby[0] = '打游戏'
     }
 ​
     return {
       name, age, job, changeInfo, hobby,
     }
 ​
   }
 }
 </script>
 <template>
   <h1>我是APP组件</h1>
   <h2>姓名:{{ person.name }}</h2>
   <h2>年龄:{{ person.age }}</h2>
   <h2>工作类型:{{ person.job.type }}</h2>
   <h2>工作薪水:{{ person.job.salary }}</h2>
   <h2>爱好:{{ person.hobby }}</h2>
   <button @click="changeInfo">修改信息</button>
 </template>
 ​
 <script>
 import { reactive } from 'vue'
 ​
 export default {
   name: 'App',
   setup(){
     let person = reactive({
       name: 'zc',
       age: 18,
       job: {
         type: 'front-end',
         salary: '50k'
       },
       hobby: ['','','']
     })
 ​
     function changeInfo(){
       
     }
 ​
     return {
       person, changeInfo
     }
 ​
   }
 }
 </script>

4.Vue3响应式原理

vue2实现原理:

  • 对象类型:通过 Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)

  • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组变更方法进行了包裹)

    vue2中需要使用Vue.set进行修改.

  • 存在的问题

    • 新增属性、删除属性、界面不更新
    • 直接通过下标修改数组,界面不更新。

Vue3实现原理:

  • 通过Proxy代理,拦截对象中任意属性的变化。
  • 通过Reflect反射,对被代理对象的属性进行操作。

5.reactive对比ref

  • 定义数据角度:ref定义基本类型,reactive定义对象或数组
  • 原理角度:ref通过Object.defineProperty()实现响应式(数据劫持),reactive 通过Proxy实现响应式,通过Reflect操作源对象内部数据。
  • 使用角度:ref操作数据需要.value,读取数据时模板中不需要。reactive都不需要.value。

6.计算属性与监视

1.computed函数

使用已有属性,计算新属性。

 <template>
     姓:<input type="text" v-model="person.firstName">
     名:<input type="text" v-model="person.lastName">
     <h2>全名:{{ person.fullName }}</h2>
     全名:<input type="text" v-model="person.fullName">
 </template>
 ​
 <script>
 import { reactive,computed } from 'vue';
 ​
 export default {
     name: 'Demo',
     setup(props, context){
         let person = reactive({
             firstName: 'z',
             lastName: 's'
         })
         //计算属性
         person.fullName = computed({
             get(){
                 return person.firstName+'-'+person.lastName
             },
             set(value){
                 const nameArr = value.split('-')
                 person.firstName = nameArr[0]
                 person.lastName = nameArr[1]
             }
         })
 ​
         return {
             person,
         }
     }
 }
 </script>

2.watch函数

 <template>
     <h2>求和为: {{ sum }}</h2>
     <button @click="sum++">点我加一</button>
     <h2>信息为 {{ msg }}</h2>
     <button @click="msg+='!'">修改信息</button>
     <h2>name: {{ person.name }}</h2>
     <h2>age: {{ person.age }}</h2>
     <h2>salary {{ person.job.salary }}k</h2>
     <button @click="person.name+='!'">change name</button>
     <button @click="person.age++">change age</button>
     <button @click="person.job.salary++">add salary</button>
 </template>
 ​
 <script>
 import { ref, reactive, watch } from 'vue';
 ​
 export default {
     name: 'Demo',
     setup(props, context){
         let sum = ref(0)
         let msg = ref('nnihao')
         //监视 -- 情况一
         // watch(sum, (newValue, oldValue)=>{
         //     console.log('sum变了'+newValue+','+oldValue)
         // })
         //监视 -- 情况二
         // watch([sum, msg], (newValue, oldValue)=>{
         //     console.log('sum变了'+newValue[0]+','+oldValue[0])
         //     console.log('msg变了'+newValue[1]+','+oldValue[1])
         // }, {immediate: true})
 ​
         let person = reactive({
             name: 'zs',
             age: 18,
             job: {
                 salary: 10
             }
         })
 ​
         //监视 -- 情况三
         // reactive 定义的数据,无法正确获取 oldValue
         // 默认开启深度监视,可以监视对象内部的对象数据变化,无法关闭
         // watch(person, (newValue, oldValue)=>{
         //     console.log('person changed!', newValue, oldValue)
         // })
 ​
         // 监视 -- 情况四
         // 监视reactive变量的某个属性
         // watch(()=>person.age, (newValue, oldValue)=>{
         //     console.log('person Age changed!', newValue, oldValue)
         // })
 ​
         // 监视 -- 情况五
         // 监视reactive变量的多个属性
         // watch([()=>person.age, ()=>person.name], (newValue, oldValue)=>{
         //     console.log('person Age or Name changed!', newValue, oldValue)
         // })
 ​
         // 监视 -- 特殊情况
         // person.job 是对象,引用地址不变,无法监视,应直接使person.job.salary
         watch(()=>person.job, (newValue, oldValue)=>{
             console.log('person job changed!', newValue, oldValue)
         })
         watch(()=>person.job.salary, (newValue, oldValue)=>{
             console.log('person job salary changed!', newValue, oldValue)
         })
         
 ​
         return {
             sum, msg, person
         }
     }
 }
 </script>

3.watchEffect函数

不用指明监视那个属性,监视的回调中用到哪个属性,就监视哪个属性。

 watchEffect(()=>{
     const x1 = sum.value
     const x2 = person.age
 })

7.Vue3声明周期

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeUnmount
  • unmounted

8.自定义hook

什么是hook?本质是一个函数,把setup函数中组合式API进行了封装。

优势:复用代码,让setup中的逻辑更清楚易懂。

 <template>
     <h2>当前鼠标坐标为: x:{{ point.x }} y:{{ point.y }}</h2>
 </template>
 ​
 <script>
 import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
 import usePoint from '../hooks/usePoint'
 export default {
     name: 'Demo',
     setup(props, context){
 /*  正常写法     
         let point = reactive({
             x: 0,
             y: 0
         })
         function savePoint(event) {
             point.x = event.pageX
             point.y = event.pageY
         }
         onMounted(()=>{
             window.addEventListener('click', savePoint)
         })
         onBeforeUnmount(()=>{
             window.removeEventListener('click',savePoint)
         })
 ​
 */
 ​
 /*  定义hook:usePoint.js        */
         let point = usePoint()
 ​
         return {
             point
         }
     }
 }
 </script>
 // usePoint.js
 import { reactive, onMounted, onBeforeUnmount } from 'vue';
 export default function (){
     let point = reactive({
         x: 0,
         y: 0
     })
     function savePoint(event) {
         point.x = event.pageX
         point.y = event.pageY
     }
     onMounted(()=>{
         window.addEventListener('click', savePoint)
     })
     onBeforeUnmount(()=>{
         window.removeEventListener('click',savePoint)
     })
     
     return point
 }

9.toRef

创建一个ref对象,其value值指向另一个对象中的某个属性。

const name = toRef(person, 'name')

应用:将响应式对象中的某个属性单独提供给外部使用。具有引用关系,同步修改。

扩展:toRefs与toRef功能一致,可以批量创建多个ref对象。toRefs(person)

 <template>
     <h2>{{ person }}</h2>
     <h2>name: {{ name }}</h2>
     <h2>age: {{ age }}</h2>
     <h2>salary {{ salary }}k</h2>
     <button @click="name+='!'">change name</button>
     <button @click="age++">change age</button>
     <button @click="salary++">add salary</button>
 </template>
 ​
 <script>
 import { reactive,toRef,toRefs } from 'vue';
 ​
 export default {
     name: 'Demo',
     setup(){
         let person = reactive({
             name: 'zs',
             age: 18,
             job: {
                 salary: 10
             }
         })
         const name = toRef(person, 'name')
         const age = toRef(person, 'age')
         const salary = toRef(person.job, 'salary')
         return {
             person, name, age, salary
         }
     }
 }
 </script>
 <template>
     <h2>{{ person }}</h2>
     <h2>name: {{ name }}</h2>
     <h2>age: {{ age }}</h2>
     <h2>salary {{ job.salary }}k</h2>
     <button @click="name+='!'">change name</button>
     <button @click="age++">change age</button>
     <button @click="job.salary++">add salary</button>
 </template>
 ​
 <script>
 import { reactive,toRef,toRefs } from 'vue';
 ​
 export default {
     name: 'Demo',
     setup(){
         let person = reactive({
             name: 'zs',
             age: 18,
             job: {
                 salary: 10
             }
         })
 ​
         return {
             person, ...toRefs(person)
         }
     }
 }
 </script>

3.其他组合API

1.shallowReactive 与 shallowRef

shallowReactive : 浅层响应式,只处理外层属性数据。不处理job.salary

shallowRef:浅层响应式,只处理基本类型的响应数据,不处理对象类型的响应数据。

2.readonly 与 shallowReadonly

readonly : 让一个响应式数据变成只读的(深只读)。person = readonly(person)

shallowReadonly:浅层只读,深层仍可改(浅只读)person = shallowReadonly(person)

应用场景:不希望数据被修改时。

3.toRaw 与 markRaw

toRaw :将一个reactive响应式对象转为普通对象

使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。

markRaw:标记一个对象,使其永远不再成为响应式对象。

应用场景:

  1. 有些值不应被设置为响应式的。例如复杂的第三方类库等。
  2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

4.customRef

创建一个自定义的ref, 并对其依赖项跟踪和更新触发进行显示的控制。

  • 实现防抖效果
 <template>
     <input type="text" v-model="keyword">
     <h3>{{ keyword }}</h3>
 </template>
 ​
 <script>
 import { ref, customRef } from 'vue';
 ​
 export default {
     name: 'Demo',
     setup(){
         // let keyword = ref('hello')  //vue 提供的ref
         // 自定义一个ref -- myRef
         function myRef(value){
             let timer
             return customRef((track, trigger)=>{
                 return {
                     get(){
                         // 模板中 {{ keyword }} 读取时
                         console.log('从 myRef 中读取数据',value)
                         track() //通知vue追踪value的修改(告知value的修改是有用的)
                         return value;
                     },
                     set(newValue){
                         console.log('修改了 myRef 中的数据',newValue)
                         clearTimeout(timer)     // 删除定时器
                         timer = setTimeout(()=>{    // 设置定时器
                             value = newValue
                             trigger()   //通知vue重新解析模板
                         },500)  //500ms后更新
                         
                     }
                 }
             })
         }
         let keyword = myRef('hello')
         
 ​
         return {
             keyword
         }
     }
 }
 </script>

5.provide 与 inject

  • 实现祖先与后代组件间通信
  • 套路,祖先组件有一个provide选项来提供数据 ,所有后代组件有一个inject选项来开始使用这些数据。
  • 具体写法
 // 祖组件中
 setup(){
     let car = reactive({
         name: '',
         price: ''
     })
     provide('car', car)
 }
 //后代组件中
 setup(){
     const car = inject('car')
     return {car}
 }

6.响应式数据的判断

  • isRef 检查一个值是否为一个 ref 对象
  • isReactive 检查一个对象是否有 reactive 的响应式代理
  • isReadonly
  • isProxy

4.组合式API的优势

1.Options API 存在的问题

配置式的API,新增或者修改一个需求,需要分别在data、methods、computed里修改。

2. Composition API

更加优雅的组织代码、函数。让相关功能的代码更加有序的组织在一起。

5.新的组件

1.Fragment

  • 在Vue2中: 组件必须有一个根标签
  • 在Vue3中: 组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处: 减少标签层级,减小内存占用

2.Teleport

Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。

 <template>
   <div>
     <button @click="isShow=true">点我弹窗</button>
     <Teleport to="body">
         <div v-if="isShow" class="mask">
             <div  class="dialog">
                 <h3>我是一个弹窗</h3>
                 <h4>一些内容</h4>
                 <button @click="isShow=false">关闭按钮</button>
             </div>
         </div>
     </Teleport>
   </div>
 </template>
 ​
 <script>
 import { ref } from 'vue'
 export default {
   name: 'Dialog',
   setup(){
     let isShow = ref(false)
     return {
         isShow
     }
   }
 }
 </script>
 ​
 <style scoped>
 .mask{
     position: absolute;
     top: 0;bottom: 0;left: 0;right: 0;
     background-color: rgba(0,0,0,0.5);
 }
   .dialog {
     position: absolute;
     top: 50%;
     left: 50%;
     transform: translate(-50%, -50%);
     text-align: center;
     width: 300px;
     height: 300px;
     background-color: green;
   }
 </style>

3.Suspense

等待异步组件时渲染一些额外内容,让应用有更好的用户体验。

使用步骤:

  • 异步引入组件
 // import Fa from './components/Fa.vue'; //静态引入
 import { defineAsyncComponent } from 'vue'; 
 const Fa = defineAsyncComponent(()=>import('./components/Fa.vue'))  //异步引入
  • 使用Suspense包裹组件,并配置好defaultfallback
 <template>
   <h1>我是APP组件</h1>
   <Suspense>
     <!-- 需要展示的内容 -->
     <template v-slot:default>
       <Fa/>
     </template>
     <!-- 未加载出时显示的内容 -->
     <template v-slot:fallback>
       <h3>加载中......</h3>
     </template>
   </Suspense>
 </template>

6.其它

1.全局API的转移

Vue3将全局API,即:Vue.xxx调整到应用实例(app)上

2.其他改变

  • data选项应始终被声明为一个函数
  • 移除keyCode作为v-on的修饰符,同时不再支持config.keyCodes
  • 移除v-on.native修饰符
  • 移除过滤器filter

常用设置和工具

1.关闭语法检查

在最外层目录新建文件vue.config,js

 module.exports = {
     lintOnSave: false, //关闭语法检查
 }

VueX

1.介绍

专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。

1.使用场景

同意数据的共享和修改

  1. 多个组件依赖于同一状态
  2. 来自不同组件的行为需要变更同一状态

2.搭建环境

1.安装
 npm i vuex
2.使用
 app.use(Vuex)

VueRouter

 npm install vue-router@4 -s
 //main.js
 import { createApp } from 'vue'
 import App from './App.vue'
 import Demo from './components/Demo'
 import Fa from './components/Fa'
 import { createRouter, createWebHashHistory } from 'vue-router'
 ​
 const routes = [
     {path: '/', component: Demo},
     {path: '/about', component: Fa},
 ]
 const router = createRouter({
     history: createWebHashHistory(),
     routes
 })
 ​
 const app = createApp(App)
 app.use(router)
 app.mount('#app')
 <template>
   <div>
     <RouterLink to="/" >首页</RouterLink>
     <RouterLink to="/about">关于</RouterLink>
   </div>
   <RouterView></RouterView>
 </template>
 ​
 <script>
 export default {
   name: 'App',
   components: {},
 }
 </script>