vue3

153 阅读9分钟

常用api

setup

执行时机:在组件被创建之前,props 被解析之后执行 参数, 返回值:对象或渲染函数,如果返回了渲染函数,则不能再返回其他 property,

ref

reactive

Ref 实现响应式(基本数据-Object.defineProperty,引用数据-proxy) reactive 定义一个对象类型的响应式数据 (proxy) 注意:reactive定义的对象如果用…解构后就不是响应式的了

vue2响应式原理存在的问题 1增加和删除对象属性无法实现响应式 2通过索引修改数组的某一项无法实现响应式

vue3 响应式原理(Proxy 与 Reflect)

computed(简单写法和完整写法)

// vue3写法
    setup(){
        let firstName=ref('')
        let lastName=ref('')
    // 简写方式 computed函数接收一个回调函数,回调函数中返回一个值
    //    const fullName = computed(()=>{
    //        return firstName.value+'-'+lastName.value
           
    //    })
    // 完整写法 :computed函数接收一个对象,对象里面有get和set方法
       const fullName = computed({
        get:()=>{ 
            return firstName.value+'-'+lastName.value
        }, 
        set:val=>{ 
            const fullNameArr=val.split('-');
            // firstName.value要加上value,ref包裹的对象在setup中使用需要.value,在模板中不需要
            firstName.value=fullNameArr[0];
            lastName.value=fullNameArr[1];
         } }
    
    )

watch/watchEffect

  1. watch监视ref定义的基本数据
  2. watch监视reactive定义的对象,嵌套对象
  3. watchEffect 当函数中用到的数据发生变化时执行
import {reactive, ref,watch,watchEffect} from '@vue/composition-api'
export default {
    setup(){
        const sum = ref(0);
        const state=reactive({
            count:0
        })
        const person=reactive({
            name:'bwf',
            age:18,
            job:{
                j1:{
                    salary:20
                }
            }
        })
        // 1.直接侦听一个 ref
        watch(sum,(newVal,oldVal)=>{
            console.log('sum发生了改变',newVal,oldVal)
        },{immediate:true})

        // 2侦听一个 getter为一个函数,不能直接侦听state.count
          watch(()=>state.count,(newVal,oldVal)=>{
            console.log('state中的count值发生了改变',newVal,oldVal)
        })
        // 3watch监视整个reactive对象,默认开启了深度监视,无法获取oldVal值
        watch(person,(newVal,oldVal)=>{
            console.log('person发生了改变',newVal,oldVal)

        },{deep:false})

        // 4watch监视reactive对象中的某个属性,若该属性又为对象,则可以开启深度监视
        watch(()=>person.job,(newVal,oldVal)=>{
            console.log('person中的salary值发生了改变',newVal,oldVal)
        },{deep:true})

        // 5侦听多个源
        watch([()=>person.name,()=>person.age],([newVal1,newVal2],[oldVal1,oldVal2])=>{
            // fix 同时侦听多个,怎么知道是哪个值改变了
            console.log('person中的name值发生了改变',[newVal1,newVal2],[oldVal1,oldVal2])
        })
        // watch(()=>person.age,(newVal,oldVal)=>{
        //     console.log('person中的age值发生了改变',newVal,oldVal)
        // })
        // watch(()=>person.job.j1.salary,(newVal,oldVal)=>{
        //     console.log('person中的salary值发生了改变',newVal,oldVal)
        // })

        watchEffect(()=>{
            console.log('watchEffect',sum.value)
        })

       
        return {
            sum,
            state,
            person
        }
    },
}

Vue3生命周期函数

TIP: 因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

 // #region 生命周期钩子 注意:这些生命周期钩子要写在return前面,否则会出现组件存在但页面不展示的问题
        // onBeforeMount(()=>{
        //     console.log('onBeforeMount')

        // }),
        // onMounted(()=>{
        //     console.log('onMounted')
        //     // 组件挂载了全局注册事件
        //     window.addEventListener('click',getPoint)
        // }),
        // onBeforeUpdate(()=>{
        //     console.log('onBeforeUpdate')
        // }),
        // onUpdated(()=>{
        //     console.log('onUpdated')
        // }),
        // onBeforeUnmount(()=>{
        //     console.log('onBeforeUnmount')
        //     // fix 这里没有生效
        //     window.removeEventListener('click',getPoint)

        // }),
        // onUnmounted(()=>{
        //     console.log('onUnmounted')
        // })
        //#endregion

vue3 hooks(组合式函数)(todo 重新补充总结一下)

cn.vuejs.org/guide/reusa…

把一个组件需要的数据,方法,生命周期等提取在一个hooks中,需要该功能的地方直接引入就可以了,这也体现了组合式api的优势。

import { reactive, onMounted, onBeforeUnmount } from '@vue/composition-api'

// 按照惯例,组合式函数名以“use”开头
const usePoint = () => {
    // 被组合式函数封装和管理的状态
    const x = ref(0) 
    const y = ref(0)
    
    // 组合式函数可以随时更改其状态。
    const update = (event) => {
        console.log('获取鼠标点击位置了', event.clientX, event.clientY)
        point.x = event.clientX
        point.y = event.clientY
    }
    
    // 一个组合式函数也可以挂靠在所属组件的生命周期上
    // 来启动和卸载副作用
    onMounted(() => {
        console.log('onMounted')
        window.addEventListener('mousemove', update)
    })
    onUnmounted(() => {
        console.log('onUnmounted')
        window.removeEventListener('mousemove', update)

    })
    return { x ,y }
}
export default usePoint;

下面是它在组件中的用法

<script setup> 
    import { useMouse } from './mouse.js'
    const { x, y } = useMouse()
</script> 

<template>
    Mouse position is at: {{ x }}, {{ y }}
</template>

TIP

每一个调用 useMouse() 的组件实例会创建其独有的 xy 状态拷贝,因此他们不会互相影响。如果你想要在组件之间共享状态,请阅读状态管理这一章。

什么是状态管理?

  1. 多个视图可能都依赖于同一份状态。 -> 将数据提升到共同的父组件中去- Props逐级透传问题
  2. 来自不同视图的交互也可能需要更改同一份状态。-> ref或emit触发 -> 健壮性不理想,代码难以维护

怎么进行状态管理?

  1. 用响应式 API 做简单状态管理(比如我们用的service进行统一状态管理,有state和各种方法)
// store.js
import { reactive } from 'vue' 
export const store = reactive(
      { 
          count: 0,
          increment() {
              this.count++ 
          } 
      }
 )
<template> 
  <button @click="store.increment()"> From B: {{ store.count }} </button> 
</template>

2.状态管理库(学无止境哪!)

Vuex Vue 之前的官方状态管理库

Pinia 由于 Pinia 在生态系统中能够承担相同的职责且能做得更好,因此 Vuex 现在处于维护模式。它仍然可以工作,但不再接受新的功能。对于新的应用,建议使用 Pinia。

Provide / Inject

1使用场景:

对于深层嵌套的组件,我们可以使用一对 provide 和 inject进行数据的传递

2优缺点:无法实现数据的追踪,在开发ui组件时可以使用

父组件不需要知道哪些子组件使用它 provide 的 property 子组件不需要知道 inject 的 property 来自哪里

3使用方法

对象方式:传递普通数据

  父组件传
  provide: {
    user: 'John Doe'
  },

  子组件接

  inject: ['user'],

局限性:无法访问组件实例数据,会报错

返回对象的函数:需要访问组件实例的数据

父组件
 provide() {
    return {
      todoLength: this.todos.length
    }
  },

子组件
 inject: ['todoLength'],

局限性:数据不是响应式的

如果我们想对祖先组件中的更改做出响应,我们需要为 provide 的 todoLength 分配一个组合式 API computed

import { computed} from '@vue/composition-api'

provide() {
    return {
      todoLength: computed(() => this.todoList.length)
    }
}

inject: ['todoLength'],

<div>{{todoLength.value}}</div>

其他api

toRef/toRefs

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

shallowRef/shallowReactive

shallowRef 如果传的是一个对象,则对象里面的属性变化不是响应式的 shallowReactive 对象的第一层是响应式的

readOnly/shallowReadOnly

readOnly(把响应式数据变成只读的,深只读) shallowReadOnly (只考虑对象的第一层不能改,深层次的可以改,浅只读)

toRaw/markRaw

customRef

下面例子实现了js的防抖功能

    const myref=(value,delay)=>{
        let timer;
        return customRef((track,trigger)=>{
          return {
            get(){
              track() //通知getter函数需要追踪值得变化
              console.log(`有人读取了我得值,${value}`)
              return value
            },
            set(newValue){
              clearInterval(timer)
              console.log(`有人修改了我得值,新值是${newValue}`)
              timer = setTimeout(()=>{
                  value=newValue
                  trigger() //修改后通知vue重新解析模板
              },delay)
            }
          }
        })
      }
      let keyword=myref('hello',1000)

响应式工具

isRef 检查某个值是否为 ref

unref() 如果参数是 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 计算的一个语法糖。

toRef() 基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。

toRefs()

isProxy

isReactive

isReadonly

Composition api 与传统Options api的比较

Composition api 要体现自己的优势需要借助hook ,把一个功能的数据,方法,生命周期封装在一个hook中

Fragment

减少标签层级,减小内存占用

Teleport

可以将一个组件移动到指定位置,弹窗组件实现.可以传送给body或其他的css选择器

  <teleport to='body'>       
          <div class="mask" v-if="show">
              <div class="dialog">
                  <h3>我是弹窗的标题</h3>
                  <p>我是弹窗的内容</p>
                  <button @click="show=false">关闭</button>
              </div> 
          </div>
      </teleport>

Suspense

可以定义异步组件没有加载时展示的效果,是vue3里面的标签

同步加载组件

import Lifecycle from './Lifecycle'

异步加载组件

import {defineAsyncComponent } from '@vue/composition-api'
const Lifecycle = defineAsyncComponent(() =>import('./Lifecycle'))

  <Suspense>
        <template v-slot:default>
          <Lifecycle></Lifecycle>
        </template>
        <template v-slot:fallback>
          <div>loading....</div>
        </template>
    </Suspense>

一些注意点总结

1.请注意如果你解构了 props 对象,解构出的变量将会丢失响应性。因此我们推荐通过 props.xxx 的形式来使用其中的 props。

import { toRefs, toRef } from 'vue'

export default {
  setup(props) {
    // 将 `props` 转为一个其中全是 ref 的对象,然后解构
    const { title } = toRefs(props)
    // `title` 是一个追踪着 `props.title` 的 ref
    console.log(title.value)

    // 或者,将 `props` 的单个属性转为一个 ref
    const title = toRef(props, 'title')
  }
}

staging-cn.vuejs.org/api/composi…

2.注意如果想判断响应式对象state中某个值得变化,不能像下面这样

  const state = reactive({
      loading:false,
      items:[]
    })
    
    //这样取值就不是响应式了,所以watch不会走
  const loading = state.loading
    
    //wrong
  watch(()=>loading,(nextValue,oldValue)=>{
      console.log('nextValue',nextValue);
      console.log('oldValue',oldValue);  
  })
  //right
    watch(()=>state.loading,(nextValue,oldValue)=>{
      console.log('nextValue',nextValue);
      console.log('oldValue',oldValue);  
  })

staging-cn.vuejs.org/api/reactiv…

const state = reactive({
  foo: 1,
  bar: 2
})

const fooRef = toRef(state, 'foo')

// 更改该 ref 会更新源属性
fooRef.value++
console.log(state.foo) // 2

// 更改源属性也会更新该 ref
state.foo++
console.log(fooRef.value) // 3

3.如果一个对象不想让vue处理为响应式对象,可以用Object.freeze()方法

const obj = {
    name:'bwf',
    age:18,
    job:{
      name:'web',
      salary:'15k'
    }
}
//Object.freeze()后,state就不再是一个proxy对象
Object.freeze(obj);
const state = reactive(obj)
console.log('obj',obj);
console.log('state',state)

4.DOM更新是异步的。

其他

异步组件

官网

1.什么是异步组件以及异步组件的用法

普通组件的加载是只要在页面中定义了该组件(在template模板中引用了,而不仅仅只是import了)就会加载进来。单纯的import不会。而异步组件是该组件真正被渲染到页面时才会加载,将组件的定义延迟加载。下面是一个很简单的示例 普通组件的用法

<template>
    <button @click="show=!show">点击展示异步组件</button>
     <div v-if="show">
          <AsyncComponet/>
     </div>
</template>

<script setup lang="ts">
 import AsyncComponet from '@/components/AsyncComponet.vue'
 const show = ref(false)
</script>

从下面network我们可以看到,初始化show为false,但是组件AsyncComponet也加载进来了

image.png

那如果我们改成异步组件的写法呢

我们可以看到异步组件的方式初始化进来没有加载AsyncComponet组件 image.png 点击show时才会加载

image.png

由此可以联想到像我们在业务中一般使用到的弹窗组件,抽屉组件都可以改成异步组件的形式,可以优化性能。

2.异步组件和路由懒加载的比较

共同点:都是为了减少初始加载时间并提高应用程序的性能 区别:1.异步组件可以在任何地方使用,不仅仅局限于路由。

2路由懒加载是指将路由对应的组件代码按需加载,只有在用户访问该路由时才会加载。在 Vue 3 中,可以使用动态导入(dynamic import)语法来实现路由懒加载。路由懒加载只能用于路由,而不能在其他地方使用。