Una introducción a la propagación de contexto en JavaScript

 

 

 

  • Clase magistral de diseño para una interfaz de usuario compleja, con Vitaly Friedman
  • SmashingConf UX y diseño, Amberes 2024

  • Índice
    1. ¿Por qué deberíamos preocuparnos por esto?
    2. ¿Qué queremos que se haga?
    3. ¿Qué soluciones podríamos probar?
    4. ¡Contexto al rescate!
    5. La anatomía del contexto
    6. Redacción de nuestra API
    7. Algunos conceptos que debemos tener en cuenta antes de sumergirnos
    8. Almacenar valores en nuestro contexto
    9. Llamando a nuestras devoluciones de llamada
    10. ¿Qué pasa si tenemos un contexto anidado?
    11. ¿Se está utilizando esto en alguna parte?
      1. Lecturas adicionales sobre la revista Smashing

    React popularizó la idea de propagación de contexto dentro de nuestras aplicaciones con su API de contexto, una alternativa a la perforación de apoyo y la sincronización de estados en diferentes partes de las aplicaciones. Este artículo ofrece una breve introducción a la propagación de contexto en JavaScript y muestra que no hay ninguna magia detrás de algunas de las API de React más útiles.

     

    React popularizó la idea de propagación de contexto dentro de nuestras aplicaciones con su API de contexto. En el mundo de React, el contexto se utiliza como una alternativa a la perforación de accesorios y la sincronización del estado en diferentes partes de las aplicaciones.

    "El contexto proporciona una manera de pasar datos a través del árbol de componentes sin tener que pasar accesorios manualmente en cada nivel".

    - Reaccionar documentos

    Puedes imaginar el contexto de React como una especie de "agujero de gusano" por el que puedes pasar valores en algún lugar de tu árbol de componentes y acceder a ellos más abajo en los componentes de tus hijos.

    El siguiente fragmento es un ejemplo bastante simplista (y bastante inútil) de la API de contexto de React, pero demuestra cómo podemos usar valores definidos más arriba en el árbol de componentes sin pasarlos explícitamente a los componentes secundarios.

     

    En el siguiente fragmento, tenemos nuestra aplicación que tiene un Colorcomponente. Ese componente Color muestra un mensaje que contiene el mensaje definido en su componente principal (la aplicación), solo que sin que se pase directamente como un accesorio al componente, sino que aparezca "mágicamente" mediante el uso de useContext.

    import {createContext, useContext} from 'react'const MyContext = createContext();function App() { return ( MyContext.Provider value={color: "red"} Color/ /MyContext.Provider );}function Color() { const {color} = useContext(MyContext); return span Your color is: {color}/span }

    Si bien el caso de uso para la propagación de contexto es claro cuando se crean aplicaciones orientadas al usuario con un marco de interfaz de usuario, la necesidad de una API similar existe incluso cuando no se utiliza ningún marco de interfaz de usuario o incluso cuando no se crea una interfaz de usuario.

    ¿Por qué deberíamos preocuparnos por esto?

    En mi opinión, hay dos razones para intentar implementarlo.

    Primero, como usuario de un marco, es muy importante comprender cómo hace las cosas. A menudo consideramos las herramientas que utilizamos como “mágicas” y cosas que simplemente funcionan. Intentar construir partes de ellos por ti mismo lo desmitifica y te ayuda a ver que no hay magia involucrada y que, bajo el capó, las cosas pueden ser bastante simples.

    En segundo lugar, la API de contexto también puede resultar útil cuando se trabaja en aplicaciones que no son de interfaz de usuario.

    Siempre que construimos cualquier tipo de aplicación de tamaño mediano a grande, nos enfrentamos a funciones que se llaman entre sí, y la pila de llamadas puede tener varias capas de profundidad. Tener que pasar argumentos más abajo puede crear mucho desorden, especialmente si no usas todas estas variables en todos los niveles. En el mundo de React, lo llamamos "perforación de puntal".

    Alternativamente, si es autor de una biblioteca y depende de las devoluciones de llamadas que le pasa el consumidor, es posible que tenga variables declaradas en diferentes niveles de su tiempo de ejecución y desee que estén disponibles más abajo. Como ejemplo, tomemos un marco de pruebas unitarias.

    describe('add', () = { it('Should add two numbers', () = { expect(add(1, 1)).toBe(2); });});

    En el siguiente ejemplo, tenemos esta estructura:

    1. describese llama y llama a la función de devolución de llamada que se le pasa.
    2. Dentro de la devolución de llamada, tenemos una itllamada.

    ¿Qué queremos que se haga?

    Ahora escribamos la implementación básica de nuestro marco de pruebas unitarias. Estoy adoptando un enfoque muy ingenuo y feliz para hacer que el código sea lo más simple posible, pero esto, por supuesto, no es algo que debas usar en la vida real.

    function describe(description, callback) { callback()}function it(text, callback) { try { callback() console.log("✅ " + text)} catch { console.log(" " + text) }}

    En el ejemplo anterior, tenemos la función "describir" que llama a su devolución de llamada. Esa devolución de llamada puede contener diferentes llamadas a "eso". "eso", a su vez, registra si la prueba tiene éxito o no.

     

    Supongamos que, junto con el mensaje de prueba, también queremos registrar el mensaje de "describe":

    describe('calculator: Add', () = { it("Should correctly add two numbers", () = { expect(add(1, 1)).toBe(2); });});

    Registraría en la consola el mensaje de prueba precedido de la descripción:

    "calculator: Add ✅ Should correctly add two numbers"

    Para hacer esto, necesitamos que de alguna manera el mensaje de descripción "salte" el código de usuario y, de alguna manera, encuentre su camino hacia la implementación de la función "it".

    ¿Qué soluciones podríamos probar?

    Al intentar resolver este problema, existen varios enfoques que podemos probar. Intentaré repasar algunos y demostrar por qué podrían no ser adecuados en nuestro escenario.

    • Usando "esto" Podríamos intentar crear una instancia de una clase y hacer que los datos se propaguen a través de "esto", pero aquí hay dos problemas. "Esto" es muy delicado. No siempre funciona como se esperaba, especialmente cuando se factorizan funciones de flecha, que utilizan alcance léxico para determinar el valor actual de "esto", lo que significa que nuestros consumidores tendrán que usar la palabra clave function. Además de eso, no existe ninguna relación entre "probar" y "describir", por lo que no existe una forma real de compartir la instancia actual.
    • Emitir un evento Para emitir un evento, necesitamos que alguien lo capte. ¿Pero qué pasa si tenemos varias suites ejecutándose al mismo tiempo? Dado que no tenemos ninguna relación entre las llamadas de prueba y sus respetadas "descripciones", ¿qué impediría que las otras suites también detecten sus eventos?
    • Almacenar el mensaje en un objeto global Los objetos globales sufren los mismos problemas que la emisión de un evento y, además, contaminamos el alcance global. Tener un objeto global también significa que nuestro valor de contexto puede ser inspeccionado e incluso modificado desde fuera de nuestra función ejecutada, lo que puede ser muy riesgoso.
    • Lanzar un error Esto técnicamente puede funcionar: nuestra "descripción" puede detectar errores arrojados por "ello", pero significa que en el primer fallo, detendremos la ejecución y no se podrán ejecutar más pruebas.

    ¡Contexto al rescate!

    A estas alturas, debes haber adivinado que estoy abogando por una solución que sería algo similar en diseño a la propia API de contexto de React, y creo que nuestro ejemplo de prueba unitaria básica podría ser un buen candidato para probarlo.

     

    La anatomía del contexto

    Analicemos cuáles son las partes que componen el contexto de React:

    1. React.createContext: crea un nuevo contexto, básicamente define un nuevo contenedor especializado para nosotros.
    2. Proveedor: el valor de retorno createContext. Este es un objeto con la propiedad "proveedor". La propiedad del proveedor es un componente en sí mismo y, cuando se usa dentro de una aplicación React, es la entrada a nuestro "agujero de gusano".
    3. React.useContext: una función que, cuando se llama dentro de un árbol de React que está envuelto con un contexto, sirve como punto de salida de nuestro agujero de gusano y permite extraer valores de él.

    Echemos un vistazo al propio contexto de React: Todo sobre las islas canarias

    El objeto de contexto React ( vista previa grande )

    Parece que el objeto de contexto de React es bastante complejo. Contiene un Proveedor y un Consumidor que en realidad son Elementos de React. Tengamos en cuenta esta estructura en el futuro.

    Sabiendo lo que sabemos ahora sobre el contexto de React, intentemos pensar cómo deberían interactuar sus diferentes partes con nuestro ejemplo de prueba unitaria. Voy a crear un escenario sin sentido para que podamos imaginar los diferentes componentes funcionando en la vida real.

    const TestContext = createContext()function describe(description, callback) { // TestContext.Provider value={{description}} callback() // /TestContext.Provider }function it(text, callback) { // const { description } = useContext(TestContext); try { callback() console.log(description + " ✅ " + text) } catch { console.log(description+ " " + text) }}

    Pero está claro que esto no puede funcionar. Primero, no podemos usar React Elements en nuestro código JS básico. En segundo lugar, no podemos usar el contexto de React fuera de React. ¿Bien? Bien.

    Así que adaptemos esa estructura a JS real:

    const TestContext = createContext()function describe(description, callback) { TestContext.Provider({description}, () = {callback() });}function it(text, callback) { const { description } = useContext(TestContext); try { callback() console.log(description + " ✅ " + text) } catch { console.log(description+ " " + text) }}

    Bien, esto está empezando a parecerse más a JavaScript. ¿Qué tenemos aquí?

    Bueno, principalmente, en lugar de nuestro componente ContextProvider, estamos usando TextContext.Provider, que toma un objeto con las referencias a nuestros valores y useContext()que sirve como nuestro portal, para que podamos acceder a nuestro agujero de gusano.

    ¿Pero esto puede funcionar? Intentemos.

    Redacción de nuestra API

    Ahora que tenemos el concepto general de cómo vamos a usar nuestro contexto, comencemos por definir las funciones que vamos a exponer. Como ya sabemos cómo se ve la API React Context, podemos basarla en eso.

    function createContext() { return { Provider, Consumer } function Provider(value, callback) {} function Consumer() {}}function useContext(ctxRef) {}

    Estamos definiendo dos funciones, al igual que React. createContexty useContext. createContext devuelve un Proveedor y un Consumidor, al igual que el contexto de React, y useContext toma una referencia de contexto.

     

    Algunos conceptos que debemos tener en cuenta antes de sumergirnos

    Lo que haremos a partir de ahora se basará en dos ideas centrales que son importantes para los desarrolladores de JavaScript. No voy a explicarlos aquí, pero si te sientes inseguro acerca de estos temas, te animamos a leer sobre ellos:

    1. Cierres de JavaScript de MDN : “Un cierre es la combinación de una función agrupada (encerrada) con referencias a su estado circundante (el entorno léxico ). En otras palabras, un cierre le da acceso al alcance de una función externa desde una función interna. En JavaScript, los cierres se crean cada vez que se crea una función, en el momento de la creación de la función ".
    2. La naturaleza sincrónica de JavaScript En esencia, Javascript es sincrónico y bloqueante. Sí, tiene promesas asíncronas, devoluciones de llamada y async/await, y requerirán un manejo especial, pero en su mayor parte, tratemos a JavaScript como sincrónico, porque a menos que lleguemos a esos reinos, o implementaciones de navegador de casos extremos heredados MUY extraños, El código JavaScript es sincrónico.

    Estas dos ideas aparentemente no relacionadas son las que permiten que nuestro contexto funcione. La suposición es que, si establecemos algún valor dentro Providery llamamos a nuestra devolución de llamada, nuestros valores permanecerán y estarán disponibles durante toda la ejecución de nuestra función sincrónica. Sólo necesitamos una forma de acceder a él. Para eso useContextes.

    Almacenar valores en nuestro contexto

    El contexto se utiliza para propagar datos a través de nuestra pila de llamadas, por lo que lo primero que queremos hacer es almacenar información en ella. Definamos una contextValuevariable dentro de nuestra createContextfunción. Residir dentro del cierre de createContext, garantiza que todas las funciones definidas dentro de createContext tendrán acceso a él incluso más adelante.

    function createContext() { let contextValue = undefined; function Provider(value, callback) {} function Consumer() {} return { Provider, Consumer }}

    Ahora que tenemos el valor almacenado en el contexto, nuestra Providerfunción puede almacenar el valor que acepta y la Consumerfunción puede devolverlo.

    function createContext() { let contextValue = undefined; function Provider(value, callback) { contextValue = value; } function Consumer() { return contextValue; } return { Provider, Consumer }}

    Para acceder a los datos desde nuestra función, simplemente podemos llamar a nuestra función Consumidor, pero para que nuestra interfaz funcione exactamente como la de React, también hagamos que useContext tenga acceso a los datos.

    function useContext(ctxRef) { return ctxRef.Consumer();}

    Llamando a nuestras devoluciones de llamada

    Ahora comienza la parte divertida. Como se mencionó, este método se basa en la naturaleza sincrónica de JavaScript. Esto significa que desde el momento en que ejecutamos nuestra devolución de llamada, sabemos , con certeza, que no se ejecutará ningún otro código, lo que significa que realmente no necesitamos proteger nuestro contexto para que no se modifique durante nuestra ejecución, sino que solo necesitamos para limpiarlo inmediatamente después de que nuestra devolución de llamada termine de ejecutarse.

     

    function createContext() { let contextValue = undefined; function Provider(value, callback) { contextValue = value; callback(); contextValue = undefined; } function Consumer() { return contextValue; } return { Provider, Consumer }}

    Eso es todo al respecto. En realidad. Si nuestra función es llamada con la función Proveedor, durante toda su ejecución tendrá acceso al valor del Proveedor.

    ¿Qué pasa si tenemos un contexto anidado?

    El anidamiento de contextos es algo que puede suceder. Por ejemplo, cuando tengo un describedentro de un describe. En tal caso, nuestro contexto se romperá al salir del contexto más interno, porque después de cada ejecución de devolución de llamada, restablecemos el valor del contexto a indefinido, y dado que ambas capas del contexto comparten el mismo cierre, el proveedor más interno se restablecerá. el valor de las capas superiores.

    function Provider(value, callback) { contextValue = value; callback(); contextValue = undefined;}

    Por suerte, es muy fácil de manejar. Al ingresar a un contexto, todo lo que necesitamos hacer es guardar su valor actual en una variable y restablecerlo cuando salgamos del contexto:

    function Provider(value, callback) { let currentValue = contextValue; contextValue = value; callback(); contextValue = currentValue;}

    Ahora, cada vez que salgamos de contexto, volverá al valor anterior y, si no hay más capas de contexto arriba, volveremos al valor inicial, que no está definido.

    Otra característica que no implementamos hoy es el valor predeterminado para el contexto. En React, puede inicializar el contexto con un valor predeterminado que será devuelto por Consumer/useContext en caso de que no estemos dentro de un contexto en ejecución.

    Si ha llegado hasta aquí, tiene todo el conocimiento y las herramientas para intentar implementarlo usted mismo. Me encantaría ver qué se le ocurre.

    ¿Se está utilizando esto en alguna parte?

    ¡Sí! De hecho, construí el paquete de contexto en NPM que hace exactamente eso, con algunas modificaciones y un montón de características más, incluida la compatibilidad completa con mecanografiado, la fusión de contextos anidados, valores de retorno de la función "Proveedor", valores iniciales de contexto e incluso registro de contexto. software intermedio.

    Puede inspeccionar el código fuente completo del paquete aquí: https://github.com/ealush/vest/blob/latest/packages/context/src/context.ts

    Y se está utilizando ampliamente dentro del marco de validación Vest , un marco de validación de formularios inspirado en bibliotecas de pruebas unitarias como Mocha o Jest. El contexto sirve como tiempo de ejecución principal de Vest, como se puede ver aquí .

    Espero que hayas disfrutado de esta breve introducción a la propagación de contexto en JavaScript y que te haya mostrado que no hay ninguna magia detrás de algunas de las API de React más útiles.

    Lecturas adicionales sobre la revista Smashing

    • " Una introducción a la API de contexto de React ", Yusuff Faruq
    • “ Variables reactivas en el cliente GraphQL Apollo ”, Daniel Don
    • “ Componentes compuestos en React ”, Ichoku Chinonso
    • " Ganchos de reacción útiles que puede utilizar en sus proyectos ", Ifeanyi Dike

    (NL, IL)Explora más en

    • javascript
    • Marcos
    • Reaccionar
    • Aplicaciones
    • API
    • Componentes web





    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

    Una introducción a la propagación de contexto en JavaScript

    Una introducción a la propagación de contexto en JavaScript

    Clase magistral de diseño para una interfaz de usuario compleja, con Vitaly Friedman SmashingConf UX y diseño, Amberes 2024 Índice

    programar

    es

    https://aprendeprogramando.es/static/images/programar-una-introduccion-a-la-propagacion-de-contexto-en-javascript-1153-0.jpg

    2024-04-04

     

    Una introducción a la propagación de contexto en JavaScript
    Una introducción a la propagación de contexto en JavaScript

    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

     

     

    Top 20