## 一、为什么使用组合式API(Composition API) #### 1、选项式API(Options API)的缺陷 在选项式API中实际上形成了一种强制的约定: * `props`里面设置接收参数; * `data`里面设置变量; * `computed`里面设置计算属性; * `watch`里面设置监听属性; * `methods`里面设置事件方法; 选项式API约定了我们什么位置必须要做什么事情,在一定的程度上分割了我们的代码,在开发中需要来回多次的去转跳不同的代码块,以找到与该关注点的部分。 #### 2、组合式API简介 通过组合式 API,我们可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与` ``` 修改路由文件 ```js import {createRouter, createWebHashHistory} from 'vue-router' import CompositionAPI from "@/views/CompositionAPI.vue"; const routes = [ { path: '/', name: 'CompositionAPI', component: CompositionAPI }, { path: '/home', name: 'home', component: () => import('../views/HomeView.vue') }, { path: '/about', name: 'about', component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue') } ] const router = createRouter({ history: createWebHashHistory(), routes }) export default router ``` 修改`App.vue` ```html CompositionAPI | Home | About ``` #### 1、创建`setup()` 当实例被创建时会调用`setup()` ```html ``` **注意**:`setup()`函数是在在Vue实例创建前被调用的,所以不能用`this`指针,在`setup()`中声明的数据必须要以对象的形式return出去才能使组件访问到。 #### 2、创建响应式数据 需要导入`ref`模块并使用`ref()`包裹需要响应的数据 其底层原理是因为数据类型分为两大类,基本数据类型是值传递,引用数据类型是引用,所以使用`ref()`包裹其值之后,就会自动创建一个对象,并将其值赋给该对象的`value`属性,在Js代码中需要使用`变量名.value`来改变原本包裹的值,在模板中可以直接同普通变量使用。 修改`CompositionAPI.vue` ```html change ``` **注意**:若要改变引用数据类型的变量引用,也需要使用ref包裹;引用式数据可直接修改其内部值,不需要使用ref包裹。 #### 3、`reactive()`与`toRefs()` 为了简化`ref()`创建的动态数据调用,可以使用`reactive()`(不是)与`toRefs()`来批量定义动态数据,且方便调用(不是)。 首先要导入`reactive`、`toRefs`包,需要声明一个常量来承接`reactive()`创建的对象,修改值直接可以通过该`对象名.属性名 = 值`来修改,在return时可以只传出一个`...toRefs(reactive对象)`即可传出全部响应式数据。 修改`CompositionAPI.vue` ```html change ``` **注意**:最后return的`...toRefs()`是将`reactive`对象转化为`ref`对象的数组,通过`...`去展开这个数组,使其每个ref独立可用。 #### 4、创建计算属性 使用`属性名: computed(()=>{})`来创建一个计算属性,要导入`computed`包才能使用。 修改`CompositionAPI.vue` ```html change ``` **注意**:在创建计算属性时,因为创建的是一个函数型对象,所以函数作用域是独立的,因此调用其他属性时需要用`local.属性名`,`local`是`reactive`对象 #### 5、数据监听 导入`watch`模块,实现数据监听要使用`watch(监听属性,(oldValue,newValue)=>{})`函数 修改`CompositionAPI.vue` ```html change ``` **注意**:若监听的不是一个对象,即可直接写属性名,若监听的是一个对象里的属性值,则需要通过匿名函数来取得这个对象的属性值才能实现监听。 #### 6、`props`参数 在`setup()`中添加`props`参数,用来代替`this.`调用props 创建`components/Hello.vue` ```html ``` **注意**:`props`属性不能省略,若不写则拿不到`props`对象 修改`CompositionAPI.vue` ```html 父组件 change 子组件 ``` #### 7、`context`参数 在`setup()`参数中添加`context`,用来向父组件传递消息,代替原本的`this.`,使用方法是`context.emit()` 修改`Hello.vue`组件 ```html 给父组件传值 ``` 修改`CompositionAPI.vue`组件 ```html 父组件 change 子组件 ``` #### 8、`provide`与`inject` 可以通过`provide`与`inject`进行多组件之间的通讯。 导入`provide`、`inject`组件后,直接可以在`setup()`函数中使用`provide('属性名',属性值)`、`inject('属性名')`进行调用 创建`components/GrandSon.vue` ```html 孙子组件 ``` 修改`CompositionAPI.vue` ```html 父组件 change 子组件 ``` #### 9、生命周期函数 调用方法对比 | 选项式API | setup () 内部调用生命周期钩子 | | --------------- | ----------------------------- | | beforeCreate() | setup() | | created() | setup() | | beforeMount() | onBeforeMount() | | mounted() | onMounted() | | beforeUpdate() | onBeforeUpdate() | | updated() | onUpdated() | | beforeUnmount() | onBeforeUnmount() | | unmounted() | onUnmounted() | 组合式API调用时需要导入各个函数(除setup)所在模块。 修改`CompositionAPI.vue` ```html 父组件 change 隐藏/显示子组件 子组件 ``` 修改`Hello.vue` ```html 给父组件传值 ``` #### 10、编程式路由(路由传参) 使用编译式路由要从`vue-router`引入`router`路由控制、`route`路由对象两个模块,并且使用`useRouter()`来获取路由控制器,使用`useRoute()`来获取路由对象。 修改`App.vue` ```html CompositionAPI Home About ``` 修改`AboutView.vue` ```html This is an about page ``` #### 11、Vuex的使用 使用前要从`vuex`中导入`useStore`模块,使用需要通过`useStore()`来获取`store`对象,用来代替`this.` Vuex的配置文件不用变 ```js import {createStore} from 'vuex' export default createStore({ state: { num: 10 }, getters: { changeNum(state) { return state.num * state.num; } }, mutations: { addNum(state, param) { state.num += param; } }, actions: { addNum(state, param) { setTimeout(() => { state.commit('addNum', param) }, 3000) } }, modules: {} }) ``` 修改`CompositionAPI.vue` ```html 父组件 change 隐藏/显示子组件 子组件 ``` #### 12、获取DOM对象 需要在DOM挂载后获取DOM对象,所以要在`onMounted()`函数中获取,通过DOM标签中的`ref`属性设置DOM对象名,在`reactive`中或直接定一个一个`ref`的`同名`对象,赋值为`null`(仅用来绑定),在挂载后会直接将DOM对象注入到该对象中。 修改`CompositionAPI.vue` ```html 父组件 我是一个DOM对象 change 隐藏/显示子组件 子组件 ``` ## 三、使用组合式API重写TodoList #### 1、修改`TodoList.vue` ```html TodoList ``` #### 2、修改`AddNew.vue` ```html 添加 ``` #### 3、修改`DoList.vue` ```html ``` Loading... ## 一、为什么使用组合式API(Composition API) #### 1、选项式API(Options API)的缺陷 在选项式API中实际上形成了一种强制的约定: * `props`里面设置接收参数; * `data`里面设置变量; * `computed`里面设置计算属性; * `watch`里面设置监听属性; * `methods`里面设置事件方法; 选项式API约定了我们什么位置必须要做什么事情,在一定的程度上分割了我们的代码,在开发中需要来回多次的去转跳不同的代码块,以找到与该关注点的部分。 #### 2、组合式API简介 通过组合式 API,我们可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与`<script setup>` 搭配使用。这个 `setup` attribute 是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。比如,`<script setup>` 中的导入和顶层变量/函数都能够在模板中直接使用。 **主要API函数** ① 响应式基础 - `ref()` - 创建响应式的基本类型数据 - `reactive()` - 创建响应式的对象 - `toRefs()` - 将reactive对象转换为ref集合 ② 计算属性和监听 - `computed()` - 创建计算属性 - `watch()` - 监听数据变化 - `watchEffect()` - 自动追踪依赖的副作用函数 ③ 生命周期钩子 - `onMounted()`, `onUpdated()`, `onUnmounted()` 等 ## 二、组合式API的使用 创建`views/CompositionAPI.vue` ```html <template> <div> test </div> </template> <script> export default { name: "CompositionAPI" } </script> <style scoped> </style> ``` 修改路由文件 ```js import {createRouter, createWebHashHistory} from 'vue-router' import CompositionAPI from "@/views/CompositionAPI.vue"; const routes = [ { path: '/', name: 'CompositionAPI', component: CompositionAPI }, { path: '/home', name: 'home', component: () => import('../views/HomeView.vue') }, { path: '/about', name: 'about', component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue') } ] const router = createRouter({ history: createWebHashHistory(), routes }) export default router ``` 修改`App.vue` ```html <template> <nav> <router-link to="/">CompositionAPI</router-link> | <router-link to="/home">Home</router-link> | <router-link to="/about">About</router-link> </nav> <router-view/> </template> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } nav { padding: 30px; } nav a { font-weight: bold; color: #2c3e50; } nav a.router-link-exact-active { color: #42b983; } </style> <script setup> </script> ``` #### 1、创建`setup()` 当实例被创建时会调用`setup()` ```html <template> <div> <span v-text="num"></span> <br> <span v-text="user"></span> </div> </template> <script> export default { name: "CompositionAPI", setup() { console.log("这是setup函数"); // undefined console.log(this); let num = 10; const user = { userId: 100, userName: "张三" } return { num, user } } } </script> <style scoped> </style> ``` **注意**:`setup()`函数是在在Vue实例创建前被调用的,所以不能用`this`指针,在`setup()`中声明的数据必须要以对象的形式return出去才能使组件访问到。 #### 2、创建响应式数据 需要导入`ref`模块并使用`ref()`包裹需要响应的数据 其底层原理是因为数据类型分为两大类,基本数据类型是值传递,引用数据类型是引用,所以使用`ref()`包裹其值之后,就会自动创建一个对象,并将其值赋给该对象的`value`属性,在Js代码中需要使用`变量名.value`来改变原本包裹的值,在模板中可以直接同普通变量使用。 修改`CompositionAPI.vue` ```html <template> <div> <span v-text="num"></span> <br> <span v-text="user"></span> <br> <span v-text="user2"></span> <br> <button @click="change">change</button> </div> </template> <script> import {ref} from "vue"; export default { name: "CompositionAPI", setup() { console.log("这是setup函数"); // undefined console.log(this); let num = ref(10); const user = { userId: 100, userName: "张三" }; let user2 = ref({ userId: 100, userName: "张三" }); function change() { num.value++; user.userName = "李四"; user2.value = {}; } return { num, user, user2, change } } } </script> <style scoped> </style> ``` **注意**:若要改变引用数据类型的变量引用,也需要使用ref包裹;引用式数据可直接修改其内部值,不需要使用ref包裹。 #### 3、`reactive()`与`toRefs()` 为了简化`ref()`创建的动态数据调用,可以使用`reactive()`(不是)与`toRefs()`来批量定义动态数据,且方便调用(不是)。 首先要导入`reactive`、`toRefs`包,需要声明一个常量来承接`reactive()`创建的对象,修改值直接可以通过该`对象名.属性名 = 值`来修改,在return时可以只传出一个`...toRefs(reactive对象)`即可传出全部响应式数据。 修改`CompositionAPI.vue` ```html <template> <div> <span v-text="num"></span> <br> <span v-text="user"></span> <br> <button @click="change">change</button> </div> </template> <script> import {ref, reactive, toRefs} from "vue"; export default { name: "CompositionAPI", setup() { console.log("这是setup函数"); // undefined console.log(this); const local = reactive({ num: 10, user: { userId: 100, userName: "张三" } }); function change() { local.num++; local.user.userName = "李四"; } return { ...toRefs(local), change } } } </script> <style scoped> </style> ``` **注意**:最后return的`...toRefs()`是将`reactive`对象转化为`ref`对象的数组,通过`...`去展开这个数组,使其每个ref独立可用。 #### 4、创建计算属性 使用`属性名: computed(()=>{})`来创建一个计算属性,要导入`computed`包才能使用。 修改`CompositionAPI.vue` ```html <template> <div> <span v-text="num"></span> <br> <span v-text="newNum"></span> <br> <span v-text="user"></span> <br> <button @click="change">change</button> </div> </template> <script> import {ref, reactive, toRefs, computed} from "vue"; export default { name: "CompositionAPI", setup() { console.log("这是setup函数"); // undefined console.log(this); const local = reactive({ num: 10, user: { userId: 100, userName: "张三" }, newNum: computed(() => { return local.num * local.num; }) }); function change() { local.num++; local.user.userName = "李四"; } return { ...toRefs(local), change } } } </script> <style scoped> </style> ``` **注意**:在创建计算属性时,因为创建的是一个函数型对象,所以函数作用域是独立的,因此调用其他属性时需要用`local.属性名`,`local`是`reactive`对象 #### 5、数据监听 导入`watch`模块,实现数据监听要使用`watch(监听属性,(oldValue,newValue)=>{})`函数 修改`CompositionAPI.vue` ```html <template> <div> <span v-text="num"></span> <br> <span v-text="newNum"></span> <br> <span v-text="user"></span> <br> <button @click="change">change</button> </div> </template> <script> import {ref, reactive, toRefs, computed, watch} from "vue"; export default { name: "CompositionAPI", setup() { console.log("这是setup函数"); // undefined console.log(this); const local = reactive({ num: 10, user: { userId: 100, userName: "张三" }, newNum: computed(() => { return local.num * local.num; }) }); // watch监听普通数据 watch(local, (newValue, oldValue) => { console.log(newValue, oldValue); }); // watch监听对象属性 watch(() => { return local.user.userName }, (oldValue, newValue) => { console.log(newValue, oldValue); }); function change() { local.num++; local.user.userName = "李四"; } return { ...toRefs(local), change } } } </script> <style scoped> </style> ``` **注意**:若监听的不是一个对象,即可直接写属性名,若监听的是一个对象里的属性值,则需要通过匿名函数来取得这个对象的属性值才能实现监听。 #### 6、`props`参数 在`setup()`中添加`props`参数,用来代替`this.`调用props 创建`components/Hello.vue` ```html <template> <div> <span v-text="params.num"></span><br> <span v-text="params.newNum"></span><br> <span v-text="params.user"></span><br> </div> </template> <script> export default { name: "Hello", props: { params: Object }, setup(props) { console.log(props); } } </script> <style scoped> </style> ``` **注意**:`props`属性不能省略,若不写则拿不到`props`对象 修改`CompositionAPI.vue` ```html <template> <div> <h2>父组件</h2> <span v-text="num"></span> <br> <span v-text="newNum"></span> <br> <span v-text="user"></span> <br> <button @click="change">change</button> <hr> <h2>子组件</h2> <hello :params="{num,newNum,user}"></hello> </div> </template> <script> import {ref, reactive, toRefs, computed, watch} from "vue"; import Hello from "@/components/Hello.vue"; export default { name: "CompositionAPI", components: {Hello}, setup() { console.log("这是setup函数"); // undefined console.log(this); const local = reactive({ num: 10, user: { userId: 100, userName: "张三" }, newNum: computed(() => { return local.num * local.num; }) }); // watch监听普通数据 watch(local, (newValue, oldValue) => { console.log(newValue, oldValue); }); // watch监听对象属性 watch(() => { return local.user.userName }, (oldValue, newValue) => { console.log(newValue, oldValue); }); function change() { local.num++; local.user.userName = "李四"; } return { ...toRefs(local), change } } } </script> <style scoped> </style> ``` #### 7、`context`参数 在`setup()`参数中添加`context`,用来向父组件传递消息,代替原本的`this.`,使用方法是`context.emit()` 修改`Hello.vue`组件 ```html <template> <div> <span v-text="params.num"></span><br> <span v-text="params.newNum"></span><br> <span v-text="params.user"></span><br> <button @click="sendMsg">给父组件传值</button> </div> </template> <script> export default { name: "Hello", props: { params: Object }, setup(props, context) { console.log(props); function sendMsg() { context.emit("childMsg", "这是给父组件传的值"); } return { sendMsg } } } </script> <style scoped> </style> ``` 修改`CompositionAPI.vue`组件 ```html <template> <div> <h2>父组件</h2> <span v-text="num"></span> <br> <span v-text="newNum"></span> <br> <span v-text="user"></span> <br> <button @click="change">change</button> <hr> <h2>子组件</h2> <hello :params="{num,newNum,user}" @childMsg="getChildMsg"></hello> </div> </template> <script> import {ref, reactive, toRefs, computed, watch} from "vue"; import Hello from "@/components/Hello.vue"; export default { name: "CompositionAPI", components: {Hello}, setup() { console.log("这是setup函数"); // undefined console.log(this); const local = reactive({ num: 10, user: { userId: 100, userName: "张三" }, newNum: computed(() => { return local.num * local.num; }) }); // watch监听普通数据 watch(local, (newValue, oldValue) => { console.log(newValue, oldValue); }); // watch监听对象属性 watch(() => { return local.user.userName }, (oldValue, newValue) => { console.log(newValue, oldValue); }); function change() { local.num++; local.user.userName = "李四"; } function getChildMsg(msg) { alert(msg); } return { ...toRefs(local), change, getChildMsg } } } </script> <style scoped> </style> ``` #### 8、`provide`与`inject` 可以通过`provide`与`inject`进行多组件之间的通讯。 导入`provide`、`inject`组件后,直接可以在`setup()`函数中使用`provide('属性名',属性值)`、`inject('属性名')`进行调用 创建`components/GrandSon.vue` ```html <template> <div> <h2>孙子组件</h2> <span v-text="msg.num"></span><br> <span v-text="msg.newNum"></span><br> <span v-text="msg.user"></span><br> </div> </template> <script> import {inject, ref} from "vue"; export default { name: "GrandSon", setup() { const msg = ref(inject('msg')); console.log(msg); return { msg } } } </script> <style scoped> </style> ``` 修改`CompositionAPI.vue` ```html <template> <div> <h2>父组件</h2> <span v-text="num"></span> <br> <span v-text="newNum"></span> <br> <span v-text="user"></span> <br> <button @click="change">change</button> <hr> <h2>子组件</h2> <hello :params="{num,newNum,user}" @childMsg="getChildMsg"></hello> </div> </template> <script> import {ref, reactive, toRefs, computed, watch, provide} from "vue"; import Hello from "@/components/Hello.vue"; export default { name: "CompositionAPI", components: {Hello}, setup() { console.log("这是setup函数"); // undefined console.log(this); const local = reactive({ num: 10, user: { userId: 100, userName: "张三" }, newNum: computed(() => { return local.num * local.num; }) }); // watch监听普通数据 watch(local, (newValue, oldValue) => { console.log(newValue, oldValue); }); // watch监听对象属性 watch(() => { return local.user.userName }, (oldValue, newValue) => { console.log(newValue, oldValue); }); function change() { local.num++; local.user.userName = "李四"; } function getChildMsg(msg) { alert(msg); } provide('msg',local); return { ...toRefs(local), change, getChildMsg } } } </script> <style scoped> </style> ``` #### 9、生命周期函数 调用方法对比 | 选项式API | setup () 内部调用生命周期钩子 | | --------------- | ----------------------------- | | beforeCreate() | setup() | | created() | setup() | | beforeMount() | onBeforeMount() | | mounted() | onMounted() | | beforeUpdate() | onBeforeUpdate() | | updated() | onUpdated() | | beforeUnmount() | onBeforeUnmount() | | unmounted() | onUnmounted() | 组合式API调用时需要导入各个函数(除setup)所在模块。 修改`CompositionAPI.vue` ```html <template> <div> <h2>父组件</h2> <span v-text="num"></span> <br> <span v-text="newNum"></span> <br> <span v-text="user"></span> <br> <button @click="change">change</button> <hr> <button @click="show">隐藏/显示子组件</button> <h2 v-if="isShow">子组件</h2> <hello v-if="isShow" :params="{num,newNum,user}" @childMsg="getChildMsg"></hello> </div> </template> <script> import { ref, reactive, toRefs, computed, watch, provide, onBeforeMount, onMounted, onBeforeUpdate, onUpdated } from "vue"; import Hello from "@/components/Hello.vue"; export default { name: "CompositionAPI", components: {Hello}, setup() { console.log("这是setup函数"); // undefined console.log(this); const local = reactive({ num: 10, user: { userId: 100, userName: "张三" }, newNum: computed(() => { return local.num * local.num; }), isShow: true }); watch(local, (newValue, oldValue) => { console.log(newValue, oldValue); }); watch(() => { return local.user.userName }, (oldValue, newValue) => { console.log(newValue, oldValue); }); // 挂载前 onBeforeMount(() => { console.log("父组件onBeforeMount"); }); // 挂载后 onMounted(() => { console.log("父组件onMounted"); }); // 数据更新前 onBeforeUpdate(() => { console.log("父组件onBeforeUpdate"); }); // 数据更新后 onUpdated(() => { console.log("父组件onUpdated"); }); function change() { local.num++; local.user.userName = "李四"; } function getChildMsg(msg) { alert(msg); } function show(){ local.isShow = !local.isShow; } provide('msg', local); return { ...toRefs(local), change, getChildMsg, show } } } </script> <style scoped> </style> ``` 修改`Hello.vue` ```html <template> <div> <span v-text="params.num"></span><br> <span v-text="params.newNum"></span><br> <span v-text="params.user"></span><br> <button @click="sendMsg">给父组件传值</button> <hr> <GrandSon></GrandSon> </div> </template> <script> import GrandSon from "@/components/GrandSon.vue"; import {onBeforeUnmount, onUnmounted} from "vue"; export default { name: "Hello", components: {GrandSon}, props: { params: Object }, setup(props, context) { console.log(props); // 卸载挂载前 onBeforeUnmount(() => { console.log("子组件onBeforeUnmount"); }); // 卸载挂载后 onUnmounted(() => { console.log("子组件onUnmounted"); }); function sendMsg() { context.emit("childMsg", "这是给父组件传的值"); } return { sendMsg } } } </script> <style scoped> </style> ``` #### 10、编程式路由(路由传参) 使用编译式路由要从`vue-router`引入`router`路由控制、`route`路由对象两个模块,并且使用`useRouter()`来获取路由控制器,使用`useRoute()`来获取路由对象。 修改`App.vue` ```html <template> <nav> <button @click="toIndex">CompositionAPI</button> <button @click="toHome">Home</button> <button @click="toAbout">About</button> </nav> <router-view/> </template> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } nav { padding: 30px; } nav a { font-weight: bold; color: #2c3e50; } nav a.router-link-exact-active { color: #42b983; } </style> <script setup> import {useRouter} from "vue-router"; const router = useRouter(); function toIndex() { router.push("/"); } function toHome() { router.push("/home"); } function toAbout() { router.push({ path: "/about", query: { name: "张三" } }); } </script> ``` 修改`AboutView.vue` ```html <template> <div class="about"> <h1>This is an about page</h1> <p v-text="$store.state.num"></p> <p v-text="$store.getters.changeNum"></p> <p v-text="this.$route.query.name"></p> </div> </template> <script> import {useRoute} from "vue-router"; export default { name: 'about', setup() { const route = useRoute(); console.log(route.query.name); } } </script> ``` #### 11、Vuex的使用 使用前要从`vuex`中导入`useStore`模块,使用需要通过`useStore()`来获取`store`对象,用来代替`this.` Vuex的配置文件不用变 ```js import {createStore} from 'vuex' export default createStore({ state: { num: 10 }, getters: { changeNum(state) { return state.num * state.num; } }, mutations: { addNum(state, param) { state.num += param; } }, actions: { addNum(state, param) { setTimeout(() => { state.commit('addNum', param) }, 3000) } }, modules: {} }) ``` 修改`CompositionAPI.vue` ```html <template> <div> <h2>父组件</h2> <span v-text="num"></span> <br> <span v-text="newNum"></span> <br> <span v-text="user"></span> <br> <hr> <span v-text="store.state"></span> <br> <button @click="change">change</button> <hr> <button @click="show">隐藏/显示子组件</button> <h2 v-if="isShow">子组件</h2> <hello v-if="isShow" :params="{num,newNum,user}" @childMsg="getChildMsg"></hello> </div> </template> <script> import { ref, reactive, toRefs, computed, watch, provide, onBeforeMount, onMounted, onBeforeUpdate, onUpdated } from "vue"; import Hello from "@/components/Hello.vue"; import {useStore} from "vuex"; export default { name: "CompositionAPI", components: {Hello}, setup() { console.log("这是setup函数"); // undefined console.log(this); const local = reactive({ num: 10, user: { userId: 100, userName: "张三" }, newNum: computed(() => { return local.num * local.num; }), isShow: true }); // 获取store对象 const store = useStore(); watch(local, (newValue, oldValue) => { console.log(newValue, oldValue); }); watch(() => { return local.user.userName }, (oldValue, newValue) => { console.log(newValue, oldValue); }); onBeforeMount(() => { console.log("父组件onBeforeMount"); }); onMounted(() => { console.log("父组件onMounted"); }); onBeforeUpdate(() => { console.log("父组件onBeforeUpdate"); }); onUpdated(() => { console.log("父组件onUpdated"); }); function change() { local.num++; local.user.userName = "李四"; } function getChildMsg(msg) { alert(msg); } function show(){ local.isShow = !local.isShow; } provide('msg', local); return { ...toRefs(local), change, getChildMsg, store, show } } } </script> <style scoped> </style> ``` #### 12、获取DOM对象 需要在DOM挂载后获取DOM对象,所以要在`onMounted()`函数中获取,通过DOM标签中的`ref`属性设置DOM对象名,在`reactive`中或直接定一个一个`ref`的`同名`对象,赋值为`null`(仅用来绑定),在挂载后会直接将DOM对象注入到该对象中。 修改`CompositionAPI.vue` ```html <template> <div> <h2>父组件</h2> <span v-text="num"></span> <br> <span v-text="newNum"></span> <br> <span v-text="user"></span> <div ref="domobj">我是一个DOM对象</div> <br> <hr> <span v-text="store.state"></span> <br> <button @click="change">change</button> <hr> <button @click="show">隐藏/显示子组件</button> <h2 v-if="isShow">子组件</h2> <hello v-if="isShow" :params="{num,newNum,user}" @childMsg="getChildMsg"></hello> </div> </template> <script> import { ref, reactive, toRefs, computed, watch, provide, onBeforeMount, onMounted, onBeforeUpdate, onUpdated } from "vue"; import Hello from "@/components/Hello.vue"; import {useStore} from "vuex"; export default { name: "CompositionAPI", components: {Hello}, setup() { console.log("这是setup函数"); // undefined console.log(this); const local = reactive({ num: 10, user: { userId: 100, userName: "张三" }, newNum: computed(() => { return local.num * local.num; }), isShow: true, // 声明响应式数据去绑定DOM对象 domobj: null }); const store = useStore(); watch(local, (newValue, oldValue) => { console.log(newValue, oldValue); }); watch(() => { return local.user.userName }, (oldValue, newValue) => { console.log(newValue, oldValue); }); onBeforeMount(() => { console.log("父组件onBeforeMount"); }); // 挂载后 onMounted(() => { // 获取DOM对象 console.log(local.domobj); console.log("父组件onMounted"); }); onBeforeUpdate(() => { console.log("父组件onBeforeUpdate"); }); onUpdated(() => { console.log("父组件onUpdated"); }); function change() { local.num++; local.user.userName = "李四"; } function getChildMsg(msg) { alert(msg); } function show() { local.isShow = !local.isShow; } provide('msg', local); return { ...toRefs(local), change, getChildMsg, store, show } } } </script> <style scoped> </style> ``` ## 三、使用组合式API重写TodoList #### 1、修改`TodoList.vue` ```html <template> <div> <h1>TodoList</h1> <AddNew @submitNew="addNewItem"></AddNew> <DoList :list="todoList" @handleJudge="judgeItem"></DoList> <hr/> <DoList :list="doneList" :listType="true" @handleDelete="deleteItem"></DoList> </div> </template> <script> import {reactive, toRefs} from "vue"; import AddNew from "@/components/AddNew.vue"; import DoList from "@/components/DoList.vue"; export default { name: "TodoList", setup() { const local = reactive({ todoList: [], doneList: [] }); function addNewItem(newItem) { local.todoList.unshift(newItem); } function deleteItem(index) { local.doneList.splice(index, 1); } function judgeItem(index) { local.doneList.unshift(local.todoList.splice(index, 1)[0]); } return { ...toRefs(local), addNewItem, deleteItem, judgeItem } }, components: { AddNew, DoList } } </script> <style scoped> </style> ``` #### 2、修改`AddNew.vue` ```html <template> <div> <input type="text" v-model="newItem"> <button @click="handleAdd">添加</button> </div> </template> <script> import {ref} from "vue"; export default { name: "AddNew", data() { return { newItem: '' } }, setup(props, context) { const newItem = ref(''); function handleAdd() { if (newItem.value === '') { alert("添加的内容不能为空"); return; } context.emit('submitNew', newItem.value); newItem.value = ''; } return { newItem, handleAdd } } } </script> <style scoped> input { outline: none; width: 300px; height: 30px; border: solid 1px #999; border-radius: 5px; padding-left: 10px; } button { border: none; outline: none; width: 80px; height: 36px; background-color: #42b983; border-radius: 10px; margin-left: 10px; color: #fff; } button:hover { background-color: #34e590; } </style> ``` #### 3、修改`DoList.vue` ```html <template> <div> <ol reversed> <li v-for="(item,index) in list" :key="index" @click="judgeItem(index)"> <span v-text="item"></span> </li> </ol> </div> </template> <script> export default { name: "DoList", props: { list: { type: Array, required: true, // 表名该参数是必要参数 }, listType: { type: Boolean, default: false } }, setup(props,context) { function judgeItem(index) { if (props.listType) { context.emit('handleDelete', index); return; } context.emit('handleJudge', index); } return{ judgeItem } } } </script> <style scoped> ol { margin-bottom: 20px; } ol li { cursor: pointer; } </style> ``` 最后修改:2025 年 10 月 30 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 👆请简单粗暴地爱我。