Composables como store en VueJs 3

Vue 22 de oct. de 2021

Holas, justamente estoy trabajando en un proyecto personal y se me ocurrió la loca idea de crear un global store sin usar vuex, con las nuevas adiciones de vue 3 de provide, inject, y composables. Si no están muy enterados de como van estas nuevas features se las resumo.

¿Qué es Provide / Inject?

Usualmente para comunicar datos entre componentes se utiliza los props y los emits, que estos tieneden que ser opciones explícitas y/o morosas, en el sentido de que hay que formar un "camino" entre los componentes para recibir o enviar datos. Y ahí es donde llega el provider/injection al rescate, donde en el provider declaramos la data y los eventos que se transmitirán a los hijos y con el inject interceptamos esto sin necesidad de ese "camino". Si desean ver la documentacion oficial Provide / inject.

¿Qué son los Composibles?

Los composables es la nueva manera de reutilizar funcionalidades similar a los customHooks en react, se puede acceder al api de vue y formar la logica dentro de este para luego exponer los metodos o propiedades que quieras que sean accesibles, la documentacion da unos cuantos ejemplos Composables.

Contexto del experimento

Queremos crear un store usando composables y provide / inject donde podamos decir que es un root store de la aplicación, como ejemplo una feature de login en la App, donde administramos el estado de autentificación desde un composable y para interactuar con este usaremos providers e injections.

Codeando

useAuth.js

import { ref, provide, readonly } from "vue";
import { useRouter } from "vue-router";
import { getItemLocalStore } from "../lib/localStoreItem";

export const useAuth = () => {
    const router = useRouter();
    const isAuthenticated = ref(getItemLocalStore("isAuthenticated", false));

    const login = ({ name, password }) => {
        if (name == "admin" && password == "admin") {
            isAuthenticated.value = true;
            localStorage.setItem("isAuthenticated", isAuthenticated.value);
            router.push({ name: "Home" });
        }
    };

    const logout = () => {
        isAuthenticated.value = false;
        localStorage.setItem("isAuthenticated", isAuthenticated.value);
        router.push({ name: "Login" });
    };

    provide("auth_login", login);
    provide("auth_logout", logout);
    provide("isAuthenticated", readonly(isAuthenticated));

    return {
        isAuthenticated: readonly(isAuthenticated),
        login,
        logout,
    };
};

router/index.js

import { createRouter, createWebHistory } from "vue-router";
import { getItemLocalStore } from "../lib/localStoreItem";

const Login = () => import("../views/Login.vue");
const Home = () => import("../views/Home.vue");

const routes = [
    {
        path: "/",
        component: Home,
        meta: { auth: true },
        name: "Home",
    },
    {
        path: "/login",
        component: Login,
        meta: { auth: false },
        name: "Login",
    },
];

const router = createRouter({
    history: createWebHistory(),
    routes,
});

router.beforeEach((to, _, next) => {
    const isAuthenticated = getItemLocalStore("isAuthenticated", false);
    if (to.meta.auth && !isAuthenticated) {
        next({ name: "Login" });
    } else if (!to.meta.auth && isAuthenticated) {
        next({ name: "Home" });
    } else {
        next();
    }
});

export default router;

App.vue

<script setup>
    import Header from "./components/Header.vue";
    import { useAuth } from "./composables/useAuth";
    useAuth();
</script>

<template>
    <div class="app">
        <Header />
        <router-view></router-view>
    </div>
</template>

Con estos tres archivos ya estamos listos para poder ya administrar todo el estado de auth en nuestra aplicación.
Y de esta manera podemos inyectar auth_login para poder loguearnos desde la vista de Login. Y si necesitáramos alguna otra funcionalidad relacionada a auth es fácil agregarlo e inyectamos esa nueva característica en donde lo necesitemos sin estar creando el camino entre componentes.

views/Login.vue

<script setup>
    import { inject, ref } from "vue";
    const loginProvided = inject("auth_login");

    const login = () => {
        loginProvided(data.value);
    };

    const data = ref({
        name: "",
        password: "",
    });
</script>

<template>
    <div class="login">
        <h1>Iniciar Sesión</h1>
        <form @submit.prevent="login()" class="login__fields">
            <label for="name">Nombre</label>
            <input id="name" type="text" v-model="data.name" />
            <label for="password">Contraseña</label>
            <input id="password" type="password" v-model="data.password" />
            <button type="submit">Loguearse</button>
        </form>
    </div>
</template>

Conclusión y limitaciones

El desarrollo del login fue sencillo, y termina siendo fácil de gestionar la autenticación dentro de la aplicación, y nos estaríamos olvidando de usar vuex para gestionar nuestro estado en este tipo de casos. Pero para proyectos grandes me sigo quedando con vuex por lo normado que esta. Ya que usando composables con provides/injections nos puede salir cualquier cosa, si no seguimos estándares en la implementación al desarrollar estos, y también evitaria usar este tipo de implementaciones en proyectos medianos a grandes con TypeScript por lo moroso de ir casteando los tipos en cada inyección.

Espero que les vea gustado este experimento, los veo en otro blog.
Si desean ver el código completo les dejo el repositorio: https://github.com/Avsco/vue-composable-auth.

Etiquetas

¿Te gustó el contenido o lo que hacemos? ¡Cualquier colaboración es agradecida para mantener los servidores o crear proyectos!

Comentarios:

¡Genial! Te has suscrito con éxito.
¡Genial! Ahora, completa el checkout para tener acceso completo.
¡Bienvenido de nuevo! Has iniciado sesión con éxito.
Éxito! Su cuenta está totalmente activada, ahora tienes acceso a todo el contenido.