Creación de una aplicación react en Visual Studio
En este tutorial, crearás una interfaz de React para una aplicación web de lista de to-do utilizando JavaScript y Visual Studio 2022. El código de esta aplicación se puede encontrar en ToDoJSWebApp.
Prerrequisitos
Asegúrese de instalar lo siguiente:
- Visual Studio 2022 o posterior. Vaya a la página de descargas de Visual Studio para instalarlo de forma gratuita.
- npm (
https://www.npmjs.com/
), que se incluye con Node.js.
Creación de la aplicación React ToDo List
En Visual Studio, seleccione Archivo > Nuevo > Proyecto para abrir el cuadro de diálogo Crear un nuevo proyecto, seleccione la plantilla React App de JavaScript y, después, elija Siguiente.
Asigne al proyecto el nombre
TodoWebApp
y seleccione Crear.Esto crea el proyecto de JavaScript mediante la herramienta de línea de comandos vite.
En el Explorador de soluciones, haga clic con el botón derecho en la carpeta
src
y elija Agregar > nueva carpeta. y cree una carpeta denominadacomponents
.Se trata de una convención común para colocar componentes en una carpeta components, pero esto no es necesario.
Haga clic con el botón derecho en la nueva carpeta, seleccione Agregar > archivo de componente JSX de React, asígnele el nombre
TodoList
y haga clic en Agregar.Esto crea un nuevo archivo JSX en la carpeta components.
Abra el componente
TodoList
y reemplace el contenido predeterminado por lo siguiente:function TodoList() { return ( <h2>TODO app contents</h2> ); }
Este componente muestra un encabezado, que reemplazará más adelante.
A continuación, conecte este componente en la aplicación.
App.jsx
es el componente principal que se carga al inicio, que representa la aplicación de lista de tareas pendientes. Este componente se usa en el archivomain.jsx
.En el Explorador de soluciones, abra
App.jsx
, quite todas las importaciones de la parte superior y borre el contenido de la instrucción return. El archivo debe tener un aspecto similar al siguiente.function App() { return ( <> <TodoList /> </> ); } export default App;
Para agregar el componente TodoList, coloque el cursor dentro del fragmento y escriba
<TodoL RETURN
. Esto agrega el componente y la declaración de importación.A continuación, borre los archivos CSS.
Abra
App.css
y elimine todo el contenido.Abra
Index.css
y quite todo el contenido, excepto los estilos de:root
::root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; font-weight: 400; color-scheme: light dark; color: rgba(255, 255, 255, 0.87); background-color: #242424; }
Ejecución de la aplicación
Seleccione el botón Iniciar depuración de la barra de herramientas o presione el método abreviado de teclado F5.
La aplicación se abre en una ventana del explorador.
Agregar funciones de la lista to-do a la aplicación
Puede dejar la aplicación abierta. A medida que realice cambios, la aplicación se actualiza automáticamente con el contenido más reciente mediante el soporte para el reemplazo en caliente de módulos de Vite. Algunas acciones, como agregar carpetas o cambiar el nombre de los archivos, requieren que detenga la depuración y, a continuación, reinicie la aplicación, pero en general puede dejar que se ejecute en segundo plano mientras desarrolla la aplicación. Abra el componente TodoList.jsx
para que podamos empezar a definirlo.
En el Explorador de soluciones, abra
TodoList.jsx
y agregue la interfaz de usuario necesaria para mostrar y administrar to-do entradas de lista. Reemplace el contenido por el código siguiente:function TodoList() { return ( <div> <h1>TODO</h1> <div> <input type="text" placeholder="Enter a task" required aria-label="Task text" /> <button className="add-button" aria-label="Add task">Add</button> </div> <ol id="todo-list"> <p>existing tasks will be shown here</p> </ol> </div> ); } export default TodoList;
El código anterior agrega un cuadro de entrada para la nueva tarea de to-do y un botón para enviar la entrada. A continuación, conecte el botón Agregar. Use el useState enlace de React para agregar dos variables de estado, una para la tarea que se agrega y otra para almacenar las tareas existentes. En este tutorial, las tareas se almacenan en memoria y no el almacenamiento persistente.
Agregue la siguiente instrucción import a
TodoList.jsx
para importaruseState
.import { useState } from 'react'
A continuación, use ese enlace para crear las variables de estado. Agregue el código siguiente en la función
TodoList
encima de la instrucción return.const [tasks, setTasks] = useState(["Drink some coffee", "Create a TODO app", "Drink some more coffee"]); const [newTaskText, setNewTaskText] = useState("");
Esto configura dos variables,
tasks
ynewTaskText
, para los datos y dos funciones a las que puede llamar para actualizar esas variables,setTasks
ysetNewTasks
. Cuando se cambia un valor para una variable de estado, React vuelve a representar automáticamente el componente.Estás casi listo para actualizar TodoList.jsx para mostrar los elementos de to-do como una lista, pero hay un concepto importante de React que aprender primero.
En React, cuando se muestra una lista de elementos, debe agregar una clave para identificar de forma única cada elemento de la lista. Esta funcionalidad se explica en profundidad en la documentación de React en representación de listas, pero aquí aprenderá los conceptos básicos. Tiene una lista de elementos pendientes para mostrar y debe asociar una clave única para cada elemento. La clave de cada elemento no debe cambiar y, por este motivo, no puede usar el índice del elemento de la matriz como clave. Necesita un identificador que no cambie durante toda la vigencia de esos valores. Usarás randomUUID() para crear un identificador único para cada to-do elemento.
Cree TodoList.jsx usando un UUID como clave para cada item de to-do. Actualice TodoList.jsx con el código siguiente.
import React, { useState } from 'react'; const initialTasks = [ { id: self.crypto.randomUUID(), text: 'Drink some coffee' }, { id: self.crypto.randomUUID(), text: 'Create a TODO app' }, { id: self.crypto.randomUUID(), text: 'Drink some more coffee' } ]; function TodoList() { const [tasks, setTasks] = useState(initialTasks); const [newTaskText, setNewTaskText] = useState(""); return ( <article className="todo-list" aria-label="task list manager"> <header> <h1>TODO</h1> <form className="todo-input" aria-controls="todo-list"> <input type="text" placeholder="Enter a task" value={newTaskText} /> <button className="add-button"> Add </button> </form> </header> <ol id="todo-list" aria-live="polite" aria-label="task list"> {tasks.map((task, index) => <li key={task.id}> <span className="text">{task.text}</span> </li> )} </ol> </article> ); } export default TodoList;
Dado que los valores de identificador se asignan fuera de la función TodoList, puede asegurarse de que los valores no cambiarán si la página se vuelve a representar. Al probar la aplicación en este estado, observará que no puede escribir en el elemento de entrada pendiente. Esto se debe a que el elemento de entrada está enlazado a
newTaskText
, que se ha inicializado en una cadena en blanco. Para permitir que los usuarios agreguen nuevas tareas, debe controlar el eventoonChange
en ese control. También necesita implementar el soporte para el botón Agregar.Agregue las funciones necesarias inmediatamente antes de la instrucción return en la función TodoList.
function handleInputChange(event) { setNewTaskText(event.target.value); } function addTask() { if (newTaskText.trim() !== "") { setTasks(t => [...t, { id: self.crypto.randomUUID(), text: newTaskText }]); setNewTaskText(""); } event.preventDefault(); }
En la función
handleInputChanged
, el nuevo valor del campo de entrada se pasa a través deevent.target.value
y ese valor se usa para actualizar el valor de la variablenewTaskText
consetNewTaskText
. En la funciónaddTask
, agregue la nueva tarea a la lista de tareas existentes mediantesetTasks
y establezca el identificador del elemento como un nuevo valor UUID. Actualice el elemento de entrada para incluironChange={handleInputChange}
y actualice el botón Agregar para incluironClick={addTask}
. Este código conecta el evento a la función que gestiona ese evento. Después de esto, debería poder agregar una nueva tarea a la lista de tareas. Las nuevas tareas se agregan a la parte inferior de la lista. Para que esta aplicación sea más útil, debe agregar compatibilidad para eliminar tareas y mover una tarea hacia arriba o hacia abajo.Agregue las funciones para admitir la eliminación, subir y bajar y, a continuación, actualizar el marcado para mostrar un botón para cada acción. Agregue el código siguiente en la función TodoList encima de la instrucción return.
function deleteTask(id) { const updatedTasks = tasks.filter(task => task.id != id); setTasks(updatedTasks); } function moveTaskUp(index) { if (index > 0) { const updatedTasks = [...tasks]; [updatedTasks[index], updatedTasks[index - 1]] = [updatedTasks[index - 1], updatedTasks[index]]; setTasks(updatedTasks); } } function moveTaskDown(index) { if (index < tasks.length) { const updatedTasks = [...tasks]; [updatedTasks[index], updatedTasks[index + 1]] = [updatedTasks[index + 1], updatedTasks[index]]; setTasks(updatedTasks); } }
La función delete toma el identificador de tarea y elimina uno de la lista y usa el método Array filter() para crear una nueva matriz excluyendo el elemento seleccionado y, a continuación, llama a
setTasks()
. Las otras dos funciones toman el índice del elemento porque este trabajo es específico del orden de elementos. TantomoveTaskUp()
comomoveTaskDown()
usan la asignación de desestructuración de matrices para intercambiar la tarea seleccionada con su vecina.A continuación, actualice la vista para incluir estos tres botones. Actualice la declaración de retorno para que incluya lo siguiente.
return ( <article className="todo-list" aria-label="task list manager"> <header> <h1>TODO</h1> <form className="todo-input" onSubmit={addTask} aria-controls="todo-list"> <input type="text" required autoFocus placeholder="Enter a task" value={newTaskText} aria-label="Task text" onChange={handleInputChange} /> <button className="add-button" aria-label="Add task"> Add </button> </form> </header> <ol id="todo-list" aria-live="polite"> {tasks.map((task, index) => <li key={task.id}> <span className="text">{task.text}</span> <button className="delete-button" onClick={() => deleteTask(task.id)}> 🗑️ </button> <button className="up-button" onClick={() => moveTaskUp(index)}> ⇧ </button> <button className="down-button" onClick={() => moveTaskDown(index)}> ⇩ </button> </li> )} </ol> </article> );
Ha agregado los botones necesarios para realizar las tareas que hemos descrito anteriormente. Usa caracteres Unicode como iconos en los botones. En el marcado, hay algunos atributos agregados para admitir la adición de CSS más adelante. También puede observar el uso de los atributos aria para mejorar la accesibilidad, siendo opcionales pero altamente recomendables. Si ejecuta la aplicación, debería ser similar a la siguiente ilustración.
Ahora debería poder realizar lo siguiente en la aplicación web TODO.
- Agregar tarea
- Eliminar tarea
- Subir tarea
- Bajar tarea
Estas funciones funcionan, pero puede refactorizar para crear un componente reutilizable para mostrar los elementos de to-do. El marcado del elemento to-do entra en un nuevo componente, TodoItem. Dado que la gestión de la lista permanece en el componente Todo, puedes pasar funciones de devolución a los botones Eliminar y Mover.
Para empezar, haga clic con el botón derecho en la carpeta componentes en el Explorador de soluciones y seleccione Agregar > Nuevo elemento.
En el cuadro de diálogo que se abre, seleccione el archivo de componente de React JSX, asígnele el nombre TodoItem y seleccione Agregar.
Agregue el código siguiente al objeto TodoItem.
En este código, pasas la tarea y las devoluciones de llamada como propiedades a este nuevo componente.
import PropTypes from 'prop-types'; function TodoItem({ task, deleteTaskCallback, moveTaskUpCallback, moveTaskDownCallback }) { return ( <li aria-label="task" > <span className="text">{task}</span> <button type="button" aria-label="Delete task" className="delete-button" onClick={() => deleteTaskCallback()}> 🗑️ </button> <button type="button" aria-label="Move task up" className="up-button" onClick={() => moveTaskUpCallback()}> ⇧ </button> <button type="button" aria-label="Move task down" className="down-button" onClick={() => moveTaskDownCallback()}> ⇩ </button> </li> ); } TodoItem.propTypes = { task: PropTypes.string.isRequired, deleteTaskCallback: PropTypes.func.isRequired, moveTaskUpCallback: PropTypes.func.isRequired, moveTaskDownCallback: PropTypes.func.isRequired, }; export default TodoItem;
El código anterior contiene el marcado del componente Todo y, al final del mismo, lo declara como
PropTypes
. Las propiedades se usan para pasar datos de un componente primario a componentes secundarios. Para obtener más información sobre las propiedades, consulte Pasar propiedades a un componente: React. Dado que las funciones de eliminación y movimiento se pasan como devoluciones de llamada, el controlador deonClick
debe actualizarse para ejecutar esas devoluciones de llamada.Agregue el código necesario. Aquí se muestra el código completo de TodoList que usa el componente TodoItem.
import React, { useState } from 'react' import TodoItem from './TodoItem' const initialTasks = [ { id: self.crypto.randomUUID(), text: 'Drink some coffee' }, { id: self.crypto.randomUUID(), text: 'Create a TODO app' }, { id: self.crypto.randomUUID(), text: 'Drink some more coffee' } ]; function TodoList() { const [tasks, setTasks] = useState(initialTasks); const [newTaskText, setNewTaskText] = useState(""); function handleInputChange(event) { setNewTaskText(event.target.value); } function addTask() { if (newTaskText.trim() !== "") { setTasks(t => [...t, { id: self.crypto.randomUUID(), text: newTaskText }]); setNewTaskText(""); } event.preventDefault(); } function deleteTask(id) { const updatedTasks = tasks.filter(task => task.id !== id); setTasks(updatedTasks); } function moveTaskUp(index) { if (index > 0) { const updatedTasks = [...tasks]; [updatedTasks[index], updatedTasks[index - 1]] = [updatedTasks[index - 1], updatedTasks[index]]; setTasks(updatedTasks); } } function moveTaskDown(index) { if (index < tasks.length) { const updatedTasks = [...tasks]; [updatedTasks[index], updatedTasks[index + 1]] = [updatedTasks[index + 1], updatedTasks[index]]; setTasks(updatedTasks); } } return ( <article className="todo-list" aria-label="task list manager"> <header> <h1>TODO</h1> <form onSubmit={addTask} aria-controls="todo-list"> <input type="text" required placeholder="Enter a task" value={newTaskText} aria-label="Task text" onChange={handleInputChange} /> <button className="add-button" aria-label="Add task"> Add </button> </form> </header> <ol id="todo-list" aria-live="polite"> {tasks.map((task, index) => <TodoItem key={task.id} task={task.text} deleteTaskCallback={() => deleteTask(task.id)} moveTaskUpCallback={() => moveTaskUp(index)} moveTaskDownCallback={() => moveTaskDown(index)} /> )} </ol> </article> ); } export default TodoList;
Ahora, el componente TodoItem se usa para representar cada elemento to-do. Observe que la clave se establece en
task.id
, que contiene el valor UUID de esa tarea. Al ejecutar la aplicación, no debería ver ningún cambio en la apariencia o el comportamiento de la aplicación porque lo refactorizaste para usar TodoItem.Ahora que tiene todas las funciones básicas admitidas, es el momento de empezar a agregar algún estilo a esto para que tenga un aspecto agradable. Empiece agregando un vínculo en Index.html para una familia de fuentes, Inter, que usará para esta aplicación. En Index.html, hay otros elementos que deben limpiarse. En concreto, el título debe actualizarse y desea reemplazar el archivo vite.svg que se usa actualmente como icono.
Actualice Index.html con el siguiente contenido.
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="/checkmark-square.svg" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>TODO app</title> <link href='https://fonts.googleapis.com/css?family=Inter' rel='stylesheet'> <script type="module" defer src="/src/main.jsx"></script> </head> <body> </body> </html>
Edite archivo main.jsx para reemplazar
root
pormain
al llamar acreateRoot
.Aquí se muestra el código completo de main.jsx.
import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import App from './App.jsx' import './index.css' createRoot(document.querySelector('main')).render( <StrictMode> <App /> </StrictMode>, )
Además de estos cambios, el archivo checkmark-square.svg se agregó a la carpeta pública. Se trata de un SVG de la imagen cuadrada de marca de verificación FluentUI, que puede descargar directamente. (Hay un paquete que puede usar para una experiencia más integrada, pero que está fuera del ámbito de este artículo).
A continuación, actualice los estilos del componente TodoList.
En la carpeta components, agregue un nuevo archivo CSS denominado TodoList.css. Puede hacer clic con el botón derecho en el proyecto y seleccionar Agregar > nuevo elemento y, a continuación, seleccionar hoja de estilos. Asigne al archivo el nombre TodoList.css.
Agregue el código siguiente a TodoList.css.
.todo-list { background-color: #1e1e1e; padding: 1.25rem; border-radius: 0.5rem; box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.3); width: 100%; max-width: 25rem; } .todo-list h1 { text-align: center; color: #e0e0e0; } .todo-input { display: flex; justify-content: space-between; margin-bottom: 1.25rem; } .todo-input input { flex: 1; padding: 0.625rem; border: 0.0625rem solid #333; border-radius: 0.25rem; margin-right: 0.625rem; background-color: #2c2c2c; color: #e0e0e0; } .todo-input .add-button { padding: 0.625rem 1.25rem; background-color: #007bff; color: #fff; border: none; border-radius: 0.25rem; cursor: pointer; } .todo-input .add-button:hover { background-color: #0056b3; } .todo-list ol { list-style-type: none; padding: 0; } .todo-list li { display: flex; justify-content: space-between; align-items: center; padding: 0.625rem; border-bottom: 0.0625rem solid #333; } .todo-list li:last-child { border-bottom: none; } .todo-list .text { flex: 1; } .todo-list li button { background: none; border: none; cursor: pointer; font-size: 1rem; margin-left: 0.625rem; color: #e0e0e0; } .todo-list li button:hover { color: #007bff; } .todo-list li button.delete-button { color: #ff4d4d; } .todo-list li button.up-button, .todo-list li button.down-button { color: #4caf50; }
A continuación, edite TodoList.jsx para agregar la siguiente importación en la parte superior del archivo.
import './TodoList.css';
Actualice el explorador después de guardar los cambios. Esto debería mejorar el estilo de la aplicación. La aplicación debe tener un aspecto similar al siguiente.
Ahora ha creado una aplicación de lista de tareas pendientes en funcionamiento que almacena los elementos de tareas pendientes en memoria. Desde este punto, podría actualizar la aplicación para almacenar los elementos de to-do en localStorage/IndexedDb, o integrarlo con una base de datos del lado servidor u otro back-end para un almacenamiento más permanente.
Resumen
En este tutorial, ha creado una nueva aplicación react mediante Visual Studio. La aplicación consta de una lista de to-do, que incluye soporte para agregar tareas, eliminar tareas y reordenarlas. Ha creado dos nuevos componentes de React y los ha usado en este tutorial.
Recursos
- Código para este ejemplo en ToDoJSWebApp
- proyectos de JavaScript y TypeScript de Visual Studio