基于 mobx 实现局部更新
前言:lowcode engine 核心逻辑是基于事件 + 模型的方式实现模型驱动UI更新的,使用 mobx 实现UI 更新的逻辑较少,如核心组件到画布、画布内组件props更新等,都是通过事件的方式来触发UI更新
那么 lowcode-engine 中,mobx到底扮演什么角色呢?
角色1:基于mobx class 的方式,构建整个引擎的模型
通过mobx的class 来实现以下模型,以及模型之间的关系
角色2:基于mobx实现UI自动化更新
大部分逻辑,我们在 《一文理清编排的本质 如何实现数据实时预览》已经分析过,是通过事件监听 + 事件触发的方式,实现两个 frame 之间的模型变化,从而触发UI的重新渲染,只有少部分逻辑,使用到了 mobx-react的自动更新逻辑,那么到底哪些地方是通过mobx-react来实现模型驱动UI渲染的呢?
首先,要知道哪些逻辑使用到mobx-react,我们需要搜索以下 mobx-react 的核心概念 @observer (只有被该decorator装饰过,该组件才具备模型驱动UI渲染)
如下图显示,大约有32个文件使用到 @observer 装饰器,以下我们抽取其中一个进行分析:
以 host-view为例,主要实现以下功能
根据选择不同设备尺寸,渲染不同尺寸页面
由于在代码逻辑里面,使用到sim.device,所以只要 device 这个被观察的熟悉发生变化,该组件就会自动触发重新渲染
@observer
class Canvas extends Component<{ host: BuiltinSimulatorHost }> {
render() {
console.trace('why rerender!!');
const sim = this.props.host;
let className = 'lc-simulator-canvas';
const { canvas = {}, viewport = {} } = sim.deviceStyle || {};
if (sim.deviceClassName) {
className += ` ${sim.deviceClassName}`;
} else if (sim.device) {
className += ` lc-simulator-device-${sim.device}`;
}
return (
<div className={className}>
<div
ref={(elmt) => sim.mountViewport(elmt)}
className="lc-simulator-canvas-viewport"
style={viewport}
>
<BemTools host={sim} />
<Content host={sim} />
</div>
</div>
);
}
}
sim 来自 host,即 BuiltinSimulatorHost,模型定义如下:有一个 @computed属性 device
export class BuiltinSimulatorHost
implements ISimulatorHost<BuiltinSimulatorProps>
{
/**
* 是否为画布自动渲染
*/
autoRender = true;
constructor(project: Project) {
makeObservable(this);
this.project = project;
this.designer = project?.designer;
this.scroller = this.designer.createScroller(this.viewport);
this.autoRender = !engineConfig.get('disableAutoRender', false);
this.componentsConsumer = new ResourceConsumer<Asset | undefined>(
() => this.componentsAsset,
);
this.injectionConsumer = new ResourceConsumer(() => {
return {
appHelper: engineConfig.get('appHelper'),
i18n: this.project.i18n,
};
});
}
@computed get device(): string {
return this.get('device') || 'default';
}
}
那么,当切换尺寸时,是如何改变 host 下的 device 属性呢?
通过 SimulatorPane 的 change 实现,调用 simulator 的 set 方法
export class SimulatorPane extends React.Component {
static displayName = 'SimulatorPane';
change = (device: string) => {
const simulator = project.simulator;
// 切换画布
// https://yuque.alibaba-inc.com/docs/share/4caeac57-e920-4a5b-b4b2-2f514cdd8d49?#
simulator?.set('device', device);
document.querySelector('.lc-simulator-canvas').style.width = null;
setTimeout(() => {
const currentWidth =
document.querySelector('.lc-simulator-canvas')?.clientWidth ||
this.state.currentWidth ||
0;
this.setState({
actived: device,
currentWidth,
});
}, 0);
};
render() {
const currentWidth = this.state.currentWidth || 0;
return (
<div className="lp-simulator-pane">
{devices.map((item, index) => {
return (
<span
key={item.key}
className={`lp-simulator-pane-item ${
this.state.actived === item.key ? 'actived' : ''
}`}
onClick={this.change.bind(this, item.key)}
>
{this.renderItemSVG(item.key)}
</span>
);
})}
</div>
);
}
}
set 方法代码如下:
export default class SimulatorHost {
private readonly [simulatorHostSymbol]: BuiltinSimulatorHost;
constructor(simulator: BuiltinSimulatorHost) {
this[simulatorHostSymbol] = simulator;
}
/**
* 设置 host 配置值
* @param key
* @param value
*/
set(key: string, value: any) {
this[simulatorHostSymbol].set(key, value);
}
}
其中 simulator 由 project 来初始化
export default class Project {
private readonly [projectSymbol]: InnerProject;
private [simulatorHostSymbol]: BuiltinSimulatorHost;
private [simulatorRendererSymbol]: any;
constructor(project: InnerProject) {
this[projectSymbol] = project;
}
static create(project: InnerProject) {
return new Project(project);
}
/**
* 获取模拟器的 host
*/
get simulatorHost() {
return SimulatorHost.create(
(this[projectSymbol].simulator as any) || this[simulatorHostSymbol],
);
}
}