Inicio Aplicacion TODO con VUE + PINIA
Post
Cancel

Aplicacion TODO con VUE + PINIA

PINIA

Pinia comenzó como un experimento para rediseñar cómo se vería una tienda para Vue con la API de composición alrededor de noviembre de 2019. Desde entonces, los principios iniciales siguen siendo los mismos, pero Pinia funciona tanto para Vue 2 como para Vue 3 y no requiere que usted para usar la API de composición. La API es la misma para ambos excepto para la instalación y SSR, y estos documentos están dirigidos a Vue 3 con notas sobre Vue 2 siempre que sea necesario para que los usuarios de Vue 2 y Vue 3 puedan leerlos. Sitio oficial

VUEX PINIA (VUEX 5)

¿Por qué debería usar Pinia?

Pinia es una biblioteca de tiendas para Vue, le permite compartir un estado entre componentes/páginas. Si está familiarizado con la API de composición, es posible que esté pensando que ya puede compartir un estado global con una exportación simple const state = reactive({}). Esto es cierto para las aplicaciones de una sola página, pero expone su aplicación a vulnerabilidades de seguridad si se representa en el lado del servidor. Pero incluso en aplicaciones pequeñas de una sola página, obtienes mucho del uso de Pinia:

  • Soporte de herramientas de desarrollo
    • Una línea de tiempo para rastrear acciones, mutaciones.
    • Las tiendas aparecen en los componentes donde se utilizan
    • Viaje en el tiempo y depuración más fácil
  • Reemplazo de módulo caliente
    • Modifica tus tiendas sin recargar tu página
    • Mantener cualquier estado existente mientras se desarrolla
  • Complementos: amplíe las funciones de Pinia con complementos
  • Compatibilidad adecuada con TypeScript o autocompletado para usuarios de JS -Soporte de representación del lado del servidor

Creacion de Proyecto

  • Crear Proyecto con Vite
1
npm init vue@latest

VUE CLI CLI de Vue

  • Limpiar App.vue y borrar componente HelloWorld.vue
1
2
3
4
5
6
7
8
9
10
<template>
Hello World!
</template>

<script>

export default {
  name: 'App',
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <link rel="icon" href="/favicon.ico" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g==" crossorigin="anonymous"
    referrerpolicy="no-referrer" />
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Vite App</title>
</head>

<body>
  <div id="app"></div>
  <script type="module" src="/src/main.js"></script>
</body>

</html>

Modificar App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
  <div class="container">
    <todo-app />
  </div>

</template>

<script>
import TodoApp from './components/TodoApp.vue'

export default {
  components: { TodoApp },
  name: 'App',
}
</script>

<style>
body {
  background-image: url(./assets/bg-desktop-dark.jpg);
  background-repeat: no-repeat;
  background-size: 100% 200px;
  background-color:#222222
}

.container {
  margin-top: 70px;
  max-width: 700px;
}
</style>

Modificar main.js

1
2
3
4
5
6
7
8
9
10
11
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

//import './assets/main.css'

const app = createApp(App)

app.use(createPinia())

app.mount('#app')

Crear Store

  • Buscar dentro de src una carpeta llamada stores y dentro crear un archivo TodoStore.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import { defineStore } from 'pinia'

export const useTodoStore = defineStore({
    id: 'todoStore',
    state: () => ({
        titulo: 'ToDo con PINIA',
        todoList: [],
        estado: 'all'
    }),
    getters: {
        todosGetter(state) {
            if (state.estado === 'all') {
                return state.todoList
            }
            if (state.estado === 'pendientes') {
                return state.todoList.filter(item => item.estado === false)
            }
            if (state.estado === 'completados') {
                return state.todoList.filter(item => item.estado === true)
            }
        },
        estados(state) {
            return state.estado
        },
        contarPendientes(state) {
            return state.todoList.filter(item => item.estado === false).length
        }
    },
    actions: {
        agregarTodoAction(todo) {
            this.todoList.push(todo)
        },
        borrarAction(id) {
            this.todoList = this.todosGetter.filter(item => item.id !== id)
        },
        completadoAction(id) {
            this.todoList = this.todosGetter.map(item => {
                if (item.id === id) {
                    item.estado = !item.estado
                }
                return item
            })
        },
        eliminarCompletadosAction() {
            this.todoList = this.todosGetter.filter(item => item.estado === false)
        },
        filtroAction(valor) {
            this.estado = valor
        }
    }
})

Componente TodoApp.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>
    <h1>{ { todoStore.titulo } }</h1>
    <todo-formulario />
    <todo-listado />
</template>

<script>
import { useTodoStore } from '../stores/TodoStore'
import TodoFormulario from './TodoFormulario.vue'
import TodoListado from './TodoListado.vue'
export default {
    components: { TodoFormulario, TodoListado },
    setup() {
        const todoStore = useTodoStore()

        todoStore.$subscribe((mutation, state) => {
            localStorage.setItem('pinia', JSON.stringify(state.todoList));
        });

        if (localStorage.getItem('pinia')) {
            todoStore.todoList = JSON.parse(localStorage.getItem('pinia'));
        }

        return { todoStore }
    }
}
</script>

<style>
h1 {
    color: #ffffff;
}
</style>

Componente TodoFormulario.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<template>
    <form @submit.prevent="agregarToDo">
        <input
          type="text"
          class="form-control my-3"
          placeholder="Ingresar ToDo"
          v-model.trim="texto">
    </form>
</template>

<script>
import { ref } from '@vue/runtime-core'
import { useTodoStore } from '../stores/TodoStore'
export default {
    setup(){
        const todoStore = useTodoStore()
        const texto = ref('')
        
        const agregarToDo = () => {
            if(texto.value === ''){
                console.log('vacio')
                return
            }
            const todo = {
                descripcion: texto.value,
                estado: false,
                id: Date.now()
            }

            todoStore.agregarTodoAction(todo)

            texto.value = ''
        }
        return { agregarToDo, texto }
    }
}
</script>

<style>
input[type="text"] {
    font-family: sans-serif;
    font-size: 18px;
    font-weight: 400;
    line-height: 12px;
    color: #d8d8d8;
    border: none;
    flex-grow: 1;
    background-color: #25273d;
    box-sizing: border-box;
    height: 48px;
}

input[type="text"]:focus {
    outline: none;
    color: #d8d8d8;
    background-color: #25273d;
}
</style>

Componente TodoListado.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<template>
    <ul class="list-group">
        <todo-item
            v-for="todo in todos"
            :key="todo.id"
            :todo="todo" />
        <li
            v-if="todos.length == 0"
            class="list-group-item">
            No hay ToDos
        </li>
        <todo-footer 
            v-if="todos.length !== 0"
        />
    </ul>

    <todo-filtro />
</template>

<script>
import { computed } from '@vue/runtime-core'
import TodoItem from './TodoItem.vue'
import TodoFooter from './TodoFooter.vue'
import TodoFiltro from './TodoFiltro.vue'
import { useTodoStore } from '../stores/TodoStore'

export default {
    components: { TodoItem, TodoFooter, TodoFiltro },
    setup() {
        const todoStore = useTodoStore()

        const todos = computed(() => todoStore.todosGetter)

        return { todos }
    }
}
</script>

<style>
</style>

Componente TodoItem.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<template>
    <li class="list-group-item d-flex justify-content-between">
        <span
          role="button"
          @click="completado(todo.id)"
          :class="{'tachado': todo.estado}">
             { { todo.descripcion } }
        </span>
        <span
          role="button"
          @click="borrar(todo.id)">
            <i class="fas fa-times"></i>
        </span>
    </li>
</template>

<script>
import { useTodoStore } from '../stores/TodoStore'
export default {
    props: {
        todo: {
            type: Object,
            required: true
        }
    },
    setup(){

        const todoStore = useTodoStore()

        const borrar = id => {
            todoStore.borrarAction(id)
        }

        const completado = id => {
            todoStore.completadoAction(id)
        }

        return { borrar, completado }
    }
}
</script>

<style>
.tachado {
    text-decoration: line-through;
}

.list-group-item {
    min-width: 300px;
    width: 100%;
    background-color: #25273d;
    border-radius: 5px;
    color: #d8d8d8;
}

.fa-times {
    color: red;
}
</style>

Componente TodoFooter.vue

  • Crear componente TodoFooter.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template>
    <li class="list-group-item d-flex justify-content-between">
        <span>
            { { contarPendientes } } Pendientes
        </span>
        <span role="button" @click="eliminarCompletados">
            Eliminar completados
        </span>
    </li>    
</template>

<script>
import { computed } from '@vue/runtime-core'
import { useTodoStore } from '../stores/TodoStore'
export default {
    setup(){
        const todoStore = useTodoStore()

        const contarPendientes = computed(() => todoStore.contarPendientes)

        const eliminarCompletados = () => {
            todoStore.eliminarCompletadosAction()
        }

        return { contarPendientes, eliminarCompletados }
    }
}
</script>

<style>
</style>

Componente TodoFiltro.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<template>
    <div class="btn-group mt-3 d-flex" role="group" aria-label="Filtro">
        <button
          type="button"
          class="btn"
          @click="filtro('pendientes')"
          :class="estado === 'pendientes' ? 'btn-success' : 'btn-dark'"
          >Pendientes</button>
        <button
          type="button"
          class="btn"
          @click="filtro('all')"
          :class="estado === 'all' ? 'btn-success' : 'btn-dark'"
          >Todos</button>
        <button
          type="button"
          class="btn"
          @click="filtro('completados')"
          :class="estado === 'completados' ? 'btn-success' : 'btn-dark'"
          >Completados</button>
    </div>
</template>

<script>
import { computed, inject } from '@vue/runtime-core';
import { useTodoStore } from '../stores/TodoStore'
export default {
    setup(){
        const todoStore = useTodoStore()
        const estado = computed(() => todoStore.estados)

        const filtro = (valor) => {
            todoStore.filtroAction(valor)
        }
        return { filtro, estado}
    }
}
</script>

<style>
</style>
  • Verificar la app en el navegador VUE APP Aplicacion de Vue en el navegador

  • Finalmente ejecutamos el comando npm run build. La aplicacion quedara lista para ser distribuida en la carpeta dist del proyecto

1
npm run dev
This post is licensed under CC BY 4.0 by the author.