前言: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 装饰器,以下我们抽取其中一个进行分析:

AA649D60-4C0D-41AC-B4FA-45D81CD8E66E-2172-000001327FEC636C.png

以 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],
    );
  }

}