🦉 Props(属性)🦉
概览
在 Owl 中,props
(属性)是一个对象,包含了父组件传递给子组件的所有数据。
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" });
}
在上面的例子中,Child
组件从父组件接收两个 props:a
和 b
。 Owl 会将它们收集到 props
对象中,并在父组件的上下文中进行求值。 因此,props.a
的值是 'fromparent'
,props.b
是 'string'
。
注意:props
是从子组件角度定义的数据结构。
定义
props
是模板中定义的所有属性组成的对象,但以下情况除外:
- 所有以
t-
开头的属性(这些是 QWeb 指令,不是 props)
例如:
<div>
<ComponentA a="state.a" b="'string'"/>
<ComponentB t-if="state.flag" model="model"/>
</div>
ComponentA
的props
是:a
和b
ComponentB
的props
是:model
Props 比较机制
当 Owl 在模板中遇到子组件时,会对其所有 props 进行浅比较(shallow comparison):
- 如果所有 props 的引用都未改变,则子组件不会重新更新。
- 如果至少有一个 props 引用改变,则子组件会被更新。
某些情况下,两个值虽然不同,但行为相同。例如,模板中定义的匿名函数总是不同的引用:
<t t-foreach="todos" t-as="todo" t-key="todo.id">
<Todo todo="todo" onDelete="() => deleteTodo(todo.id)" />
</t>
此时可以使用 .alike
后缀:
<t t-foreach="todos" t-as="todo" t-key="todo.id">
<Todo todo="todo" onDelete.alike="() => deleteTodo(todo.id)" />
</t>
这会告诉 Owl:这个 prop 在比较时应被视为等价(即忽略它的比较)。
⚠️ 注意:并非所有匿名函数都适合使用 .alike
,特别是捕获了可变数据的函数:
<t t-foreach="todos" t-as="todo" t-key="todo.id">
<!-- 可能错误!todo.isCompleted 可能会变化 -->
<Todo todo="todo" toggle.alike="() => toggleTodo(todo.isCompleted)" />
</t>
绑定函数型 props
如果需要将一个回调函数作为 prop 传递,由于组件是 class-based,通常需要绑定 this
:
class SomeComponent extends Component {
static template = xml`
<div>
<Child callback="doSomething"/>
</div>`;
setup() {
this.doSomething = this.doSomething.bind(this);
}
doSomething() {
// ...
}
}
Owl 提供了 .bind
后缀来简化这个操作:
class SomeComponent extends Component {
static template = xml`
<div>
<Child callback.bind="doSomething"/>
</div>`;
doSomething() {
// ...
}
}
.bind
后缀也隐含 .alike
,因此不会引起额外渲染。
可翻译的 props
如果要传递面向用户的字符串给子组件,通常需要进行翻译。
可以使用 .translate
后缀:
<t t-name="ParentComponent">
<Child someProp.translate="some message"/>
</t>
注意:
.translate
的值不是 JS 表达式,而是普通字符串;- 翻译在传递前就已完成;
- 如果需要动态插值,请在 JS 中处理后再传入。
动态 Props
可以使用 t-props
指令一次性传入一个对象作为 props:
<div t-name="ParentComponent">
<Child t-props="some.obj"/>
</div>
class ParentComponent {
static components = { Child };
some = { obj: { a: 1, b: 2 } };
}
默认 Props
可以使用 defaultProps
设置默认值:
class Counter extends owl.Component {
static defaultProps = {
initialValue: 0,
};
...
}
如果父组件未传入 initialValue
,则使用默认值 0
。
Props 校验(验证)
随着应用变复杂,随意使用 props 会带来两个问题:
- 无法从代码快速了解组件应如何使用;
- 易出错(如错误使用、父组件变更未同步等)
为了解决这些问题,Owl 提供 props 类型系统。
使用方法:
- 使用静态的
props
属性(不是实例上的this.props
) - 可选(可以不定义)
- 每次创建/更新组件时验证
- 只在开发模式下生效
- 若值不符合定义,会抛出错误
- 若父组件传入未定义的 key,也会报错(除非使用特殊键
*
) - 支持对象或字符串数组格式
字符串数组格式(简写)
static props = ['message', 'id', 'date']; // 都是必需项
static props = ['message', 'size?']; // size 是可选项
static props = ['message', 'id', '*']; // 允许额外 props
对象格式(推荐)
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 },
};
支持的类型:
- 基础类型:
Number
,String
,Boolean
,Object
,Array
,Date
,Function
- 自定义类构造器(如
Person
) - 数组元素类型通过
element
指定 - 对象内部结构通过
shape
或values
指定 - 自定义校验:使用
validate
函数 - 指定值:使用
{ value: xxx }
- 是否可选:使用
optional: true
特殊键
*
:允许存在额外 props,不校验其类型
示例
static props = {
messageIds: { type: Array, element: Number },
someObj2: {
type: Object,
shape: {
id: Number,
name: { type: String, optional: true },
url: String
}
},
someObj3: {
type: Object,
values: { type: Array, element: String },
},
kindofsmallnumber: {
type: Number,
validate: n => (0 <= n && n <= 10)
},
size: {
validate: e => ["small", "medium", "large"].includes(e)
},
someId: [Number, { value: false }],
};
校验逻辑由 validate
工具函数 实现。
最佳实践
- 子组件不得修改 props。它们是父组件拥有的:
class MyComponent extends Component {
constructor(parent, props) {
super(parent, props);
props.a.b = 43; // 千万不要这么做!
}
}
props 应视为只读。如果需要修改,应该通过事件告知父组件。
props 可包含任何类型的值:字符串、对象、类实例、回调函数等(回调建议改为事件通信)。