Índice
Escribir aplicaciones Vue a gran escala puede ser un desafío. Usar el estado compartido en sus aplicaciones Vue 3 puede ser una solución para reducir esta complejidad. Hay varias soluciones comunes para resolver el estado. En este artículo, profundizaré en los pros y los contras de enfoques como fábricas, objetos compartidos y el uso de Vuex. También les mostraré lo que viene en Vuex 5 que podría cambiar la forma en que todos usamos el estado compartido en Vue 3.
El estado puede ser difícil. Cuando iniciamos un proyecto Vue simple, puede resultar sencillo simplemente mantener nuestro estado de trabajo en un componente en particular:
setup() { let books: Work[] = reactive([]); onMounted(async () = { // Call the API const response = await bookService.getScienceBooks(); if (response.status === 200) { books.splice(0, books.length, ...response.data.works); } }); return { books };},
Cuando su proyecto es una sola página que muestra datos (tal vez para ordenarlos o filtrarlos), esto puede resultar atractivo. Pero en este caso, este componente obtendrá datos en cada solicitud. ¿Qué pasa si quieres conservarlo? Ahí es donde entra en juego la gestión estatal. Como las conexiones de red suelen ser costosas y en ocasiones poco confiables, sería mejor mantener este estado mientras navega por una aplicación.
Otro problema es la comunicación entre componentes. Si bien puede utilizar eventos y accesorios para comunicarse directamente con los padres e hijos, manejar situaciones simples como el manejo de errores y las banderas de ocupado puede ser difícil cuando cada una de sus vistas/páginas es independiente. Por ejemplo, imagine que tiene un control de nivel superior conectado para mostrar el error y la animación de carga:
// App.vuetemplate div router-link to="/"h1Bookcase/h1/router-link div v-if="error"{{ error }}/div div v-if="isBusy" Loading... /div router-view :key="$route.fullPath"/router-view /div/template
Sin una forma efectiva de manejar este estado, se podría sugerir un sistema de publicación/suscripción, pero de hecho, compartir datos es más sencillo en muchos casos. Si quieres tener un estado compartido, ¿cómo lo haces? Veamos algunas formas comunes de hacer esto.
Nota : encontrará el código de esta sección en la rama "principal" del proyecto de ejemplo en GitHub .
Estado compartido en Vue 3
Desde que me mudé a Vue 3, migré completamente para usar la API de composición. Para el artículo, también estoy usando TypeScript, aunque no es necesario en los ejemplos que les muestro. Si bien puedes compartir el estado de la forma que quieras, te mostraré varias técnicas que encuentro que son los patrones más utilizados. Cada uno tiene sus pros y sus contras, así que no tomes nada de lo que hablo aquí como dogma.
Las técnicas incluyen:
- Fábricas ,
- Singletons compartidos ,
- Vuex 4 ,
- Vuex5 .
Nota : Vuex 5, al momento de escribir este artículo, se encuentra en la etapa RFC (Solicitud de comentarios), por lo que quiero prepararlo para saber hacia dónde se dirige Vuex, pero en este momento no existe una versión funcional de esta opción.
Vamos a profundizar en…
Fábricas
Nota : El código de esta sección se encuentra en la rama "Fábricas" del proyecto de ejemplo en GitHub .
El patrón de fábrica consiste simplemente en crear una instancia del estado que le interesa. En este patrón, devuelve una función que es muy parecida a la función de inicio en la API de composición. Crearías un alcance y construirías los componentes de lo que estás buscando. Por ejemplo:
export default function () { const books: Work[] = reactive([]); async function loadBooks(val: string) { const response = await bookService.getBooks(val, currentPage.value); if (response.status === 200) { books.splice(0, books.length, ...response.data.works); } } return { loadBooks, books };}
Puede solicitar solo las partes de los objetos creados en fábrica que necesita de esta manera:
// In Home.vue const { books, loadBooks } = BookFactory();
Si agregamos una isBusy
bandera para mostrar cuándo ocurre la solicitud de red, el código anterior no cambia, pero usted puede decidir dónde va a mostrar isBusy
:
export default function () { const books: Work[] = reactive([]); const isBusy = ref(false); async function loadBooks(val: string) { isBusy.value = true; const response = await bookService.getBooks(val, currentPage.value); if (response.status === 200) { books.splice(0, books.length, ...response.data.works); } } return { loadBooks, books, isBusy };}
En otra vista (¿vue?), podrías solicitar el indicador isBusy sin tener que saber cómo funciona el resto de la fábrica:
// App.vueexport default defineComponent({ setup() { const { isBusy } = BookFactory(); return { isBusy } },})
Pero es posible que hayas notado un problema; Cada vez que llamamos a la fábrica, obtenemos una nueva instancia de todos los objetos. Hay ocasiones en las que desea que una fábrica devuelva nuevas instancias, pero en nuestro caso estamos hablando de compartir el estado, por lo que debemos mover la creación fuera de la fábrica:
const books: Work[] = reactive([]);const isBusy = ref(false);async function loadBooks(val: string) { isBusy.value = true; const response = await bookService.getBooks(val, currentPage.value); if (response.status === 200) { books.splice(0, books.length, ...response.data.works); }}export default function () { return { loadBooks, books, isBusy };}
Ahora la fábrica nos proporciona una instancia compartida o un singleton si lo prefiere. Si bien este patrón funciona, puede resultar confuso devolver una función que no crea una nueva instancia cada vez.
Debido a que los objetos subyacentes están marcados, const
no debería poder reemplazarlos (y romper la naturaleza única). Entonces este código debería quejarse:
// In Home.vue const { books, loadBooks } = BookFactory(); books = []; // Error, books is defined as const
Por lo tanto, puede ser importante asegurarse de que el estado mutable se pueda actualizar (por ejemplo, usar books.splice()
en lugar de asignar los libros).
Otra forma de manejar esto es utilizar instancias compartidas.
Instancias compartidas
El código de esta sección se encuentra en la rama "SharedState" del proyecto de ejemplo en GitHub .
Si va a compartir el estado, también podría tener claro el hecho de que el estado es un producto único. En este caso, puede importarse simplemente como un objeto estático. Por ejemplo, me gusta crear un objeto que pueda importarse como objeto reactivo:
export default reactive({ books: new ArrayWork(), isBusy: false, async loadBooks() { this.isBusy = true; const response = await bookService.getBooks(this.currentTopic, this.currentPage); if (response.status === 200) { this.books.splice(0, this.books.length, ...response.data.works); } this.isBusy = false; }});
En este caso, simplemente importa el objeto (al que llamo tienda en este ejemplo):
// Home.vueimport state from "@/state";export default defineComponent({ setup() { // ... onMounted(async () = { if (state.books.length === 0) state.loadBooks(); }); return { state, bookTopics, }; },});
Entonces resulta fácil vincularse al Estado:
!-- Home.vue --div div v-for="book in state.books" :key="book.key" router-link :to="{ name: 'book', params: { id: book.key } }" BookInfo :book="book" / /router-link/div
Al igual que los otros patrones, obtienes el beneficio de poder compartir esta instancia entre vistas:
// App.vueimport state from "@/state";export default defineComponent({ setup() { return { state }; },})
Luego, esto puede vincularse al mismo objeto (ya sea una página principal Home.vue
u otra página en el enrutador):
!-- App.vue -- div router-link to="/"h1Bookcase/h1/router-link div v-if="state.isBusy"Loading.../div router-view :key="$route.fullPath"/router-view /div
Ya sea que utilice el patrón de fábrica o la instancia compartida, ambos tienen un problema común: el estado mutable. Puede tener efectos secundarios accidentales de enlaces o cambios de estado de código cuando no lo desea. En un ejemplo trivial como el que estoy usando aquí, no es lo suficientemente complejo como para preocuparse. Pero a medida que crea aplicaciones cada vez más grandes, querrá pensar más detenidamente en la mutación de estado. Ahí es donde Vuex puede venir al rescate.
Vuex 4
El código para esta sección se encuentra en la rama "Vuex4" del proyecto de ejemplo en GitHub .
Vuex es el administrador estatal de Vue. Fue construido por el equipo central aunque se gestiona como un proyecto independiente. El propósito de Vuex es separar el estado de las acciones que desea realizarle. Todos los cambios de estado tienen que pasar por Vuex, lo que significa que es más complejo, pero obtienes protección contra cambios de estado accidentales.
La idea de Vuex es proporcionar un flujo predecible de gestión estatal. Las vistas fluyen hacia acciones que, a su vez, utilizan mutaciones para cambiar el estado, lo que, a su vez, actualiza la vista. Al limitar el flujo de cambio de estado, debería tener menos efectos secundarios que cambien el estado de sus aplicaciones; Por lo tanto, será más fácil crear aplicaciones más grandes. Vuex tiene una curva de aprendizaje, pero con esa complejidad se obtiene previsibilidad.
Además, Vuex admite herramientas de tiempo de desarrollo (a través de Vue Tools) para trabajar con la gestión del estado, incluida una función llamada viaje en el tiempo. Esto le permite ver un historial del estado y avanzar y retroceder para ver cómo afecta a la aplicación.
También hay momentos en los que Vuex también es importante.
Para agregarlo a su proyecto Vue 3, puede agregar el paquete al proyecto:
npm i vuex
O, alternativamente, puede agregarlo utilizando Vue CLI:
vue add vuex
Al usar la CLI, creará un punto de partida para su tienda Vuex; de lo contrario, deberá conectarla manualmente al proyecto. Veamos cómo funciona esto.
Primero, necesitará un objeto de estado creado con la función createStore de Vuex:
import { createStore } from 'vuex'export default createStore({ state: {}, mutations: {}, actions: {}, getters: {}});
Como puede ver, la tienda requiere que se definan varias propiedades. El estado es solo una lista de los datos a los que desea darle acceso a su aplicación:
import { createStore } from 'vuex'export default createStore({ state: { books: [], isBusy: false }, mutations: {}, actions: {}});
Tenga en cuenta que el estado no debería utilizar envoltorios ref o reactivos . Estos datos son el mismo tipo de datos compartidos que utilizamos con las instancias o fábricas compartidas. Esta tienda será un singleton en su aplicación, por lo tanto, los datos del estado también se compartirán.Te recomendamos Recetas faciles y rápidas
A continuación, veamos las acciones. Las acciones son operaciones que desea habilitar y que involucran al estado. Por ejemplo:
actions: { async loadBooks(store) { const response = await bookService.getBooks(store.state.currentTopic, if (response.status === 200) { // ... } } },
A las acciones se les pasa una instancia de la tienda para que pueda acceder al estado y otras operaciones. Normalmente, desestructuraríamos sólo las partes que necesitamos:
actions: { async loadBooks({ state }) { const response = await bookService.getBooks(state.currentTopic, if (response.status === 200) { // ... } } },
La última parte de esto son las mutaciones. Las mutaciones son funciones que pueden mutar de estado. Sólo las mutaciones pueden afectar el estado. Entonces, para este ejemplo, necesitamos mutaciones que cambien el estado:
mutations: { setBusy: (state) = state.isBusy = true, clearBusy: (state) = state.isBusy = false, setBooks(state, books) { state.books.splice(0, state.books.length, ...books); } },
Las funciones de mutación siempre pasan el objeto de estado para que puedas mutar ese estado. En los dos primeros ejemplos, puede ver que estamos configurando explícitamente el estado. Pero en el tercer ejemplo, pasamos el estado a set. Las mutaciones siempre toman dos parámetros: el estado y el argumento al llamar a la mutación.
Para llamar a una mutación, usarías la función de confirmación en la tienda. En nuestro caso, simplemente lo agregaré a la desestructuración:
actions: { async loadBooks({ state, commit }) { commit("setBusy"); const response = await bookService.getBooks(state.currentTopic, if (response.status === 200) { commit("setBooks", response.data); } commit("clearBusy"); } },
Lo que verá aquí es cómo la confirmación requiere el nombre de la acción. Hay trucos para hacer que esto no solo use hilos mágicos, pero voy a saltearlos por ahora. Este uso de cuerdas mágicas es una de las limitaciones del uso de Vuex.
Si bien el uso de commit puede parecer un contenedor innecesario, recuerde que Vuex no le permitirá mutar el estado excepto dentro de la mutación, por lo tanto, solo lo harán las llamadas a través de commit .
También puedes ver que la llamada a setBooks toma un segundo argumento. Este es el segundo argumento que se denomina mutación. Si necesitara más información, deberá empaquetarla en un solo argumento (otra limitación de Vuex actualmente). Suponiendo que necesitara insertar un libro en la lista de libros, podría llamarlo así:
commit("insertBook", { book, place: 4 }); // object, tuple, etc.
Entonces podrías simplemente desestructurarlo en las piezas que necesitas:
mutations: { insertBook(state, { book, place }) = // ... }
¿Es esto elegante? Realmente no, pero funciona.
Ahora que nuestra acción funciona con mutaciones, necesitamos poder usar la tienda Vuex en nuestro código. Realmente hay dos formas de llegar a la tienda. Primero, al registrar la tienda con la aplicación (por ejemplo, main.ts/js), tendrá acceso a una tienda centralizada a la que tendrá acceso en cualquier lugar de su aplicación:
// main.tsimport store from './store'createApp(App) .use(store) .use(router) .mount('#app')
Tenga en cuenta que esto no es agregar Vuex, sino la tienda real que está creando. Una vez agregado esto, puedes simplemente llamar useStore
para obtener el objeto de la tienda:
import { useStore } from "vuex";export default defineComponent({ components: { BookInfo, }, setup() { const store = useStore(); const books = computed(() = store.state.books); // ...
Esto funciona bien, pero prefiero importar la tienda directamente:
import store from "@/store";export default defineComponent({ components: { BookInfo, }, setup() { const books = computed(() = store.state.books); // ...
Ahora que tienes acceso al objeto de la tienda, ¿cómo lo usas? Para el estado, necesitarás envolverlos con funciones calculadas para que los cambios se propaguen a tus enlaces:
export default defineComponent({ setup() { const books = computed(() = store.state.books); return { books }; },});
Para invocar acciones, deberá llamar al método de envío :
export default defineComponent({ setup() { const books = computed(() = store.state.books); onMounted(async () = await store.dispatch("loadBooks")); return { books }; },});
Las acciones pueden tener parámetros que agrega después del nombre del método. Por último, para cambiar el estado, deberá llamar al compromiso tal como lo hicimos dentro de las Acciones. Por ejemplo, tengo una propiedad de paginación en la tienda y luego puedo cambiar el estado con commit :
const incrementPage = () = store.commit("setPage", store.state.currentPage + 1);const decrementPage = () = store.commit("setPage", store.state.currentPage - 1);
Tenga en cuenta que llamarlo así arrojaría un error (porque no puede cambiar el estado manualmente):
const incrementPage = () = store.state.currentPage++; const decrementPage = () = store.state.currentPage--;
Este es el verdadero poder aquí, queremos controlar dónde se cambia el estado y no tener efectos secundarios que produzcan errores más adelante en el desarrollo.
Es posible que se sienta abrumado por la cantidad de piezas móviles en Vuex, pero realmente puede ayudar a administrar el estado en proyectos más grandes y complejos. No diría que lo necesitas en todos los casos, pero habrá grandes proyectos en los que te ayudará en general.
El gran problema de Vuex 4 es que trabajar con él en un proyecto TypeScript deja mucho que desear. Ciertamente puedes crear tipos de TypeScript para ayudar en el desarrollo y la compilación, pero requiere muchas piezas móviles.
Ahí es donde Vuex 5 pretende simplificar cómo funciona Vuex en TypeScript (y en proyectos de JavaScript en general). Veamos cómo funcionará una vez que se lance a continuación.
Vuex 5
Nota : El código para esta sección se encuentra en la rama "Vuex5" del proyecto de ejemplo en GitHub .
En el momento de escribir este artículo, Vuex 5 no es real. Es un RFC (Solicitud de comentarios). Es un plan. Es un punto de partida para el debate. Así que mucho de lo que puedo explicar aquí probablemente cambiará un poco. Pero para prepararte para el cambio en Vuex, quería darte una idea de hacia dónde se dirige. Debido a esto, el código asociado con este ejemplo no se compila.
Los conceptos básicos de cómo funciona Vuex no han cambiado desde sus inicios. Con la introducción de Vue 3, se creó Vuex 4 principalmente para permitir que Vuex trabaje en nuevos proyectos. Pero el equipo está tratando de detectar los verdaderos problemas con Vuex y resolverlos. Para ello están planeando algunos cambios importantes:
- No más mutaciones: las acciones pueden mutar el estado (y posiblemente a cualquier persona).
- Mejor soporte para TypeScript.
- Mejor funcionalidad multitienda.
Entonces, ¿cómo funcionaría esto? Comencemos creando la tienda:
export default createStore({ key: 'bookStore', state: () = ({ isBusy: false, books: new ArrayWork() }), actions: { async loadBooks() { try { this.isBusy = true; const response = await bookService.getBooks(); if (response.status === 200) { this.books = response.data.works; } } finally { this.isBusy = false; } } }, getters: { findBook(key: string): Work | undefined { return this.books.find(b = b.key === key); } }});
El primer cambio a ver es que cada tienda ahora necesita su propia clave. Esto es para permitirle recuperar múltiples tiendas. A continuación, notará que el objeto de estado ahora es una fábrica (por ejemplo, regresa de una función, no creada durante el análisis). Y ya no hay sección de mutaciones. Por último, dentro de las acciones, puede ver que accedemos al estado solo como propiedades en el this
puntero. Ya no tendrás que pasar por el estado y comprometerte con las acciones. Esto no solo ayuda a simplificar el desarrollo, sino que también facilita la inferencia de tipos para TypeScript.
Para registrar Vuex en su aplicación, deberá registrar Vuex en lugar de su tienda global:
import { createVuex } from 'vuex'createApp(App) .use(createVuex()) .use(router) .mount('#app')
Finalmente, para usar la tienda, importará la tienda y luego creará una instancia de la misma:
import bookStore from "@/store";export default defineComponent({ components: { BookInfo, }, setup() { const store = bookStore(); // Generate the wrapper // ...
Observe que lo que se devuelve de la tienda es un objeto de fábrica que devuelve esta instancia de la tienda, sin importar cuántas veces llame a la fábrica. El objeto devuelto es solo un objeto con las acciones, el estado y los captadores como ciudadanos de primera clase (con información de tipo):
onMounted(async () = await store.loadBooks());const incrementPage = () = store.currentPage++;const decrementPage = () = store.currentPage--;
Lo que verá aquí es que los estados (p. ej. currentPage
) son simplemente propiedades simples. Y las acciones (por ejemplo loadBooks
) son solo funciones. El hecho de que estés usando una tienda aquí es un efecto secundario. Puede tratar el objeto Vuex como solo un objeto y continuar con su trabajo. Esta es una mejora significativa en la API.
Otro cambio que es importante señalar es que también puedes generar tu tienda usando una sintaxis similar a la API de composición:
export default defineStore("another", () = { // State const isBusy = ref(false); const books = reactive(new Array≷Work()); // Actions async function loadBooks() { try { this.isBusy = true; const response = await bookService.getBooks(this.currentTopic, this.currentPage); if (response.status === 200) { this.books = response.data.works; } } finally { this.isBusy = false; } } findBook(key: string): Work | undefined { return this.books.find(b = b.key === key); } // Getters const bookCount = computed(() = this.books.length); return { isBusy, books, loadBooks, findBook, bookCount }});
Esto le permite crear su objeto Vuex tal como lo haría con sus vistas con la API de composición y posiblemente sea más simple.
Un principal inconveniente de este nuevo diseño es que se pierde la no mutabilidad del estado. Hay discusiones sobre la posibilidad de habilitar esto (solo para desarrollo, al igual que Vuex 4), pero no hay consenso sobre cuán importante es esto. Personalmente creo que es un beneficio clave para Vuex, pero tendremos que ver cómo se desarrolla.
¿Dónde estamos?
Administrar el estado compartido en aplicaciones de una sola página es una parte crucial del desarrollo de la mayoría de las aplicaciones. Tener un plan de juego sobre cómo desea hacerlo en Vue es un paso importante en el diseño de su solución. En este artículo, le mostré varios patrones para administrar el estado compartido, incluido lo que viene para Vuex 5. Con suerte, ahora tendrá el conocimiento para tomar la decisión correcta para sus propios proyectos.
(vf, yk, il)Explora más en
- vista
- Aplicaciones
- javascript
Tal vez te puede interesar:
- ¿Deberían abrirse los enlaces en ventanas nuevas?
- 24 excelentes tutoriales de AJAX
- 70 técnicas nuevas y útiles de AJAX y JavaScript
- Más de 45 excelentes recursos y repositorios de fragmentos de código
Gestión del estado compartido en Vue 3
FábricasInstancias compartidasVuex 4Vuex 5Tal vez te puede interesar:SmashingConf New York 2024 Typography Masterclass, with Elliot Jay Stocks Índice
programar
es
2025-01-15
Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente