Skip to content

🦉 Props(属性)🦉

概览

在 Owl 中,props(属性)是一个对象,包含了父组件传递给子组件的所有数据。

js
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:ab。 Owl 会将它们收集到 props 对象中,并在父组件的上下文中进行求值。 因此,props.a 的值是 'fromparent'props.b'string'

注意:props从子组件角度定义的数据结构。


定义

props 是模板中定义的所有属性组成的对象,但以下情况除外

  • 所有以 t- 开头的属性(这些是 QWeb 指令,不是 props)

例如:

xml
<div>
    <ComponentA a="state.a" b="'string'"/>
    <ComponentB t-if="state.flag" model="model"/>
</div>
  • ComponentAprops 是:ab
  • ComponentBprops 是:model

Props 比较机制

当 Owl 在模板中遇到子组件时,会对其所有 props 进行浅比较(shallow comparison)

  • 如果所有 props 的引用都未改变,则子组件不会重新更新。
  • 如果至少有一个 props 引用改变,则子组件会被更新。

某些情况下,两个值虽然不同,但行为相同。例如,模板中定义的匿名函数总是不同的引用:

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

此时可以使用 .alike 后缀:

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

这会告诉 Owl:这个 prop 在比较时应被视为等价(即忽略它的比较)。

⚠️ 注意:并非所有匿名函数都适合使用 .alike,特别是捕获了可变数据的函数:

xml
<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

js
class SomeComponent extends Component {
  static template = xml`
    <div>
      <Child callback="doSomething"/>
    </div>`;

  setup() {
    this.doSomething = this.doSomething.bind(this);
  }

  doSomething() {
    // ...
  }
}

Owl 提供了 .bind 后缀来简化这个操作:

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

  doSomething() {
    // ...
  }
}

.bind 后缀也隐含 .alike,因此不会引起额外渲染。


可翻译的 props

如果要传递面向用户的字符串给子组件,通常需要进行翻译。

可以使用 .translate 后缀:

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

注意:

  • .translate 的值不是 JS 表达式,而是普通字符串;
  • 翻译在传递前就已完成;
  • 如果需要动态插值,请在 JS 中处理后再传入。

动态 Props

可以使用 t-props 指令一次性传入一个对象作为 props:

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

默认 Props

可以使用 defaultProps 设置默认值:

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

如果父组件未传入 initialValue,则使用默认值 0


Props 校验(验证)

随着应用变复杂,随意使用 props 会带来两个问题:

  • 无法从代码快速了解组件应如何使用;
  • 易出错(如错误使用、父组件变更未同步等)

为了解决这些问题,Owl 提供 props 类型系统。

使用方法:

  • 使用静态的 props 属性(不是实例上的 this.props
  • 可选(可以不定义)
  • 每次创建/更新组件时验证
  • 只在开发模式下生效
  • 若值不符合定义,会抛出错误
  • 若父组件传入未定义的 key,也会报错(除非使用特殊键 *
  • 支持对象或字符串数组格式

字符串数组格式(简写)

js
static props = ['message', 'id', 'date']; // 都是必需项
static props = ['message', 'size?'];      // size 是可选项
static props = ['message', 'id', '*'];    // 允许额外 props

对象格式(推荐)

js
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 指定
  • 对象内部结构通过 shapevalues 指定
  • 自定义校验:使用 validate 函数
  • 指定值:使用 { value: xxx }
  • 是否可选:使用 optional: true

特殊键

  • *:允许存在额外 props,不校验其类型

示例

js
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。它们是父组件拥有的:
js
class MyComponent extends Component {
  constructor(parent, props) {
    super(parent, props);
    props.a.b = 43; // 千万不要这么做!
  }
}
  • props 应视为只读。如果需要修改,应该通过事件告知父组件。

  • props 可包含任何类型的值:字符串、对象、类实例、回调函数等(回调建议改为事件通信)。

本站内容仅供学习与参考