Vue3 的Composition API

一.Composition API(常用部分)

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

2.比较Vue2与Vue3的响应式

  1. vue2的响应式

核心:

  • 对象: 通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截)
  • 数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
Object.defineProperty(data, 'count', {
    get () {}, 
    set () {}
})
问题:
    1.对象直接新添加的属性或删除已有属性, 界面不会自动更新
    2.直接通过下标替换元素或更新length, 界面不会自动更新 arr[1] = {}
  1. Vue3的响应式

核心:

  • 通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等…
  • 通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作
<script>
        // 目标对象
        const user = {
            name:'小明',
            age:20,
            wife:{
                name:'大明',
                age:10
            }
        };
        // 把目标对象变成代理对象
        // 参数1 user ---> target目标对象
        // 参数2 handler ---> 处理器对象,用来监视数据及数据的操作
        /*
            核心:
                通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等...
                通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作
        */ 
        const proxyUser = new Proxy(user,{
            // 获取(读取)拦截的数据  
            get(target,prop){
                console.log('我是get方法')
                return Reflect.get(target,prop);
            },
            // 设置拦截的数据(修改目标对象的数据)   val 当前参数的值
            set(target,prop,val){
                console.log('我是set方法')
                return Reflect.set(target,prop,val)
            },
            // 删除拦截的数据
            deleteProperty(target,prop){
                return Reflect.deleteProperty(target,prop)
            }
        });
        // 读取属性值
    	console.log(proxyUser===user)
        // 修改目标对象
        proxyUser.name = '张三'
        console.log(user)
        // 添加属性
	    proxyUser.sex = '男'
	    console.log(user)
        // 删除目标对象
        delete proxyUser.name
    </script>

3.setup细节

  1. setup执行的时机
  • 在beforeCreate之前执行(一次), 此时组件对象还没有创建
  • this是undefined, 不能通过this来访问data/computed/methods / props
  1. setup的返回值
  • 一般都返回一个对象: 为模板提供数据, 也就是模板中可以直接使用此对象中的所有属性/方法
  • 返回对象中的属性会与data函数返回对象的属性合并成为组件对象的属性
  • 返回对象中的方法会与methods中的方法合并成功组件对象的方法
  • 如果有重名, setup优先
  • 注意点:
    1. 一般不要混合使用: methods中可以访问setup提供的属性和方法, 但在setup方法中不能访问data和methods
    2. setup不能是一个async函数: 因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性数据
  1. setup的参数
  • setup(props, context) / setup(props, {attrs, slots, emit})
  • props: 包含props配置声明且传入了的所有属性的对象
  • attrs: 包含没有在props配置中声明的属性的对象, 相当于 this.$attrs
  • slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
  • emit: 用来分发自定义事件的函数, 相当于 this.$emit
<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'], // 可选的, 声明了更利于程序员阅读, 且可以对分发的事件数据进行校验
  data () {
    console.log('data', this)
    return {
      // n: 1
    }
  },
  beforeCreate () {
    console.log('beforeCreate', this)
  },
  methods: {
    // update () {
    //   this.n++
    //   this.m++
    // }
  },
  // setup (props, context) {
  setup (props, {attrs, emit, slots}) {
    console.log('setup', this)
    console.log(props.msg, attrs.msg2, slots, emit)
    const m = ref(2)
    const n = ref(3)
    function update () {
      // console.log('--', this)
      // this.n += 2 
      // this.m += 2
      m.value += 2
      n.value += 2
      // 分发自定义事件
      emit('fn', '++')
    }
    return {
      m,
      n,
      update,
    }
  },
})
</script>

4.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 lang="ts">
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>

5.计算属性(computed函数)

import { defineComponent,computed, reactive } from 'vue'
setup(props,context) {
        const firstName = reactive({
            name: '东方',
            age:'不败'
        })
        // 计算属性
        // 第一种方式
        // 有getter与setter的计算属性
        const fullName2 = computed({
            get(){
                return firstName.name + '_' +firstName.age
            },
            set(val:string) {
                // console.log(val)
                const obj = val.split('_')
                firstName.name = obj[0]
                firstName.age = obj[1]

                // console.log(obj)
            }
        })
        // 第二种方式   
        // 只有getter的计算属性
        // const fullName2 = computed(()=>{
        //     return '哈哈哈哈哈'
        // })
        return { 
            firstName,
            fullName2
        }
}

6.事件监听

import { defineComponent,ref,watch, watchEffect } from 'vue'

export default defineComponent({
    setup(props,context) {
        const firstName = reactive({
            name: '东方',
            age:'不败'
        })
        // 监听  监听数据变化   immediate: 进入页面就执行一次  deep 深度监听
        const fullName3 = ref('')
        // 第一种方式
        // firstName 代表监视的数据
        // watch(firstName,({name,age})=>{
        //     fullName3.value = name + age
        // },{immediate:true,deep:true})
        // 第二种方式  
        // 特点:不需要配置immediate,本身默认就会进行监听,(默认执行一次,只要页面一加载就会立即执行)
        watchEffect(()=>{
            fullName3.value = firstName.name + firstName.age
        })
        // 第三种 监听多个数据
        /* 
        watch多个数据: 
          使用数组来指定
          如果是ref对象, 直接指定  例如:fullName3
          如果是reactive对象中的属性,  必须通过函数来指定 例如:user
        */
        watch([() => firstName.name, () => firstName.age, fullName3], (values) => {
          console.log('监视多个数据', values)
        })
        return { 
            fullName3,
            firstName
        }
    },
})

7.生命周期

与 2.x 版本生命周期相对应的组合式 API
我们导入它们并在我们的代码中访问它们:

 导入在setup中定义的生命周期函数
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted,onErrorCaptured } from 'vue'

export default {
// setup相当于vue2中的beforeCreate和created,并且在两者之前执行
  setup() {
  	1.挂载前/onBeforeMount(() => {
      // ... 
    })
    onMounted(() => {
      // ... 
    })
    2.更新前/onBeforeUpdate(() => {
      // ... 
    })
    DOM更新后,不会保证所有的子组件也都一起被重绘
    onUpdated(() => {
      // ... 
    })
    3.销毁前(卸载组件实例之前,此时实例仍然是完全正常的)
    onBeforeUnmount(() => {
      // ... 
    })
     销毁后(卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。)
    onUnmounted(() => {
      // ... 
    })
    当捕获一个来自子孙组件的异常时激活钩子函数
    onErrorCaptured(() => {
      // ... 
    })
  }
}

8.自定义hook函数

  • 使用Vue3的组合API封装的可复用的功能函数
  • 自定义hook的作用类似于vue2中的mixin技术
  • 自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂
    示例代码:
    用户鼠标点击页面获取页面坐标
  1. App.vue:
<template>
    <h2>点击页面获取坐标</h2>
    <h2>x:{{x}},y{{y}}</h2>
</template>

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

export default defineComponent({
    name: 'App',
    setup() {
        const {x,y} = hooksComponent()
        return { 
            x,
            y
        }
    },
})
</script>
  1. hook.ts
import {ref,onMounted,onBeforeUnmount} from 'vue'
export default function(){
        const x = ref(-1)
        const y = ref(-1)
        // 事件处理函数
        const clickHandler = (e: MouseEvent)=>{
            console.log(e)
            x.value = e.pageX
            y.value = e.pageY
        }
        // 在页面加载完成后
        onMounted(()=>{
        	// 事件监听
            window.addEventListener('click',clickHandler)
        })
        // 在页面销毁前的API
        onBeforeUnmount(()=>{
            window.removeEventListener('click',clickHandler)
        })
        return { 
            x,
            y,
            clickHandler
        }
    
}

9.toRefs

toRefs()将响应式的对象变为普通对象 再解构,在模板中就可以直接使用属性,不用state.name

  1. 使用reactive 实现数据的响应式
<template>
  <div>
    <h2>toRef的使用</h2>
    <p> 姓名:{{state.name }} </p>
    <p>年龄:{{ state.age }} </p>
    <button @click="changeHander">改变内容</button>
  </div>
</template>
<script>
import {   reactive }  from "vue"
export default {
  name: "App",
  setup() {
   let  state=reactive({
     name:'李四',
     age:40
   })
   function changeHander(){
     state.name="李四222"
     state.age=80
   }
   return {state,changeHander}
  }
};
</script>
虽然上面可以使用数据的响应式。
但是有时属性值很多,
可能有达到几十个值。
这样在视图上一个个属性点(.) 会很麻烦
  1. 初次使用toRefs(没有进行响应式)
<template>
  <div>
    <h2>toRef的使用</h2>
    <p> 姓名:{{name }} </p>
    <p>年龄:{{ age }} </p>
    <button @click="changeHander">改变内容</button>
  </div>
</template>
<script>
import {   reactive, toRefs }  from "vue"
export default {
  name: "App",
  setup() {
    let  state=reactive({
        name:'李四',
        age:40
    })
    //  toRefs 可以把一个响应式对象转换为普通的对象。
    //  该普通对象的每一个值都是ref
    // age: ObjectRefImpl {_object: Proxy, _key: "age", __v_isRef: true}
    // name: ObjectRefImpl {_object: Proxy, _key: "name", __v_isRef: true}
    let state2 = toRefs(state);
    console.log( 'state2==>ref',state2 )
    function changeHander(){
        // 由于变成了ref,所以我们需要使用value
        state2.name.value="李四222"
        state2.age.value=80
    }
    //  ...state虽然我们通过解构的方式。不要在视图上进行点了。但是数据却没有响应
    return {...state2,changeHander}
  }
};
</script>
  1. 在toRefs的时候进行解构(数据响应)
1.第一种方式:
<template>
  <div>
    <h2>toRef的使用</h2>
    <p> 姓名:{{name }} </p>
    <p>年龄:{{ age }} </p>

    <button @click="changeHander">改变内容</button>
  </div>
</template>
<script>
import {   reactive, toRefs }  from "vue"
export default {
  name: "App",
  setup() {
    let  state=reactive({
        name:'李四',
        age:40
    })
    // 这里进行了一次解构
    let {name,age }=toRefs(state);
    function changeHander(){
        name.value="李四222"
        age.value=80
    }
    return {name,age,changeHander}
  }
};
</script>
2.第二种方式:
<p>姓名:{{name}}</p>
<p>
年龄:{{age}}</p>

setup() {
    const data = reactive({ // 建立一个响应式对象
          name: "小四",
          age: 18,
          ...
     })
}
return {
  //必须返回 模板中才能使用
  ...toRefs(data),    //look
  ...
};

10.ref获取元素

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

<template>
    <h2>hhh</h2>
    <h2 ref="haha">哈哈哈哈哈哈</h2>
</template>

<script lang="ts">
import { defineComponent,onMounted,ref } from 'vue'

export default defineComponent({
// vue2 获取元素 this.$refs.haha
    name:'App',
    setup() {
        // ref获取元素: 利用ref函数获取组件中的标签元素
        /**
         * 1.首先我们创建了一个 haha 的监听对象,
         * 2.然后将这个监听对象暴露出去,
         * 3.从而实现 setup 函数中和 节点haha 的绑定。
         */
         // vue3 获取元素
        const haha = ref<HTMLElement | null>(null)
        onMounted(()=>{
            console.log(haha)
        })
        return {
            haha
        }
    }
})
</script>

注意点: 但由于 setup 函数的执行时间要先于 html 标签的渲染,所以我们不能直接在 setup 函数中初始化 box 标签。可以在 onMounted 中

总结:今天学习了setup的一些细节,vue2响应式与vue3响应式的对比,还有计算属性和事件监听,vue3的生命周期把vue2中的销毁前后替换成了onBeforeUnmount和onUnmounted,vue3中的自定义hook函数相当于vue2中的mixin,都可以复用代码等,以上就是今天的学习总结.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值