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
yb
, - 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 dethis.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 clasePerson
, 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 propiedadesid
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 queid
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á validandoelement
: si el tipo eraArray
, entonces la claveelement
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 eraObject
, entonces la claveshape
describe la interfaz del objeto. Si no está definida, entonces solo validamos el objeto, no sus elementos.values
: si el tipo fueraObject
, entonces la clavevalues
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).