ianstormtaylor / slate

A completely customizable framework for building rich text editors. (Currently in beta.)
http://slatejs.org
MIT License
30.01k stars 3.26k forks source link

performance of nested blocks - seems to cause the parent up to the root to be re-rendered? #4141

Open aliak00 opened 3 years ago

aliak00 commented 3 years ago

Problem Changing the text value of a nested block causes all the parents to be re-rendered. Ref this image:

image

I added the capital 'A' to the 'i' block and the render tree performance looks like:

image

There seems to be a lot of possibly unnecessary rendering of unchanged leaves/blocks happening. Is there anyway around this?

The editor tree is organized like:

value: [
  {
    "type": "paragraph",
    "children": [
      {
        "text": "hello"
      }
    ]
  },
  {
    "type": "paragraph",
    "children": [
      {
        "text": "hippy dippy"
      }
    ]
  },
  {
    "type": "paragraph",
    "children": [
      {
        "text": "yep"
      }
    ]
  },
  {
    "type": "node",
    "children": [
      {
        "type": "paragraph",
        "children": [
          {
            "text": "hello"
          }
        ]
      },
      {
        "type": "paragraph",
        "children": [
          {
            "text": "there"
          }
        ]
      },
      ...
]

Solution Changing one character in a deeply nested structure should just render that one leaf node? I'm not familiar with the slate core so I'm not sure what a viable solution is. I'm just opening this up for a discussion and ideas (or maybe I'm just using slate/react wrong?? 🤷‍♂️ )

Maybe using something like mobx inside slate to handle rendering changes could help here?

aliak00 commented 3 years ago

Err. Maybe this should be moved to the discussions and not issues 😬

aliak00 commented 3 years ago

One more observation. When I tab one of the leaf Text nodes, it results in a wrapNodes call, and then it looks like the entire tree is re-rendered.

zbeyens commented 3 years ago

Not a direct answer but we can see "Element (Memo)" meaning that it's using React.memo to memoize the unchanged elements. See MemoizedElement. However in a huge document the performance gets affected by the Children component:

CleanShot 2021-03-25 at 20 51 54

JM-Mendez commented 3 years ago

ThIs happens for 2 related reasons:

  1. The way slate memoizes elements
  2. The way react works

In slate, an element is composed of its properties which includes the children array. Therefore when any child element or text node changes, a new array is created breaking memoization for that element's tree. Sibling elements are unaffected.

But even if you forced the root node to memoize, then no children rerender. That's how react works. Bailing on parent rerender implicitly means that its children have also not changed. Therefore react can only realistically memoize the lowest unchanged node in the tree.

The only way around this would be to let the browser handle the text insertion if the text node properties have not changed. And that would require more intimate knowledge of the text formatting, e.g bold, italics, etc which are currently deferred to the consumer of this library