草庐IT

vue3组合式API介绍

huangchengpei 2023-10-27 原文

为什么要使用Composition API?

根据官方的说法,vue3.0的变化包括性能上的改进、更小的 bundle 体积、对 TypeScript 更好的支持、用于处理大规模用例的全新 API,全新的api指的就是本文主要要说的组合式api。

在 vue3 版本之前,我们复用组件(或者提取和重用多个组件之间的逻辑),通常有以下几种方式:

  • Mixin:命名空间冲突 & 渲染上下文中暴露的 property 来源不清晰。例如在阅读一个运用了多个 mixin 的模板时,很难看出某个 property 是从哪一个 mixin 中注入的。
  • Renderless Component:无渲染组件需要额外的有状态的组件实例,从而使得性能有所损耗
  • Vuex:就会变得更加复杂,需要去定义 Mutations 也需要去定义 Actions

上述提到的几种方式,也是我们项目中正在使用的方式。对于提取和重用多个组件之间的逻辑似乎并不简单。我们甚至采用了 extend 来做到最大化利用已有组件逻辑,因此使得代码逻辑依赖严重,难以阅读和理解。
Vue3 中的 Composition API 便是解决这一问题;且完美支持类型推导,不再是依靠一个简单的 this 上下文来暴露 property(比如 methods 选项下的函数的 this 是指向组件实例的,而不是这个 methods 对象)。其是一组低侵入式的、函数式的 API,使得我们能够更灵活地「组合」组件的逻辑。

业务实践

组合式api的出现就能解决以上两个问题,此外,它也对TypeScript类型推导更加友好。
在具体使用上,对vue单文件来说,模板部分和样式部分基本和以前没有区别,组合式api主要影响的是逻辑部分。下面是一个经典的vue2的计数器案例.:

vue2 实现

//Counter.vue
export default {
  data: () => ({
    count: 0
  }),
  methods: {
    increment() {
      this.count++;
    }
  },
  computed: {
    double () {
      return this.count * 2;
    }
  }
}

vue3 composition api

当我们在组件间提取并复用逻辑时,组合式API 是十分灵活的。一个组合函数仅依赖它的参数和 Vue 全局导出的 API,而不是依赖其微妙的 this 上下文。你可以将组件内的任何一段逻辑导出为函数以复用它。

  • 基于响应式
  • 提供 vue 的生命周期钩子
  • 组件销毁时自动销毁依赖监听
  • 可复用的逻辑
// Counter.vue
import { ref, computed } from "vue";

export default {
  setup() {
    const count = ref(0);
    const double = computed(() => count * 2)
    function increment() {
      count.value++;
    }
    return {
      count,
      double,
      increment
    }
  }
}

代码提取

Composition API的第一个明显优点是提取逻辑很容易。使用Composition提取上面Counter.vue组件代码。

//useCounter.js 组合函数
import { ref, computed } from "vue";

export default function () {
  const count = ref(0);
  const double = computed(() => count * 2)
  function increment() {
    count.value++;
  }
  return {
    count,
    double,
    increment
  }
}

代码重用

要在组件中使用该函数,我们只需将模块导入组件文件并调用它(注意导入是一个函数)。这将返回我们定义的变量,随后我们可以从 setup 函数中返回它们。

// MyComponent.js
import useCounter from "./useCounter.js";

export default {
  setup() {
    const { count, double, increment } = useCounter();
    return {
      count,
      double,
      increment
    }
  }
} 

相比而言,组合式 API:

  • 暴露给模板的 property 来源十分清晰,因为它们都是被组合逻辑函数返回的值
  • 不存在命名空间冲突,可以通过解构任意命名
  • 不再需要仅为逻辑复用而创建新的组件实例


常用api介绍

setup

export default {
  setup(props, context) {
    console.log(context); // { attrs, slots, emit }
    //context.emit('emitFun', {emit: true})
    return { privateMsg: props.msg };
  }
}

setup函数是组件内使用 component API 的入口。是在组件实例被创建时, 初始化了 props 之后调用,处于 created 前。还有以下特点:
1.可以返回一个对象或函数,对象的属性会合并到模板渲染的上下文中;
2.第一个参数是响应式的props对象,注意不能解构 props 对象,会使其失去响应性。 **
也不可直接修改 props,会触发警告
3.第二个参数是一个上下文对象,暴露了 attrs,slots,emit 对象
4.
this 在 setup 函数中不可用。**因为它不会找到组件实例。setup 的调用发生在 data、computed 和 methods 被解析之前,所以它们无法在 setup 中被获取。

props与上下文对象attrs的区别:
1、props 要先声明才能取值,attrs 不用先声明
2、props 声明过的属性,attrs 里不会再出现
3、props 不包含事件,attrs 包含。vue2中的$listeners 被整合到 $attrs

reactive

<template>
  <div>
    <p>{{data.msg}}</p>
    <button @click="updateData">更新数据</button>
  </div>
</template>

<script>
import { reactive } from "vue";

export default {
  name: "ReactiveObject",
  setup() {
    const data = reactive({ msg: "hello world" });
    const updateData = () => {
      data.msg= "hello world " + new Date().getTime();
    };
    return { data, updateData };
  },
};
</script>

reactive函数接收一个普通对象然后返回对象的响应式代理,同 Vue.observable。
原理:通过proxy对数据进行封装,当数据变化时,触发模板等内容的更新。

ref

<template>
  <div>
    <p>{{msg}}</p>
    <button @click="updateMessage">更新数据</button>
  </div>
</template>

<script>
import { ref } from "vue";

export default {
  name: "ReactiveSingleValue",
  setup() {
    const msg= ref("hello world");
    const updateMessage = () => {
      msg.value = "hello world " + new Date().getTime();
    };
    return { msg, updateMessage };
  },
};
</script>

ref和reactive存在一定的相似性,所以需要完全理解它们才能高效的在各种场景下选择不同的方式,它们之间最明显的区别是ref使用的时候需要通过.value来取值,reactive不用。ref是property而reactive是proxy,reactive能够深度监听各种类型对象的变化,ref是处理诸如number,string之类的基本数据类型。
它们的区别也可以这么理解,ref是使某一个数据提供响应能力,而reactive是为包含该数据的一整个对象提供响应能力。
在模板里使用ref和嵌套在响应式对象里时不需要通过.value,会自己解开:

除了响应式ref还有一个引用DOM元素的ref,2.x里面是通过this.$refs.xxx来引用,但是在setup里面没有this,所以也是通过创建一个ref来使用:

<template>
    <div ref="node"></div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
    setup() {
        const node = ref(null)
        onMounted(() => {
            console.log(node.value)  // 此处就是dom元素 <div ref="node"></div>
        })
        return {
            node
        }
    }
}
</script>

computed

传入一个 getter 函数,返回一个默认不可修改的 ref 对象,同 vue 2.x 中的计算属性 computed

const count = ref(0)
const sum = computed(() => count.value + 1)
console.log(sum.value) // 1
sum.value = 3 // 错误

也可传入一个 get 和 set 函数对象,创建一个可修改的计算状态

const count = ref(0)

const sum = computed({
  get: () => count.value + 1,
  set: (value) => {
    count.value = value - 1
  }
})

sum.value = 55
console.log(sum, count) // 1, 54

watchEffect

import { reactive, watchEffect } from "vue";
export default {
  name: "WatchEffect",
  setup() {
    const data = reactive({ count: 1 });
    watchEffect(() => console.log(`侦听器:${data.count}`));
    setInterval(() => {
      data.count++;
    }, 1000);
    return { data };
  },
};

watchEffect用来监听数据的变化,它会立即执行一次,之后会追踪函数里面用到的所有响应式状态,当变化后会重新执行该回调函数。

watch

完全等效于 2.x 中 watch 选项,对比 watchEffect,watch 允许我们:

  • 懒执行副作用;
  • 更明确哪些状态的改变会触发侦听器重新运行副作用;
  • 访问侦听状态变化前后的值。
// 监听一个 getter
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    console.log(count, prevCount)
  }
)

// 直接监听一个 ref
const count = ref(0)
watch(count, (count, prevCount) => {
  console.log(count, prevCount)
}, {
  deep: true, // 深度监听
  immediate: true // 初始化执行一次
})

// 监听多个数据
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  console.log([foo, bar], [prevFoo, prevBar])
})

toRefs

把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref,和响应式对象 property 一一对应。可以被解构且保持响应性

<template>
  <div>
    <h1>解构响应式对象数据</h1>
    <p>Username: {{username}}</p>
    <p>Age: {{age}}</p>
  </div>
</template>

<script>
import { reactive, toRefs } from "vue";
export default {
  name: "DestructReactiveObject",
  setup() {
    const user = reactive({
      username: "haihong",
      age: 10000,
    });
    return { ...toRefs(user) };
  },
};
</script>

toRef

toRef 可以用来为一个 reactive 对象的属性创建一个 ref。这个 ref 可以被传递并且能够保持响应性。

setup() {
  const user = reactive({ age: 1 });
  const age = toRef(user, "age");

  age.value++;
  console.log(user.age); // 2

  user.age++;
  console.log(age.value); // 3
}

Provide/Inject

为了增加 provide 值和 inject 值之间的响应性,我们可以在 provide 值时使用 refreactive
当使用响应式 provide / inject 值时,建议尽可能将对响应式 property 的所有修改限制在定义 provide 的组件内部。然而,有时我们需要在注入数据的组件内部更新 inject 的数据。在这种情况下,我们建议 provide 一个方法来负责改变响应式 property。
最后,如果要确保通过 provide 传递的数据不会被 inject 的组件更改,我们建议对提供者的 property 使用 readonly。

<!-- src/components/MyMap.vue -->
<template>
  <MyMarker />
</template>

<script>
import { provide, reactive, readonly, ref } from 'vue'
import MyMarker from './MyMarker.vue'

export default {
  components: {
    MyMarker
  },
  setup() {
    const location = ref('North Pole')
    const geolocation = reactive({
      longitude: 90,
      latitude: 135
    })

    const updateLocation = () => {
      location.value = 'South Pole'
    }

    provide('location', readonly(location))
    provide('geolocation', readonly(geolocation))
    provide('updateLocation', updateLocation)
  }
}
</script>
<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'

export default {
  setup() {
    const userLocation = inject('location', 'The Universe')
    const userGeolocation = inject('geolocation')
    const updateUserLocation = inject('updateLocation')

    return {
      userLocation,
      userGeolocation,
      updateUserLocation
    }
  }
}
</script>

生命周期函数

与 2.x 版本生命周期相对应的组合式 API
~~beforeCreate~~ -> 使用 setup()
~~created~~ -> 使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured

只需要将之前的生命周期改成onXXX的形式即可,需要注意的是created、beforeCreate两个钩子被删除了,生命周期函数只能在setup函数里使用。

总结

使用组合式api还是需要一点时间来适应的,首先需要能区分ref和reactive,不要在基本类型和引用类型、响应式和非响应式对象之间搞混,其次就是如何拆分好每一个use函数,组合式api带来了更好的代码组织方式,但也更容易把代码写的更难以维护,比如setup函数巨长。

简单总结一下升级思路,data选项里的数据通过reactive进行声明,通过...toRefs()返回;computed、mounted等选项通过对应的computed、onMounted等函数来进行替换;methods里的函数随便在哪声明,只要在setup函数里返回即可。

有关vue3组合式API介绍的更多相关文章

  1. ruby-on-rails - ActionController::RoutingError: 未初始化常量 Api::V1::ApiController - 2

    我有用于控制用户任务的Rails5API项目,我有以下错误,但并非总是针对相同的Controller和路由。ActionController::RoutingError:uninitializedconstantApi::V1::ApiController我向您描述了一些我的项目,以更详细地解释错误。应用结构路线scopemodule:'api'donamespace:v1do#=>Loginroutesscopemodule:'login'domatch'login',to:'sessions#login',as:'login',via::postend#=>Teamroutessc

  2. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  3. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  4. ruby-on-rails - Mandrill API 模板 - 2

    我正在使用Mandrill的RubyAPIGem并使用以下简单的测试模板:testastic按照Heroku指南中的示例,我有以下Ruby代码:require'mandrill'm=Mandrill::API.newrendered=m.templates.render'test-template',[{:header=>'someheadertext',:main_section=>'Themaincontentblock',:footer=>'asdf'}]mail(:to=>"JaysonLane",:subject=>"TestEmail")do|format|format.h

  5. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  6. ruby-on-rails - 在 Ruby (on Rails) 中使用 imgur API 获取图像 - 2

    我正在尝试使用Ruby2.0.0和Rails4.0.0提供的API从imgur中提取图像。我已尝试按照Ruby2.0.0文档中列出的各种方式构建http请求,但均无济于事。代码如下:require'net/http'require'net/https'defimgurheaders={"Authorization"=>"Client-ID"+my_client_id}path="/3/gallery/image/#{img_id}.json"uri=URI("https://api.imgur.com"+path)request,data=Net::HTTP::Get.new(path

  7. ruby-on-rails - 使用 HTTParty 的非常基本的 Rails 4.1 API 调用 - 2

    Rails相对较新。我正在尝试调用一个API,它应该向我返回一个唯一的URL。我的应用程序中捆绑了HTTParty。我已经创建了一个UniqueNumberController,并且我已经阅读了几个HTTParty指南,直到我想要什么,但也许我只是有点迷路,真的不知道该怎么做。基本上,我需要做的就是调用API,获取它返回的URL,然后将该URL插入到用户的数据库中。谁能给我指出正确的方向或与我分享一些代码? 最佳答案 假设API为JSON格式并返回如下数据:{"url":"http://example.com/unique-url"

  8. ruby-on-rails - 是否使用 API - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我的公司有一个巨大的数据库,该数据库接收来自多个来源的(许多)事件,用于监控和报告目的。到目前为止,数据中的每个新仪表板或图形都是一个新的Rails应用程序,在巨大的数据库中有额外的表,并且可以完全访问数据库内容。最近,有一个想法让外部(不是我们公司,而是姊妹公司)客户访问我们的数据,并且决定我们应该公开一个只读的RESTfulAPI来查询我们的数据。我的观点是-我们是否也应该为我们的自己

  9. ruby - Ruby 中的必应搜索 API - 2

    我读了"BingSearchAPI-QuickStart"但我不知道如何在Ruby中发出这个http请求(Weary)如何在Ruby中翻译“Stream_context_create()”?这是什么意思?"BingSearchAPI-QuickStart"我想使用RubySDK,但我发现那些已被弃用前(Rbing)https://github.com/mikedemers/rbing您知道Bing搜索API的最新包装器(仅限Web的结果)吗? 最佳答案 好吧,经过一个小时的挫折,我想出了一个办法来做到这一点。这段代码很糟糕,因为它是

  10. ruby - 最多 n 的组合 - 2

    给定一个数组a,什么是实现其组合直到第n的最佳方法?例如:a=%i[abc]n=2#Expected=>[[],[:a],[:b],[:c],[:a,b],[:b,:c],[:c,:a]] 最佳答案 做如下:a=%w[abc]n=30.upto(n).flat_map{|i|a.combination(i).to_a}#=>[[],["a"],["b"],["c"],["a","b"],#["a","c"],["b","c"],["a","b","c"]] 关于ruby-最多n的组合,我

随机推荐