Effects
Effects 让你观察 signal 的变化并对它们做出反应。与 signals 不同,effects 不是惰性的:
createEffect()在其任何依赖项更改后立即运行回调。createDeferredEffect()在其任何依赖项更改的每一帧结束时运行回调。
它们对于副作用很有用,例如响应某些 signal 的变化来修改节点层次结构或生成背景动画。
在修改节点属性时,最好使用 signals。
Overview
Effects 使用 createEffect() 和 createDeferredEffect() 函数创建。第一个参数指定要调用的回调:
import {createSignal, createEffect} from '@motion-canvas/core';
const signal = createSignal(0);
createEffect(() => {
console.log('Signal changed: ', signal());
});
创建后,可以使用返回的函数处理 effects:
const unsubscribe = createEffect(() => {
console.log('Signal changed: ', signal());
});
// 做一些事情
unsubscribe();
Explanation
要理解 signals 和 effects 之间的区别,请考虑 signals 部分的修改示例:
const radius = createSignal(1);
const area = createSignal(() => {
console.log('area recalculated!');
return Math.PI * radius() * radius();
});
createEffect(() => {
area();
});
// area recalculated!
radius(2);
// area recalculated!
radius(3);
// area recalculated!
radius(4);
// area recalculated!
这一次,area signal 在 effect 中被调用,而不是直接调用。现在,每当 radius signal 更改时,就会触发 effect,它会立即重新计算 area signal。
这是一种折衷方案。一方面,你现在可以立即响应更改,而无需每帧调用 signal。另一方面,effect 的所有依赖项不再是惰性的。通常这不应该成为问题,但如果 effect 的依赖项经常更改且计算成本很高,这可能会影响寻道性能。
Deferred effects
如果你的 effect 引用了多个经常更改的依赖项,你可以使用 createDeferredEffect() 将它们推迟到当前帧的结束。这样,即使多个依赖项更改,effect 也只会执行一次。缺点是延迟 effect 引起的副作用不会立即可见。它们将在当前生成器步骤完成之后但在渲染开始之前变为可见。
const a = createSignal(1);
const b = createSignal(2);
let value = 0;
createEffect(() => {
console.log('effect invoked');
value = a() + b();
});
// effect invoked
let deferredValue = 0;
createDeferredEffect(() => {
console.log('deferred effect invoked');
deferredValue = a() + b();
});
// deferred effect invoked
a(2);
// effect invoked
b(3);
// effect invoked
console.log(value); // 5 - effect 的值会立即更新。
console.log(deferredValue); // 3 - deferred effect 的值尚未准备好。
yield; // deferred effect invoked
console.log(deferredValue); // 5
Complex example
下面的示例演示了如何使用 effects 自动更新节点层次结构,以便显示的圆圈数量与 count signal 的值匹配:
Click to preview animation
import {Circle, Layout, makeScene2D} from '@motion-canvas/2d';
import {
createEffect,
createRef,
createSignal,
spawn,
waitFor,
} from '@motion-canvas/core';
export default makeScene2D(function* (view) {
const count = createSignal(0);
const container = createRef<Layout>();
view.add(<Layout alignItems={'center'} ref={container} layout />);
const circles: Circle[] = [];
createEffect(() => {
const targetCount = Math.round(count());
let i = circles.length;
// 添加任何缺少的圆圈
for (; i < targetCount; i++) {
const circle = (<Circle fill={'white'} />) as Circle;
circles.push(circle);
container().add(circle);
spawn(circle.size(80, 0.3));
}
// 删除任何多余的圆圈
for (; i > targetCount; i--) {
const circle = circles.pop()!;
spawn(circle.size(0, 0.3).do(() => circle.remove()));
}
});
count(1);
yield* waitFor(1);
count(6);
yield* waitFor(1);
count(4);
yield* count(0, 2);
yield* waitFor(1);
});