Back to Home

馃敭 - Seamos reactivos con Proxy

6 min read

Hace pocos d铆as estuve investigando un poco sobre VueJS, ya que soy un completo principiante con este interesante framework del cliente, pero en mi pronta investigaci贸n, encontr茅 que lanzaron la nueva versi贸n de esta tecnolog铆a que ser铆a la versi贸n 3, que actualmente ya se encuentra p煤blica y estable.

Resulta que su flujo de informaci贸n es completamente reactiva, adem谩s encontr茅 que explicaban con breves ejemplos como es el flujo de todo, pero pause en algo muy puntual, y es que usan una clase nativa de JavaScript, llamada Proxy, y es de lo que hablaremos hoy.

驴Que es Proxy?

Basicamente puedes observar el comportamiento de un objeto, constructor, hasta de una funci贸n pero agregando tus mutaciones personalizadas.

En otras palabras, los Proxy pueden escuchar el comportamiento que tenga un objeto nativo o funci贸n dentro de JavaScript, siendo alterados bajo nuestro criterio.

Vamos a un caso particular:

const data = { role: 'Dev' }

const proxy = new Proxy(data, {
  get(target, property) {
    // Imprimir ambos campos
    console.log({ target, property })
  }
})

console.log(proxy.role)

En los casos donde debe imprimir valores, ocurre lo siguiente:

// 1er console.log
{
  target: { role: 'Dev' },
  property: 'role'
}

// 2do console.log
undefined

En el primer caso, se puede observar que el par谩metro target devuelve el valor del objeto -como referencia de este- y el segundo par谩metro contiene la propiedad que ha sido llamada en nuestro objeto.

En el segundo caso simplemente retorna undefined debido a que al ser un objeto proxy, y estamos personalizando el comportamiento -que en este caso es un get- no estamos retornando un valor en esa funci贸n de un proxy.

const data = { role: 'Dev' }

const proxy = new Proxy(data, {
  get(target, property) {
    // Imprimir ambos campos
    console.log({ target, property })
    // Retornar el valor
    return target[property]
  }
})

console.log(proxy.role)

Ahora en nuestro caso del segundo resultado si retornaria el proximo valor:

// 2do console.log
'Dev'

Tanto como la funci贸n nativa de Proxy - me refiero al m茅todo get - existen m谩s muchos basados en su API

Caso de Uso

En este caso crearemos unas directivas en nuestras etiquetas de HTML, teniendo en cuenta el siguiente criterio:

  • data-get : Etiqueta que tenga esta directiva (personalizadas gracias al prefijo data-*) tendr谩 asociada el nombre de la data reactiva y sera asignada por medio del campo value
  • name : Etiqueta que tenga esta directiva (nativa) tendr谩 asociada el nombre de la data reactiva y ser谩 la que genere los eventos de inserci贸n.

En el primer archivo a crear ser谩 index.html que tiene la siguiente estructura:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Proxy</title>
</head>
<body>
  <h1>Proxy</h1>
  <h2 data-get="username"></h2>
  <div class="wrapper">
    <input type="text" name="username"/>
  </div>
  <script src="./proxy.js"></script>
</body>
</html>

Como mencion茅 anteriormente, existir谩 una etiqueta que mostrara el resultado en tiempo de ejecuci贸n -en este caso es en h2- y quien ingresa valores ser铆a nuestra etiqueta input para cumplir este caso.

En el segundo archivo ser谩 llamado proxy.js con las siguientes instrucciones:

// Define initial data
const initialData = {
  username: ''
}

En la primera instrucci贸n que definimos el objeto global, la aplicaci贸n entender铆a que la llave - en este caso seria username - tambi茅n ser铆a el valor de referencia clave para los atributos que usaremos para actualizar los datos en ejecuci贸n.

Despu茅s de definir nuestro estado inicial, procedemos a implementar nuestra funci贸n que usara Proxy para la observaci贸n del estado inicial, esta funci贸n la llamaremos reactMin

// Define proxy function
function reactMin(data) {
  // Create handler object
  const handler = {
    set(target, property, value) {
      if (typeof value === 'string') {
        const elements = document.querySelectorAll(`[data-get="${property}"]`)
        elements.forEach(element => {
          element.textContent = value
        })
      }

      Reflect.set(target, property, value)
    }
  }

  // Instance proxy state
  const proxy = new Proxy(data, handler)

  // Create event listeners for all inputs
  function setDataEvents(proxyData) {
    return Object.keys(proxyData).forEach(key => {

      const element = document.querySelector(`[name=${key}]`)

      element.addEventListener('input', (event) => {
        Reflect.set(proxyData, key, event.target.value)
      })
    })
  }

  setDataEvents(proxy)

  return proxy
}

Este codigo se ha definido los siguientes bloques:

  • handler: Este contiene todos los metodos que la API de Proxy soporta actualmente. Este caso demasiado puntual solo usaremos el metodo set para observar si la propiedad tendra un cambio "en camino" para poder alterarlo a que tambien modifique los atributos en el HTML con el nombre del campo existente.
  • proxy: Esta variable crea la instancia relacionada a la API de Proxy solo recibiendo el objeto el cual ser谩 observado y el segundo parametro seria el handler.
  • setDataEvents: Este ultimo va a generar solo los inputs del HTML que contengan el valor de alguno de los campos observados en el proxy - que en nuestro caso solo ser谩 username.
const app = reactMin(initialData)

Por ultimo, creamos nuestro proxy apuntando al objeto inicial - que en este caso se llama initialData - para proceder en hacer cambios a futuro.

Hemos notado tambien que usamos una clase llamada Reflect, hereda todas las acciones de Proxy que en resumen es una trampa para que el cambio sea mas efectivo a la hora de hacer una actualizacion para el proxy -el proxy siempre tenga en cuenta esas acciones

// Regular
Reflect.set(obj, key, value)

// Es lo mismo que...
obj[key] = value

Si deseas saber mas sobre esta clase, aqui puedes encontrar mas info sobre ella.

Resultado Final

Extra

Tambien podemos cambiar el valor de nuestro campo username haciendo en momento de ejecucion, como por ejemplo insertar el valor de la respuesta de una peticion y tambien operaciones asincronas:

function getAsyncName(id = 1) {
  return fetch(`https://<hostname>/fake/users/${id}`)
         .then(response => response.json())
}


// Recordemos que tenemos una variable llamada `app`
const app = reactMin(initialData)

// Realizamos una operacion asincrona
(async () => {
  app.username = 'Loading...'
  const { name } = await getAsyncName(2)
  app.username = name
})()
~~~

El resultado seria el siguiente:

<figure class="videoContainer">
  <video loop autoplay muted>
    <source src="/video/demo_proxy_async.mp4" type="video/mp4"/>
  </video>
</figure>

## Conclusi贸n

Logramos entender un poco el prop贸sito b谩sico y abstracto de que son los proxys, y su utilidad a la hora de generar observadores para generar acciones para `X` propiedad.

Tambi茅n es importante entender de que en esto ha sido inspirado muchas librerias y frameworks reactivos, con el prop贸sito de generar una acci贸n/efecto en todos las variables que esperan ser cambiadas, aun as铆 podemos ser reactivos fuera de estas tecnolog铆as, y evaluar nuestros propios m茅todos.

Espero que haya sido de mucha ayuda, 馃馃徑.