Objetos primitivos en JavaScript: cuándo usarlos (Parte 2)

 

 

 

  • SmashingConf Friburgo 2024
  • Listas de verificación de diseño de interfaz inteligente

  • Índice
    1. Hacer objetos primitivos a granel
    2. revisa esa radio
    3. Alternativa a las cuerdas
    4. Terminando
      1. Lecturas adicionales sobre SmashingMag

    En la Parte 1 de la serie, Kirill cubrió cómo hacer que los objetos JavaScript normales se comporten como valores primitivos. Ahora, analicemos de cerca la utilidad de los objetos primitivos y exploremos cómo la reducción de capacidades podría ser un beneficio para su proyecto.

     

    Escribir programas en JavaScript es accesible al principio. El lenguaje es indulgente y uno se acostumbra a sus posibilidades. Con el tiempo y la experiencia trabajando en proyectos complejos, se empiezan a apreciar cosas como el control y la precisión en el flujo de desarrollo.

    Otra cosa que quizás empieces a apreciar es la previsibilidad, pero eso es una garantía mucho menor en JavaScript. Si bien los valores primitivos son suficientemente predictivos, los objetos no lo son. Cuando obtienes un objeto como entrada, debes verificar todo:

    • ¿Es un objeto?
    • ¿Tiene esa propiedad que estás buscando?
    • Cuando una propiedad tiene undefined, ¿es ese su valor o falta la propiedad en sí?

    Es comprensible que este nivel de incertidumbre te deje un poco paranoico en el sentido de que empieces a cuestionar todas tus elecciones. Posteriormente, su código se vuelve defensivo. Piensas más en si has manejado todos los casos defectuosos o no (es probable que no lo hayas hecho). Y al final, su programa es principalmente una colección de controles en lugar de aportar un valor real al proyecto.

    Al hacer que los objetos sean primitivos , muchos de los posibles puntos de falla se mueven a un solo lugar: aquel donde se inicializan los objetos. Si puede asegurarse de que sus objetos se inicialicen con un determinado conjunto de propiedades y que esas propiedades contengan ciertos valores, no tiene que verificar cosas como la existencia de propiedades en ningún otro lugar de su programa. Podría garantizar que undefinedes un valor si es necesario.

    Veamos una de las formas en que podemos crear objetos primitivos. No es la única manera ni siquiera la más interesante. Más bien, su propósito es demostrar que trabajar con objetos de sólo lectura no tiene por qué ser engorroso o difícil.

    Nota : También te recomiendo que consultes la primera parte de la serie , donde cubrí algunos aspectos de JavaScript que ayudan a acercar los objetos a valores primitivos, lo que a cambio nos permite beneficiarnos de características comunes del lenguaje que normalmente no están asociadas con un objeto, como comparaciones y operadores aritméticos.

     

    Hacer objetos primitivos a granel

    La forma más simple y primitiva (juego de palabras) de crear un objeto primitivo es la siguiente:

    const my_object = Object.freeze({});

    Esta única línea da como resultado un objeto que puede representar cualquier cosa. Por ejemplo, podría implementar una interfaz con pestañas utilizando un objeto vacío para cada pestaña.

    import React, { useState } from "react";const summary_tab = Object.freeze({});const details_tab = Object.freeze({});function TabbedContainer({ summary_children, details_children }) { const [ active, setActive ] = useState(summary_tab); return ( div className="tabbed-container" div className="tabs" label className={active === summary_tab ? "active" : ""} onClick={() = { setActive(summary_tab); }} Summary /label label className={active === details_tab ? "active": ""} onClick={() = { setActive(details_tab); }} Details /label /div div className="tabbed-content" {active === summary_tab summary_children} {active === details_tab details_children} /div /div );}export default TabbedContainer;

    Si eres como yo, ese tabselemento pide a gritos que lo modifiquen. Si observa detenidamente, notará que los elementos de la pestaña son similares y necesitan dos cosas, como una referencia de objeto y una cadena de etiqueta . Incluyamos la labelpropiedad en los tabsobjetos y movamos los objetos mismos a una matriz. Y como no planeamos cambiar tabsde ninguna manera, también hagamos que esa matriz sea de solo lectura mientras estamos en ello.

    const tab_kinds = Object.freeze([ Object.freeze({ label: "Summary" }), Object.freeze({ label: "Details" })]);

    Eso hace lo que necesitamos, pero es detallado. El enfoque que veremos ahora se utiliza a menudo para ocultar operaciones repetidas para reducir el código a solo los datos. De esa forma, es más evidente cuando los datos son incorrectos. Lo que también queremos es freezeobjetos (incluida la matriz) de forma predeterminada en lugar de que sea algo que tengamos que recordar escribir. Por la misma razón, el hecho de que tengamos que especificar un nombre de propiedad cada vez deja lugar a errores, como errores tipográficos.

    Para inicializar fácil y consistentemente matrices de objetos primitivos, utilizo una populatefunción. En realidad, no tengo una sola función que haga el trabajo. Normalmente creo uno cada vez según lo que necesito en este momento. En el caso particular de este artículo, este es uno de los más sencillos. Así es como lo haremos:

    function populate(...names) { return function(...elements) { return Object.freeze( elements.map(function (values) { return Object.freeze(names.reduce( function (result, name, index) { result[name] = values[index]; return result; }, Object.create(null) )); }) ); };}

    Si esto le parece denso, aquí hay uno que es más legible:

     

    function populate(...names) { return function(...elements) { const objects = []; elements.forEach(function (values) { const object = Object.create(null); names.forEach(function (name, index) { object[name] = values[index]; }); objects.push(Object.freeze(object)); }); return Object.freeze(objects); };}

    Con ese tipo de función a mano, podemos crear la misma matriz de objetos con pestañas así:

    const tab_kinds = populate( "label")( [ "Summary" ], [ "Details" ]);

    Cada matriz en la segunda llamada representa los valores de los objetos resultantes. Ahora digamos que queremos agregar más propiedades. Necesitaríamos agregar un nuevo nombre a la primera llamada y un valor a cada matriz en la segunda llamada.

    const tab_kinds = populate( "label", "color", "icon")( [ "Summary", colors.midnight_pink, " " ], [ "Details", colors.navi_white, " " ]);

    Con algo de espacio en blanco, podrías hacer que parezca una mesa. De esa manera, es mucho más fácil detectar un error en definiciones grandes.

    Quizás hayas notado que populatedevuelve otra función. Hay un par de razones para mantenerlo en dos llamadas a funciones. Primero, me gusta cómo dos llamadas contiguas crean una línea vacía que separa claves y valores. En segundo lugar, me gusta poder crear este tipo de generadores para objetos similares. Por ejemplo, digamos que necesitamos crear esos objetos de etiqueta para diferentes componentes y queremos almacenarlos en diferentes matrices.

    Volvamos al ejemplo y veamos qué ganamos con la populatefunción:

    import React, { useState } from "react";import populate_label from "./populate_label";const tabs = populate_label( [ "Summary" ], [ "Details" ]);const [ summary_tab, details_tab ] = tabs;function TabbedContainer({ summary_children, details_children }) { const [ active, setActive ] = useState(summary_tab); return ( div className="tabbed-container" div className="tabs" {tabs.map((tab) = ( label key={tab.label} className={tab === active ? "active" : ""} onClick={() = { setActive(tab); }} {tab.label} /label )} /div div className="tabbed-content" {summary_tab === active summary_children} {details_tab === active details_children} /div /div );}export default TabbedContainer;

    El uso de objetos primitivos simplifica la escritura de la lógica de la interfaz de usuario. Captain Tsubasa Spain

     

    Usar funciones como populatees menos engorroso para crear estos objetos y ver cómo se ven los datos.

    revisa esa radio

    Una de las alternativas al enfoque anterior que he encontrado es retener el activeestado (ya sea que la pestaña esté seleccionada o no) almacenado como una propiedad del tabsobjeto:

    const tabs = [ { label: "Summary", selected: true }, { label: "Details", selected: false },];

    De esta manera, reemplazamos tab === activepor tab.selected. Eso podría parecer una mejora, pero mira cómo tendríamos que cambiar la pestaña seleccionada:

    function select_tab(tab, tabs) { tabs.forEach((tab) = tab.selected = false); tab.selected = true;}

    Debido a que esto es lógico para un botón de opción, solo se puede seleccionar un elemento a la vez. Entonces, antes de configurar un elemento para que se seleccione, primero debemos asegurarnos de que todos los demás elementos no estén seleccionados. Sí, es una tontería hacerlo así para una matriz con sólo dos elementos, pero el mundo real está lleno de listas más largas que las de este ejemplo.

    Con un objeto primitivo, necesitamos una única variable que represente el estado seleccionado . Sugiero configurar la variable en uno de los elementos para convertirlo en el elemento seleccionado actualmente o configurarla undefinedsi su implementación no permite ninguna selección.

    Con elementos de opción múltiple como casillas de verificación, el enfoque es casi el mismo. Reemplazamos la variable de selección con una matriz. Cada vez que se selecciona un elemento, lo enviamos a esa matriz o, en el caso de Redux , creamos una nueva matriz con ese elemento presente. Para deseleccionarlo, lo empalmamos o filtramos el elemento.

    let selected = []; // Nothing is selected.// Select.selected = selected.concat([ to_be_selected ]);// Unselect.selected = selected.filter((element) = element !== to_be_unselected);// Check if an element is selected.selected.includes(element);

    Nuevamente, esto es sencillo y conciso. No es necesario recordar si la propiedad se llama selectedo active; usas el objeto mismo para determinar eso. Cuando su programa se vuelve más complejo, esas líneas serán las que tendrán menos probabilidades de ser refactorizadas.

    Al final, no es trabajo de un elemento de la lista decidir si se selecciona o no . No debería contener esta información en su estado. Por ejemplo, ¿qué pasa si está seleccionado y no seleccionado simultáneamente en varias listas a la vez?

     

    Alternativa a las cuerdas

    Lo último que me gustaría tocar es un ejemplo del uso de cadenas que encuentro a menudo.

    El texto es una buena compensación para la interoperabilidad. Define algo como una cadena y obtiene instantáneamente una representación de un contexto. Es como obtener una descarga de energía instantánea al comer azúcar. Al igual que con el azúcar, en el mejor de los casos no obtendrás nada a largo plazo. Dicho esto, es insatisfactorio e inevitablemente vuelves a tener hambre.

    El problema de las cuerdas es que son para humanos. Es natural para nosotros distinguir las cosas dándoles un nombre. Pero un programa no comprende el significado de esos nombres.

    La mayoría de los editores de código y entornos de desarrollo integrados (IDE) no entienden las cadenas. En otras palabras, sus herramientas no le dirán si la cadena es correcta o no.

    Su programa sólo sabe si dos cadenas son iguales o no. E incluso entonces, decir si las cadenas son iguales o desiguales no necesariamente proporciona una idea de si alguna de esas cadenas contiene o no un error tipográfico.

    Los objetos proporcionan más formas de ver que algo anda mal antes de ejecutar su programa. Como no se pueden tener literales para objetos primitivos, tendría que obtener una referencia de algún lugar. Por ejemplo, si es una variable y cometes un error tipográfico, obtendrás un error de referencia . Existen herramientas que podrían detectar ese tipo de cosas antes de guardar el archivo.

    Si obtuviera sus objetos de una matriz u otro objeto, JavaScript no le dará un error cuando la propiedad o el índice no exista. Lo que obtienes es undefined, y eso es algo que puedes verificar. Tienes una sola cosa que comprobar. Con las cadenas, tienes sorpresas que quizás quieras evitar, como cuando están vacías.

    Otro uso de cadenas que trato de evitar es comprobar si obtenemos el objeto que queremos. Por lo general, se hace almacenando una cadena en una propiedad denominada id. Digamos que tenemos una variable. Para verificar si contiene el objeto que queremos, es posible que debamos verificar si una cadena en la idpropiedad coincide con la que esperamos. Para hacer eso, primero verificaríamos si la variable contiene un objeto. Si la variable contiene un objeto, pero el objeto carece de la idpropiedad, entonces obtenemos undefinedy estamos bien. Sin embargo, si tenemos uno de los valores inferiores en esa variable, entonces no podremos solicitar la propiedad directamente. En cambio, tenemos que hacer algo para asegurarnos de que solo lleguen objetos a este punto o realizar ambas comprobaciones en el lugar.

    const myID = "Oh, it's so unique";function magnification(value) { if (value typeof value === "object" value.id === myID) { // do magic }}

    Así es como podemos hacer lo mismo con objetos primitivos:

    import data from "./the file where data is stored";function magnification(value) { if (value === data.myObject) { // do magic }}

    La ventaja de las cadenas es que son un elemento único que se puede utilizar para la identificación interna y se pueden reconocer inmediatamente en los registros. Seguro que son fáciles de usar desde el primer momento, pero no son tus amigos a medida que aumenta la complejidad de un proyecto.

    Creo que hay pocos beneficios en confiar en cadenas para cualquier otra cosa que no sea la salida al usuario . La falta de interoperabilidad de cadenas en objetos primitivos podría solucionarse gradualmente y sin necesidad de cambiar la forma en que se manejan operaciones básicas, como las comparaciones.

    Terminando

    Trabajar directamente con objetos nos libera de los problemas que conllevan otros métodos. Nuestro código se vuelve más simple porque escribimos lo que su programa necesita hacer. Al organizar su código con objetos primitivos, nos afecta menos la naturaleza dinámica de JavaScript y parte de su bagaje. Los objetos primitivos nos dan más garantías y un mayor grado de previsibilidad.

    Lecturas adicionales sobre SmashingMag

    • API de JavaScript que no conoces , Juan Diego Rodríguez
    • Bibliotecas útiles de cuadrícula de datos de JavaScript , Zara Cooper
    • Creación de un componente de diagrama de Gantt interactivo con Vanilla JavaScript (Parte 1) , Anna Prenzel
    • Una mirada al Remix y las diferencias con Next.js , Facundo Giuliani

    (gg, yk, il)Explora más en

    • javascript
    • Herramientas
    • Técnicas





    Tal vez te puede interesar:

    1. ¿Deberían abrirse los enlaces en ventanas nuevas?
    2. 24 excelentes tutoriales de AJAX
    3. 70 técnicas nuevas y útiles de AJAX y JavaScript
    4. Más de 45 excelentes recursos y repositorios de fragmentos de código

    Objetos primitivos en JavaScript: cuándo usarlos (Parte 2)

    Objetos primitivos en JavaScript: cuándo usarlos (Parte 2)

    SmashingConf Friburgo 2024 Listas de verificación de diseño de interfaz inteligente Índice Hacer objeto

    programar

    es

    https://pseint.es/static/images/programar-objetos-primitivos-en-javascript-cuando-usarlos-parte-2-1174-0.jpg

    2024-04-04

     

    Objetos primitivos en JavaScript: cuándo usarlos (Parte 2)
    Objetos primitivos en JavaScript: cuándo usarlos (Parte 2)

     

     

    Top 20