Vue3 Composition API 学习

一、vue-cli创建项目

选择安装typescript及使用vue3.x其余一路回车跳过

## 安装或者升级
npm install -g @vue/cli
## 保证 vue cli 版本在 4.5.0 以上
vue --version
## 创建项目
vue create my-project
  • 选择自定义组件,回车。
    Vue3 Composition API 学习_第1张图片
  • 空格键选中typescript,回车。
    Vue3 Composition API 学习_第2张图片
  • 选择3.x回车
    Vue3 Composition API 学习_第3张图片
  • 后续操作全部回车跳过
    Vue3 Composition API 学习_第4张图片
  • 项目创建成功
//切换目录
cd 项目路径
//启动
npm run serve

二、目录介绍

Vue3 Composition API 学习_第5张图片

  • main.ts介绍
//程序的主入口文件
//引入createApp函数,创建对应的应用,产生应用的实例对象
import { createApp } from 'vue'
//引入app组件,是所有组件的父组件
import App from './App.vue'
//创建app应用返回对应的实例对象,调用mount方法进行挂载
createApp(App).mount('#app')
  • App.vue介绍
<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
</template>

<script lang="ts">
//defineComponent函数,目的是定义一个组件,内部可以传入一个配置对象
import { defineComponent } from 'vue';
//引入一个子组件
import HelloWorld from './components/HelloWorld.vue';
//暴露出去一个定义好的组件
export default defineComponent({
  name: 'App',//当前组件的名称
  components: {//组册子级组件
    HelloWorld
  }
});
</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>

三、Composition API(常用部分)

3.1 setup

  • 新的option, 所有的组合API函数都在此使用, 只在初始化时执行一次
  • 函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用

3.1.1 setup执行的时机

  • 在beforeCreate之前执行(一次), 此时组件对象还没有创建
  • this是undefined, 不能通过this来访问data/computed/methods / props
  • 其实所有的composition API相关回调函数中也都不可以

3.1.2 setup的返回值

  • 一般都返回一个对象: 为模板提供数据, 也就是模板中可以直接使用此对象中的所有属性/方法
  • 返回对象中的属性会与data函数返回对象的属性合并成为组件对象的属性
  • 返回对象中的方法会与methods中的方法合并成功组件对象的方法
  • 如果有重名, setup优先
  • 一般不要混合使用: methods中可以访问setup提供的属性和方法, 但在setup方法中不能访问data和methods
  • setup不能是一个async函数: 因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性数据

3.1.3 setup的参数

  • setup(props, context) / setup(props, {attrs, slots, emit})
  • props: 包含props配置声明且传入了的所有属性的对象
  • attrs: 包含没有在props配置中声明的属性的对象, 相当于 this.$attrs
  • slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
  • emit: 用来分发自定义事件的函数, 相当于 this.$emit(自定义事件写在子组件上)

3.2 ref

  • 作用: 定义一个数据的响应式
  • 语法: const xxx = ref(initValue):
    创建一个包含响应式数据的引用(reference)对象
    js中操作数据: xxx.value
    模板中操作数据: 不需要.value
  • 一般用来定义一个基本类型的响应式数据

3.2.1 reactive与ref-细节

  • 是Vue3的 composition API中2个最重要的响应式API
  • ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)
  • 如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象
  • ref内部: 通过给value属性添加getter/setter来实现对数据的劫持
  • reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据
  • ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value)
<template>
  <h1>哈哈。。。我真帅。。。今年{{num}}...</h1>
  <h1>我明年{{count}}岁了</h1>
  <button @click="addCount">加一年</button><button @click="reCount">重置</button>
</template>
<script lang="ts">
import { defineComponent,ref } from 'vue';
export default defineComponent({
  name: 'App',//当前组件的名称
  setup(){
    const count = ref(18)
    function addCount(){
      count.value++
    }
    function reCount(){
      count.value = 18
    }
    return {
      count,
      addCount,
      reCount
    }
  }
});
</script>

3.3 reactive

  • 作用: 定义多个数据的响应式
  • const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
  • 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
<template>

  <h1>哈哈。。。我真帅。。。今年18...</h1>
  <h3>姓名:{{user.name}}</h3>
  <h3>年龄:{{user.age}}</h3>
  <h3>老婆:{{user.wife}}</h3>
  <button @click="upUser">更新</button>
</template>

<script lang="ts">

import { defineComponent,ref,reactive } from 'vue';

export default defineComponent({
  name: 'App',//当前组件的名称
  
  setup(){
    const user = reactive({
      name:"BOB",
      age:18,
      wife:{name:"alice",age:20}
    })

    // const upUser = function(){
    //   user.age = 23
    // }

    const upUser = () =>{
      user.age = 23
    }

    return {
      user,
      upUser
    }
  }
 
});
//如果操作代理对象目标对象的内容也会变化,界面也会更新变化。user就是代理对象。
</script>

3.4 计算属性和监视

3.4.1 computed函数:

  • 与computed配置功能一致
  • 只有getter
  • 有getter和setter

3.4.2 watch函数

  • 与watch配置功能一致
  • 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
  • 默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次 通过配置deep为true,
    来指定深度监视

3.4.3 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 lang="ts">
/*
计算属性与监视
1. computed函数: 
  与computed配置功能一致
  只有getter
  有getter和setter
2. watch函数
  与watch配置功能一致
  监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
  默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
  通过配置deep为true, 来指定深度监视
3. watchEffect函数
  不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
  默认初始时就会执行第一次, 从而可以收集需要监视的数据
  监视数据发生变化时回调
*/

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

export default {

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

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

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

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

    const fullName3 = ref('')

    /* 
    watchEffect: 监视所有回调中使用的数据
    */
    
    // watchEffect(() => {
    //   console.log('watchEffect')
    //   fullName3.value = user.firstName + '-' + user.lastName
    // }) 
    

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

    /* 
    watch一个数据
      默认在数据发生改变时执行回调
    */
    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)
    })

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

3.5 生命周期

  • beforeCreate -> 使用 setup()
  • created -> 使用 setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured
    Vue3 Composition API 学习_第6张图片

3.6 自定义hook函数

  • 使用Vue3的组合API封装的可复用的功能函数
  • 自定义hook的作用类似于vue2中的mixin技术
  • 自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂
import {ref,onMounted,onBeforeUnmount} from 'vue';
export default function(){
    const x = ref(-1)
    const y = ref(-1)
    const clickhandel = (event:MouseEvent) =>{
    x.value = event.pageX
    y.value = event.pageY
    }
    //组件加载完成添加监听点击事件
    onMounted(()=>{
    window.addEventListener('click',clickhandel);
    })
    //组件卸载前取消监听事件
    onBeforeUnmount(()=>{
    window.removeEventListener('click',clickhandel)
    })
    return {
    x,
    y
    }
}
import { ref } from 'vue'
import axios from 'axios'

/* 
使用axios发送异步ajax请求
*/
export default function useUrlLoader<T>(url: string) {
  const result = ref<T | null>(null)
  const loading = ref(true)
  const errorMsg = ref(null)

  axios.get(url)
    .then(response => {
      loading.value = false
      result.value = response.data
    })
    .catch(e => {
      loading.value = false
      errorMsg.value = e.message || '未知错误'
    })

  return {
    loading,
    result,
    errorMsg,
  }
}
//调用
<template>
  <h2>App</h2>
  <div>x:{{x}},y:{{y}}</div>
</template>

<script lang="ts">
  import { defineComponent} from 'vue';
  import useClickPos from './hooks/useClickPos'

  export default defineComponent({
    name: 'App',
    setup(){
      const {x,y} = useClickPos()
      return {
        x,
        y
      }
   }
  })
</script>

3.7 toRefs

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

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

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

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

  return toRefs(state)
}

const {foo2, bar2} = useReatureX()

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

3.8 ref获取元素

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

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

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

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

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

    return {
      inputRef
    }
  },
}
</script>

四、Composition API(其它部分)

4.1 shallowReactive 与 shallowRef

  • shallowReactive : 只处理了对象内最外层属性的响应式(也就是浅响应式)

  • shallowRef: 只处理了value的响应式, 不进行对象的reactive处理

  • 什么时候用浅响应式呢?

    一般情况下使用ref和reactive即可
    如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
    如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef

4.2 readonly 与 shallowReadonly

  • readonly:

    深度只读数据
    获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
    只读代理是深层的:访问的任何嵌套 property 也是只读的。

  • shallowReadonly

    浅只读数据
    创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换

  • 应用场景:

    在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除

4.3 toRaw 与 markRaw

  • toRaw

    返回由 reactive 或 readonly 方法转换成响应式代理的普通对象。
    这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。

  • markRaw

    标记一个对象,使其永远不会转换为代理。返回对象本身

  • 应用场景:

    有些值不应被设置为响应式的,例如复杂的第三方类实例或 Vue 组件对象。
    当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。

4.4 toRef

  • 为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的

  • 区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响

  • 应用: 当要将 某个prop 的 ref 传递给复合函数时,toRef 很有用

4.5 customRef

  • 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
  • 需求: 使用 customRef 实现 debounce 的示例
/* 
实现函数防抖的自定义ref
*/
function useDebouncedRef<T>(value: T, delay = 200) {
  let timeout: number
  return customRef((track, trigger) => {
    return {
      get() {
        // 告诉Vue追踪数据
        track()
        return value
      },
      set(newValue: T) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          // 告诉Vue去触发界面更新
          trigger()
        }, delay)
      }
    }
  })

4.6 provide 与 inject

  • provide和inject提响应式数据的判断
 //提供数据
 provide('color', color)
 //注册数据
 const color = inject('color')

4.7 响应式数据的判断

isRef: 检查一个值是否为一个 ref 对象
isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

五、新组件

5.1 Fragment(片断)

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

5.2 Teleport(瞬移)

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

5.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>
<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>

你可能感兴趣的:(vue3,vue.js)