vue

14 nov 2024

Thin composables pattern

Descobri essa semana que a minha forma preferida de trabalhar com composables tem um nome bonito, “thin composables pattern”.

A ideia é a seguinte: ao invés de ter toda a lógica de alguma feature dentro de um grande composable ou em um só lugar (numa tela, por exemplo), separamos a regra de negócio de um lado e uma camada fina (thin layer) de reatividade do outro. Ou seja, toda parte de negócio fica em um arquivo js/ts “puro” e a parte que lida com os aspectos específicos do Vue e de implementação fica em um composable menor, mantendo-o apenas orquestrando a parte reativa, ciclos de vida, propriedades computadas, watchers, etc, que serão usados dentro dos componentes ou de telas.

Isso nos concede uma boa organização de código e separação de responsabilidades. Cada parte trabalha com uma única coisa. O que mais gosto é utilizar vários composables (pequenas features) em uma tela, assim consigo organizar loaders, tratativa de erros e demais pontos específicos da aplicação. Como os composables podem trabalhar também com o ciclo de vida, ou seja, chamando funções como onMounted, isso abre ainda mais possibilidades de uso.

/* /composables/useNotes.ts */
interface UseNotes {
  user: Ref<User | undefined>
}

export function useNotes({ user }: UseNotes) {
  const services = useServices()
  const logger = useLogger()

  const loading = ref(true)
  const notes = ref<Note[]>()

  const getNotes = async () => {
    if (!user.value) return

    try {
      loading.value = true

      const response = await services.getNotes({ userId: user.value.id })
      // if (!response) { createError() }

      notes.value = response
    } catch (error) {
      logger(error)
    } finally {
      loading.value = false
    }
  }

  onMounted(() => getNotes())

  return { loading, notes }
}

Uma nota sobre SSR: no exemplo acima a função getNotes é chamada quando o composable é iniciado. Essa é uma ótima implementação para códigos rodando no cliente. Como a camada de serviço fica separada, podemos chamar direto a função (do serviço) dentro de um useAsyncData e utilizar de todo o poder do Nuxt para tratar isso no lado do servidor, tendo assim todos os benefícios do SSR. Isso tudo depende do caso de uso, mas fica simples mudar a implementação, caso necessário.

É a maneira mais elegante de trabalhar com Vue? Provavelmente não. Eu particularmente sempre usei assim (ou algo parecido com isso), e vi esse tipo de implementação ser citada em cursos e, mais recenetemente, nesse podcast, então acho que estou no caminho, não?

Como tudo na área de desenvolvimento, é só mais uma das milhares de maneiras de resolver os mesmos problemas.