1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 | # composition(组合式api) ## 1.为什么使用composition vue3里面不需要Mixins了?因为有compoition api 能讲逻辑进行抽离和复用 大型组件中,其中**逻辑关注点**按颜色进行分组。 这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。 如果能够将同一个逻辑关注点相关代码收集在一起会更好。而这正是组合式 API 使我们能够做到的  ## 2.setup函数 执行顺序在beforeCreate,created之前,不能在此操作data ### props 使用 `setup` 函数时,它将接收两个参数: 1. `props` 2. `context` ### Props `setup` 函数中的第一个参数是 `props`。正如在一个标准组件中所期望的那样,`setup` 函数中的 `props` 是响应式的,当传入新的 prop 时,它将被更新。 ```js // MyBook.vue export default { props: { title: String }, setup(props) { console.log(props.title) } } ``` > WARNING 但是,因为 `props` 是响应式的,你**不能使用 ES6 解构**,它会消除 prop 的响应性。 如果需要解构 prop,可以在 `setup` 函数中使用 [`toRefs`](https: //v3.cn.vuejs.org/guide/reactivity-fundamentals.html#响应式状态解构) 函数来完成此操作: ```js // MyBook.vue import { toRefs } from 'vue' setup(props) { const { title } = toRefs(props) console.log(title.value) } ``` 如果 `title` 是可选的 prop,则传入的 `props` 中可能没有 `title` 。在这种情况下,`toRefs` 将不会为 `title` 创建一个 ref 。你需要使用 `toRef` 替代它: ```js // MyBook.vue import { toRef } from 'vue' setup(props) { const title = toRef(props, 'title' ) console.log(title.value) } ``` ### Context 传递给 `setup` 函数的第二个参数是 `context`。`context` 是一个普通 JavaScript 对象,暴露了其它可能在 `setup` 中有用的值: ```js // MyBook.vue export default { setup(props, context) { // Attribute (非响应式对象,等同于 $attrs) console.log(context.attrs) // 插槽 (非响应式对象,等同于 $slots) console.log(context.slots) // 触发事件 (方法,等同于 $emit) console.log(context.emit) // 暴露公共 property (函数) console.log(context.expose) } } ``` `context` 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 `context` 使用 ES6 解构。 ```js // MyBook.vue export default { setup(props, { attrs, slots, emit, expose }) { ... } } ``` ### setup中的this 在 `setup()` 内部,` this ` 不是该活跃实例的引用**,因为 `setup()` 是在解析其它组件选项之前被调用的,所以 `setup()` 内部的 ` this ` 的行为与其它选项中的 ` this ` 完全不同。这使得 `setup()` 在和其它选项式 API 一起使用时可能会导致混淆。 beforeCreate setup(){} ### setup中获取data 通过getCurrentInstance获取的是当前组件的实例,但是我们无法去获取具体的data属性,因为setup的执行顺序在created之前 我们的编程思路不应该考虑在setup中如何获取data,我们正确的做法是使用composition给我们提供的响应式api ```js import {getCurrentInstance} from "vue" export default { setup(props,context){ console.log(props) console.log(context) console.log(getCurrentInstance()) } } ``` ## 3.响应式语法 ### ref ref 可以对数据进行响应,也可以对复杂数据类型进行响应,也可以用于对模板的引用 在 Vue 3.0 中,我们可以通过一个新的 ` ref ` 函数使任何响应式变量在任何地方起作用,如下所示: ```js import { ref } from 'vue' const counter = ref (0) ``` ` ref ` 接收参数并将其包裹在一个带有 `value` property 的对象中返回,然后可以使用该 property 访问或更改响应式变量的值: ```js import { ref } from 'vue' const counter = ref (0) console.log(counter) // { value: 0 } console.log(counter.value) // 0 counter.value++ console.log(counter.value) // 1 ``` 将值封装在一个对象中,看似没有必要,但为了保持 JavaScript 中不同数据类型的行为统一,这是必须的。这是因为在 JavaScript 中,`Number` 或 `String` 等基本类型是通过值而非引用传递的:  ### 模板引用 在使用组合式 API 时,响应式引用和模板引用的概念是统一的。为了获得对模板内元素或组件实例的引用,我们可以像往常一样声明 ref 并从 setup() 返回: - 作用在dom元素返回dom - 作用在组件返回组件实例 ```jsx <template> <div ref = "root" >This is a root element</div> </template> <script> import { ref , onMounted } from 'vue' export default { setup() { const root = ref ( null ) onMounted(() => { // DOM 元素将在初始渲染后分配给 ref console.log(root.value) // <div>This is a root element</div> }) return { root } } } </script> ``` ### ref在v-for中的使用 组合式 API 模板引用在 `v- for ` 内部使用时没有特殊处理。相反,请使用函数引用执行自定义处理: ```html <template> <div v- for = "(item, i) in list" : ref = "el => { if (el) divs[i] = el }" > {{ item }} </div> </template> <script> import { ref , reactive, onBeforeUpdate } from 'vue' export default { setup() { const list = reactive([1, 2, 3]) const divs = ref ([]) // 确保在每次更新之前重置ref onBeforeUpdate(() => { divs.value = [] }) return { list, divs } } } </script> ``` ### 模板改变后引用 侦听模板引用的变更可以替代前面例子中演示使用的生命周期钩子。 但与生命周期钩子的一个关键区别是,`watch()` 和 `watchEffect()` 在 DOM 挂载或更新*之前*运行副作用,所以当侦听器运行时,模板引用还未被更新。 ```vue <template> <div ref = "root" >This is a root element</div> </template> <script> import { ref , watchEffect } from 'vue' export default { setup() { const root = ref ( null ) watchEffect(() => { // 这个副作用在 DOM 更新之前运行,因此,模板引用还没有持有对元素的引用。 console.log(root.value) // => null }) return { root } } } </script> ``` 因此,使用模板引用的侦听器应该用 `flush: 'post' ` 选项来定义,这将在 DOM 更新*后*运行副作用,确保模板引用与 DOM 保持同步,并引用正确的元素。 ```vue <template> <div ref = "root" >This is a root element</div> </template> <script> import { ref , watchEffect } from 'vue' export default { setup() { const root = ref ( null ) watchEffect(() => { console.log(root.value) // => <div>This is a root element</div> }, { flush: 'post' }) return { root } } } </script> ``` ### reactive 使用reactive可以直接将对象变成响应式,甚至可以往对象中继续添加computed这样的属性,比如 ```js setup() { let rea = reactive({ name: "李雷" , count: 1, jisuan: computed(() => rea.count + 99) }) function increase() { rea.count++ } } ``` 这样写的好处就是,可以不需要每次在处理这个数据的使用想 ref 那样去.value了,但是这样写的时候也需要注意,返回数据的时候如果操作不当,那么就会丢失对数据的响应,比如下面这样 ```js return { ...rea, increase } //这样写能够展示数据,但是不能对数据保持响应 ``` 那这个时候我们就需要使用到下面个方法了,toRefs 一份完整的代码 ```jsx <template id= "test" > <fieldset> <legend>测试组件</legend> <button @click= "increaseCapacity()" >++</button> <h1>{{capacity}}</h1> <h2>{{spacesLeft}}</h2> </fieldset> </template> <scirpt> const { createApp,reactive, computed, toRefs } = Vue; let app = createApp({}) app.component( 'test-com' , { template: "#test" , setup() { let event = reactive({ capacity: 4, attending: [ "Tim" , "Bob" , "Joe" ], spaceLeft: computed(() => event .capacity- event .attending.length + 99) }) function increaseCapacity() { event .capacity++ } return { ... event , increaseCapacity } //这样写能够展示数据,但是不能对数据保持响应 } }) app.mount( '#app' ) </script> ``` ### ref和reactive的区别 1. reactive在使用的时候,会自动解包不需要像 ref 那样去.value 2. reactive不能对简单数据类型进行响应,需要在reactive中传入引用数据类型 ### toRefs 如果直接返回reactive对应的变量,那么会失去响应,正确的写法是将reactive的属性放到toRefs方法中,保持其响应式 <img src= "img/image-20220728155544943.png" alt= "image-20220728155544943" style= "zoom:45%;" /> 可以这样写 ```js return { ...toRefs( event ), increaseCapacity } ``` 如果不需要返回方法,只需要 event 那么也可以这样写 ```js return toRefs( event ) ``` ## 4.methods 如果我们想要让这个值增加,那么在composition的代码中应该如何编写?传统的方式我们可以使用mehods来定义方法,但是现在我们只需要写成传统函数就可以了 - 传统写法 <img src= "img/image-20220728154943165.png" alt= "image-20220728154943165" style= "zoom:50%;" /> - composition - 使用 ref 响应属性的时候,不然要忘记了.value属性 <img src= "img/image-20220728153215064.png" alt= "image-20220728153215064" style= "zoom:20%;" /> ## 5.computed属性 接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 [ ref ](https: //v3.cn.vuejs.org/api/refs-api.html#ref) 对象。 ```js const count = ref (1) const plusOne = computed(() => count.value + 1) console.log(plusOne.value) // 2 plusOne.value++ // 错误 ``` 或者,接受一个具有 ` get ` 和 ` set ` 函数的对象,用来创建可写的 ref 对象。 ```js const count = ref (1) const plusOne = computed({ get : () => count.value + 1, set : val => { count.value = val - 1 } }) plusOne.value = 1 console.log(count.value) // 0 ``` ## 6.生命周期 在composition中使用 "on" 来访问组件的钩子 created,beforeCreated在组合式api这种模式下,是没有的 ### 新增钩子 - errorCaptured 在捕获一个来自后代组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 ` false ` 以阻止该错误继续向上传播。 一般情况下可以用抽象Error组件,然后专门用于处理错误 单独创建一个叫做Error的组件 ```vue <template> <slot></slot> </template> <script setup> import { onErrorCaptured } from 'vue' ; onErrorCaptured((err, instance, info) => { console.log(err) console.log(instance) console.log(info) // 可以在这通过条件判断来决定是否要阻止错误的传播 //return true 继续传播 //return false 阻止错误的传播 return false }) </script> ``` 后续使用,将这个Error组件作为其它组件的父组件使用 ```vue <template> <fieldset> <legend>app</legend> <div id= "app" > <Error> <lifeCircle></lifeCircle> <reactiveCom></reactiveCom> <counter></counter> <computedCom></computedCom> </Error> </div> </fieldset> </template> <script setup> import reactiveCom from "./components/01.Reactive.vue" import counter from "./components/02.计数器/counter.vue" import computedCom from "./components/03.计算属性computed.vue" import lifeCircle from "./components/03.生命周期.vue" import Error from "./components/Error.vue" </script> ``` - renderTracked 跟踪虚拟 DOM 重新渲染时调用。钩子接收 `debugger event ` 作为参数。此事件告诉你哪个操作跟踪了组件以及该操作的目标对象和键。 ```js 当组件第一次渲染时,这将被记录下来 ``` - renderTriggered 当虚拟 DOM 重新渲染被触发时调用。和 [`renderTracked`](https: //v3.cn.vuejs.org/api/options-lifecycle-hooks.html#rendertracked) 类似,接收 `debugger event` 作为参数。此事件告诉你是什么操作触发了重新渲染,以及该操作的目标对象和键。 ```js import {onMounted} from "vue" export default { setup(){ onMounted(()=>{ console.log( 'onMounted' ) }) } } ``` | 选项式 API | Hook inside `setup` | | ------------------------------------------- | ------------------- | | `beforeCreate` | Not needed* | | `created` | Not needed* | | `beforeMount` | `onBeforeMount` | | `mounted` | `onMounted` | | `beforeUpdate` | `onBeforeUpdate` | | `updated` | `onUpdated` | | `beforeUnmount` | `onBeforeUnmount` | | `unmounted` | `onUnmounted` | | `errorCaptured` | `onErrorCaptured` | | `renderTracked`跟踪虚拟dom更新的时候,会调用 | `onRenderTracked` | | `renderTriggered` | `onRenderTriggered` | | `activated` | `onActivated` | | `deactivated` | `onDeactivated` |  ## 7.watch ### `watch` 响应式更改 就像我们在组件中使用 `watch` 选项并在 `user` property 上设置侦听器一样,我们也可以使用从 Vue 导入的 `watch` 函数执行相同的操作。它接受 3 个参数: - 一个想要侦听的**响应式引用**或 getter 函数 - 一个回调 - 可选的配置选项 **下面让我们快速了解一下它是如何工作的** ```js import { ref , watch } from 'vue' const counter = ref (0) watch(counter, (newValue, oldValue) => { console.log( 'The new counter value is: ' + counter.value) }) ``` 每当 `counter` 被修改时,例如 `counter.value=5`,侦听将触发并执行回调 (第二个参数),在本例中,它将把 ` 'The new counter value is:5' ` 记录到控制台中。 **以下是等效的选项式 API:** ```js export default { data() { return { counter: 0 } }, watch: { counter(newValue, oldValue) { console.log( 'The new counter value is: ' + this .counter) } } } ``` 有关 `watch` 的详细信息,请参阅我们的[深入指南](https: //v3.cn.vuejs.org/guide/reactivity-computed-watchers.html#watch)。 ### 在setup语法糖获取props 明确引入`defineProps`来获取props 如果是定义触发的事件名可以使用`defineEmits`来进行声明 ```vue <script setup> const props = defineProps({ foo: String }) const emit = defineEmits([ 'change' , 'delete' ]) // setup code </script> ``` **现在我们将其应用到我们的示例中:** ```js <script setup> import { watch, ref , defineProps, toRefs } from "vue" //监听props的变化 let props = defineProps({ msg: String, age: String }) // console.log(props) //1.监听写法,第一个参数传入一个函数,返回一个需要监听的字段 watch(() => props.msg, (newVal, oldVal) => { console.log( 'newVal' , newVal) console.log( 'oldVal' , oldVal) }) //2.第二种监听props变化的写法 //toRefs可以保持解构之后的响应性 let { age } = toRefs(props) watch(age, (newVal, oldVal) => { console.log( 'newVal' , newVal) console.log( 'oldVal' , oldVal) }) </sript> ``` 你可能已经注意到在我们的 `setup` 的顶部使用了 `toRefs`。这是为了确保我们的侦听器能够根据 `user` prop 的变化做出反应。 ### watch可以一次侦听多个 侦听器还可以使用数组同时侦听多个源: ```js const firstName = ref ( '' ) const lastName = ref ( '' ) watch([firstName, lastName], (newValues, prevValues) => { console.log(newValues, prevValues) }) firstName.value = 'John' // logs: ["John", ""] ["", ""] lastName.value = 'Smith' // logs: ["John", "Smith"] ["John", ""] ``` 尽管如此,如果你在同一个函数里同时改变这些被侦听的来源,侦听器仍只会执行一次: ```js setup() { const firstName = ref ( '' ) const lastName = ref ( '' ) watch([firstName, lastName], (newValues, prevValues) => { console.log(newValues, prevValues) }) const changeValues = () => { firstName.value = 'John' lastName.value = 'Smith' // 打印 ["John", "Smith"] ["", ""] } return { changeValues } } ``` ### 强制触发侦听器 > 多个同步更改只会触发一次侦听器。我们也办法强制触发 通过更改设置 `flush: 'sync' `,我们可以为每个更改都强制触发侦听器,尽管这通常是不推荐的。或者,可以用 [nextTick](https: //v3.cn.vuejs.org/api/global-api.html#nexttick) 等待侦听器在下一步改变之前运行。例如: ```js import {next} const changeValues = async () => { firstName.value = 'John' // 打印 ["John", ""] ["", ""] await nextTick() lastName.value = 'Smith' // 打印 ["John", "Smith"] ["John", ""] } ``` ### 监听对象中属性变化 ```vue <template> <div> <div>{{obj.name}}</div> <div>{{obj.age}}</div> <button @click= "changeName" >改变值</button> </div> </template> <script> import { reactive, watch } from 'vue' ; export default { setup(){ const obj = reactive({ name: 'zs' , age:14 }); const changeName = () => { obj.name = 'ls' ; }; watch(() => obj.name,() => { console.log( '监听的obj.name改变了' ) }) return { obj, changeName, } } } </script> ``` ### 深度监听 (deep)、默认执行(immediate) ```javascript <template> <div> <div>{{obj.brand.name}}</div> <button @click= "changeBrandName" >改变值</button> </div> </template> <script> import { reactive, ref , watch } from 'vue' ; export default { setup(){ const obj = reactive({ name: 'zs' , age:14, brand:{ id:1, name: '宝马' } }); const changeBrandName = () => { obj.brand.name = '奔驰' ; }; watch(() => obj.brand,() => { console.log( '监听的obj.brand.name改变了' ) },{ deep: true , immediate: true , }) return { obj, changeBrandName, } } } </script> ``` ## 8.watchEffect高级侦听器(变化) ### watchEffect 的使用 watchEffect 也是一个帧听器,是一个副作用函数。 它会监听引用数据类型的所有属性,不需要具体到某个属性,一旦运行就会立即监听,组件卸载的时候会停止监听。 ```javascript <template> <div> <input type= "text" v-model= "obj.name" > </div> </template> <script> import { reactive, watchEffect } from 'vue' ; export default { setup(){ let obj = reactive({ name: 'zs' }); watchEffect(() => { console.log( 'name:' ,obj.name) }) return { obj } } } </script> ``` <img src= "img/4.png" alt= "在这里插入图片描述" style= "zoom: 67%;" /> ### 停止侦听 当 watchEffect 在组件的 setup() 函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。 在一些情况下,也可以显式调用返回值以停止侦听: ```javascript <template> <div> <input type= "text" v-model= "obj.name" > <button @click= "stopWatchEffect" >停止监听</button> </div> </template> <script> import { reactive, watchEffect } from 'vue' ; export default { setup(){ let obj = reactive({ name: 'zs' }); const stop = watchEffect(() => { console.log( 'name:' ,obj.name) }) const stopWatchEffect = () => { console.log( '停止监听' ) stop(); } return { obj, stopWatchEffect, } } } </script> ```  ### onInvalidate 当执行副作用函数时,它势必会对系统带来一些影响,如在副作用函数里执行了一个定时器`setInterval`,因此我们必须处理副作用。`Vue3`的`watchEffect`侦听副作用传入的函数可以接收一个 `onInvalidate` 函数作为入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发: - 副作用即将重新执行时(即依赖的值改变) - 侦听器被停止 (通过显示调用返回值停止侦听,或组件被卸载时隐式调用了停止侦听) ```js import { watchEffect, ref } from 'vue' const count = ref (0) watchEffect((onInvalidate) => { console.log(count.value) onInvalidate(() => { console.log( '执行了onInvalidate' ) }) }) setTimeout(()=> { count.value++ }, 1000) ``` 上述代码打印的顺序为: `0` -> `执行了onInvalidate,最后执行` -> `1` ### 案例: 有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (场景:有一个页码组件里面有5个页码,点击就会异步请求数据。于是做一个监听,监听当前页码,只要有变化就请求一次。问题:如果点击的比较快,从1到5全点了一遍,那么会有5个请求,最终页面会显示第几页的内容?第5页?那是假定请求第5页的ajax响应的最晚,事实呢?并不一定。于是这就会导致错乱。还有一个问题,连续快速点5次页码,等于我并不想看前4页的内容,那么是不是前4次的请求都属于带宽浪费?这也不好。 于是官方就给出了一种解决办法: 侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参,用来注册清理失效时的回调。 当以下情况发生时,这个失效回调会被触发: - 副作用即将重新执行时; - 侦听器被停止 (如果在 setup() 或生命周期钩子函数中使用了 watchEffect,则在组件卸载时) ```javascript watchEffect(onInvalidate => { const token = performAsyncOperation(id.value) onInvalidate(() => { // id has changed or watcher is stopped. // invalidate previously pending async operation token.cancel() }) }) ``` 构建一个结构 ```javascript import axios from 'axios' ; import { ref , watchEffect } from 'vue' ; export default { setup() { let pageNumber = ref (1); let content = ref ( '' ); const changePageNumber = () => { pageNumber.value++; } watchEffect((onInvalidate) => { // const CancelToken = axios.CancelToken; // const source = CancelToken.source(); // onInvalidate(() => { // source.cancel(); // }); axios. get (`http: //chst.vip:1234/api/getmoneyctrl?pageid=${pageNumber.value}`, { // cancelToken: source.token, }).then((response) => { content.value = response.data.result[0].productName; }). catch (function (err) { if (axios.isCancel(err)) { console.log( 'Request canceled' , err.message); } }); }); return { pageNumber, content, changePageNumber, }; }, }; </script> ``` 上面注释掉的代码**先保持注释**,然后经过多次疯狂点击之后,得到这个结果,显然,内容错乱了:  现在**取消注释**,重新多次疯狂点击,得到的结果就正确了:  除了最后一个请求,上面那些请求有2种结局: - 一种是响应的太快,来不及取消的请求,这种请求会返回200,不过既然它响应太快,没有任何一次后续 ajax 能够来得及取消它,说明任何一次后续请求开始之前,它就已经结束了,那么它一定会被后续某些请求所覆盖,所以这类请求的 content 会显示一瞬间,然后被后续的请求覆盖,绝对不会比后面的请求还晚。 - 另一种就是红色的那些被取消的请求,因为响应的慢,所以被取消掉了。 所以最终结果一定是正确的,而且节省了很多带宽,也节省了系统开销。 ### 面试题 当一个用户快速的点击分页的时候,导致页面数据加载混乱怎么处理? 答:我用的是vue3,会在watchEffect的onInvalidate中调用axios.cancel这个方法来取消上一个axios请求就可以了 如果你在vue2中会怎么做? 如果是这样的话,可以使用防抖的思路,用户一直点击的时候,总是取消上一个axios请求,停止点击之后会发送最后一个请求 ### watchEffect的配置项 watchEffect(()=>{},{flush: "post" }),第二个参数是个配置项,有个属性叫flush,有几个值 - pre dom加载之前运行 - post dom加载之后运行watchEffect的回调 - sync 如果在相同的函数中修改多个数据,而这些数据被watchEffect侦听了,只会触发一次回调,如果希望每次修改都触发,那么可以加上flush: 'sync' ### 总结 **watch 特点** watch 监听函数可以添加配置项,也可以配置为空,配置项为空的情况下,watch的特点为: - 有惰性:运行的时候,不会立即执行; - 更加具体:需要添加监听的属性; - 可访问属性之前的值:回调函数内会返回最新值和修改之前的值; - 可配置:配置项可补充 watch 特点上的不足: immediate:配置 watch 属性是否立即执行,值为 true 时,一旦运行就会立即执行,值为 false 时,保持惰性。 deep:配置 watch 是否深度监听,值为 true 时,可以监听对象所有属性,值为 false 时保持更加具体特性,必须指定到具体的属性上。 **watchEffect 特点** - 非惰性:一旦运行就会立即执行; - 更加抽象:使用时不需要具体指定监听的谁,回调函数内直接使用就可以; - 不可访问之前的值:只能访问当前最新的值,访问不到修改之前的值; ## 9.自定义指令的使用 ## vite代理配置 ```js // vite.config.js import { defineConfig } from "vite" ; export default defineConfig({ server: { proxy: { "/api" : { target: "http://localhost:3001" , changeOrigin: true , rewrite: (path) => path.replace(/^\/api/, "" ), }, }, }, }); ``` ## 响应式原理 vue3的响应式主要使用了es6的reflect和proxy  |
右侧赞助一下 代码改变世界一块二块也是爱
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
2020-11-08 数据类型分类
2020-11-08 重复赋值 数据交换 查看程序执行结果
2020-11-08 JS注释 JS变量