返回 slidev
slides.md
根目录 / demo / composable-vue / slides.md
1 ---
2 layout: cover
3 download: 'https://antfu.me/talks/2021-04-29'
4 highlighter: shiki
5 monaco: true
6 info: |
7 ## Composable Vue
8
9 Patterns and tips for writing good composable logic in Vue
10
11 [Anthony Fu](https://antfu.me/) at [VueDay 2021](https://2021.vueday.it/)
12
13 - [Recording](https://www.youtube.com/watch?v=IMJjP6edHd0)
14 - [Transcript](https://antfu.me/posts/composable-vue-vueday-2021)
15 - [Source code](https://github.com/antfu/talks/tree/master/2021-04-29)
16 ---
17
18 # Composable Vue
19
20 Patterns and tips for writing good composable logic in Vue
21
22 <div class="uppercase text-sm tracking-widest">
23 Anthony Fu
24 </div>
25
26 <div class="abs-bl mx-14 my-12 flex">
27 <img src="https://2020.vueday.it/img/themes/vueday/vueday-logo.png" class="h-8" alt="">
28 <div class="ml-3 flex flex-col text-left">
29 <div><b>Vue</b>Day</div>
30 <div class="text-sm opacity-50">Apr. 29th, 2021</div>
31 </div>
32 </div>
33
34 ---
35 layout: 'intro'
36 ---
37
38 # Anthony Fu
39
40 <div class="leading-8 opacity-80">
41 Vue core team member and Vite team member.<br>
42 Creator of VueUse, i18n Ally and Type Challenges.<br>
43 A fanatical full-time open sourceror.<br>
44 </div>
45
46 <div class="my-10 grid grid-cols-[40px_1fr] w-min gap-y-4">
47 <ri-github-line class="opacity-50"/>
48 <div><a href="https://github.com/antfu" target="_blank">antfu</a></div>
49 <ri-twitter-line class="opacity-50"/>
50 <div><a href="https://twitter.com/antfu7" target="_blank">antfu7</a></div>
51 <ri-user-3-line class="opacity-50"/>
52 <div><a href="https://antfu.me" target="_blank">antfu.me</a></div>
53 </div>
54
55 <img src="https://antfu.me/avatar.png" class="rounded-full w-40 abs-tr mt-16 mr-12" alt="A comic art image of Anthony Fu"/>
56
57 ---
58 name: Sponsors
59 layout: center
60 ---
61
62 <img class="h-100 -mt-10" src="https://cdn.jsdelivr.net/gh/antfu/static/sponsors.png" alt="A list of the sponsor logos" /><br>
63 <div class="text-center text-xs opacity-50 -mt-8 hover:opacity-100">
64 <a href="https://github.com/sponsors/antfu" target="_blank">
65 Sponsor me at GitHub
66 </a>
67 </div>
68
69 ---
70 layout: center
71 ---
72
73 # Composable Vue
74
75 ---
76 name: VueUse
77 layout: center
78 ---
79
80 <div class="grid grid-cols-[3fr_2fr] gap-4">
81 <div class="text-center pb-4">
82 <img class="h-50 inline-block" src="https://vueuse.org/favicon.svg" alt="">
83 <div class="opacity-50 mb-2 text-sm">
84 Collection of essential Vue Composition Utilities
85 </div>
86 <div class="text-center">
87 <a class="!border-none" href="https://www.npmjs.com/package/@vueuse/core" target="__blank"><img class="h-4 inline mx-0.5" src="https://img.shields.io/npm/v/@vueuse/core?color=a1b858&label=" alt="NPM version"></a>
88 <a class="!border-none" href="https://www.npmjs.com/package/@vueuse/core" target="__blank"><img class="h-4 inline mx-0.5" alt="NPM Downloads" src="https://img.shields.io/npm/dm/@vueuse/core?color=50a36f&label="></a>
89 <a class="!border-none" href="https://vueuse.org" target="__blank"><img class="h-4 inline mx-0.5" src="https://img.shields.io/static/v1?label=&message=docs%20%26%20demos&color=1e8a7a" alt="Docs & Demos"></a>
90 <img class="h-4 inline mx-0.5" alt="Function Count" src="https://img.shields.io/badge/-114%20functions-13708a">
91 <br>
92 <a class="!border-none" href="https://github.com/vueuse/vueuse" target="__blank"><img class="mt-2 h-4 inline mx-0.5" alt="GitHub stars" src="https://img.shields.io/github/stars/vueuse/vueuse?style=social"></a>
93 </div>
94 </div>
95 <div class="border-l border-main !all:leading-12 !all:list-none my-auto">
96
97 - Works for both Vue 2 and 3
98 - Tree-shakeable ESM
99 - CDN compatible
100 - TypeScript
101 - Rich ecosystems
102
103 </div>
104 </div>
105
106 ---
107 layout: center
108 class: text-center
109 ---
110
111 # Composition API
112
113 a brief go-through
114
115 ---
116
117 <div class="grid grid-cols-2 gap-x-4"><div>
118
119 # Ref
120
121 ```ts {monaco}
122 import { ref } from 'vue'
123
124 let foo = 0
125 let bar = ref(0)
126
127 foo = 1
128 bar = 1 // ts-error
129 ```
130
131 <div v-click>
132
133 ### Pros
134
135 - More explicit, with type checking
136 - Less caveats
137
138 ### Cons
139
140 - `.value`
141
142 </div>
143
144 </div><div>
145
146 # Reactive
147
148 ```ts {monaco}
149 import { reactive } from 'vue'
150
151 const foo = { prop: 0 }
152 const bar = reactive({ prop: 0 })
153
154 foo.prop = 1
155 bar.prop = 1
156 ```
157
158 <div v-click>
159
160 ### Pros
161
162 - Auto unwrapping (a.k.a `.value` free)
163
164 ### Cons
165
166 - Same as plain objects on types
167 - Destructure loses reactivity
168 - Need to use callback for `watch`
169
170 </div>
171 </div></div>
172
173 ---
174
175 # Ref Auto Unwrapping <MarkerCore />
176
177 Get rid of `.value` for most of the time.
178
179 <div class="grid grid-cols-2 gap-x-4">
180
181 <v-clicks :every='2'>
182
183 - `watch` accepts ref as the watch target, and returns the unwrapped value in the callback
184
185 ```ts
186 const counter = ref(0)
187
188 watch(counter, (count) => {
189 console.log(count) // same as `counter.value`
190 })
191 ```
192
193 - Ref is auto unwrapped in the template
194
195 ```html
196 <template>
197 <button @click="counter += 1">
198 Counter is {{ counter }}
199 </button>
200 </template>
201 ```
202
203 - Reactive will auto-unwrap nested refs.
204
205 <div>
206
207 ```ts {monaco}
208 import { reactive, ref } from 'vue'
209
210 const foo = ref('bar')
211 const data = reactive({ foo, id: 10 })
212 data.foo // 'bar'
213 ```
214
215 </div>
216
217 </v-clicks>
218
219 </div>
220
221 ---
222
223 # `unref` - Opposite of Ref <MarkerCore />
224
225 - If it gets a Ref, returns the value of it.
226 - Otherwise, returns as-is.
227
228 <div class="grid grid-cols-2 gap-x-4 mt-4">
229
230 <div v-click>
231
232 ### Implementation
233
234 ```ts
235 function unref<T>(r: Ref<T> | T): T {
236 return isRef(r) ? r.value : r
237 }
238 ```
239
240 </div><div v-click>
241
242 ### Usage
243
244 ```ts {monaco}
245 import { ref, unref } from 'vue'
246
247 const foo = ref('foo')
248 unref(foo) // 'foo'
249
250 const bar = 'bar'
251 unref(bar) // 'bar'
252 ```
253
254 </div></div>
255
256 ---
257 layout: center
258 class: text-center
259 ---
260
261 # Patterns & Tips
262
263 of writing composable functions
264
265 ---
266
267 # What's Composable Functions
268
269 Sets of reusable logic, separation of concerns.
270
271 <div v-click class="grid grid-cols-[1fr_130px]">
272
273 ```ts
274 export function useDark(options: UseDarkOptions = {}) {
275 const preferredDark = usePreferredDark() // <--
276 const store = useLocalStorage('vueuse-dark', 'auto') // <--
277
278 return computed<boolean>({
279 get() {
280 return store.value === 'auto'
281 ? preferredDark.value
282 : store.value === 'dark'
283 },
284 set(v) {
285 store.value = v === preferredDark.value
286 ? 'auto'
287 : v ? 'dark' : 'light'
288 },
289 })
290 }
291 ```
292
293 <div class="grid">
294 <DarkToggle class="m-auto"/>
295 </div>
296
297 </div>
298
299 <div v-click class="abs-b mx-14 my-12">
300 <VueUse name="useDark"/>
301 </div>
302
303 ---
304
305 # Think as "Connections"
306
307 The `setup()` only runs **once** on component initialization, to construct the relations between your state and logic.
308
309 - Input → Output<sup class="ml-1 opacity-50">Effects</sup>
310 - Output reflects to input's changes automatically
311
312 <div class="grid grid-cols-[auto_1fr] gap-4">
313 <Connections v-click class="mt-4"/>
314 <div v-click class="p-4">
315 <h3 class="pb-2">SpreadSheet Formula</h3>
316 <img class="h-40" src="https://cdn.wallstreetmojo.com/wp-content/uploads/2019/01/Division-Formula-in-Excel-Example-1-1.png" alt="">
317 </div>
318 </div>
319
320 ---
321
322 # One Thing at a Time
323
324 Just the same as authoring JavaScript functions.
325
326 - Extract duplicated logics into composable functions
327 - Have meaningful names
328 - Consistent naming conversions - `useXX` `createXX` `onXX`
329 - Keep function small and simple
330 - "Do one thing, and do it well"
331
332 ---
333
334 # Passing Refs as Arguments <MarkerPattern />
335
336 <div class="grid grid-cols-[160px_1fr_180px] gap-x-4">
337
338 <div />
339
340 ### Implementation
341
342 ### Usage
343
344 <v-clicks :every='3'>
345
346 <div class="my-auto leading-6 text-base opacity-75">
347 Plain function
348 </div>
349
350 ```ts
351 function add(a: number, b: number) {
352 return a + b
353 }
354 ```
355
356 ```ts
357 const a = 1
358 const b = 2
359
360 const c = add(a, b) // 3
361 ```
362
363 <div class="my-auto leading-6 text-base opacity-75">
364 Accepts refs,<br>
365 returns a reactive result.
366 </div>
367
368 ```ts
369 function add(a: Ref<number>, b: Ref<number>) {
370 return computed(() => a.value + b.value)
371 }
372 ```
373
374 ```ts
375 const a = ref(1)
376 const b = ref(2)
377
378 const c = add(a, b)
379 c.value // 3
380 ```
381
382 <div class="my-auto leading-6 text-base opacity-75">
383 Accepts both refs and plain values.
384 </div>
385
386 ```ts
387 function add(
388 a: Ref<number> | number,
389 b: Ref<number> | number,
390 ) {
391 return computed(() => unref(a) + unref(b))
392 }
393 ```
394
395 ```ts
396 const a = ref(1)
397
398 const c = add(a, 5)
399 c.value // 6
400 ```
401
402 </v-clicks>
403
404 </div>
405
406 ---
407
408 # MaybeRef <MarkerTips/>
409
410 A custom type helper
411
412 ```ts
413 type MaybeRef<T> = Ref<T> | T
414 ```
415
416 <v-click>
417
418 In VueUse, we use this helper heavily to support optional reactive arguments
419
420 ```ts
421 export function useTimeAgo(
422 time: Date | number | string | Ref<Date | number | string>,
423 ) {
424 return computed(() => someFormating(unref(time)))
425 }
426 ```
427
428 ```ts {monaco}
429 import type { Ref } from 'vue'
430 import { computed, unref } from 'vue'
431
432 type MaybeRef<T> = Ref<T> | T
433
434 export function useTimeAgo(
435 time: MaybeRef<Date | number | string>,
436 ) {
437 return computed(() => someFormating(unref(time)))
438 }
439 ```
440
441 </v-click>
442
443 ---
444
445 # Make it Flexible <MarkerPattern />
446
447 Make your functions like LEGO, can be used with different components in different ways.
448
449 <div class="grid grid-cols-2 gap-x-4">
450
451 <div v-click>
452
453 ### Create a "Special" Ref
454
455 ```ts {monaco}
456 import { useTitle } from '@vueuse/core'
457
458 const title = useTitle()
459
460 title.value = 'Hello World'
461 // now the page's title changed
462 ```
463
464 </div><div v-click>
465
466 ### Binding an Existing Ref
467
468 ```ts {monaco}
469 import { useTitle } from '@vueuse/core'
470 import { computed, ref } from 'vue'
471
472 const name = ref('Hello')
473 const title = computed(() => {
474 return `${name.value} - World`
475 })
476
477 useTitle(title) // Hello - World
478
479 name.value = 'Hi' // Hi - World
480 ```
481
482 </div></div>
483
484 <div v-click class="abs-b mx-14 my-12">
485 <VueUse name="useTitle"/>
486 </div>
487
488 ---
489
490 # `useTitle` <Marker class="text-blue-400">Case</Marker>
491
492 Take a look at `useTitle`'s implementation
493
494 <div class="grid grid-cols-2 gap-4">
495 <v-clicks>
496
497 ```ts {monaco}
498 import type { MaybeRef } from '@vueuse/core'
499 import { ref, watch } from 'vue'
500
501 export function useTitle(
502 newTitle: MaybeRef<string | null | undefined>,
503 ) {
504 const title = ref(newTitle || document.title)
505
506 watch(title, (t) => {
507 if (t != null)
508 document.title = t
509 }, { immediate: true })
510
511 return title
512 }
513 ```
514
515 ```html
516
517 <-- 1. use the user provided ref or create a new one
518
519 <-- 2. sync ref changes to the document title
520
521 ```
522
523 </v-clicks>
524 </div>
525
526 ---
527
528 # "Reuse" Ref <MarkerCore />
529
530 <v-clicks>
531
532 If you pass a `ref` into `ref()`, it will return the original ref as-is.
533
534 ```ts
535 const foo = ref(1) // Ref<1>
536 const bar = ref(foo) // Ref<1>
537
538 foo === bar // true
539 ```
540
541 ```ts
542 function useFoo(foo: Ref<string> | string) {
543 // no need!
544 const bar = isRef(foo) ? foo : ref(foo)
545
546 // they are the same
547 const bar = ref(foo)
548
549 /* ... */
550 }
551 ```
552
553 Extremely useful in composable functions that take uncertain argument types.
554
555 </v-clicks>
556
557 ---
558
559 # `ref` / `unref` <MarkerTips />
560
561 <div v-click>
562
563 - `MaybeRef<T>` works well with `ref` and `unref`.
564 - Use `ref()` when you want to normalized it as a Ref.
565 - Use `unref()` when you want to have the value.
566
567 <br>
568
569 ```ts
570 type MaybeRef<T> = Ref<T> | T
571
572 function useBala<T>(arg: MaybeRef<T>) {
573 const reference = ref(arg) // get the ref
574 const value = unref(arg) // get the value
575 }
576 ```
577
578 </div>
579
580 ---
581
582 # Object of Refs <MarkerPattern />
583
584 Getting benefits from both `ref` and `reactive` for authoring composable functions
585
586 <div class="mt-1" />
587 <div class="grid grid-cols-2 gap-x-4">
588 <v-clicks>
589
590 ```ts {monaco}
591 import { reactive, ref } from 'vue'
592
593 function useMouse() {
594 return {
595 x: ref(0),
596 y: ref(0),
597 }
598 }
599
600 const { x, y } = useMouse()
601 const mouse = reactive(useMouse())
602
603 mouse.x === x.value // true
604 ```
605
606 <div class="px-2 py-4">
607
608 - Destructurable as Ref
609 - Convert to reactive object to get the auto-unwrapping when needed
610
611 </div>
612
613 </v-clicks>
614 </div>
615
616 ---
617
618 # Async to "Sync" <MarkerTips />
619
620 With Composition API, we can actually turn async data into "sync"
621
622 <div v-click>
623
624 ### Async
625
626 ```ts
627 const data = await fetch('https://api.github.com/').then(r => r.json())
628
629 // use data
630 ```
631
632 </div>
633 <div v-click>
634
635 ### Composition API
636
637 ```ts
638 const { data } = useFetch('https://api.github.com/').json()
639
640 const user_url = computed(() => data.value?.user_url)
641 ```
642
643 </div>
644 <div v-click>
645
646 Establish the "Connections" first, then wait for data to be filled up. The idea is similar to SWR (stale-while-revalidate)
647
648 </div>
649
650 ---
651
652 # `useFetch` <Marker class="text-blue-400">Case</Marker>
653
654 <v-click>
655
656 ```ts
657 export function useFetch<R>(url: MaybeRef<string>) {
658 const data = shallowRef<T | undefined>()
659 const error = shallowRef<Error | undefined>()
660
661 fetch(unref(url))
662 .then(r => r.json())
663 .then(r => data.value = r)
664 .catch(e => error.value = e)
665
666 return {
667 data,
668 error,
669 }
670 }
671 ```
672
673 </v-click>
674
675 <div v-click class="abs-b mx-14 my-12">
676 <VueUse name="useFetch"/>
677 </div>
678
679 ---
680
681 # Side-effects Self Cleanup <MarkerPattern />
682
683 The `watch` and `computed` will stop themselves on components unmounted.<br>
684 We'd recommend following the same pattern for your custom composable functions.
685
686 <div v-click>
687
688 ```ts {monaco}
689 import { onUnmounted } from 'vue'
690
691 export function useEventListener(target: EventTarget, name: string, fn: any) {
692 target.addEventListener(name, fn)
693
694 onUnmounted(() => {
695 target.removeEventListener(name, fn) // <--
696 })
697 }
698 ```
699
700 </div>
701
702 <div v-click class="abs-b mx-14 my-12">
703 <VueUse name="useEventListener"/>
704 </div>
705
706 <!--
707 Lower the mental burden
708 -->
709
710 ---
711
712 # `effectScope` RFC <Marker class="text-purple-400">Upcoming</Marker>
713
714 A new API to collect the side effects automatically. Likely to be shipped with Vue 3.1<br>
715 https://github.com/vuejs/rfcs/pull/212
716
717 ```ts
718 // effect, computed, watch, watchEffect created inside the scope will be collected
719
720 const scope = effectScope(() => {
721 const doubled = computed(() => counter.value * 2)
722
723 watch(doubled, () => console.log(double.value))
724
725 watchEffect(() => console.log('Count: ', double.value))
726 })
727
728 // dispose all effects in the scope
729 stop(scope)
730 ```
731
732 ---
733 disabled: true
734 ---
735
736 # Template Ref <MarkerTips />
737
738 To get DOM element, you can pass a ref to it, and it will be available after component mounted
739
740 <div v-click>
741
742 ```ts {monaco}
743 import { defineComponent, onMounted, ref } from 'vue'
744
745 export default defineComponent({
746 setup() {
747 const element = ref<HTMLElement | undefined>()
748
749 onMounted(() => {
750 element.value // now you have it
751 })
752
753 return { element }
754 },
755 })
756 ```
757
758 ```html {monaco}
759 <template>
760 <div ref="element"><!-- ... --></div>
761 </template>
762 ```
763
764 </div>
765
766 ---
767 disabled: true
768 ---
769
770 # Template Ref <MarkerTips />
771
772 Use `watch` instead of `onMounted` to unify the handling for template ref changes.
773
774 <div>
775 <v-click>
776
777 ```ts {monaco}
778 import { defineComponent, ref, watch } from 'vue'
779
780 export default defineComponent({
781 setup() {
782 const element = ref<HTMLElement | undefined>()
783
784 watch(element, (el) => {
785 // clean up previous side effect
786 if (el) {
787 // use the DOM element
788 }
789 })
790
791 return { element }
792 },
793 })
794 ```
795
796 </v-click>
797 </div>
798
799 ---
800
801 # Typed Provide / Inject <MarkerCore/>
802
803 Use the `InjectionKey<T>` helper from Vue to share types across context.
804
805 <div v-click>
806
807 ```ts {monaco}
808 // context.ts
809 import type { InjectionKey } from 'vue'
810
811 export interface UserInfo {
812 id: number
813 name: string
814 }
815
816 export const injectKeyUser: InjectionKey<UserInfo> = Symbol('user')
817 ```
818
819 </div>
820
821 ---
822
823 # Typed Provide / Inject <MarkerCore/>
824
825 Import the key from the same module for `provide` and `inject`.
826
827 <div class="grid grid-cols-2 gap-4">
828 <v-clicks>
829
830 ```ts {monaco}
831 // parent.vue
832 import { provide } from 'vue'
833 import { injectKeyUser } from './context'
834
835 export default {
836 setup() {
837 provide(injectKeyUser, {
838 id: '7', // type error: should be number
839 name: 'Anthony',
840 })
841 },
842 }
843 ```
844
845 ```ts {monaco}
846 // child.vue
847 import { inject } from 'vue'
848 import { injectKeyUser } from './context'
849
850 export default {
851 setup() {
852 const user = inject(injectKeyUser)
853 // UserInfo | undefined
854
855 if (user)
856 console.log(user.name) // Anthony
857 },
858 }
859 ```
860
861 </v-clicks>
862 </div>
863
864 ---
865
866 # Shared State <MarkerPattern />
867
868 By the nature of Composition API, states can be created and used independently.
869
870 <div class="grid grid-cols-2 gap-4">
871
872 <v-click>
873
874 ```ts
875 // shared.ts
876 import { reactive } from 'vue'
877
878 export const state = reactive({
879 foo: 1,
880 bar: 'Hello',
881 })
882 ```
883
884 </v-click>
885
886 <div>
887 <v-clicks>
888
889 ```ts
890 // A.vue
891 import { state } from './shared.ts'
892
893 state.foo += 1
894 ```
895
896 ```ts
897 // B.vue
898 import { state } from './shared.ts'
899
900 console.log(state.foo) // 2
901 ```
902
903 </v-clicks>
904 </div>
905 </div>
906
907 <h3 v-click class="opacity-100">⚠️ But it's not SSR compatible!</h3>
908
909 ---
910
911 # Shared State (SSR friendly) <MarkerPattern />
912
913 Use `provide` and `inject` to share the app-level state
914
915 <div class="grid grid-cols-[max-content_1fr] gap-4">
916
917 <v-click>
918
919 ```ts
920 export const myStateKey: InjectionKey<MyState> = Symbol('state')
921
922 export function createMyState() {
923 const state = {
924 /* ... */
925 }
926
927 return {
928 install(app: App) {
929 app.provide(myStateKey, state)
930 },
931 }
932 }
933
934 export function useMyState(): MyState {
935 return inject(myStateKey)!
936 }
937 ```
938
939 </v-click>
940
941 <div>
942 <v-clicks>
943
944 ```ts
945 // main.ts
946 const App = createApp(App)
947
948 app.use(createMyState())
949 ```
950
951 ```ts
952 // A.vue
953
954 // use everywhere in your app
955 const state = useMyState()
956 ```
957
958 <div class="my-3">
959
960 - [Vue Router v4](https://github.com/vuejs/vue-router-next) is using the similar approach
961
962 </div>
963
964 </v-clicks>
965 </div>
966
967 </div>
968
969 ---
970
971 # useVModel <MarkerTips />
972
973 A helper to make props/emit easier
974
975 <div class="grid grid-cols-2 gap-4">
976
977 <v-click>
978
979 ```ts
980 export function useVModel(props, name) {
981 const emit = getCurrentInstance().emit
982
983 return computed({
984 get() {
985 return props[name]
986 },
987 set(v) {
988 emit(`update:${name}`, v)
989 },
990 })
991 }
992 ```
993
994 </v-click>
995
996 <div>
997
998 <v-click>
999
1000 ```ts
1001 export default defineComponent({
1002 setup(props) {
1003 const value = useVModel(props, 'value')
1004
1005 return { value }
1006 },
1007 })
1008 ```
1009
1010 </v-click>
1011 <br>
1012 <v-click>
1013
1014 ```html
1015 <template>
1016 <input v-model="value" />
1017 </template>
1018 ```
1019
1020 </v-click>
1021 </div>
1022 </div>
1023
1024 <div v-click class="abs-b mx-14 my-12">
1025 <VueUse name="useVModel"/>
1026 </div>
1027
1028 ---
1029 disabled: true
1030 ---
1031
1032 # useVModel (Passive) <MarkerTips />
1033
1034 Make the model able to be updated **independently** from the parent logic
1035
1036 <v-click>
1037
1038 ```ts
1039 export function usePassiveVModel(props, name) {
1040 const emit = getCurrentInstance().emit
1041 const data = ref(props[name]) // store the value in a ref
1042
1043 watch(() => props.value, v => data.value = v) // sync the ref whenever the prop changes
1044
1045 return computed({
1046 get() {
1047 return data.value
1048 },
1049 set(v) {
1050 data.value = v // when setting value, update the ref directly
1051 emit(`update:${name}`, v) // then emit out the changes
1052 },
1053 })
1054 }
1055 ```
1056
1057 </v-click>
1058
1059 ---
1060 layout: center
1061 ---
1062
1063 # All of them work for both Vue 2 and 3
1064
1065 ---
1066
1067 # `@vue/composition-api` <Marker class="text-teal-400">Lib</Marker>
1068
1069 Composition API support for Vue 2.<br><carbon-logo-github class="inline-block"/> [vuejs/composition-api](https://github.com/vuejs/composition-api)
1070
1071 ```ts
1072 import VueCompositionAPI from '@vue/composition-api'
1073 import Vue from 'vue'
1074
1075 Vue.use(VueCompositionAPI)
1076 ```
1077
1078 ```ts
1079 import { reactive, ref } from '@vue/composition-api'
1080 ```
1081
1082 ---
1083
1084 # Vue 2.7 <Marker class="text-purple-400">Upcoming</Marker>
1085
1086 [Plans in Vue 2.7](https://github.com/vuejs/rfcs/blob/ie11/active-rfcs/0000-vue3-ie11-support.md#for-those-who-absolutely-need-ie11-support)
1087
1088 - Backport `@vue/composition-api` into Vue 2's core.
1089 - `<script setup>` syntax in Single-File Components.
1090 - Migrate codebase to TypeScript.
1091 - IE11 support.
1092 - LTS.
1093
1094 ---
1095
1096 # Vue Demi <Marker class="text-teal-400">Lib</Marker>
1097
1098 Creates Universal Library for Vue 2 & 3<br><carbon-logo-github class="inline-block"/> [vueuse/vue-demi](https://github.com/vueuse/vue-demi)
1099
1100 ```ts
1101 // same syntax for both Vue 2 and 3
1102 import { defineComponent, reactive, ref } from 'vue-demi'
1103 ```
1104
1105 <img class="h-50 mx-auto" src="https://cdn.jsdelivr.net/gh/vueuse/vue-demi/assets/banner.png" alt="" />
1106
1107 ---
1108
1109 # Recap
1110
1111 - Think as "Connections"
1112 - One thing at a time
1113 - Accepting ref as arguments
1114 - Returns an object of refs
1115 - Make functions flexible
1116 - Async to "sync"
1117 - Side-effect self clean up
1118 - Shared state
1119
1120 ---
1121 layout: center
1122 class: 'text-center pb-5 :'
1123 ---
1124
1125 # Thank You!
1126
1127 Slides can be found on [antfu.me](https://antfu.me)
1128
1128 lines MARKDOWN