Vue3的学习---11

11. Vue组合式API

11.1 为什么要使用Composition API

11.1.1 一个Options API实例

在前面都是采用Options API(基于选项的API)来些一个组件,下面是一个实例

<template>num:{{ num }}<br>double:{{ double }}<button @click="add">加</button>
</template><script lang="ts">export default {data() {return {num: 0}},computed: {double() {return this.count * 2}}methods:{add() {this.num++}}}
</script>

11.1.2 OptionsAPI存在的问题

存在的一些问题和局限性:

  1. 代码冗长:使用 Option API 时,组件的逻辑和状态管理分散在 datamethodscomputed 等多个选项中,导致代码结构不够紧凑,阅读和维护起来较为困难。

  2. 逻辑复用困难:在 Option API 中,逻辑复用主要通过 mixins 实现,但 mixins 存在命名冲突和隐式依赖的问题,使得代码的可维护性和可读性降低。

  3. 上下文依赖:在 Option API 中,this 指向组件实例,但在某些情况下(如在生命周期钩子中),this 的指向可能会引起混淆,导致难以调试的问题。

  4. 类型推断困难:由于 Option API 的结构,TypeScript 的类型推断在某些情况下不够直观,需要额外的工作来确保类型安全。

  5. 状态和逻辑分离:Option API 将状态(data)和逻辑(methodscomputed)分开定义,这在大型组件中可能导致状态和逻辑的分离,增加理解和维护的难度。


11.1.3 Composition API简介

Composition API可以用函数的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。

说明:以上四张动图原创作者:大帅老猿

采用Composition API来重写上面的组件:

<template>num:{{ num }}<br>double:{{ double }}<button @click="add">加</button>
</template><script lang="ts">import { reactive, computed} from "vue"export default {setup() {const state = reactive({num: 0,double: computed(() => state.count * 2)})function add() {state.num++}return {state,add}}}
</script>

11.2 Composition API

11.2.1 setup()入口

setup() 是 Vue 3 中 Composition API 的一个核心函数,是组件内使用Composition API的入口点,在vue实例创建完成前被调用,所以,setup函数中没有this指针

<template><div></div>
</template><script>
export default {setup() {console.log('这是setup函数');}
}
</script><style></style>

setup() 用于在组件创建之前执行一些初始化逻辑,并且返回的对象可以包含响应式状态、计算属性、方法等,这些都可以在模板中直接使用。

<template><div><p>{{ num }}</p><p>{{ users.userID }},{{ users.name }}</p></div>
</template><script>
export default {setup() {let num = 100const users = {userID: 1,name: '张三'}return {num,users}}
}
</script><style></style>

11.2.2 ref响应式监听

ref 是 Vue 3 中用于创建响应式数据的函数之一。它可以将一个普通变量转换为响应式变量,使得当该变量的值发生变化时,Vue 能够自动追踪并更新相关的视图。

<template><div><p>{{ num }}</p><p>{{ users.userID }},{{ users.name }}</p><button @click="change">change</button></div>
</template><script>
import { ref } from 'vue'export default {setup() {let num = ref(100) // 使用ref包裹数据成为响应式数据let users = ref({userID: 1,name: '张三'})function change() {num.value = 200      // ref包裹的数据,使用时必须要通过value属性users.value.name = '李四'}return {num,users,change}}
}
</script><style></style>

响应式原理

ref 内部使用了 Vue 3 的响应式系统,通过 Proxy 对象来实现数据的拦截和追踪。当 ref 创建的变量被修改时,Vue 会自动检测到变化,并通知相关的依赖(如模板、计算属性等)进行更新。

与其他响应式 API 的对比

  • ref vs reactive
    • ref 适用于单个值的响应式,返回一个包含 value 属性的对象。
    • reactive 适用于对象的响应式,返回一个代理对象,可以直接访问和修改对象的属性。
  • ref vs computed
    • ref 用于创建可变的响应式变量。
    • computed 用于创建计算属性,其值是基于其他响应式数据计算得出的,并且是只读的。

11.2.3 reactive与toRefs

reactive 用于将一个普通对象转换为响应式对象。这个响应式对象的所有属性都会被代理,使得当这些属性发生变化时,Vue 能够自动追踪并更新相关的视图。

toRefs 用于将一个响应式对象的每个属性转换为单独的 ref。这在解构响应式对象时非常有用,可以保持每个属性的响应式特性。

<template><div><p>{{ num }}</p><p>{{ users.userID }},{{ users.name }}</p><button @click="change">change</button></div>
</template><script>
import { ref, reactive, toRefs } from 'vue'export default {setup() {const state = reactive({num: 100,users: {userID: 1,name: '张三'}})function change() {state.num = 200      // ref包裹的数据,使用时必须要通过value属性state.users.name = '李四'}return {// ...为扩展运算符...toRefs(state),   // 能够将state中的数据进行展开,暴露给外部change}}
}
</script><style></style>

对比 reactivetoRefs

  • reactive
    • 用于将一个普通对象转换为响应式对象。
    • 返回一个代理对象,可以直接访问和修改对象的属性。
    • 适用于需要整体管理一个对象的场景。
  • toRefs
    • 用于将一个响应式对象的每个属性转换为单独的 ref
    • 返回一个包含 ref 属性的对象,每个属性都可以单独访问和修改。
    • 适用于需要解构响应式对象并保持每个属性的响应式特性的场景。

11.2.4 computed()的用法

在 Vue.js 中,computed 属性用于定义计算属性。计算属性是基于其依赖的数据动态计算得出的属性,当依赖的数据发生变化时,计算属性会自动重新计算。计算属性通常用于简化模板中的复杂逻辑,使代码更加清晰和易于维护。

<template><div><p>{{ num }}</p><p>{{ newnum }}</p><p>{{ users.userID }},{{ users.name }}</p><button @click="change">change</button></div>
</template><script>
import { ref, reactive, toRefs, computed } from 'vue'export default {setup() {const state = reactive({num: 100,newnum: computed(() => state.num + 100),users: {userID: 1,name: '张三'}})function change() {state.num = 200state.users.name = '李四'}return {...toRefs(state),change}}
}
</script><style></style>

11.2.5 watch()的用法

在 Vue.js 中,watch 属性用于监听数据的变化,并在数据变化时执行特定的操作。watch 属性通常用于处理异步操作或开销较大的操作,以及在数据变化时执行复杂的逻辑。

监听器函数可以接收两个参数:

  • newValue:变化后的新值。
  • oldValue:变化前的旧值。
<template><div><p>{{ num }}</p><p>{{ newnum }}</p><p>{{ users.userID }},{{ users.name }}</p><button @click="change">change</button></div>
</template><script>
import { ref, reactive, toRefs, computed, watch } from 'vue'export default {setup() {const state = reactive({num: 100,newnum: computed(() => state.num + 100),users: {userID: 1,name: '张三'}})/*watch(state, (newVal, oldVal) => {console.log(newVal, oldVal)})*/// 监听某一个数据的变化watch(() => state.num, (newVal, oldVal) => {console.log(newVal, oldVal)})function change() {state.num = 200state.users.name = '李四'}return {...toRefs(state),change}}
}
</script><style></style>

11.2.6 setup()参数

11.2.6.1 props参数

setup() 函数接收两个参数:propscontext。其中,props 参数用于接收父组件传递的属性。

子组件:

<template><div>我是子组件</div>
</template><script>
export default {setup(props) {console.log(props)console.log(props.msg)},props: {msg: String}
}
</script><style></style>

父组件:

<template><div><Hello msg="Hello"></Hello></div>
</template><script>
import { ref, reactive, toRefs, computed, watch } from 'vue'
import Hello from '@/components/Hello.vue';export default {components: {Hello}
}
</script><style></style>
11.2.6.2 context参数

context 参数用于给父组件传值。

context 参数是一个对象,包含以下属性:

  • attrs:非响应式的属性集合,包含父组件传递的但未在 props 中定义的属性。
  • slots:插槽内容,包含父组件传递的插槽内容。
  • emit:用于触发自定义事件的函数。
  • expose:用于暴露组件内部方法或属性的函数。

子组件:

<template><div>我是子组件<button @click="send">给父组件传值</button></div>
</template><script>
export default {setup(props,context) {console.log(props)console.log(props.msg)function send() {context.emit('childmsg', '我是子组件发送的消息')}return {send}},props: {msg: String}
}
</script><style></style>

父组件:

<template><div><Hello msg="Hello" @childmsg="get"></Hello></div>
</template><script>
import { ref, reactive, toRefs, computed, watch } from 'vue'
import Hello from '@/components/Hello.vue';export default {setup() {function get(value) {console.log(value)}return {get}},components: {Hello}
}
</script><style></style>

11.3 Composition API的使用

11.3.1 provide与inject的使用

在 Vue.js 中,provideinject 是一对用于跨层级组件通信的 API。provide 用于在祖先组件中提供数据,而 inject 用于在后代组件中注入这些数据。这种方式非常适合于需要在多个层级的组件之间共享数据的场景。

父组件:

<template><div><Hello></Hello></div>
</template><script>
import {  reactive, toRefs, provide } from 'vue'
import Hello from '@/components/Hello.vue';export default {setup() {const state = reactive({})provide('msg', 'hello')return {...toRefs(state), }},components: {Hello}
}
</script><style></style>

子组件:

<template><div>我是子组件<SubHello></SubHello></div>
</template><script>
import SubHello from './SubHello.vue';export default {setup(props, context) {},components: {SubHello}
}
</script><style></style>

孙子组件:

<template><div>我是孙子组件</div>
</template><script>
import { inject } from 'vue';export default {setup() {console.log(inject('msg'))}
}
</script><style></style>

11.3.2 vue生命周期的用法

选项式API setup()内部调用声明周期的钩子
beforeCreate() setup()
created() setup()
beforeMount() onBeforeMount()
mounted() onMounted()
beforeUpdated() onBeforeUpdate()
updated() onUpdated()
beforeUnmount() onBeforeUnmount()
unmounted() onUnmounted()
<template><div><Hello></Hello></div>
</template><script>
import { reactive, toRefs, provide, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
import Hello from '@/components/Hello.vue';export default {setup() {const state = reactive({})provide('msg', 'hello')console.log('这里是setup,就是实例创建前和创建后的生命周期')onBeforeMount(() => {console.log('DOM挂载前')})onMounted(() => {console.log('DOM挂载后')})onBeforeUpdate(() => {console.log('数据更新前')})onUpdated(() => {console.log('数据更新后')})onBeforeUnmount(() => {console.log('实例卸载前')})onUnmounted(() => {console.log('实例卸载后')})return {...toRefs(state),}},components: {Hello}
}
</script><style></style>

11.3.3 编程式路由的使用

在Composition API中使用编程式路由

App.vue:

<template><nav><button @click="toHome">Home</button><button @click="toAbout">About</button></nav><router-view/>
</template><script>
import { useRouter } from 'vue-router'export default {setup() {const router = useRouter()function toHome() {router.push('/')}function toAbout() {router.push({ path: '/about', query: { name: '张三' } })}return {toHome,toAbout}}
}
</script><style></style>

AboutView.vue:

<template><div class="about">{{ $route.query.name }}</div>
</template><script>
import { useRoute } from 'vue-router';export default {name: 'AboutView',setup() {const route = useRoute()console.log(route.query.name)}
}
</script>

11.3.4 Vuex的使用

在Composition API中使用Vuex

<template><div><Hello></Hello></div>
</template><script>
import { reactive, toRefs } from 'vue'
import { useStore } from 'vuex'export default {setup() {const state = reactive({})const store = useStore()console.log(store.state.num)console.log(store.getters.newNum)return {...toRefs(state),}}
}
</script><style></style>

11.3.5 获取DOM的使用

在Composition API中获取DOM

<template><div><div ref="myRef">hello</div></div>
</template><script>
import { reactive, toRefs, onMounted, ref } from 'vue'
import { useStore } from 'vuex'export default {setup() {const state = reactive({})const myRef = ref(null)onMounted(() => {console.log(myRef.value)})return {...toRefs(state),myRef}}
}
</script><style></style>

11.4 使用Composition API重写ToDoList

AddNew:

<template><div><input type="text" v-model="newItem" placeholder="添加ToDo"><button @click="handleAdd">添加</button></div>
</template><script>
import { reactive, toRefs } from 'vue'export default {setup(props, context) {const state = reactive({newItem: ''})function handleAdd() {if(state.newItem === '') {alert('不能为空')return} else {context.emit('submitNewItem', state.newItem)state.newItem = ''}}return {...toRefs(state),handleAdd}}
}
</script><style scoped>input {width: 300px;height: 30px;border: none;outline: none;border: solid 1px #999;border-radius: 5px;padding-left: 10px;}button {width: 80px;height: 36px;border-radius: 5px;margin-left: 10px;border: none;outline: none;background-color: #41b883;color: #fff;}
</style>

TheList:

<template><div><ol><li v-for="(item, index) in list" :key="index" @click="judgeItem(index)">{{ item }}</li></ol></div>
</template><script>
export default {setup(props, context) {function judgeItem(index) {if (props.listType) {context.emit('deleteItem', index)} else {context.emit('judgeItem', index)}}return {judgeItem}},props: {list: {type: Array,required: true},listType: {type: Boolean,default: false}}
}
</script><style scoped>
ol {margin-top: 20px;
}ol li {cursor: pointer;
}
</style>

TodoList:

<template><div><h1>ToDoList</h1><AddNew @submitNewItem="addItem"></AddNew><TheList :list="todoList" @judgeItem="toDone"></TheList><hr><TheList :list="doneList" @deleteItem="deleteItem" :listType="true"></TheList></div>
</template><script>
import AddNew from '../components/AddNew.vue'
import TheList from '../components/TheList.vue'
import { reactive,toRefs } from 'vue';export default {setup() {const state = reactive({todoList: [],doneList: []})function addItem(newItem) {state.todoList.push(newItem)}function toDone(index) {state.doneList.push(state.todoList.splice(index, 1)[0])}function deleteItem(index) {state.doneList.splice(index, 1)}return {...toRefs(state),addItem,toDone,deleteItem}},components: {AddNew,TheList}
}
</script><style></style>

11.5 setup语法糖

setup 语法糖是 Vue 3 中引入的一种新的组合式 API 语法,用于在组件中定义和初始化响应式状态、计算属性、方法等。它提供了一种更直观和简洁的方式来组织和复用逻辑代码。setup 语法糖的优势在于它将组件的逻辑代码集中在一个地方,使得代码更易于理解和维护。同时,它也提供了更好的类型推断支持,使得在 TypeScript 项目中使用更加方便。

11.5.1 setup语法糖的基本结构

<template><div>{{ msg }}</div>
</template><script setup>
const msg = 'hello'
</script><style></style>
  • 在script标签中使用setup属性即可
  • 运行时,script标签中的内容会被重新编译成setup()函数的形式
  • 声明的数据、函数不需要通过return暴露即可被template所使用

11.5.2 响应式数据的使用

11.5.2.1 声明响应式数据

当数据被声明为响应式数据时,无论是let还是const声明都可以。

<template><div>{{ num }}<button @click="add">加</button></div>
</template><script setup>
import { ref } from 'vue'let num = ref(0)function add() {num.value++
}
</script><style></style>
11.5.2.2 ref与reactive的区别
<template><div>{{ num }}<br><p>{{ dept.deptno }} , {{ dept.dname }}</p><ul><li v-for="item in userArr" :key="item">{{ item.userID }} , {{ item.userName }}</li></ul><button @click="add">加</button></div>
</template><script setup>
import { ref } from 'vue'let num = ref(0)const dept = ref({deptno: 100,dname: 'IT'
})const userArr = ref([{userID: 1,userName: '张三'},{userID: 2,userName: '李四'}
])function add() {num.value++// dept.value.dname = 'IT云学堂'// userArr.value[1].userName = '王五'dept.value = {}userArr.value = []
}
</script><style></style>

refreactive都是用于创建响应式数据的API,但它们有一些关键的区别:

  1. 数据类型
    • ref:通常用于创建单一值的响应式数据,例如数字、字符串或布尔值。ref返回的对象包含一个.value属性,通过这个属性可以访问和修改其值。
    • reactive:用于创建对象或数组的响应式数据。reactive返回的对象可以直接访问和修改其属性。
  2. 使用方式
    • ref:使用时需要通过.value属性来访问和修改值。
    • reactive:使用时直接访问和修改对象的属性。
  3. 响应式深度
    • ref:创建的响应式数据是浅层的,即只有.value属性是响应式的。
    • reactive:创建的响应式数据是深层的,即对象的所有嵌套属性都是响应式的。
  4. 解构赋值
    • ref:可以直接解构赋值,但解构后的变量不再是响应式的。
    • reactive:不能直接解构赋值,因为解构后的变量会失去响应性。需要使用toRefs函数来保持响应性。

总结来说,ref适用于简单的单一值,而reactive适用于复杂对象或数组。选择使用哪种方式取决于需要处理的响应式数据的类型和结构。

11.5.3 其它语法的使用

下面例子演示computed计算属性、watch监听、生命周期函数的使用

<template><div><p>{{ num }}</p><p>{{ newNum }}</p><button @click="add">加</button></div>
</template><script setup>
import { ref, computed, watch, onMounted } from 'vue'const num = ref(0)const newNum = computed(() => {return num.value*2
})watch(num, (newVal, oldVal) => {console.log(newVal, oldVal)
})onMounted(() => {console.log('DOM挂载后')
})function add() {num.value++
}
</script><style></style>

11.5.4 引入组件的使用

引入的组件不必注册,可直接使用

<template><div>我是子组件<SubHello></SubHello></div>
</template><script setup>
import SubHello from './SubHello.vue';
</script><style></style>

11.5.5 父子组件传值的使用

11.5.5.1 defineProp的使用

父组件:


子组件:


11.5.5.2 defineEmits的使用

父组件:

<template><div><Hello msg="hello" num="10"></Hello></div>
</template><script setup>
import Hello from '@/components/Hello.vue';</script><style></style>

子组件:

<template><div>我是子组件,{{ msg }},{{ num }}<SubHello></SubHello></div>
</template><script setup>
import SubHello from './SubHello.vue';// 接受父组件传递过来的值
/*
const myProps = defineProps({msg: {type: String},num: {type: Number,required: true}
})*///  另一种写法
const myProps = defineProps(['msg', 'num'])
</script><style></style>

11.6 使用setup语法糖重写ToDoList

AddNew:

<template><div><input type="text" v-model="newItem" placeholder="添加ToDo"><button @click="handleAdd">添加</button></div>
</template><script setup>
import { ref } from 'vue'const newItem = ref('')
const emit = defineEmits(['submitNewItem'])function handleAdd() {if (newItem.value === '') {alert('不能为空')return} else {emit('submitNewItem', newItem.value)newItem.value = ''}
}
</script><style scoped>
input {width: 300px;height: 30px;border: none;/* outline: none;是为了防止在输入框中输入时,光标闪烁 */outline: none;border: solid 1px #999;border-radius: 5px;padding-left: 10px;
}button {width: 80px;height: 36px;border-radius: 5px;margin-left: 10px;border: none;outline: none;background-color: #41b883;color: #fff;
}
</style>

TheList:

<template><div><ol><li v-for="(item, index) in list" :key="index" @click="judgeItem(index)">{{ item }}</li></ol></div>
</template><script setup>
const emit = defineEmits(['judgeItem', 'deleteItem'])
const props = defineProps({list: {type: Array,required: true},listType: {type: Boolean,default: false}
})function judgeItem(index) {if (props.listType) {emit('deleteItem', index)} else {emit('judgeItem', index)}
}
</script><style scoped>
ol {margin-top: 20px;
}ol li {cursor: pointer;
}
</style>

TodoList:

<template><div><h1>ToDoList</h1><AddNew @submitNewItem="addItem"></AddNew><TheList :list="todoList" @judgeItem="toDone"></TheList><hr><TheList :list="doneList" @deleteItem="deleteItem" :listType="true"></TheList></div>
</template><script setup>
import AddNew from '../components/AddNew.vue'
import TheList from '../components/TheList.vue'
import { ref } from 'vue';const todoList = ref([])
const doneList = ref([])function addItem(newItem) {todoList.value.push(newItem)
}function toDone(index) {doneList.value.push(todoList.value.splice(index, 1)[0])
}function deleteItem(index) {doneList.value.splice(index, 1)
}  
</script><style></style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/789370.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二. Spring Boot 中的 “依赖管理和自动配置” 详解透彻到底(附+详细代码流程)

二. Spring Boot 中的 “依赖管理和自动配置” 详解透彻到底(附+详细代码流程) @目录二. Spring Boot 中的 “依赖管理和自动配置” 详解透彻到底(附+详细代码流程)1. 如何理解 “ 约定优于配置 ”2. Spring Boot 依赖管理 和 自动配置2.1 Spring Boot 的依赖管理2.1.1 什么…

Datawhale AI 夏令营 第五期 CV方向 02进阶

上次的baseline方案,训练的模型,获得分数并不高,DataWhale提供了两个上分的思路增加训练数据集 切换不同模型预训练权重增加训练集的大小通常可以提高模型的泛化能力,因为更多的数据可以帮助模型学习到更多的特征和模式。但是,越大的数据集,就意味着需要更多的计算资源和…

luoguP5369 [PKUSC2018] 最大前缀和

题目n<=20 题解 想了半天3位状态的折半,然后发现空间开不下(时间也不太行) 所以放弃思考,直接枚举答案答案是a中的一个集合,设为S;记集合S的和为sum[S] 考虑当S确定时,有多少种方案能使答案恰好为sum[S]。为了处理多种sum相同的情况,记S为从前往后考虑,第一次出现最…

Java Script网页设计案例

本文提供了一个简单的JavaScript网页设计案例,该案例将实现一个动态的待办事项列表(Todo List)。用户可以在页面上添加新的待办事项,标记它们为已完成,以及删除它们。这个案例将使用HTML来构建页面结构,CSS来美化页面,以及JavaScript来添加动态功能。1. JavaScript网页设…

ggml 简介

ggml 是一个用 C 和 C++ 编写、专注于 Transformer 架构模型推理的机器学习库。该项目完全开源,处于活跃的开发阶段,开发社区也在不断壮大。ggml 和 PyTorch、TensorFlow 等机器学习库比较相似,但由于目前处于开发的早期阶段,一些底层设计仍在不断改进中。 相比于 llama.cp…

层序遍历(广度优先搜索)-102

题目描述 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。解题思路 这里我们层次遍历我们需要使用到队列这个数据结构,我们依次从根节点开始遍历,我们需要使用一个变量来记录此时我们队列中元素的数量,因为这样我们才知道这一层…

CF1980F1 F2 Field Division

前言 纪念一下独立做出来的 \(2400\) 的题 Easy version 思路 先说 \(Easy\) 版本的 我们走路的方式只有可能是这种样子:(出处:luogu user FiraCode) 不想手绘图了 即对列排序后,所形成的一个行编号上升的序列 所以 \(Easy\) 就很简单了,对于每一列的最大值,如果大于当前前…

一篇文章讲清楚Java中的反射

介绍 每个类都有一个 Class 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。 类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 Class.forName("com.mysql.jdbc.Driver"…

django timezone.now 小了8小时

django.util.timezone.now()原因:setting.py中设置了时区:LANGUAGE_CODE = en-usTIME_ZONE = UTCUSE_I18N = TrueUSE_TZ = True # 若数据库中存储的是UTC时间,但在模板显示的时候,会转成TIME_ZONE所示的本地时间进行显示****将TIME_ZONE时区改为:TIME_ZONE = Asia/Shangha…

枚举实现原理

枚举的定义 在JDK1.5之前,我们要是想定义一些有关常量的内容,例如定义几个常量,表示从周一到周末,一般都是在一个类,或者一个接口中,写类似于如下代码: public class WeekDayConstant {public static final int MONDAY = 0;public static final int TUESDAY = 1;public …