组件生命周期方法
组件有许多生命周期方法,可以用来知道组件何时 "will" 和 "did" 加载,更新和渲染。可以将这些方法添加到组件中,以便在适当的时候挂钩到操作中。
在组件类中实现以下方法之一,Stencil 将自动按正确的顺序调用它们:
connectedCallback()
每次组件连接到 DOM 时调用。当组件第一次被连接时,这个方法在 componentWillLoad 之前被调用。
需要注意的是,这个方法可以被多次调用,每次调用时,元素都会在 DOM 中被 attached 或 moved 。对于每次在 DOM 中添加或移动元素时都需要运行的逻辑,使用这个生命周期方法被认为是最佳实践。
const el = document.createElement("my-cmp");
document.body.appendChild(el);
// connectedCallback() called
// componentWillLoad() called (first time)
el.remove();
// disconnectedCallback()
document.body.appendChild(el);
// connectedCallback() called again, but `componentWillLoad()` is not.这个 lifecycle 钩子遵循与自定义元素规范中描述的相同的语义。
disconnectedCallback()
每次组件与 DOM 断开连接时都会调用,也就是说,它可以被触发多次,不要与 onDestroy 类型的事件混淆。
这个 lifecycle 钩子遵循与自定义元素规范中描述的相同的语义。
componentWillLoad()
在组件第一次连接到 DOM 之后调用一次。由于这个方法只被调用一次,所以它是一个异步加载数据和设置状态的好地方,而不会触发额外的重新渲染。
可以返回一个 promise,用于等待第一次 render() 。
componentDidLoad()
在组件完全加载和第一个 render() 发生后调用一次。
componentShouldUpdate()
当组件的 Prop 或 State 属性发生变化并且即将请求渲染时,这个钩子会被调用。这个钩子接收三个参数:新值、旧值和改变后状态的名称。它应该返回一个布尔值来表示组件是否应该重新渲染(true)或否(false)。
需要注意的是,此方法不会在初始渲染之前执行,即当组件第一次附加到 dom 时,也不会在已经计划在下一帧中进行渲染时执行。
假设组件的以下两个属性同步变化:
component.somePropA = 42;
component.somePropB = 88;componentShouldUpdate 将首先被调用并传入参数: 42、undefined 和 somePropA 。如果它返回 true ,钩子将不会被再次调用,因为重新渲染已经被安排好了。 相反,如果第一个 hook 返回 false,那么 componentShouldUpdate 将被再次调用,以 88 、undefined 和 somePropB 作为参数, 由 component.somePropB = 88 触发突变。
由于这个 hook 的执行可能是有条件的,依赖它来监视 prop 的变化是不好的,而是使用@Watch装饰器。
componentWillRender()
在每次 render() 之前调用。可以返回一个 promise,它可以用来等待即将到来的渲染。
componentDidRender()
在每次 render() 之后调用。
componentWillUpdate()
当组件即将更新时调用,因为某些Prop()或State()发生了变化。但它不会在第一次 render() 前被调用。
可以返回一个 promise,用于等待下一次渲染。
componentDidUpdate()
在组件更新后调用。 但它不会在第一次 render() 后被调用。
Rendering State
我们总是建议在 componentWillRender() 中进行任何渲染状态更新,因为这是在 render() 方法之前被调用的方法。或者使用componentDidLoad()、componentDidUpdate() 和 componentDidRender() 方法更新渲染状态将导致另一次渲染,这对性能来说并不理想。
如果状态必须在 componentDidUpdate() 或 componentDidRender() 中更新,它有可能使组件陷入无限循环。如果在 componentDidUpdate() 中更新状态是不可避免的,那么该方法还应该提供一种方法来检测 props 或 state 是否“脏”(数据是否实际不同或与之前相同)。通过脏检查,componentDidUpdate() 能够避免渲染相同的数据,从而再次调用 componentDidUpdate()。
生命周期结构
生命周期方法的一个有用特性是,它们也将子组件的生命周期考虑在内。例如,如果父组件 cmp-a 有一个子组件 cmp-b ,那么在 cmp-b 完成加载之前,cmp-a 不会被认为是 "loaded" 的。另一种说法是,最深的组件首先完成加载,然后 componentDidLoad() 调用冒泡。
同样需要注意的是,即使 Stencil 可以延迟加载组件,并且具有异步渲染,生命周期方法仍然以正确的顺序被调用。因此,虽然顶层组件可能已经加载,但它的所有生命周期方法仍然会以正确的顺序调用,这意味着它将等待子组件完成加载。相反的情况也是如此,子组件可能已经准备好了,而父组件还没有。
在下面的例子中,我们有一个简单的组件层次结构。编号的列表显示了生命周期方法被触发的顺序。
<cmp-a>
<cmp-b>
<cmp-c></cmp-c>
</cmp-b>
</cmp-a>cmp-a-componentWillLoad()cmp-b-componentWillLoad()cmp-c-componentWillLoad()cmp-c-componentDidLoad()cmp-b-componentDidLoad()cmp-a-componentDidLoad()
即使有些组件可能已经加载,也可能没有加载,整个组件层次结构都会等待它的子组件完成加载和渲染。
异步生命的周期方法
生命周期方法也可以返回 Promise,它允许方法异步检索数据或执行任何异步任务。一个很好的例子是获取要在组件中渲染的数据。例如,您正在读取的这个网站在渲染之前首先获取内容数据。 但是因为 fetch() 是异步的,所以重要的是 componentWillLoad() 返回一个 Promise,以确保父组件在其所有内容渲染之前不会被认为是“已加载”。
下面是一个简单的例子,展示了 componentWillLoad() 如何让它的父组件等待它完成数据加载。
componentWillLoad() {
return fetch('/some-data.json')
.then(response => response.json())
.then(data => {
this.content = data;
});
}示例
这个简单的例子展示了一个时钟,并每秒更新当前时间。计时器在组件被添加到 DOM 时启动。一旦它从 DOM 中移除,定时器就会停止。
import { Component, State, h } from "@stencil/core";
@Component({
tag: "custom-clock",
})
export class CustomClock {
timer: number;
@State() time: number = Date.now();
connectedCallback() {
this.timer = window.setInterval(() => {
this.time = Date.now();
}, 1000);
}
disconnectedCallback() {
window.clearInterval(this.timer);
}
render() {
const time = new Date(this.time).toLocaleTimeString();
return <span>{time}</span>;
}
}