vue3.0基础使用(附代码)

1. 认识Vue3

1) 了解相关信息

2) 性能提升:

  • 打包大小减少41%

  • 初次渲染快55%, 更新渲染快133%

  • 内存减少54%

  • 使用Proxy代替defineProperty实现数据响应式

  • 重写虚拟DOM的实现和Tree-Shaking

3) 新增特性

  • Composition (组合) API

  • setup

    • ref 和 reactive

    • computed 和 watch

    • 新的生命周期函数

    • provide与inject

    • ...

  • 新组件

    • Fragment - 文档碎片

    • Teleport - 瞬移组件的位置

    • Suspense - 异步加载组件的loading界面

  • 其它API更新

    • 全局API的修改

    • 将原来的全局API转移到应用对象

    • 模板语法变化

2. 创建vue3项目

1) 使用 vue-cli 创建

文档: https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create

npm install -g @vue/cli
## 保证 vue cli 版本在 4.5.0 以上
vue --version
## 创建项目
vue create my-project

然后的步骤

  • Please pick a preset - 选择 Manually select features

  • Check the features needed for your project - 选择上 TypeScript ,特别注意点空格是选择,点回车是下一步

  • Choose a version of Vue.js that you want to start the project with - 选择 3.x (Preview)

  • Use class-style component syntax - 直接回车

  • Use Babel alongside TypeScript - 直接回车

  • Pick a linter / formatter config - 直接回车

  • Use history mode for router? - 直接回车

  • Pick a linter / formatter config - 直接回车

  • Pick additional lint features - 直接回车

  • Where do you prefer placing config for Babel, ESLint, etc.? - 直接回车

  • Save this as a preset for future projects? - 直接回车

2) 使用 vite 创建

  • 文档: https://v3.cn.vuejs.org/guide/installation.html

  • vite 是一个由原生 ESM 驱动的 Web 开发构建工具。在开发环境下基于浏览器原生 ES imports 开发,

  • 它做到了本地快速开发启动, 在生产环境下基于 Rollup 打包。

    • 快速的冷启动,不需要等待打包操作;

    • 即时的热模块更新,替换性能和模块数量的解耦让更新飞起;

    • 真正的按需编译,不再等待整个应用编译完成,这是一个巨大的改变。

3. Composition API(常用部分)

文档:

https://composition-api.vuejs.org/zh/api.html

1) setup

  • 新的option, 所有的组合API函数都在此使用, 只在初始化时执行一次

  • 函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用

2) ref

  • 作用: 定义一个数据的响应式

  • 语法: const xxx = ref(initValue):

    • 创建一个包含响应式数据的引用(reference)对象

    • js中操作数据: xxx.value

    • 模板中操作数据: 不需要.value

  • 一般用来定义一个基本类型的响应式数据

<template>
 <h2>{{count}}</h2>
 <hr>
 <button @click="update">更新</button>
</template>

<script>
import { ref } from 'vue'
export default {

/* 使用vue3的composition API */
 setup () {

   // 定义响应式数据 ref对象
   const count = ref(1)
   console.log(count)

   // 更新响应式数据的函数
   function update () {
     // alert('update')
     count.value += 1
   }

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

3) reactive

  • 作用: 定义多个数据的响应式

  • const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象

  • 响应式转换是“深层的”:会影响对象内部所有嵌套的属性

  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的

<template>
   <h2>name: {{state.name}}</h2>
   <h2>age: {{state.age}}</h2>
   <h2>wife: {{state.wife}}</h2>
   <hr>
   <button @click="update">更新</button>
</template>

<script>
/* 
reactive: 
 作用: 定义多个数据的响应式
 const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
*/
import { reactive } from 'vue'
export default {
   setup () {
     /* 
     定义响应式数据对象
     */
     const state = reactive({
       name: 'tom',
       age: 25,
       wife: {
         name: 'marry',
         age: 22
       },
     })
     console.log(state, state.wife)

     const update = () => {
       state.name += '--'
       state.age += 1
       state.wife.name += '++'
       state.wife.age += 2
     }

     return {
       state,
       update,
     }
   }
}
</script>

4) 比较Vue2与Vue3的响应式(重要)

vue2的响应式

  • 核心:

    • 对象: 通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截)

    • 数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持

Object.defineProperty(data, 'count', {
 get () {}, 
 set () {}
})
  • 问题

    • 对象直接新添加的属性或删除已有属性, 界面不会自动更新

    • 直接通过下标替换元素或更新length, 界面不会自动更新 arr[1] = {}

Vue3的响应式

new Proxy(data, {
   // 拦截读取属性值
   get (target, prop) {
     return Reflect.get(target, prop)
   },
   // 拦截设置属性值或添加新属性
   set (target, prop, value) {
     return Reflect.set(target, prop, value)
   },
   // 拦截删除属性
   deleteProperty (target, prop) {
     return Reflect.deleteProperty(target, prop)
   }
})

proxy.name = 'tom' 
<!DOCTYPE html>
<html lang="en">
  <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>Proxy 与 Reflect</title>
  </head>
  <body>
   <script>
     const user = {
       name: "apple",
       age: 12
     };

     /* 
     proxyUser是代理对象, user是被代理对象
     后面所有的操作都是通过代理对象来操作被代理对象内部属性
     */
     const proxyUser = new Proxy(user, {

       get(target, prop) {
         console.log('劫持到了get()~', prop)
         return Reflect.get(target, prop)
       },

       set(target, prop, val) {
         console.log('劫持到了set()~', prop, val)
         return Reflect.set(target, prop, val);
       },

       deleteProperty (target, prop) {
         console.log('劫持到了delete属性~', prop)
         return Reflect.deleteProperty(target, prop)
       }
    });
   // 读取属性值
   //console.log(proxyUser===user)
   //console.log('proxyUser.name:::', proxyUser.name)
   //console.log('proxyUser.user:::', proxyUser.age)
   // 设置属性值
   //proxyUser.name = 'orange'
   //proxyUser.age = 13
   //console.log('user:::', user)
   // 添加属性
   //proxyUser.sex = '男'
   //console.log('user:::', user)
   // 删除属性
   //delete proxyUser.sex
   //console.log('user:::', user)
   </script>
  </body>
</html>

5) setup细节

  • setup执行的时机

    • 在beforeCreate之前执行(一次), 此时组件对象还没有创建

    • this是undefined, 不能通过this来访问data/computed/methods / props

    • 其实所有的composition API相关回调函数中也都不可以

  • setup的返回值

    • 一般都返回一个对象: 为模板提供数据, 也就是模板中可以直接使用此对象中的所有属性/方法

    • 返回对象中的属性会与data函数返回对象的属性合并成为组件对象的属性

    • 返回对象中的方法会与methods中的方法合并成功组件对象的方法

    • 如果有重名, setup优先

    • 注意:

      • 一般不要混合使用: methods中可以访问setup提供的属性和方法, 但在setup方法中不能访问data和methods

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

  • setup的参数

    • setup(props, context) / setup(props, {attrs, slots, emit})

    • props: 包含props配置声明且传入了的所有属性的对象

    • attrs: 包含没有在props配置中声明的属性的对象, 相当于 this.$attrs

    • slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots

    • emit: 用来分发自定义事件的函数, 相当于 this.$emit

<template>
   <h2>App</h2>
   <p>msg: {{msg}}</p>
   <button @click="fn('--')">更新</button>

   <child :msg="msg" msg2="cba" @fn="fn"/>
</template>

<script lang="ts">
import { reactive, ref } from 'vue'
import child from './child.vue'

export default {
 components: { child },
 setup () {
   const msg = ref('abc')

   function fn (content: string) {
     msg.value += content
   }
   return {
     msg,
     fn
     }
   }
}
</script>
<template>
 <div>
   <h3>{{n}}</h3>
   <h3>{{m}}</h3>

   <h3>msg: {{msg}}</h3>
   <h3>msg2: {{$attrs.msg2}}</h3>

   <slot name="xxx"></slot>

   <button @click="update">更新</button>
 </div>
</template>

<script lang="ts">

import { ref, defineComponent } from 'vue'

export default defineComponent({
 name: 'child',
 props: ['msg'],

 emits: ['fn'], // 可选的, 声明了更利于程序员阅读, 且可以对分发的事件数据进行校验

 setup (props, {attrs, emit, slots}) {

   console.log(props.msg, attrs.msg2, slots, emit)

   const m = ref(2)
   const n = ref(3)

   function update () {
     m.value += 2
     n.value += 2

     // 分发自定义事件
     emit('fn', '++')
   }

   return {
     m,
     n,
     update,
     }
   },
})
</script>

6) reactive与ref-细节

  • 是Vue3的 composition API中2个最重要的响应式API

  • ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)

  • 如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象

  • ref内部: 通过给value属性添加getter/setter来实现对数据的劫持

  • reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据

  • ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value)

<template>
  <h2>App</h2>
  <p>m1: {{m1}}</p>
  <p>m2: {{m2}}</p>
  <p>m3: {{m3}}</p>
  <button @click="update">更新</button>
</template>

<script>
import {
  reactive,
  ref
} from 'vue'

export default {

  setup () {
    const m1 = ref('abc')
    const m2 = reactive({x: 1, y: {z: 'abc'}})

    // 使用ref处理对象  ==> 对象会被自动reactive为proxy对象
    const m3 = ref({a1: 2, a2: {a3: 'abc'}})
    console.log(m1, m2, m3)
    console.log(m3.value.a2) // 也是一个proxy对象

    function update() {
      m1.value += '--'
      m2.x += 1
      m2.y.z += '++'

      m3.value = {a1: 3, a2: {a3: 'abc---'}}
      m3.value.a2.a3 += '==' // reactive对对象进行了深度数据劫持
      console.log(m3.value.a2)
    }

    return {
      m1,
      m2,
      m3,
      update
    }
  }
}
</script>

7) 计算属性与监视

  • computed函数:

    • 与computed配置功能一致

    • 只有getter

    • 有getter和setter

  • watch函数

    • 与watch配置功能一致

    • 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调

    • 默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次

    • 通过配置deep为true, 来指定深度监视

  • watchEffect函数

    • 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据

    • 默认初始时就会执行第一次, 从而可以收集需要监视的数据

    • 监视数据发生变化时回调

<template>
  <h2>App</h2>
  fistName: <input v-model="user.firstName"/><br>
  lastName: <input v-model="user.lastName"/><br>
  fullName1: <input v-model="fullName1"/><br>
  fullName2: <input v-model="fullName2"><br>
  fullName3: <input v-model="fullName3"><br>

</template>

<script>

import {
  reactive,
  ref,
  computed,
  watch,
  watchEffect
} from 'vue'

export default {

  setup () {
    const user = reactive({
      firstName: 'A',
      lastName: 'B'
    })

    // 1.只有getter的计算属性
    const fullName1 = computed(() => {
      console.log('fullName1')
      return user.firstName + '-' + user.lastName
    })

    // 2.有getter与setter的计算属性
    const fullName2 = computed({
      get () {
        console.log('fullName2 get')
        return user.firstName + '-' + user.lastName
      },

      set (value) {
        console.log('fullName2 set')
        const names = value.split('-')
        user.firstName = names[0]
        user.lastName = names[1]
      }
    })

    const fullName3 = ref('')

    /* 使用watch的2个特性:深度监视和初始化立即执行 */
    watch(user, () => {
      fullName3.value = user.firstName + '-' + user.lastName
    }, {
      immediate: true,  // 是否初始化立即执行一次, 默认是false
      deep: true, // 是否是深度监视, 默认是false
    })

    /* 
    watch一个ref对象数据
      默认在数据发生改变时执行回调
    */
    watch(fullName3, (value) => {
      console.log('watch')
      const names = value.split('-')
      user.firstName = names[0]
      user.lastName = names[1]
    })

    /* 
    watch多个数据: 
      使用数组来指定
      如果是ref对象, 直接指定
      如果是reactive对象中的属性,  必须通过函数来指定
    */
    watch([() => user.firstName, () => user.lastName, fullName3], (values) => {
      console.log('监视多个数据', values)
    })
       /* 
    watchEffect: 监视所有回调中使用的数据
    */
    /* 
    watchEffect(() => {
      console.log('watchEffect')
      fullName3.value = user.firstName + '-' + user.lastName
    }) 
    */

    return {
      user,
      fullName1,
      fullName2,
      fullName3
    }
  }
}
</script>

8) 生命周期

vue2.x的生命周期

[图片上传失败...(image-3484e0-1644895211640)]

vue3的生命周期

[图片上传失败...(image-cb9666-1644895211640)]

与 2.x 版本生命周期相对应的组合式 API

  • beforeCreate -> 使用 setup()

  • created -> 使用 setup()

  • beforeMount -> onBeforeMount

  • mounted -> onMounted

  • beforeUpdate -> onBeforeUpdate

  • updated -> onUpdated

  • beforeDestroy -> onBeforeUnmount

  • destroyed -> onUnmounted

  • errorCaptured -> onErrorCaptured

新增的钩子函数

组合式 API 还提供了以下调试钩子函数:

  • onRenderTracked

  • onRenderTriggered

<div class="about">
  <h2>msg: {{msg}}</h2>
  <hr>
  <button @click="update">更新</button>
</div>
</template>

<script lang="ts">
import {
  ref,
  onMounted,
  onUpdated,
  onUnmounted, 
  onBeforeMount, 
  onBeforeUpdate,
  onBeforeUnmount
} from "vue"

export default {
  beforeCreate () {
    console.log('beforeCreate()')
  },

  created () {
    console.log('created')
  },

  beforeMount () {
    console.log('beforeMount')
  },

  mounted () {
    console.log('mounted')
  },

  beforeUpdate () {
    console.log('beforeUpdate')
  },

  updated () {
    console.log('updated')
  },

  beforeUnmount () {
    console.log('beforeUnmount')
  },

  unmounted () {
     console.log('unmounted')
  },

  setup() {

    const msg = ref('abc')

    const update = () => {
      msg.value += '--'
    }

    onBeforeMount(() => {
      console.log('--onBeforeMount')
    })

    onMounted(() => {
      console.log('--onMounted')
    })

    onBeforeUpdate(() => {
      console.log('--onBeforeUpdate')
    })

    onUpdated(() => {
      console.log('--onUpdated')
    })

    onBeforeUnmount(() => {
      console.log('--onBeforeUnmount')
    })

    onUnmounted(() => {
      console.log('--onUnmounted')
    })

    return {
      msg,
      update
    }
  }
}
</script>
  <h2>App</h2>
  <button @click="isShow=!isShow">切换</button>
  <hr>
  <Child v-if="isShow"/>
</template>

<script lang="ts">
import Child from './Child.vue'
export default {

  data () {
    return {
      isShow: true
    }
  },

  components: {
    Child
  }
}
</script>

9) toRefs

把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref

应用: 当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用

问题: reactive 对象取出的所有属性值都是非响应式的

解决: 利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性

  <h2>App</h2>
  <h3>foo: {{foo}}</h3>
  <h3>bar: {{bar}}</h3>
  <h3>foo2: {{foo2}}</h3>
  <h3>bar2: {{bar2}}</h3>

</template>

<script lang="ts">
import { reactive, toRefs } from 'vue'
/*
toRefs:
  将响应式对象中所有属性包装为ref对象, 并返回包含这些ref对象的普通对象
  应用: 当从合成函数返回响应式对象时,toRefs 非常有用,
        这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
*/
export default {

  setup () {

    const state = reactive({
      foo: 'a',
      bar: 'b',
    })

    const stateAsRefs = toRefs(state)

    setTimeout(() => {
      state.foo += '++'
      state.bar += '++'
    }, 2000);

    const {foo2, bar2} = useReatureX()

    return {
      // ...state,
      ...stateAsRefs,
      foo2, 
      bar2
    }
  },
}

function useReatureX() {
  const state = reactive({
    foo2: 'a',
    bar2: 'b',
  })

  setTimeout(() => {
    state.foo2 += '++'
    state.bar2 += '++'
  }, 2000);

  return toRefs(state)
}

</script>

10) provide和inject

利用ref函数获取组件中的标签元素

provide :向子组件以及子孙组件传递数据。接收两个参数,第一个参数是 key,即数据的名称;第二个参数为 value,即数据的值 inject :接收父组件或祖先组件传递过来的数据。接收一个参数 key,即父组件或祖先组件传递的数据名称

import {provide} from 'vue'
export default {
    setup() {
        const obj= {
            name: 'apple',
            color: 'red'
        }

        // 向子组件以及子孙组件传递名为info的数据
        provide('info', obj)
    }
}

// B.vue
<div>{{ form.name }}</div>

import {inject} from 'vue'
export default {
    setup() {   
        // 接收A.vue传递过来的数据
        const form= inject('info')  // { name: 'apple', color: 'red'}
    }
}

11) ref获取元素

利用ref函数获取组件中的标签元素

功能需求: 让输入框自动获取焦点

<template>
  <h2>App</h2>
  <input type="text">
  <input type="text" ref="inputRef">
</template>

<script>
import { onMounted, ref } from 'vue'
/* 
ref获取元素: 利用ref函数获取组件中的标签元素
功能需求: 让输入框自动获取焦点
*/
export default {
  setup() {
    const inputRef = ref(null)

    onMounted(() => {
      inputRef.value && inputRef.value.focus()
    })

    return {
      inputRef
    }
  },
}
</script>

4. 新组件

1) Fragment(片断)

  • 在Vue2中: 组件必须有一个根标签

  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中

  • 好处: 减少标签层级, 减小内存占用

<template>
    <h2>aaaa</h2>
    <h2>aaaa</h2>
</template>

2) Teleport(瞬移)

  • Teleport 提供了一种干净的方法, 让组件的html在父组件界面外的特定标签(很可能是body)下插入显示

ModalButton.vue

<template>
  <button @click="modalOpen = true">
      Open full screen modal! (With teleport!)
  </button>

  <teleport to="body">
    <div v-if="modalOpen" class="modal">
      <div>
        I'm a teleported modal! 
        (My parent is "body")
        <button @click="modalOpen = false">
          Close
        </button>
      </div>
    </div>
  </teleport>
</template>

<script>
import { ref } from 'vue'
export default {
  name: 'modal-button',
  setup () {
    const modalOpen = ref(false)
    return {
      modalOpen
    }
  }
}
</script>

<style>
.modal {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  background-color: rgba(0,0,0,.5);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.modal div {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background-color: white;
  width: 300px;
  height: 300px;
  padding: 5px;
}
</style>

App.vue

<template>
  <h2>App</h2>
  <modal-button></modal-button>
</template>

<script>
import ModalButton from './ModalButton.vue'

export default {
  setup() {
    return {
    }
  },

  components: {
    ModalButton
  }
}
</script>

3) Suspense(不确定的)

  • 它们允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验
<template>
  <Suspense>
    <template v-slot:default>
      <AsyncComp/>
      <!-- <AsyncAddress/> -->
    </template>

    <template v-slot:fallback>
      <h1>LOADING...</h1>
    </template>
  </Suspense>
</template>

<script lang="ts">
/* 
异步组件 + Suspense组件
*/
// import AsyncComp from './AsyncComp.vue'
import AsyncAddress from './AsyncAddress.vue'
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => import('./AsyncComp.vue'))
export default {
  setup() {
    return {

    }
  },

  components: {
    AsyncComp,
    AsyncAddress
  }
}
</script>
  • AsyncComp.vue
<template>
  <h2>AsyncComp22</h2>
  <p>{{msg}}</p>
</template>

<script lang="ts">

export default {
  name: 'AsyncComp',
  setup () {
    // return new Promise((resolve, reject) => {
    //   setTimeout(() => {
    //     resolve({
    //       msg: 'abc'
    //     })
    //   }, 2000)
    // })
    return {
      msg: 'abc'
    }
  }
}
</script>
  • AsyncAddress.vue
<template>
<h2>{{data}}</h2>
</template>

<script lang="ts">
import axios from 'axios'
export default {
  async setup() {
    const result = await axios.get('/data/address.json')
    return {
      data: result.data
    }
  }
}
</script>
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容