赞助
posts - 449,comments - 12,views - 11万
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 使我们能够做到的
 
![重构后的文件夹组件](https://user-images.githubusercontent.com/499550/62783026-810e6180-ba89-11e9-8774-e7771c8095d6.png)
 
## 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` 等基本类型是通过值而非引用传递的:
 
![按引用传递与按值传递](https://blog.penjee.com/wp-content/uploads/2015/02/pass-by-reference-vs-pass-by-value-animation.gif)
 
 
 
### 模板引用
 
在使用组合式 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`     |
 
![组件生命周期图示](https://cn.vuejs.org/assets/lifecycle.16e4c08e.png)
 
## 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>
```
 
![在这里插入图片描述](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1375eaaec5cb41139d14c47277f44612~tplv-k3u1fbpfcp-zoom-in-crop-mark:3024:0:0:0.awebp)
 
### 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>
 
 
```
 
上面注释掉的代码**先保持注释**,然后经过多次疯狂点击之后,得到这个结果,显然,内容错乱了:
 
![在这里插入图片描述](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/52c46d680203449eb17b159efb96dee9~tplv-k3u1fbpfcp-zoom-in-crop-mark:3024:0:0:0.awebp)
 
现在**取消注释**,重新多次疯狂点击,得到的结果就正确了:
 
![在这里插入图片描述](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/61472e6fffd543aea6d247b5bfc42cf2~tplv-k3u1fbpfcp-zoom-in-crop-mark:3024:0:0:0.awebp)
 
除了最后一个请求,上面那些请求有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
 
![image-20220728152059790](img/vue3-2.png)

  

posted on   Tsunami黄嵩粟  阅读(62)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
历史上的今天:
2020-11-08 数据类型分类
2020-11-08 重复赋值 数据交换 查看程序执行结果
2020-11-08 JS注释 JS变量
< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5

点击右上角即可分享
微信分享提示