OWL教程3 OWL五大组成部分之二组件系统
OWL教程3 OWL五大组成部分之二组件系统
参考文档:https://github.com/odoo/owl/blob/master/doc/reference/component.md
粗略的讲,Owl有五个主要部分:
- 虚拟DOM系统(src/blockdom)
- 组件系统(src/component)
- 模板编译器(src/compiler 目录)
- 一个小的运行时让各层组合在一起(src/app)
- 响应式系统(src/reactivity.ts)
2.组件
2.1 概览
一个Owl组件是一个小的类,描述了部分用户接口,它是组件树的一部分,它有一个环境变量(env),从父组件传播到子组件. (被所有组件共享)
Owl组件通过继承Component类来定义,例如,这里是一个Counter组件的实现:
const { Component, xml, useState } = owl;class Counter extends Component {static template = xml``;state = useState({ value: 0 });increment() {this.state.value++;}
}
这个例子中,我们使用xml助手定义了内联模板,使用了useState钩子,它返回一个响应式的参数.
2.2 属性和方法
Component类有一个很小的API
env(object) 组件环境变量
props(object)
父组件传递给子组件的属性值. 注意: props由父组件拥有,所以它不应该由子组件修改.(否则可能会意想不到的风险,因为父组件不知道它被修改了)
props可以动态的被父组件修改, 在这种情况下,子组件会依次调用下列声明周期函数
willUpdateProps, willPatch and patched.
render(deep=false)
调用这个方法会直接已发一次重新渲染. 注意在响应式系统中中,这很少需要手动去做. 另外,渲染操作是异步的,所以Dom只会有一点点延迟(在下一个动画帧,如果没有组件延迟的话)
默认情况下, 只渲染组件自己本身,如果需要渲染所有的子组件,需要设置deep=true
2.3 静态属性
-
template (string): 组件渲染使用的模板名称,注意可以使用xml助手来定义一个内联模板. -
components (object, optional): 模板中使用的所有子组件class ParentComponent extends owl.Component {static components = { SubComponent }; } -
props (object, optional): 用于验证父组件传递给子组件的参数, (开发模式下) See Props Validation for more information.class Counter extends owl.Component {static props = {initialValue: Number,optional: true,}; } -
defaultProps (object, optional): 默认属性, 当props传递给对象的 时候,如果有参数没有赋值,会用默认值代替,注意一点,不会修改初始化的对象,而是新建一个对象来代替See default props for more informationclass Counter extends owl.Component {static defaultProps = {initialValue: 0,}; }
2.4 生命周期循环
一个坚实的,健壮的组件系统需要完整的生命周期系统来帮助开发者来编写组件,这里是关于Owl组件生命周期的完整描述
| Method | Hook | Description |
|---|---|---|
| setup | none | setup |
| willStart | onWillStart | async, before first rendering |
| willRender | onWillRender | just before component is rendered |
| rendered | onRendered | just after component is rendered |
| mounted | onMounted | just after component is rendered and added to the DOM |
| willUpdateProps | onWillUpdateProps | async, before props update |
| willPatch | onWillPatch | just before the DOM is patched |
| patched | onPatched | just after the DOM is patched |
| willUnmount | onWillUnmount | just before removing component from DOM |
| willDestroy | onWillDestroy | just before component is destroyed |
| error | onError | catch and handle errors (see error handling page) |
总结一波:
11个钩子可以分成几组:
1 error 处理错误
2 setup willDestroy 总是被执行,setup类似构造函数, willDestroy 最后做一些清理操作.
3 mounted unmounted 一对相反的方法,不一定会被执行,但是要么都执行,要不都不执行, 适合添加事件监听器
4 willstart(只执行一次,可以用来异步加载资源) willrender rendered
5 willupdateprops( 更新props的时候可以异步加载资源) willpatch patched
setup
在组件构建好之后会立刻执行setup函数,这是一个生命周期方法,类似于构造函数,除了它不接收任何参数.
这里是调用钩子函数的合适地方, 请注意,在组件生命周期中使用setup钩子的一个主要原因是可以对其进行monkey patch。这是Odoo生态系统中的一个常见需求。
setup() {useSetupAutofocus();
}
什么叫monkey patch ?( 来自chatgpt)
“Monkey patch”(猴子补丁)是指在运行时修改现有的代码或类的行为的一种技术。猴子补丁是动态语言(如Python)中的一种常用技巧,允许程序员在不修改原始源代码的情况下进行临时或局部的修改。
通过猴子补丁,您可以在运行时动态地更改类、方法或函数的行为,添加新的功能,修改现有的行为或修复bug。这种技术在开发过程中非常有用,尤其是当您无法修改或无权访问原始源代码时。
猴子补丁的原理是利用动态类型语言的特性,可以在运行时修改对象的方法和属性。这允许您在不修改原始类定义的情况下,通过重新定义方法或添加属性来改变类的行为。
以下是一个简单的Python示例,演示了如何使用猴子补丁来修改现有类的行为:
class MyClass:def my_method(self):return "Original behavior"def modified_method(self):return "Modified behavior"obj = MyClass()
print(obj.my_method()) # 输出:Original behaviorMyClass.my_method = modified_method # 猴子补丁,将my_method重新定义为modified_methodprint(obj.my_method()) # 输出:Modified behavior
在这个例子中,我们定义了一个类MyClass和一个名为my_method的方法。然后,我们使用猴子补丁技术,将my_method重新定义为modified_method。这样,实例化的对象obj调用my_method时,将输出"Modified behavior",而不是原始的"Original behavior"。
猴子补丁虽然可以灵活地修改现有的代码行为,但也需要谨慎使用。过度使用猴子补丁可能导致代码难以理解、调试和维护。因此,应在慎重思考和评估后,选择是否使用猴子补丁来解决特定的问题。
willStart
willstart是一个异步的钩子在初始化渲染组件之间用来执行一些操作(大多数时候是异步的)
它在初始化渲染之前只执行一次, 有时候很有用,比如,
1 在渲染组件之前加载一些js文件
, 2 或者从服务器加载数据
setup() {onWillStart(async () => {this.data = await this.loadData()});}
在这一时刻,组件还没有渲染出来,注意, 过慢的willstart代码会减慢用户界面的渲染. 因此,有时候要让这个方法尽快执行.
willRender
不太常用,但是如果在需要渲染组件之前执行一些代码的话(更准确的说,它编译好的模板函数执行时),
setup() {onWillRender(() => {// do something});}
willRender 钩子在渲染模板之前执行,父组件先,然后子组件.
rendered
不太常用,但是如果需要在组件渲染后,(更准确的说,当它的编译模板函数执行完后)调用
setup() {onRendered(() => {// do something});}
rendered钩子仅仅在渲染完模板后调用,注意: 在这一刻,真实的dom可能还不存在(特别是第一次渲染),或者还没有更新, 这将在下一个动画帧中被显示,只要所有的组件都准备好了.
mounted
mounted钩子在每次组件被附加到dom的时候执行, 在初始化渲染完成后, 在这一刻,组件已经激活.
这是个合适的位置用来添加监听器,或者跟Dom交互
跟它相反的是willUnmount, 如果一个组件加载了,那么他在未来某一时刻一定会执行unmount.
mounted方法会在子组件递归的调用,首先子组件,然后父组件.
修改Mounted钩子的状态是被允许的(但是不鼓励). 这样做会引发重新渲染,这将不会被用户察觉,但是会稍微减慢组件的速度.
setup() {onMounted(() => {// do something here});}
willUpdateProps
willUpdateProps是一个异步的钩子,在新的props设置之前被调用, 如果组件需要执行一些异步的任务这将很有用,例如, 假设props是记录id,根据id获取记录数据.
onWillUpdateProps 钩子用来注册一个函数在这一刻执行.
setup() {onWillUpdateProps(nextProps => {return this.loadData({id: nextProps.id});});}
注意: 它将接收新的props作为参数
这个钩子在第一次被渲染的时候不会执行, 但是willstart会执行来类似的事情,另外,跟大多数钩子一样,根据通常的顺序执行,先父组件,然后子组件.
willPatch
这个钩子在DOM pathing进程开始的时候执行,第一渲染的时候不执行, 这可以用来从DOM中获取信息,例如,当前scrollbar的位置.
注意, 不允许修改状态, 这个方法仅仅在给真实DOM patch的时候调用,仅仅用来保存真实DOM的状态,另外组件不在真实dom中也不允许调用.
setup() {onWillPatch(() => {this.scrollState = this.getScrollSTate();});}
跟大多数钩子一样,根据通常的顺序执行,先父组件,然后子组件.
patched
当组件更新完DOM的时候会调用这个钩子(最有可能通过改变了它的状态/props或者环境)
这个组件在第一次渲染的时候不会调用, 它用来跟DOM交互, 如果组件不在真实dom中,它不会执行.
setup() {onPatched(() => {this.scrollState = this.getScrollSTate();});}
在这个钩子里更新组件状态是可能的,但是不鼓励. 注意,这里的更新会产生额外的重新渲染,这里一定要小心,要避免死循环式的重新渲染.
想mounted一样,patched钩子的执行顺序 先子后父.
willUnmount
当组件从DOM卸载的时候会调用这个钩子,这里是移除监听器的好地方.
setup() {onMounted(() => {// add some listener});onWillUnmount(() => {// remove listener});}
这个方法是mounted方法相反的方法,注意,如果一个组件在mount之前就被销毁了,那么这个方法就不执行了.
执行顺序,先父后子.
willDestroy
有时候,组件需要在setup中做些动作,然后当他们不活跃的时候需要清理. 然后willUnmount钩子并不适合用来做清理操作,因为,组件可能在安装之前就被销毁了, willDestroy就很有用了,因为它总是被执行.
setup() {onWillDestroy(() => {// do some cleanup});}
执行顺序: 先子后父
onError
不幸的是,组件可能会在运行时崩溃, 这是一个不幸的显示, 这就是Owl为啥要提供一种方式来处理这些错误
当我们需要拦截并正确响应某些子组件中发生的错误时,onError钩子非常有用。有关详细信息,请参阅有关错误处理的页面。
setup() {onError(() => {// do something});}
2.5 子组件
用子组件来定义父组件是很方便的,这叫做合成,实践中很有用. 在Owl中要做到这点只需要两点:
1 在模板中通过首字母大写的标签来引用子组件并且可以传递参数
2 在组件的静态属性Component中注册子组件
class Child extends Component {static template = xml`child component `;
}class Parent extends Component {static template = xml` `;static components = { Child };
}
2.6 动态子组件
这不常用,但是有时候我们需要一个动态的子组件名称,这种情况, t-component指令可以用来接收动态的值.这应该是一个表达式用来计算组件类,例如
class A extends Component {static template = xml`child a`;
}
class B extends Component {static template = xml`child b`;
}
class Parent extends Component {static template = xml` `;state = useState({ child: "a" });get myComponent() {return this.state.child === "a" ? A : B;}
}
2.7 status助手
有时候需要一种方法来查看当前的组件处于哪种状态,要做到这点,可以使用status助手
const { status } = owl;
// assume component is an instance of a Componentconsole.log(status(component));
// logs either:
// - 'new', if the component is new and has not been mounted yet
// - 'mounted', if the component is currently mounted
// - 'destroyed' if the component is currently destroyed
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
