Saltearse al contenido

Props

En Owl, props (abreviatura de properties) es un objeto que contiene cada pieza de información proporcionada a un componente por su padre.

class Child extends Component {
static template = xml`<div><t t-esc="props.a"/><t t-esc="props.b"/></div>`;
}
class Parent extends Component {
static template = xml`<div><Child a="state.a" b="'string'"/></div>`;
static components = { Child };
state = useState({ a: "fromparent" });
}

En este ejemplo, el componente Child recibe dos propiedades de su componente padre: a y b Owl las recopila en un objeto props y cada valor se evalúa en el contexto del componente padre. Por lo tanto, props.a es igual a 'fromparent' y props.b es igual a 'string'.

Tenga en cuenta que props es un objeto que solo tiene sentido desde la perspectiva del componente secundario.

Definición

El objeto props está formado por todos los atributos definidos en la plantilla, con las siguientes excepciones:

  • todos los atributos que comienzan con t- no son propiedades (son directivas QWeb),

En el siguiente ejemplo:

<div>
<ComponentA a="state.a" b="'string'"/>
<ComponentB t-if="state.flag" model="model"/>
</div>

El objeto props contiene las siguientes claves:

  • para ComponenteA: a y b,
  • para ComponenteB: modelo,

Comparación de props

Cada vez que Owl encuentra un subcomponente en una plantilla, realiza una comparación superficial de todas las propiedades. Si todas son referencialmente iguales, entonces el subcomponente ni siquiera se actualizará. De lo contrario, si al menos una propiedad ha cambiado, Owl la actualizará.

Sin embargo, en algunos casos, sabemos que dos valores son diferentes, pero tienen el mismo efecto y Owl no debería considerarlos diferentes. Por ejemplo, las funciones anónimas en una plantilla siempre son diferentes, pero la mayoría de ellas no deberían considerarse diferentes:

<t t-foreach="todos" t-as="todo" t-key="todo.id">
<Todo todo="todo" onDelete="() => deleteTodo(todo.id)" />
</t>

En ese caso, se puede utilizar el sufijo .alike:

<t t-foreach="todos" t-as="todo" t-key="todo.id">
<Todo todo="todo" onDelete.alike="() => deleteTodo(todo.id)" />
</t>

Esto le indica a Owl que esta propiedad específica siempre debe considerarse equivalente (o, en otras palabras, debe eliminarse de la lista de propiedades comparables).

Tenga en cuenta que, aunque la mayoría de las funciones anónimas probablemente deberían considerarse “similares”, esto no es necesariamente cierto en todos los casos. Depende de qué valores captura la función anónima. El siguiente ejemplo muestra un caso en el que probablemente sea incorrecto utilizar “.alike”.

<t t-foreach="todos" t-as="todo" t-key="todo.id">
<!-- Probably wrong! todo.isCompleted may change -->
<Todo todo="todo" toggle.alike="() => toggleTodo(todo.isCompleted)" />
</t>

Funciones de enlace en props

Es común tener la necesidad de pasar una devolución de llamada como una propiedad. Dado que los componentes Owl se basan en clases, la devolución de llamada con frecuencia debe estar vinculada a su componente propietario. Por lo tanto, se puede hacer lo siguiente:

class SomeComponent extends Component {
static template = xml`
<div>
<Child callback="doSomething"/>
</div>`;
setup() {
this.doSomething = this.doSomething.bind(this);
}
doSomething() {
// ...
}
}

Sin embargo, este es un caso de uso tan común que Owl proporciona un sufijo especial para hacer justamente eso: .bind. Se ve así:

class SomeComponent extends Component {
static template = xml`
<div>
<Child callback.bind="doSomething"/>
</div>`;
doSomething() {
// ...
}
}

El sufijo .bind también implica .alike, por lo que estas propiedades no causarán representaciones adicionales.

Props traducibles

Cuando necesitas pasar una cadena que se ve en la pantalla del usuario a un subcomponente, probablemente quieras que se traduzca. Desafortunadamente, debido a que las propiedades son expresiones arbitrarias, no sería práctico para Owl averiguar qué partes de la expresión son cadenas y traducirlas, y también dificulta que las herramientas extraigan estas cadenas para generar términos para traducir. Si bien puedes solucionar este problema haciendo la traducción en JavaScript o usando t-set con un cuerpo (el cuerpo de t-set se traduce) y pasando la variable como una propiedad, este es un caso de uso lo suficientemente común como para que Owl proporcione un sufijo para este propósito: .translate.

<t t-name="ParentComponent">
<Child someProp.translate="some message"/>
</t>

Tenga en cuenta que el contenido de este atributo NO se trata como una expresión de JavaScript: se trata como una cadena, como si fuera un atributo de un elemento HTML, y se traduce antes de pasarlo al componente. Si necesita interpolar algunos datos en la cadena, deberá hacerlo en JavaScript.

Props dinámicas

La directiva t-props se puede utilizar para especificar propiedades totalmente dinámicas:

<div t-name="ParentComponent">
<Child t-props="some.obj"/>
</div>
class ParentComponent {
static components = { Child };
some = { obj: { a: 1, b: 2 } };
}

Props por defecto

Si se define la propiedad estática defaultProps, se utilizará para completar las propiedades recibidas por el padre, si faltan.

class Counter extends owl.Component {
static defaultProps = {
initialValue: 0,
};
...
}

En el ejemplo anterior, la propiedad initialValue ahora está establecida de manera predeterminada en 0.

Validación de props

A medida que una aplicación se vuelve compleja, puede resultar bastante inseguro definir propiedades de manera informal. Esto genera dos problemas:

  • Es difícil saber cómo se debe utilizar un componente mirando su código.
  • inseguro, es fácil enviar propiedades incorrectas a un componente, ya sea refactorizando un componente o uno de sus padres.

Un sistema de tipos de accesorios resuelve ambos problemas al describir los tipos y formas de los accesorios. Así es como funciona en Owl:

  • La clave props es una clave estática (por lo tanto, diferente de this.props en una instancia de componente)
  • es opcional: está bien que un componente no defina una clave props.
  • Las propiedades se validan siempre que se crea o actualiza un componente.
  • Las propiedades solo se validan en el modo dev (ver cómo configurar una aplicación)
  • Si una clave no coincide con la descripción, se genera un error.
  • Valida las claves definidas en props (estáticos). Las claves adicionales proporcionadas por el padre causarán un error (a menos que esté presente la propiedad especial *).
  • Es un objeto o una lista de cadenas.
  • Una lista de cadenas es una definición simplificada de propiedades, que solo incluye el nombre de las propiedades. Además, si el nombre termina con ?, se considera opcional.
  • Todas las propiedades son obligatorias de forma predeterminada, a menos que se definan con optional: true (en ese caso, solo se hace si hay un valor)
  • Los tipos válidos son: Number, String, Boolean, Object, Array, Date, Function y todas las funciones constructoras (por lo tanto, si tiene una clase Person, se puede usar como tipo)
  • Las matrices son homogéneas (todos los elementos tienen el mismo tipo/forma)

Para cada clave, una definición de prop es un valor booleano, un constructor, una lista de constructores o un objeto:

  • un valor booleano: indica que la propiedad existe y es obligatoria.
  • un constructor: esto debe describir el tipo, por ejemplo: id: Número describe las propiedades id como un número
  • un objeto que describe un valor como tipo. Esto se hace utilizando la clave value. Por ejemplo, {value: false} especifica que el valor correspondiente debe ser igual a false.
  • una lista de constructores. En ese caso, esto significa que permitimos más de un tipo. Por ejemplo, id: [Number, String] significa que id puede ser una cadena o un número.
  • un objeto. Esto permite una definición más expresiva. Las siguientes subclaves están permitidas (pero no son obligatorias):
    • type: el tipo principal del objeto que se está validando
    • element: si el tipo era Array, entonces la clave element describe el tipo de cada elemento en el array. Si no está definida, entonces solo validamos el array, no sus elementos,
    • shape: si el tipo era Object, entonces la clave shape describe la interfaz del objeto. Si no está definida, entonces solo validamos el objeto, no sus elementos.
    • values: si el tipo fuera Object, entonces la clave values describe la interfaz de valores en el objeto, esto permite validar objetos que se usan como mapeos, donde las claves no se conocen de antemano pero sí la forma de los valores.
    • validate: esta es una función que debe devolver un valor booleano para determinar si el valor es válido o no. Útil para la lógica de validación personalizada.
    • opcional: si es verdadero, la propiedad no es obligatoria

Hay una propiedad especial * que significa que se permiten propiedades adicionales. Esto a veces es útil para componentes genéricos que propagarán algunas o todas sus propiedades a sus componentes secundarios.

Tenga en cuenta que no se pueden definir valores predeterminados para propiedades obligatorias. Si lo hace, se generará un error de validación de propiedades.

Ejemplos:

class ComponentA extends owl.Component {
static props = ['id', 'url'];
...
}
class ComponentB extends owl.Component {
static props = {
count: {type: Number},
messages: {
type: Array,
element: {type: Object, shape: {id: Boolean, text: String }}
},
date: Date,
combinedVal: [Number, Boolean],
optionalProp: { type: Number, optional: true }
};
...
}
// only the existence of those 3 keys is documented
static props = ['message', 'id', 'date'];
// only the existence of those 3 keys is documented. any other key is allowed.
static props = ['message', 'id', 'date', '*'];
// size is optional
static props = ['message', 'size?'];
static props = {
messageIds: {type: Array, element: Number}, // list of number
otherArr: {type: Array}, // just array. no validation is made on sub elements
otherArr2: Array, // same as otherArr
someObj: {type: Object}, // just an object, no internal validation
someObj2: {
type: Object,
shape: {
id: Number,
name: {type: String, optional: true},
url: String
}
}, // object, with keys id (number), name (string, optional) and url (string)
someObj3: {
type: Object,
values: { type: Array, element: String },
}, // object with arbitary keys where values are arrays of strings
someFlag: Boolean, // a boolean, mandatory (even if `false`)
someVal: [Boolean, Date], // either a boolean or a date
otherValue: true, // indicates that it is a prop
kindofsmallnumber: {
type: Number,
validate: n => (0 <= n && n <= 10)
},
size: {
validate: e => ["small", "medium", "large"].includes(e)
},
someId: [Number, {value: false}], // either a number or false
};

nota: el código de validación de props se hace mediante la función de utilidad validate.

Buenas prácticas

Un objeto props es una colección de valores que provienen del padre. Como tal, son propiedad del padre y nunca deben ser modificados por el hijo:

class MyComponent extends Component {
constructor(parent, props) {
super(parent, props);
props.a.b = 43; // Never do that!!!
}
}

Las propiedades deben considerarse de solo lectura desde la perspectiva del componente secundario. Si es necesario modificarlas, la solicitud de actualización debe enviarse al componente principal (por ejemplo, con un evento).

Cualquier valor puede ir en una propiedad. Se pueden proporcionar cadenas, objetos, clases o incluso devoluciones de llamadas a un componente secundario (pero, en el caso de las devoluciones de llamadas, parece más apropiado comunicarse con eventos).