@composi/core:

Renderización

Defición de Marcado

Por defecto, Composi usa JSX para definir el marcado que representa. Esto es declarativo y hace que sea fácil razonar sobre cuál será el resultado.

Importar h y render

Antes de que podamos hacer algo, necesitamos importar dos funciones esenciales de @composi/core: h y render:

import { h, render } from '@composi/core'

h hace que Babel transforme las etiquetas de JSX en funciones que crean nodos virtuales. Estos se convertirán en nodos DOM reales durante la rendirización.

JSX

Puedes usar JSX para definir el marcado que crea un componente funcional. A diferencia de React, no tiene que usar atributos especiales o notación de camello con atributos. Utiliza los atributos HTML normales: onclick={}, class='', for='', etc. Debido a que esto es JSX, que es una variedad de XML, debe terminar las etiquetas que se cierran automáticamente con una barra inclinada: <br/>, <input type='text' />, <img src='' />, etc.

Componentes Functionales

Para crear un componente que se puede renderizar, se necesita empezar con una función. El nomfre de ésta tiene que estar en mayúsculas. Aquí es un ejemplo sencillo de una función Hola Mundo.

import { h, render } from '@composi/core'
function HolaMundo() {
  return (
    <h1>¡Hola, Mundo!</h1>
  )
}

Date cuenta que esta función hace una sóla cosa, devuelve algunas etiquetas de JSX.

Renderización

Ahora podemos pasar esta función a la función render como una etiqueta. La función render espera dos argumentos: la etiqueta para renderizar y el contenedor en el cual va renderizar. La etiqueta debe ser una función escrita como una etiqueta JSX. El contenedor puede ser una referencia de nodo DOM, o un selector.

import { h, render } from '@composi/core'
function HolaMundo() {
  return (
    <h1>Hola, Mundo!</h1>
  )
}
// Renderiza el componente en el DOM:
render(<HolaMundo />, document.body)

Para actualizar un componente, tan sólo pásalo a la función render con los valores nuevos para sus propiedades. Este ejemplo muestra cómo hacer esoß:

See the Pen @composi/core - Hola Mundo by Robert Biggs (@rbiggs) on CodePen.

Selector vs Referencia de DOM

En el ejemplo anterior, estamos usando el texto "header" como contenedor para el componente. Esto está bien para la renderización ocasional. Sin embargo, si el componente puede procesarse muchas veces en rápida sucesión, como cuando se anima un componente, sería mejor usar una referencia de DOM para ese contenedor. Esto se debe a que cuando proporciona un selector de text para el contenedor, cada vez que se procesa el componente, Composi tiene que consultar el DOM para ese selector. Obtener una referencia al contenedor primero y usar eso en la función significa que el procesamiento puede comenzar de inmediato a computar las differences para parchear el DOM.

Pasando Hijos a un Componente

A veces es posible que desees pasar componentes niños a un componente. Puedes habilitar cualquier componente funcional para que acepte hijos arbitrarios pasándole un segundo parámetro después del argument de props (propiedades). Luego, puede emitir lo que sean los hijos procesando el valor children dentro de llaves. Observe cómo lo hacemos en este ejemplo. Tenemos un BordeLujoso que acepta cualquier componente secundario. Esto significa que podemos pasar cualquier cosa que necesitemos como niño. En este caso, pasamos a otro componente, DiálogoDeMensage, pero podría ser cualquier cosa. Esto hace más fácil que se reutilice el componente BordeLujoso.

See the Pen @composi/core - Passing Children to Component by Robert Biggs (@rbiggs) on CodePen.

Propiedades (Props)

Para hacer que un componente sea dinámico, necesitamos poder pasarle algunos datos. Lo hacemos a través de props, que es la form por la cual accedemos a las propiedadedes de un componente. Este es el principal argumento que recibe cualquier etiqueta JSX. Cualquier propiedad que le des a la etiqueta estará disponible desde este objeto. Vamos a rehacer nuestro ejemplo anterior para usar props. Observe cómo accedemos al valor de saludo del objeto props dentro del cuerpo de la función HolaMundo:

See the Pen @composi/core - HolaMundo-2 by Robert Biggs (@rbiggs) on CodePen.

Puesto que ya estamos usand props en el componente, podemos actualizar el componente en cualqier momento por renderizarlo on un valor nuevo para el prop. Supone que queremos actualizar el valor de nuestro ejemplo de HolaMunto después de cinco segundos. Para actualizar el componente, tan sólo tenemos que cambiar el valor de la propiedad cuando regresamos a rendizarlo de nuevo:

See the Pen @composi/core - Hola Mundo-3 by Robert Biggs (@rbiggs) on CodePen.

Para más detalles sobre como usar props, consulta la documentación.

Renderización Conditional

A veces necesitas renderizar un componente basado en un cierto criterio. Hay dos formas de manejar la representación condicional: directamente en la declaración de retorno (return) o antes de la declaración de retorno.

Lógica en la Sentencia Return

La forma más común de controlar como se renderiza un componente es hacerlo directamente dentro de la sentencia de retorno (return). Para hacer esto, utilizas cheques booleanos con JavaScript operadores lógicos (&&, ||, y !) o con operador ternario. De hecho, puedes usarlos para obtener un marcado diferente según los controles condicionales. Debido a que está dentro de una sentencia de retorno, no puedes usar ninguna estructura condicional (if, if else, else).

Operadores Lógicos

Puede usar operadores lógicos para determinar qué devolver en su declaración. Observe cómo utilizamos el operador AND (&&) y luego el operador OR (||) para cambiar a la otra opción. Sin embargo, cuando desees un if/else necesitas usar el operador || para el else. El problema es que es fácil olvidarse de poner el operador ||, en cuyo caso la segunda condición nunca ocurrirá. Esto resulta en situaciones difíciles de depurar.

// Dos componentes para renderizar:
function SaludarUsuario(props) {
  return <h1>¡Bienvenido de nuevo!</h1>
}

function SaludarVisitante(props) {
  return <h1>¡Regístrate, por favor!</h1>
}

function Saludo(props) {
  const sesiónIniciada = props.sesiónIniciada
  // Con operadores lógicos:
  return (
    sesiónIniciada && <SaludarUsuario /> ||
    !sesiónIniciada && <SaludarVisitante />
  )
}

El uso del operador AND es excelente cuando solo deseas generar resultados basados en una sóla condición. En ese caso viene siendo lo mismo que if. Observa cómo en esta versión, está muy claro cuál es la intención. Sin embargo no es ramificación de lógica.

// Sólo renderiza el saludo si el usuario inició sesión.
// No hay renderización alternativa.
function Saludo(props) {
  const sesiónIniciada = props.sesiónIniciada
  // Con operadores lógicos:
  return (
    sesiónIniciada && <SaludarUsuario />
  )
}

Aquí es un ejemplo que muestra cómo usar los operadoes lógicos puede resultar en logica difícil de entender cuando los componentes son complejos:

See the Pen @composi/core - Conditional-1 es by Robert Biggs (@rbiggs) on CodePen.

Operador Ternario

El operador ternario es más claro sobre lo que está pasando y más compacto. Esta es una gran opción para etiquetas simples. Sin embargo, si sus etiquetas tienen muchas propiedades, una expresión ternaria puede ser difícil de leer.

function Saludo(props) {
  const sesiónIniciada = props.sesiónIniciada
  // Con un sentencia conditional ternaria:
  return sesiónIniciada ? <SaludarUsuario /> : <SaludarVisitante />
}

Aquí hay un ejemplo del uso de un operador ternario con componentes complejos. Observa que tiene la misma estructura básica que el ejemplo anterior utilizando operadores lógicos:

See the Pen @composi/core - Conditional-2 es by Robert Biggs (@rbiggs) on CodePen.

La Lógica Antes de Return

Si necesita una representación condicional sin los inconvenientes de los operadores lógicos y ternarios, puede usar sentencias condicionales estándar para determinar qué devolver. Muchos desarrolladores prefieren la sintaxis más compacta de los operadores lógicos ternarios. Sin embargo, con las declaraciones condicionales no hay ambigüedad sobre lo que está sucediendo.

function Saludo(props) {
  const sesiónIniciada = props.sesiónIniciada
  // With conditional statements:
  if (sesiónIniciada) {
    return <SaludarUsuario />
  } else {
    return <SaludarVisitante />
  }
}

A algunos desarrolladores y linters que chequean el estilo de código no les gustan sentencias de retorno dentro de las estructuras condicionales. Podemos camibar eso para fijar el problema:

function Saludo(props) {
  const sesiónIniciada = props.sesiónIniciada
  // Con estructura condicional:
  if (sesiónIniciada) {
    return <SaludarUsuario />
  }
  // Si la lógica falla,
  // devuele el saludo de visitante:
  return <SaludarVisitante />
}

Aquí está el ejemplo vivo. Este tiene el código más fácil de leer:

See the Pen @composi/core - Conditional-3 es by Robert Biggs (@rbiggs) on CodePen.

A fin de cuentas, elige el método que funcione mejor para ti.

No Renderizar un Componente

A veces no quieres renderizar un componente cuando una condición sucede. Podemos realizar esto por usar la estructura conditional anterior. Aquí es un ejemplo:

See the Pen @composi/core - Conditional-4 es by Robert Biggs (@rbiggs) on CodePen.

Fíjate como cuando mostrarAdvertencia es verdadero, devolvemos el componente, de lo contrario, devolvemos nulo. También podríamos eliminar la sentencia de else por brevedad. También podríamos usar un operador lógico simple para esto:

function BannerAdvertencia() {
  return (
    mostrarAdvertencia && (
      <div class="warning">
        ¡Cuidado!
      </div>
    )
  )
}

En ese caso, el use del operaor lógico es claro y conciso. También podríamos usar un operador ternario para esto:

function BannerAdvertencia() {
  return (
    mostrarAdvertencia ? (
      <div class="warning">
        ¡Cuidado!
      </div>
    ) : null
  )
}

En este caso, el operador tenario no es tan conciso como el operador lógico. El operador ternario require lógica de if/else, mientras que el operador ternario no deja una sóla condición de if.

Límites del Contendior

Cuando @composi/core renderiza un componente en un contenedor, almacena en caché el nodo virtual del componente en el contenedor mismo como propiedad. La próxima vez que se renderice ese componente, Composi toma el nodo virtual almacenado en caché del contenedor y lo usa para descubrir las diferencias y parchear el DOM. Esto significa que un contenedor solo puede tener un componente. Cuando se renderiza un componente en un contenedor, si dicho contenedor tiene contenido estático, el componente se agregará después del contenido.

Si necesita generar más de un componente en el mismo contenedor, envuélvalos en un componente principal y procesarlos en el componente. No puede usar la etiqueta Fragment para esto, ya que debe ser consumida por una etiqueta que devuelve un elemento singular.

Rebotar las Renderizaciones

Es posible que esté usando algún tipo de evento, como la posición del cursor, para activar una actualización. Esto es muy común para la animación. En ese caso, desearía envolver la función de render en requestAnimationFrame. Esto hará que se omitan intentos innecesarios de renderizar más rápido de lo que el navegador puede mantener. El formato es así:

requestAnimationFrame(() => {
  render(, 'body')
})

Consulta la documentación de cómo hacer esto con @composi/datastore