fix: when update styles of node, it's react node does't update. (#6208)

* feat: shape upsert support hook callback

* fix: fix g node doesnot update when node props change

* test: update test case

---------

Co-authored-by: antv <antv@antfin.com>
This commit is contained in:
Aaron 2024-08-22 19:15:31 +08:00 committed by GitHub
parent 7cb9d269e0
commit b0453225a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 148 additions and 16 deletions

View File

@ -1,4 +1,4 @@
import type { NodeData } from '@antv/g6';
import type { Graph as G6Graph, NodeData } from '@antv/g6';
import { ExtensionCategory, register } from '@antv/g6';
import { GNode, Group, Image, Rect, Text } from '../../src';
import { Graph } from '../../src/graph';
@ -14,8 +14,9 @@ type Datum = {
failure: number;
};
const Node = ({ data, size }: { data: NodeData; size: [number, number] }) => {
const Node = ({ graph, data, size }: { graph: G6Graph; data: NodeData; size: [number, number] }) => {
const [width, height] = size;
const { lineWidth = 1 } = graph.getElementRenderStyle(data.id);
const { name, type, status, success, time, failure } = data.data as Datum;
const color = status === 'success' ? '#30BF78' : '#F4664A';
@ -51,7 +52,14 @@ const Node = ({ data, size }: { data: NodeData; size: [number, number] }) => {
return (
<Group>
<Rect width={width} height={height} stroke={color} fill={'white'} radius={radius}>
<Rect
width={width}
height={height}
stroke={color}
fill={'white'}
radius={radius}
lineWidth={lineWidth ? lineWidth : 1}
>
<Rect width={width} height={20} fill={color} radius={[radius, radius, 0, 0]}>
<Image
src={
@ -102,10 +110,12 @@ export const GNodeDemo = () => {
type: 'g',
style: {
size: [180, 60],
component: (data: NodeData) => <Node data={data} size={[180, 60]} />,
component: function (this: G6Graph, data: NodeData) {
return <Node graph={this} data={data} size={[180, 60]} />;
},
},
},
behaviors: ['drag-element', 'zoom-canvas', 'drag-canvas'],
behaviors: ['drag-element', 'zoom-canvas', 'drag-canvas', 'click-select'],
}}
/>
);

View File

@ -1,5 +1,5 @@
import type { DisplayObjectConfig } from '@antv/g';
import { ElementEvent, Group } from '@antv/g';
import { Group } from '@antv/g';
import type { BaseNodeStyleProps } from '@antv/g6';
import { Rect } from '@antv/g6';
import { render } from '@antv/react-g';
@ -29,15 +29,13 @@ export class GNode extends Rect {
const { component } = attributes;
const [width, height] = this.getSize();
const dom = this.upsert('key', Group, { width, height }, container)!;
dom.isMutationObserved = true;
dom.addEventListener(ElementEvent.MOUNTED, () => {
// component 已经被回调机制自动创建为 ReactNode
// component has been automatically created as ReactNode by the callback mechanism
render(component as unknown as ReactNode, dom);
return this.upsert('key', Group, { width, height }, container, {
afterCreate: (dom) => {
render(component as unknown as ReactNode, dom);
},
afterUpdate: (dom) => {
render(component as unknown as ReactNode, dom);
},
});
return dom;
}
}

View File

@ -0,0 +1,62 @@
import type { BaseShapeStyleProps } from '@/src';
import { BaseShape } from '@/src';
import { Circle } from '@antv/g';
describe('element shape', () => {
it('upsert hooks', () => {
interface ShapeStyleProps extends BaseShapeStyleProps {
shape: any;
}
const beforeCreate = jest.fn();
const afterCreate = jest.fn();
const beforeUpdate = jest.fn();
const afterUpdate = jest.fn();
const beforeDestroy = jest.fn();
const afterDestroy = jest.fn();
class Shape extends BaseShape<ShapeStyleProps> {
render() {
this.upsert('circle', Circle, this.attributes.shape, this, {
beforeCreate,
afterCreate,
beforeUpdate,
afterUpdate,
beforeDestroy,
afterDestroy,
});
}
}
const shape = new Shape({
style: {
shape: { r: 10 },
},
});
expect(beforeCreate).toHaveBeenCalledTimes(1);
expect(afterCreate).toHaveBeenCalledTimes(1);
expect(beforeUpdate).toHaveBeenCalledTimes(0);
expect(afterUpdate).toHaveBeenCalledTimes(0);
expect(beforeDestroy).toHaveBeenCalledTimes(0);
expect(afterDestroy).toHaveBeenCalledTimes(0);
shape.update({ shape: { r: 20 } });
expect(beforeCreate).toHaveBeenCalledTimes(1);
expect(afterCreate).toHaveBeenCalledTimes(1);
expect(beforeUpdate).toHaveBeenCalledTimes(1);
expect(afterUpdate).toHaveBeenCalledTimes(1);
expect(beforeDestroy).toHaveBeenCalledTimes(0);
expect(afterDestroy).toHaveBeenCalledTimes(0);
shape.update({ shape: false });
expect(beforeCreate).toHaveBeenCalledTimes(1);
expect(afterCreate).toHaveBeenCalledTimes(1);
expect(beforeUpdate).toHaveBeenCalledTimes(1);
expect(afterUpdate).toHaveBeenCalledTimes(1);
expect(beforeDestroy).toHaveBeenCalledTimes(1);
expect(afterDestroy).toHaveBeenCalledTimes(1);
});
});

View File

@ -46,6 +46,7 @@ export abstract class BaseShape<StyleProps extends BaseShapeStyleProps> extends
* @param Ctor - <zh> | <en> shape type
* @param style - <zh> | <en> shape style
* @param container - <zh> | <en> container
* @param hooks - <zh> | <en> hooks
* @returns <zh> | <en> shape instance
*/
protected upsert<T extends DisplayObject>(
@ -53,30 +54,41 @@ export abstract class BaseShape<StyleProps extends BaseShapeStyleProps> extends
Ctor: { new (...args: any[]): T },
style: T['attributes'] | false,
container: DisplayObject,
hooks?: UpsertHooks,
): T | undefined {
const target = this.shapeMap[className] as T | undefined;
// remove
// 如果 style 为 false则删除图形 / remove shape if style is false
if (style === false) {
if (target) {
hooks?.beforeDestroy?.(target);
container.removeChild(target);
delete this.shapeMap[className];
hooks?.afterDestroy?.(target);
}
return;
}
// create
if (!target || target.destroyed || !(target instanceof Ctor)) {
target?.destroy();
if (target) {
hooks?.beforeDestroy?.(target);
target?.destroy();
hooks?.afterDestroy?.(target);
}
hooks?.beforeCreate?.();
const instance = new Ctor({ className, style });
container.appendChild(instance);
this.shapeMap[className] = instance;
hooks?.afterCreate?.(instance);
return instance;
}
// update
hooks?.beforeUpdate?.(target);
updateStyle(target, style);
hooks?.afterUpdate?.(target);
return target;
}
@ -248,3 +260,52 @@ function releaseAnimation(target: DisplayObject, animation: IAnimation) {
if (index > -1) target.activeAnimations.splice(index, 1);
});
}
/**
* <zh/> upsert
*
* <en/> Shape upsert method lifecycle hooks
*/
export interface UpsertHooks {
/**
* <zh/>
*
* <en/> Before creating the shape
*/
beforeCreate?: () => void;
/**
* <zh/>
*
* <en/> After creating the shape
* @param instance - <zh/> | <en/> shape instance
*/
afterCreate?: (instance: DisplayObject) => void;
/**
* <zh/>
*
* <en/> Before updating the shape
* @param instance - <zh/> | <en/> shape instance
*/
beforeUpdate?: (instance: DisplayObject) => void;
/**
* <zh/>
*
* <en/> After updating the shape
* @param instance - <zh/> | <en/> shape instance
*/
afterUpdate?: (instance: DisplayObject) => void;
/**
* <zh/>
*
* <en/> Before destroying the shape
* @param instance - <zh/> | <en/> shape instance
*/
beforeDestroy?: (instance: DisplayObject) => void;
/**
* <zh/>
*
* <en/> After destroying the shape
* @param instance - <zh/> | <en/> shape instance
*/
afterDestroy?: (instance: DisplayObject) => void;
}

View File

@ -159,6 +159,7 @@ export type {
LabelStyleProps,
PolygonStyleProps,
} from './elements/shapes';
export type { UpsertHooks } from './elements/shapes/base-shape';
export type { ContourLabelStyleProps, ContourStyleProps } from './elements/shapes/contour';
export type { BaseLayoutOptions, WebWorkerLayoutOptions } from './layouts/types';
export type { CategoricalPalette } from './palettes/types';