vue3的组合式API及一些新变化

创建vue 项目
vue-cli创建 vue create vue3_test
vite创建

常用的组合式(composition )API

1. setup

理解:是vue3.0中的一个配置项,值为一个函数;
组件中用到的数据、方法都要配置到setup中;
有两种返回值:1. 若返回一个对象,则对象的方法、属性都可在模板中直接使用;2.若返回一个渲染函数,则可以自定义渲染内容;
注意:1. 尽量不要混用vue2和vue3;setup不能访问vue.x的配置(data/methods/computed…);如果有重名,setup优先;2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

//子组件
<template>
  <div class="hello">
    <h4>子组件信息</h4>
    <h4>姓名:{{person.name}}</h4>
    <h4>年龄:{{person.age}}</h4>
	<!-- 具名插槽 -->
    <slot name="asdf"></slot>
    <slot name="qwer"></slot>

    <button @click="testChild">测试子组件的事件</button>
  </div>
</template>
<script>
import { reactive } from 'vue'
export default {
  name: 'HelloWorld',
  props: ['msg', 'school'],
  setup(props, context){
    // console.log(props,'-----------')
    //console.log(context,'-----------')
    // console.log(context.emit,'-----------')
    // console.log(context.slots,'-----------')

    let person = reactive({
      name: '小张',
      age: 22,
    })

    function testChild(){
      context.emit('msgClick', 1111111111)
    }

    return {
      person,
      testChild
    }
  }
}
</script>


//父组件
<template>
  <div>
    <HelloWorld @msgClick="showMsg" msg="你好啊" school="斯坦福">
      <!-- 插槽 -->
      <template v-slot:asdf>
        <div>插槽1占位</div>
      </template>
      <template v-slot:qwer>
        <div>插槽2占位</div>
      </template>
    </HelloWorld>
  </div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import { reactive } from 'vue'
export default {
  name: 'App',
  setup(){
	function showMsg(value){
      alert(`触发了消息事件,参数是${value}`)
    }
    
    return {
      showMsg
    }
  },
  components: {
    HelloWorld
  }
</script>

setup的执行时机:在beforeCreat之前执行一次,this是undefined;
参数:
props:值为对象,包含 组件外部传递过来 且 组件内部声明接受了的属性;
context:上下文对象。

  1. attrs:值为对象,包含 组件外部传递过来,但没有在props配置中的属性,相当于this.$attrs;
  2. slot:收到的插槽内容,相当于htis.$slots;
  3. emit:分发自定义事件的函数,相当于this.$emit
2. ref 与 reactive函数

ref
用于定义一个响应式数据。const xxx = ref(initValue)
操作数据时:xxx.value;模板中读取直接 <div>{{xxx}}</div>
基本类型的数据 依然是靠Object.defineProperty()的get和set完成的;
对象类型的数据 是借助vue3.0中的一个reactive函数

reactive
作用:定义一个对象类型的响应式数据。
const 代理对象 = reactive(源对象) 接收一个对象或数组,返回一个代理器对象(proxy对象);
处理对象时 是深层次的;内部基于es6 proxy实现,通过代理对象操作源对象内部数据进行操作;

<template>
  <div>
    <h4 v-show="person.name">姓名:{{person.name}}</h4>
    <h4>年龄:{{person.age}}</h4>
    <h4 v-show="person.sex">性别:{{person.sex}}</h4>
    <h4>工作:{{person.job.type}}--{{person.job.salary}}</h4>
    <h4>爱好:{{person.hobby}}</h4>
    <button @click="changeInfo">更改信息</button>
    <button @click="addSex">添加性别</button>
    <button @click="deleteName">删除姓名</button>
  </div>
</template>
<script>
  setup(){
    let person = reactive({
      name: '小张',
      age: 22,
      job: {
        type: "UI设计",
        salary: "15K"
      },
      hobby: ['吃饭','打游戏','逛街']
    })

    function changeInfo(){
      // refImpl reference引用 
      person.name = '小李'
      person.age = 18

      person.job.type = "java开发"
      person.job.salary = "25K"

      person.hobby[1] = '睡觉'
    }

    function addSex(){
      person.sex = '男'
    }

    function deleteName(){
      delete person.name
    }
    
    return {
      person,
      addSex,
      deleteName,
      changeInfo
    }
  },
</script>

ref 和 reactive的区别

  1. 定义数据:ref用于定义基本数据类型;reactive用于定义对象(或数组)类型数据;(注:ref也可用于定义对象或数组类型,它内部会通过reactive转化为代理对象;
  2. 原理角度:ref通过Object.defineProperty() 的get和set实现响应式(数据劫持);reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据;
  3. 使用角度:ref操作数据需要.value,读取数据模板中不需要.value;reactive操作数据与读取数据都不需要.value。
3. 计算属性与监视

computed函数

//父组件
<template>
  <div class="parentBox">
	<Demo></Demo>
  </div>
</template>

//子组件
<template>
  <div class="childBox">
    <h4>个人信息</h4>
    <h4>姓:<input type="text" v-model="person.firstname"></h4>
    <h4>名:<input type="text" v-model="person.lastname"></h4>
    <h4>姓名是:<input type="text" v-model="person.fullname"></h4>
  </div>
</template>
<script>
import { reactive, computed } from 'vue'
export default {
  name: 'Demo',
  setup(){
    let person = reactive({
      firstname: '张',
      lastname: '三',
    })

    // 简写.(没有考虑计算属性被修改的情况)
    // person.fullname = computed(()=>{
    //   return person.firstname + '-' + person.lastname
    // })

    // 完整写法 (考虑修改计算属性的读和写)
    person.fullname = computed({
      get(){
        return person.firstname + '-' + person.lastname
      },
      set(value){
        const nameArr = value.split('-')
        // console.log(value, nameArr, 'aaaaaaa')
        person.firstname = nameArr[0]
        person.lastname = nameArr[1]
      }
    })

    return {
      person,
    }
  }
}
</script>

watch函数
既要指明监视的属性,也要指明监视的回调

<script>
import { reactive, ref, watch } from 'vue'
export default {
  name: 'WatchCom',
  setup(){
    let sum = ref(0)
    let msg = ref('你好啊')

    let person = reactive({
      name: '小张',
      age: 12,
      job:{
        j1:{
          salary: 15
        }
      }
    })


    // 情况一:监听ref所定义的普通类型
    // watch(sum, (oldV, newV)=>{
    //   console.log(oldV, newV)
    // })

    // 情况二、监听ref所定义的多个普通类型
    // watch([sum, msg], (oldV, newV)=>{
    //   console.log(oldV, newV)
    // })

    // 情况三:监听reactive 响应对象;
    // 此时 1.oldValue 失效,2.强制开启了深度监视(deep不会生效),返回一个proxy对象
    // watch(person, (oldV, newV)=>{
    //   console.log(oldV, newV)
    // },{deep:fasle})//deep配置不会生效

    // 情况四:监听响应式对象的某个属性, 第一个参数要传入一个函数表达式
    // watch(()=>person.name, (oldV, newV)=>{
    //   console.log(oldV, newV)
    // })

    // 情况五:监听响应式对象的多个属性
    // watch([()=>person.name,()=>person.age], (oldV, newV)=>{
    //   console.log(oldV, newV)
    // })

    // 情况六:监听reative对象里的嵌套对象,此时deep是生效的
    // watch(()=>person.job, (oldV, newV)=>{
    //   console.log(oldV, newV)
    // },{deep:true})//deep配置生效
    
    return {
      sum,
      msg,
      person
    }
  }
}
</script>

watchEffect函数
不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性;
watchEffect有点像computed:都是里面用到(所依赖的)数据发生改变时执行;
但computed注重计算出来的值(回调的返回值),所以必须要写返回值;
而watchEffect更注重的是过程(回调函数的函数体),因此不用写返回值;

//所指定的函数中用到的数据只要发生变化,则直接重新执行回调
watchEffect(()=>{
  const x1 = sum.value
  const x2 = person.job.j1.salary
  console.log('watchEffect回调执行了')
})
4. vue3生命周期函数
beforeCreate ====>  setup()
created ====> setup()
beforeMount ====> onBeforeMount()
mounted ====> onMounted()
beforeUpdate ====>  onBeforeUpdate()
updated ====> onUpdated()
beforeUnmount ====> onBeforeUnmount()
unmounted ====> onUnmounted()
5.自定义hook函数

hook是什么:本质是一个函数,把setup中使用的composition API进行封装;
类似于vue2中的mixin;
优势:复用代码,让setup中的逻辑更清晰易懂;

//src下新建文件夹 hooks/usePoint.js
import { onBeforeUnmount, onMounted, reactive } from 'vue'
export default function (){
  let point = reactive({
    x: 0,
    y: 0
  })

  function savePoint(e){
    console.log(e)
    point.x = e.pageX
    point.y = e.pageY
  }

  onMounted(()=>{
    window.addEventListener('click', savePoint)
  })

  onBeforeUnmount(()=>{
    window.removeEventListener('click', savePoint)
  })

  return point
}

//模板中使用:
<script>
import usePoint from "../hooks/usePoint"
setup(){
  let point = usePoint()
  return {
    point
  }
}
</script>
6.toRef 函数与toRefs函数

toRef:
作用:创建一个ref对象,其value值指向另一个对象中的某一个属性;
语法:const name = toRef(person, 'name')
应用:要将响应式对象中的属性单独提供给外部使用时;
扩展:toRefs 与 toRef 功能一致,但可以创建多个ref对象,语法:toRefs(person)

let person = reactive({
  name: '小张',
  age: 12,
  job:{
    j1:{
      salary: 15
    }
  }
})

// let name = toRef(person,'name')
let age = toRef(person,'age')
let salary = toRef(person.job.j1,'salary')

age.value++
console.log(person.age,'111111')//13

person.job.j1.salary++
console.log(salary.value,'3333')//16

toRefs:
作用:创建多个ref对象,将响应式对象(person)转化为一个普通对象(x),
普通对象(x)里的每个property指向原始对象(person)对应的property的ref

const x = toRefs(person)
console.log(x,'222222')
console.log(x.name.value, person.name)//小张 小张
x.age.value++
console.log(person.age)//14
7.shallowReactive 与 shallowRef

shallowReactive:只处理对象外层属性的响应式(浅响应式)。
shallowRef:只处理基本数据的响应式,不进行对象的响应式处理。
使用场景:
如果一个有对象数据,结构比较深,但变化时只是外层属性变化 => shallowReactive;
如果一个有对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换 => shallowRef;

let person = shallowReactive({  //只考虑第一层数据响应式,对象里层不触发响应式
  name: '小张',
  age: 12,
  job:{
    j1:{
      salary: 15
    }
  }
})

let x = shallowRef({ //数据是对象时,得到的是普通的值,不触发响应式;数据是基本类型时,作用等同于ref
  y:0
})
8.readonly与 shallowReadonly

readonly: 让一个响应式数据变成只读(深只读);
shallowReadonly:让一个响应式数据变为只读的(浅只读)。
使用场景: 不希望数据被修改时。比如:使用别的封装组件中的数据

let p = readonly(person) //深只读
let p = shallowReadonly(person) //浅只读,深嵌套的读不到
let sum = readonly(sum)
9.toRow与 markRaw

toRow:将reactive生成的响应式对象转化为普通对象;
使用场景:用于读取响应式对象(person)对应的普通对象§,对这个普通对象的所以操作 不会引起页面的更新;
markRaw:
作用:标记一个对象,使其永远不会再成为响应式对象。
使用场景:1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

10.customRef

创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。通俗就是对其变化能进行追踪并写相应逻辑。

<template>
  <div class="hello">
    <input type="text" v-model="keyWord">
    <h4>{{keyWord}}</h4>
  </div>
</template>

<script>
import { customRef, ref } from 'vue'
export default {
  name: 'Demo',
  setup(){
    //实现防抖效果
    function myRef(value, delay){
      console.log('value',value)
      let timer;
      return customRef((track, trigger)=>{
        return {
          get(){
            // 步骤三:通知Vue追踪value的变化
            track()
            // console.log(`从myref读取----${value}`)
            return value
          },
          set(newVal){
            // console.log(`修改值为:${value}`)
            clearTimeout(timer)
            timer = setTimeout(()=>{
              // 步骤一:重新赋值 
              value = newVal
              // 步骤二:通知Vue去重新解析模板
              trigger()
            }, delay)
          }
        }
      })
    }

    let keyWord = myRef('hello', 500)//使用自定义ref
    // let keyWord = ref('hello')//使用vue提供的ref
    
    return {keyWord}
  }
}
</script>

11. provide 与 inject

作用:实现祖孙之间通信

//祖组件中:
setup(){
  ............
  let car = reactive({ name:'奔驰', price:'40w' })
  provide('car,', car)
  .............
}

//后代组件接收
setup(props, context){
  //............
  const car = inject('car')
  return { car }
  //.............
}
12. 响应式数据的判断

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

13. composition API的优势

Options API存在的问题:
使用传统的API,新增或修改一个需求,需要分别在data,methods,computed里修改。通常一个工程文件中数据量较多,则使得阅读和维护不方便。

优势:
更有序的组织代码、函数,让相关功能更有序的组织到一起;
compositionAPI
compositionAPI

新组件
Fragment

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

<template>
  <h4>个人信息</h4>
  <h4>姓:<input type="text" v-model="person.firstname"></h4>
  <h4>名:<input type="text" v-model="person.lastname"></h4>
  <h4>姓名是:<input type="text" v-model="person.fullname"></h4>
</template>

fragment

Teleport

传送组件。可以将组件html结构移动到目指定的位置;
举例:dialog 可放在body里,有自己的样式,不影响其他的组件

<template>
  <div class="">
    <button @click="isShow=true">点我弹窗</button>
    <teleport to="body">
      <div class="mask" v-if="isShow">
        <div class="dialogBox">
          <h3>我是一个弹窗</h3>
          <h4>一些内容</h4>
          <h4>一些内容</h4>
          <h4>一些内容</h4>
          <button @click="isShow=false">关闭弹窗</button>
        </div>
      </div>
    </teleport>
  </div>
</template>

teleport

Suspence

作用:等待异步组件时渲染一些额外内容,让应用有更好的用户体验
使用步骤:
1.引入异步组件

import { defineAsyncComponent } from 'vue'
// import ChildCom from "./components/ChildCom";//静态引入
const child = defineAsyncComponent(()=> import('./components/ChildCom.vue'))//动态引入

2.使用Suspense包裹组件,并配置好default 与 fallback

<template>
  <div class="app">
    <h3>我是App组件</h3>
    <Suspense>
      <template v-slot:default>
        <child></child>
      </template>
      <template v-slot:fallback>
        <h4>加载中。。。。</h4>
      </template>
    </Suspense>
  </div>
</template>
其他改变:
1. 全局API的转移

Vue 2.x 有许多全局API和配置。例如:注册全局组件、注册全局指令

// 注册全局组件
Vue.component('MyButton',{
  data:()=>({
    count:0
  }),
  template:'<button @click="count++">点我+1</button></button>'
})

//注册全局指令
Vue.directive('focus',{
  inserted: el => el.focus()
})

Vue3.0对这些API做出了调整:
将全局API,即Vue.xxx调整到应用实例(app)上

2.x 全局 API(Vue)3.x 实例 API (app)
Vue.config.xxxxapp.config.xxxx
Vue.config.productionTip移除
Vue.componentapp.component
Vue.directiveapp.directive
Vue.mixinapp.mixin
Vue.useapp.use
Vue.prototypeapp.config.globalProperties
2. 其他改变
  1. data选项应始终被声明为一个函数

  2. 过度类名的更改:
    Vue2.x写法

.v-enter,
.v-leave-to {
  opacity: 0;
}
.v-leave,
.v-enter-to {
  opacity: 1;
}

vue3.0的写法

.v-enter-from,
.v-leave-to {
  opacity: 0;
}
​
.v-leave-from,
.v-enter-to {
  opacity: 1;
}
  1. 移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes
  2. 移除v-on.native修饰符
    父组件中绑定事件
<my-component
  v-on:close="handleComponentEvent"
  v-on:click="handleNativeClickEvent"
/>

子组件中声明自定义事件

<script>
  export default {
    emits: ['close']
  }
</script>
  1. 移除过滤器(filter)
    过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。

附加:

vue2响应式实现原理:

对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持);
数组类型:通过重写更新数组的一系列来实现拦截。(对数组的变更方法进行了包裹)

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

存在问题:
1.新增属性、删除属性 界面不会更新;
2.直接通过下标修改数组,界面不会自动更新;

对应解决方法:新增属性需要 this.$set(this.person, 'sex', '男');或 Vue.$set(this.person, 'sex', '男')
删除属性需要 this.$delete(this.person, 'name', '小张');或 Vue.$delete(this.person, 'name', '小李')
修改数组下标:this.$set(this.person.hobby, 0, '睡觉');或 this.person.hobby.splice(0,1,'睡觉')

vue3.0响应式

实现原理:
通过 proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除;
通过 reflect(反射):对源对象属性进行操作。

// 模拟vue2实现响应式
let person = {
  name:'小张',
  age:13
}

let x = {}
Object.defineProperty(x, 'name',{
  configurable: true,
  get(){ // 有人读取name时调用
    return person.name
  },
  set(value){
    console.log('有人修改了name属性')
    return person.name = value
  },
})
Object.defineProperty(x, 'age',{
  get(){ // 有人读取age时调用
    return person.age
  },
  set(value){
    console.log('有人修改了age属性')
    return person.age = value
  },
})


// 模拟vue3实现响应式
//源数据
let people = {
  name:'小张',
  age:13
}
// 代理数据
const y = new Proxy(people, {
  // 有人读取p的某个属性时调用
  get(target, propName){
    console.log(`有人读取了p的${propName}属性`)
    // return target[propName]
    return Reflect.get(target, propName)
  },
  // 有人修改 或 追加 p的某个属性时调用
  set(target, propName, value){
    // console.log(target, propName, value) //{name: '小张', age: 13} 'name' '小李'
    console.log(`有人修改了p的${propName}属性`)
    // return target[propName] = value
    return Reflect.get(target, propName, value)
  },
  // 删除p的某个属性时调用
  deleteProperty(target, propName){
    // console.log(target, propName)
    console.log(`有人删除了p的${propName}属性`)
    // return delete target[propName]
    return Reflect.deleteProperty(target, propName)
  }
})

reflect 的使用

let obj = {
  a:1, b:2
}
// 读取
// 传统方式: 
obj.a //1
// reflect方式:
Reflect.get(obj, 'a')  // 1
// 修改
obj.a = 100
Reflect.set(obj, 'a', 100) //true
// 删除
delete obj.a
Reflect.deleteProperty(obj, 'a') //true
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值