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

fix(vue-node-registry): 修复Teleport+KeepAlive场景下DOM重复渲染的问题(#1768) #1895

Merged
merged 5 commits into from
Oct 9, 2024

Conversation

wbccb
Copy link
Contributor

@wbccb wbccb commented Oct 1, 2024

fix #1768
fix #1862

问题发生的原因

使用KeepAlive

当我们在page1的时候,先触发了<TeleportContainer />的渲染,然后执行new LogicFlow()

// examples/vue3-app/src/views/LFChartView.vue
<template>
  <div class="w-full h-full">
    <div ref="container" class="flow w-full h-full overflow-hidden" />
    <TeleportContainer />
  </div>
</template>
  linkChart = LinkChart.create({
    container: container.value,
    graphData: graphData.value
  })
// ...省略一系列new LogicFlow()的代码

当执行new LogicFlow()然后进行LogicFlow.render()后,我们会触发teleport.ts的connect方法,将当前的flowId:nodeId作为id添加到items中

const items = reactive<{ [key: string]: any }>({})

export function connect(
  id: string,
  component: any,
  container: HTMLDivElement,
  node: BaseNodeModel,
  graph: GraphModel,
) {
  if (active) {
    items[id] = markRaw(
      defineComponent({
        render: () =>
          h(Teleport, { to: container } as any, [
            h(component, { node, graph }),
          ]),
        provide: () => ({
          getNode: () => node,
          getGraph: () => graph,
        }),
      }),
    )
  }
}

此时,由于items是响应式的 => 它会触发收集它依赖的effect执行,也就是getTeleport()setup(){return ()=> {}}执行

进行items的遍历,然后触发渲染出Page1

export function getTeleport(): any {
  if (!isVue3) {
    throw new Error('teleport is only available in Vue3')
  }
  active = true

  return defineComponent({
    setup() {
      return () =>
        h(
          Fragment,
          {},
          Object.keys(items).map((id) => h(items[id])),
        )
    },
  })
}

当我们从page1->page2的时候,跟上面的流程类似,我们会进行new LogicFlow(),又会重新触发一次teleport.tsconnect方法,将当前的flowId:nodeId作为id添加到items中,此时items有两个flowId的数据!

注意:因为page1持有一个new LogicFlow()page2也持有一个new LogicFlow(),两者的flowId是不同的

然后跟上面流程一样,由于items是响应式的 => 它会触发收集它依赖的effect执行,也就是getTeleport()setup(){return ()=> {}}执行

而由于KeepAlive的特性,Page1并没有实际销毁,它只是被隐藏了!因此此时是存在两个effect的!!

  • Page1: getTeleport()setup(){return ()=> {}}
  • Page2: getTeleport()setup(){return ()=> {}}

两个effect

我们在getTeleport(key)的时候传入对应的变量名,我们可以从图中看到,我们虽然隐藏了Page1,但是Page1<TeleportContainer />还是因为持有items这个响应式数据而触发重新渲染

由于此时Page1和Page2都共用一个items(具有两个id),因此

  • Page1: getTeleport()setup(){return ()=> {}} => 生成Page1和Page2
  • Page2: getTeleport()setup(){return ()=> {}} => 生成Page1和Page2

这就导致了当我们从page1->page2的时候,界面出现了两个Page2,其中1个Page2Page1<TeleportContainer />触发setup渲染items生成的

如果此时我们从page1->page2->page1,我们也可以发现界面也出现了两个Page1,就是上面的流程生成的数据

两个effect


而为什么Page1的<TeleportContainer />触发setup渲染items能生成Page2呢?

那是因为connect的时候传递的container都是已确定好的,也就是items[Page2相关的id]就只会生成Page2

export function connect(
  id: string,
  component: any,
  container: HTMLDivElement,
  node: BaseNodeModel,
  graph: GraphModel,
) {
  if (active) {
    items[id] = markRaw(
      defineComponent({
        render: () =>
          h(Teleport, { to: container } as any, [
            h(component, { node, graph }),
          ]),
        provide: () => ({
          getNode: () => node,
          getGraph: () => graph,
        }),
      }),
    )
  }
}

不使用KeepAlive

通过上面的分析,其实我们就能明白为什么不使用KeepAlive不会出现重复的Page2的现象了,那是因为Page1会自动销毁,因此不会因为items的变化而触发重新渲染

非keepAlive模式

但是不使用KeepAlive还是存在问题,问题就是当我们频繁page1->page2->page1->page2->page1->>page2->page1切换的时候,我们发现,items里面的数据越来越多,就算我们主动触发LogicFlow.clearData()也无法改变,因此这里的itemsid销毁事件是缺失的

解决方法

KeepAlive模式

为了更好解决这个问题,需要理清一下目前整体代码逻辑,以及为什么要使用<TeleportContainer />的写法

teleport (1)

那为什么一定要这么写呢?可以不写吗?不写也可以渲染的

那得从teleport这个组件的作用说起,本质就是在某一个组件里面写对应的逻辑代码,比如传参之类的根据这个组件的业务去决定,但是它实际渲染会根据to: container内容去挂载 DOM

如下面所示,当我们使用<TeleportContainer>时,我们可以在渲染的时候再传入特定的 props,比如 test=1,那么这个时候我们就可以在渲染h(Teleport)传入特定的参数,而这个参数会跟new LogicFlow的参数共同作用于我们构建出来的.vue 组件

换句话,就是你可以在很远的组件中进行new LogicFlow(传入一些参数),这些参数在构建你声明的 vue组件时会传入,然后你可以在距离new LogicFlow(传入一些参数)的地方进行<TeleportContainer>触发teleport渲染,同时传入一些你所在组件的props特定值的参数,这样效果等同于 vue3teleport!
image

所以目前的模式是没什么问题,那么问题出在哪里呢?

我们的本意是想
我们想要实现的

但是现实是

两个effect

因此我们的解决方法就是
解决方法1

我们强制传入对应的flowId,进行items的筛选

解决方法3

解决方法2

不使用KeepAlive

解决方法4

文档以及examples更新

文档

增加了flowId的示例传入

examples

  • 增加flowId的传入的示例
  • 增加了非keepAlive模式下的销毁测试代码示例

Copy link

changeset-bot bot commented Oct 1, 2024

⚠️ No Changeset found

Latest commit: e0d5443

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@boyongjiong
Copy link
Collaborator

大佬,你这些 PR 的内容能不能整理成文章,我在 LogicFlow 掘金号上发一下,哈哈哈,或者你用你的号发,我用 LogicFlow 官方号转发一波,我感觉都挺有用的

@wbccb
Copy link
Contributor Author

wbccb commented Oct 9, 2024

可以的 完全没问题 LogicFlow 掘金号发就行🤔️甚至有空的话 这一块文章可以做大点,比如

  • 对于一些比较有用的pr内容询问作者是否可以发到掘金以及官方文档,然后提交一个占位的.md,比如

1895

  • 新建立issues类型,每一篇文章创建一个issues,比如下图,然后assign给开发者

1895-1

  • 开发者提交pr进行issues的关闭,Maintainer审查文章内容是否需要修改(比如排版、内容缺失、错别字),修改完成=>同意merge后就可以发到掘金(当然是否同步到官方文档另说,只是借用github作为文章源地址的存放、讨论和修改)

  • 甚至如果开发者没空,Maintainer也可以自己将开发者的内容整理一下提交一个pr,艾特开发者进行review看看有没有错误发生,然后merge后就可以发到掘金(当然是否同步到官方文档另说,只是借用github作为文章源地址的存放、讨论和修改)

上面的流程简单点说就是

  • 出版社看到一个作者的初稿,觉得这个方向可以,可以出书,邀请作者写这方面的书 => 审查pr、建立占位的.md、建立issues
  • 作者同意后写书提交给出版本审查 => 提交pr,Maintainer审查,沟通修改内容
  • 修改完成,出书 => 同意merge,发布到掘金

@boyongjiong boyongjiong merged commit 0c1059e into didi:master Oct 9, 2024
@boyongjiong
Copy link
Collaborator

可以的 完全没问题 LogicFlow 掘金号发就行🤔️甚至有空的话 这一块文章可以做大点,比如

  • 对于一些比较有用的pr内容询问作者是否可以发到掘金以及官方文档,然后提交一个占位的.md,比如

1895

  • 新建立issues类型,每一篇文章创建一个issues,比如下图,然后assign给开发者

1895-1

  • 开发者提交pr进行issues的关闭,Maintainer审查文章内容是否需要修改(比如排版、内容缺失、错别字),修改完成=>同意merge后就可以发到掘金(当然是否同步到官方文档另说,只是借用github作为文章源地址的存放、讨论和修改)
  • 甚至如果开发者没空,Maintainer也可以自己将开发者的内容整理一下提交一个pr,艾特开发者进行review看看有没有错误发生,然后merge后就可以发到掘金(当然是否同步到官方文档另说,只是借用github作为文章源地址的存放、讨论和修改)

上面的流程简单点说就是

  • 出版社看到一个作者的初稿,觉得这个方向可以,可以出书,邀请作者写这方面的书 => 审查pr、建立占位的.md、建立issues
  • 作者同意后写书提交给出版本审查 => 提交pr,Maintainer审查,沟通修改内容
  • 修改完成,出书 => 同意merge,发布到掘金

安排

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