Vue: Composables и TS это вам не Mixins и JS. С ними сложнее

от автора

Пришел к хитрому паттерну. Делюсь.

Будет полезен тем кому нравиться или приходится работать с Vue.
В подходящей ситуации он сэкономит кучу времени и поможет избежать дублирования кода.

Контекст

  • Есть несколько компонентов.

  • Компоненты должны выполнять одинаковую сложную логику.

  • Компоненты должны принимать одинаковые свойства и эмитить одинаковые события.

  • Свойства из composable могут быть опциональными со значениями по умолчанию.

  • Каждый компонент выглядит совершенно по разному (разная вёрстка).

  • Каждый компонент, опционально, в дополнение к общему, может:

    • принимать собственные свойства

    • эмитить собственные события

    • выполнять дополнительную логику.

Пожелания к реализации

  • Избежать дублирования событий и свойств.

  • Избежать дублирования логики.

Проблематика : Composables vs Mixins

Раньше, во Vue2 без TS и с mixin-ами вместо composables,
можно было легко вынести логику (функции-методы) в mixin,
описать в этом mixin-е общие свойства,
события вообще не нужно было типизировать — их достаточно было просто эмитить.
Потом этот mixin нужно было подключить к компоненту, и вуаля! Всё работает.
В чем-то похоже на mixin-ы из классического ООП.

Теперь, во Vue3 + TS + composables
нужно хитро жонглировать типами,
чтобы добиться поведения, схожего с тем,
которое можно было реализовать на Vue2 mixin-ами без типизации.

Минималистичный пример решения проблемы

// composable useSomething.ts  // Объявляем и экспортируем типы общих свойств... export type Props = {   propFromSomething?: string } // ... значения по умолчанию для общих свойств ... export const DEFAULT_PROPS = {   propFromSomething: 'bar', } // ... и общие события. export type Emit = {   // Трюк с передачей эмитов в composable работает с такой сигнатурой.   // Таким-же способом нужно описывать события и в компоненте который использует этот composable.    (event: 'eventFromSomething', payload: string): void;   // Например так не работает:   // eventFromSomething: [value: string] }  // Вышеописанные типы ни в какой файл не выносим, // храним в том же файле, что и сам composable. // Потому что они будут нужны лишь в самом composable, и в тех компонентах,  // в которых применяется данный composable.  export function useSomething({ props, emit }: {   // Props из компонента при помощи intersection включает в себя Props из composable,   // поэтому с типизацией всё в порядке.   // Required, потому что опциональные свойства заменятся дефолтовыми при передаче,   // и все будут заполнены значениями.   props: Required<Props>,   // emit из компонента при помощи intersection включает в себя Emit из composable,   // поэтому с типизацией всё в порядке.   emit: Emit, }) {   async function methodFromSomething() {     emit('eventFromSomething', props.propFromSomething)   }    return {     methodFromSomething   } } 
// Component.ts import {   useSomething,   type Props as PropsFromSomething,   DEFAULT_PROPS as DEFAULT_PROPS_FROM_SOMETHING,   type Emit as EmitFromSomething, } from "./useSomething";  // Добавляем к свойствам компонента свойства из composable. export type Props = {   propFromComponent?: string } & PropsFromSomething  // Оттуда-же добавляем дефолтовые значения. const props = withDefaults(defineProps<Props>(), {   propFromComponent: 'foo',   ...DEFAULT_PROPS_FROM_SOMETHING })  // И добавляем к событиям компонента события из composable. const emit = defineEmits<{   (event: 'eventFromComponent', payload: boolean): void; } & EmitFromSomething>()  const { methodFromSomething } = useSomething({ props, emit }) 

В приведенном минималистичном примере все выглядит просто,
но дойти до этого было сложнее.

Ничего подобного я не видел ни в руководстве по Vue, ни в обучающих материалах.
Комплексный универсальный пример не могли предложить ни StackOverflow, ни нейросети.

В документации по Vue вообще написано что эмитить события из composable — плохая практика.
С чем лично я не согласен.

Пришлось искать решение самостоятельно.


ссылка на оригинал статьи https://habr.com/ru/articles/927214/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *