Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[V5]The custom node will call drawKeyShape when the construction of the custom node is not completely completed, resulting in an exception #6232

Closed
zmbxy opened this issue Aug 23, 2024 · 10 comments

Comments

@zmbxy
Copy link

zmbxy commented Aug 23, 2024

Describe the bug / 问题描述

自定义节点时,通过debug打印执行顺序发现,自定义节点时,在constructor中完成super调用后,在执行constructor中super之后代码之前,执行drawKeyShape,此时,由于自定义节点未完成构造,导致在drawKeyShape中提前访问了未配置好的数据,出现异常错误。

image

Reproduction link / 重现链接

https://stackblitz.com/edit/vitejs-vite-wfzclz?file=src%2FApp.tsx

Steps to Reproduce the Bug or Issue / 重现步骤

G6 Version / G6 版本

🆕 5.x

Operating System / 操作系统

macOS

Browser / 浏览器

Chrome

Additional context / 补充说明

No response

@Aarebecca Aarebecca changed the title 【v5】自定义节点,会在未完全完成自定义节点的构造时调用drawKeyShape,导致异常 【V5】自定义节点会在未完全完成自定义节点的构造时调用drawKeyShape,导致异常 Aug 26, 2024
@github-actions github-actions bot changed the title 【V5】自定义节点会在未完全完成自定义节点的构造时调用drawKeyShape,导致异常 【V5】The custom node will call drawKeyShape when the construction of the custom node is not completely completed, resulting in an exception Aug 26, 2024
@Aarebecca Aarebecca changed the title 【V5】The custom node will call drawKeyShape when the construction of the custom node is not completely completed, resulting in an exception [V5]The custom node will call drawKeyShape when the construction of the custom node is not completely completed, resulting in an exception Aug 26, 2024
@Aarebecca
Copy link
Contributor

你可以更详细描述你的问题

@zmbxy
Copy link
Author

zmbxy commented Aug 26, 2024

constructor(options: RouterNodeStyleProps) {
    console.log('1. svg node constructor...');
    super(options);
    console.log('2. svg node constructor...');

    // 自定义节点的配置,实际是通过options传入的
    this.getType = (data: NodeData) => SvgNodeType;
  }

第我期望写一个自定义节点,继承自BaseNode它能够根据业务数据,绘制不同的svg图标。如constructor,我提供了一个getType方法用来根据业务数据获取节点类型

protected drawKeyShape(attributes: Required<RouterNodeStyleProps>, shapeGroup: Group) {
    const graph = attributes.context.graph;
    const nodeId = shapeGroup.id;
    const nodeData = graph.getNodeData(nodeId);

    // 这里提前访问了constructor中未完成定义的变量,报错
    const svgNodeType = this.getType(nodeData);
    const svgPath = this.nodeMap[svgNodeType];

    return undefined;
}

然后在绘制图形(调用drawKeyShape)时,调用this.getType,并传入当前节点的原始业务数据,绘制对应的节点图形。

但现在的状况是,由于自定义节点中的函数调用,会在调用super方法后,执行this.getType赋值之前,调用drawKeyShape函数,此时会在drawKeyShape中调用一个未定义的this.getType属性报错。

@Aarebecca
Copy link
Contributor

@zmbxy 为什么要在构造函数中赋值 this.getType,直接定义为成员方法不就行了吗

@zmbxy
Copy link
Author

zmbxy commented Aug 26, 2024

我现在有两张不同业务的图,它们的节点图形不同,数据不同,我希望我的自定义节点能够在外部动态配置svg节点或者后续的替换也好,把svg节点绘制这块统一一下,外部自行根据节点数据,绘制不同的图形,如果不能动态配置,那我只能在drawKeyShape 中通过写代码逻辑控制,但这样一来,实现就很麻烦了,需要写很多硬编码在里面了。

@zmbxy
Copy link
Author

zmbxy commented Aug 27, 2024

@Aarebecca 我参考G6实现,写了一段简短的代码来复现这个问题,和我前面提到的那个问题是相同的结果

interface BaseShapeOptions {
  a: number;
  b: string;
}

abstract class BaseShape {
  options: BaseShapeOptions;

  constructor(options: BaseShapeOptions) {
    this.options = options;
    this.render();
    console.log('this is is CustomNode: ', this instanceof CustomNode);
  }

  public abstract render(): void;
}

abstract class BaseNode extends BaseShape {

  constructor(options: BaseShapeOptions) {
    super(options);
  }

  public abstract drawKeyShape(): void;

  render(): void {
    this.drawKeyShape();
  }
}

class CustomNode extends BaseNode {

  c: number;

  constructor(options: BaseShapeOptions) {
    console.log('========== 1.开始实例化自定义节点 ==========');
    super(options);
    console.log('========== 2.初始化自定义节点独有的属性 ==========');
    this.c = 10;
    console.log('========== 3.自定义节点实例化完成 ==========');
  }

  drawKeyShape(): void {
    console.log('========== 4.调用绘制图形方法,绘制图形 ==========')
  }
}

new CustomNode({ a: 1, b: '2' });

以上代码的打印结果如下:

========== 1.开始实例化自定义节点 ==========
========== 4.调用绘制图形方法,绘制图形 ==========
base shape this:  true
========== 2.初始化自定义节点独有的属性 ==========
========== 3.自定义节点实例化完成 ==========

出现问题的点在于abstract class BaseShape,在实例化时就调用了render函数导致的,使用面向对象编程,我们是为了易扩展、易维护,而现在这种状况,除了能重写一下drawKeyShape,其它的好像也扩展不了什么,就丢失了它的意义了。

而且从另一个角度来说,我的自定义节点未实例化完成,就去调用它的方法,这种感觉就像是我定义了一个关于汽车的要点要素,然后我去按照这个框架,自己造一辆车,但是我车没造好,就要它能跑起来,这显然是不合理的。

这只是我自己的一些想法,建议团队可以考虑一下。

@zmbxy
Copy link
Author

zmbxy commented Aug 30, 2024

@Aarebecca 哈喽,这个问题我看好几天没有回复了,这个还有在跟踪吗?

我们目前还在用4.x版本,现在也在了解,熟悉v5版本新API,为v4版本迁移做准备,如果团队不认为这是个问题或者设计就是如此,那我就想其它方法来封装了。

@Aarebecca
Copy link
Contributor

@zmbxy G6 5.0 的元素基类会在构造方法中调用 render 以执行各部分图形的绘制流程。当要进行元素自定义时,通常有以下两种途径:

  1. 直接覆写基类的绘制方法,例如覆写 drawKeyShape 将主图形绘制为其他图形

可以参考内置节点的实现,该情况下需要遵从基类元素的绘制流程

class Circle extends BaseNode {
  drawKeyShape(attributes, container) {
    // draw key shape here
  }
}
  1. 新增自定义绘制方法,例如新增 drawCustomShape 方法,并在 render 中调用该方法

使用该方式你可以在不影响 render逻辑的情况下任意定制你的绘制流程

// 在圆形节点的基础上额外绘制自定义元素
class CustomNode extends Circle {
  render(attributes, container) {
    super.render(attributes, container);
    this.drawCustomShape(attributes, container);
  }

  drawCustomShape(attributes, container) {
    // draw custom shape here
  }
}

"abstract class BaseShape,在实例化时就调用了render函数"
至于你提到的问题,如果你想充分的定制绘制逻辑,可以覆写 render 方法,并不要调用 super.render,然后完全重新编写绘制逻辑即可

@zmbxy
Copy link
Author

zmbxy commented Aug 30, 2024

@Aarebecca
image
那请问一下,如果要自定义属性,有办法么?

@Aarebecca
Copy link
Contributor

@zmbxy

自定义属性的传入:
Graph Options

const graph = new Graph({
  node: {
    style: {
      customStyle: d => d.data.xxx // 使用数据属性作为自定义属性
    },
  },
});

消费自定义属性:

class CustomNode extends Circle {
  render(attributes, container) {
    super.render(attributes, container);
    this.drawCustomShape(attributes, container);
  }

  drawCustomShape(attributes, container) {
    // get custom style from attributes
    const { customStyle } = attributes;
  }
}

@zmbxy
Copy link
Author

zmbxy commented Aug 30, 2024

好的🙏🙏🙏🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants