Saltearse al contenido

Hooks

React popularizó los hooks como una forma de resolver los siguientes problemas:

  • Ayuda a reutilizar la lógica con estado entre componentes
  • Ayuda a organizar el código por función en componentes complejos
  • Utiliza el estado en componentes funcionales, sin escribir una clase.

Los hooks Owl tienen la misma función, excepto que funcionan para componentes de clase (nota: los hooks React no funcionan en componentes de clase y, tal vez por eso, parece existir la idea errónea de que los hooks se oponen a la clase. Esto claramente no es cierto, como lo demuestran los hooks Owl).

Los hooks funcionan maravillosamente con los componentes Owl: resuelven los problemas mencionados anteriormente y, en particular, son la manera perfecta de hacer que su componente sea reactivo.

Reglas de los hooks

Solo hay una regla: cada hook para un componente debe llamarse en el método setup o en los campos de clase:

// ok
class SomeComponent extends Component {
state = useState({ value: 0 });
}
// also ok
class SomeComponent extends Component {
setup() {
this.state = useState({ value: 0 });
}
}
// not ok: this is executed after the constructor is called
class SomeComponent extends Component {
async willStart() {
this.state = useState({ value: 0 });
}
}

Ciclo de vida de los hooks

Todos los hooks del ciclo de vida están documentados en detalle en su sección específica.

HookDescripción
setupconfiguración
willStartasíncrono, antes de la primera renderización
willRenderJusto antes de que se renderice el componente
renderedJusto después de que se renderiza el componente
mountedJusto después de que el componente se renderiza y se agrega al DOM
willUpdatePropsasíncrono, antes de actualizar las propiedades
willPatchJusto antes de que se parchee el DOM
patchedJusto después de que se parchea el DOM
willUnmountJusto antes de eliminar el componente del DOM
willDestroyJusto antes de que se destruya el componente
errorCaptura y gestiona errores (Ver Manejo de Errores)

Otros Hooks

useState

El hook useState es ciertamente el hook más importante para los componentes Owl: esto es lo que permite que un componente sea reactivo, que reaccione al cambio de estado.

Al hook useState se le debe dar un objeto o una matriz, y devolverá una versión observada del mismo (usando un Proxy).

const { useState, Component } = owl;
class Counter extends Component {
static template = xml`
<button t-on-click="increment">
Click Me! [<t t-esc="state.value"/>]
</button>`;
state = useState({ value: 0 });
increment() {
this.state.value++;
}
}

Es importante recordar que useState solo funciona con objetos o matrices. Es necesario, ya que Owl necesita reaccionar ante un cambio de estado.

useRef

El hook useRef es útil cuando necesitamos una forma de interactuar con alguna parte interna de un componente, renderizado por Owl. Solo funciona en un elemento html etiquetado con la directiva t-ref:

<div>
<input t-ref="someInput"/>
<span>hello</span>
</div>

En este ejemplo, el componente podrá acceder a la entrada con el hook useRef:

class Parent extends Component {
inputRef = useRef("someInput");
someMethod() {
// here, if component is mounted, refs are active:
// - this.inputRef.el is the input HTMLElement
}
}

Como se muestra en el ejemplo anterior, se accede a la instancia HTMLElement real con la tecla el.

La directiva t-ref también acepta valores dinámicos con interpolación de cadenas (como las directivas t-attf- y t-component). Por ejemplo,

<div t-ref="div_{{someCondition ? '1' : '2'}}"/>

Aquí, las referencias deben configurarse de la siguiente manera:

this.ref1 = useRef("div_1");
this.ref2 = useRef("div_2");

Solo se garantiza que las referencias estén activas mientras el componente principal esté montado. Si no es así, al acceder a el en él se devolverá null.

useSubEnv y useChildSubEnv

A veces, el entorno resulta útil para compartir información común entre todos los componentes, pero otras veces queremos limitar ese conocimiento a un subárbol.

Por ejemplo, si tenemos un componente de vista de formulario, tal vez nos gustaría que algún objeto model esté disponible para todos los subcomponentes, pero no para toda la aplicación. Aquí es donde el hook useChildSubEnv puede ser útil: permite que un componente agregue información al entorno de manera que solo sus componentes secundarios puedan acceder a ella:

class FormComponent extends Component {
setup() {
const model = makeModel();
// model will be available on this.env for this component and all children
useSubEnv({ model });
// someKey will be available on this.env for all children
useChildSubEnv({ someKey: "value" });
}
}

Los hooks useSubEnv y useChildSubEnv toman un argumento: un objeto que contiene una clave o valor que se agregará al entorno actual. Estos hooks crearán un nuevo objeto de entorno con la nueva información:

  • useSubEnv asignará este nuevo env a sí mismo y a todos los componentes secundarios
  • useChildSubEnv solo asignará este nuevo env a todos los componentes secundarios.

Como es habitual en Owl, los entornos creados con estos dos hooks se congelan para evitar modificaciones no deseadas.

Tenga en cuenta que ambos hooks se pueden llamar una cantidad arbitraria de veces. El env se actualizará en consecuencia.

useExternalListener

El hook useExternalListener ayuda a resolver un problema muy común: agregar y eliminar un detector en algún objetivo cada vez que se monta o desmonta un componente. Toma un objetivo como su primer argumento y reenvía los otros argumentos a addEventListener. Por ejemplo, un menú desplegable (o su elemento principal) puede necesitar escuchar un evento click en window para cerrarse:

useExternalListener(window, "click", this.closeMenu, { capture: true });

useComponent

El hook useComponent es útil como bloque de construcción para algunos hooks personalizados, que pueden necesitar una referencia al componente que los llama.

function useSomething() {
const component = useComponent();
// now, component is bound to the instance of the current component
}

useEnv

El hook useEnv es útil como bloque de construcción para algunos hooks personalizados, que pueden necesitar una referencia al entorno del componente que los llama.

function useSomething() {
const env = useEnv();
// now, env is bound to the env of the current component
}

useEffect

Este hook ejecutará una devolución de llamada cuando se monte y se parchee un componente, y ejecutará una función de limpieza antes de parchear y antes de desmontar el componente (solo si algunas dependencias han cambiado).

Tiene casi la misma API que el hook useEffect de React, excepto que las dependencias están definidas por una función en lugar de solo las dependencias.

El hook useEffect utiliza dos funciones: la función de efecto y la función de dependencia. La función de efecto realiza una tarea y devuelve (opcionalmente) una función de limpieza. La función de dependencia devuelve una lista de dependencias, estas dependencias se pasan como parámetros en la función de efecto. Si alguna de estas dependencias cambia, el efecto actual se limpiará y se volverá a ejecutar.

Aquí hay un ejemplo sin ninguna dependencia:

useEffect(
() => {
window.addEventListener("mousemove", someHandler);
return () => window.removeEventListener("mousemove", someHandler);
},
() => []
);

En el ejemplo anterior, la lista de dependencias está vacía, por lo que el efecto solo se limpia cuando se desmonta el componente.

Si se omite la función de dependencia, el efecto se limpiará y se volverá a ejecutar en cada parche.

Aquí hay otro ejemplo de cómo se podría implementar un hook useAutofocus con el hook useEffect:

function useAutofocus(name) {
let ref = useRef(name);
useEffect(
(el) => el && el.focus(),
() => [ref.el]
);
}

Este hook toma el nombre de una directiva t-ref válida, que debería estar presente en la plantilla. Luego, verifica cada vez que se monta o se aplica un parche al componente si la referencia no es válida y, en este caso, se centrará en el elemento del nodo. Este hook se puede utilizar de la siguiente manera:

class SomeComponent extends Component {
static template = xml`
<div>
<input />
<input t-ref="myinput"/>
</div>`;
setup() {
useAutofocus("myinput");
}
}

Ejemplo: posición del mouse

Aquí está el ejemplo clásico de un gancho no trivial para rastrear la posición del mouse.

const { useState, onWillDestroy, Component } = owl;
// Definimos aquí un comportamiento personalizado: este gancho rastrea el estado del mouse.
// position
function useMouse() {
const position = useState({ x: 0, y: 0 });
function update(e) {
position.x = e.clientX;
position.y = e.clientY;
}
window.addEventListener("mousemove", update);
onWillDestroy(() => {
window.removeEventListener("mousemove", update);
});
return position;
}
// Main root component
class Root extends Component {
static template = xml`<div>Mouse: <t t-esc="mouse.x"/>, <t t-esc="mouse.y"/></div>`;
// Este hook está vinculado a la propiedad 'mouse'.
mouse = useMouse();
}

Ten en cuenta que usamos el prefijo “use” para los ganchos, al igual que en React. Esto es solo una convención.