vadimdemedes / ink

🌈 React for interactive command-line apps
https://term.ink
MIT License
26.42k stars 593 forks source link

Improve diffing of styles #566

Closed vadimdemedes closed 1 year ago

vadimdemedes commented 1 year ago

Problem

style property of Ink nodes contains the changed fields of style object, rather than a full style object itself. As a result, style in internal representation of an Ink node is inconsistent what's actually passed via a style prop to ink-box (rendered by Box) or ink-text (rendered by Text).

Here's example code demonstrating the problem:

const {rerender} = render(<Box borderStyle="round" borderColor="green"/>);
// Box's style = { borderStyle: 'round', borderColor: 'green' }
// Ink node's style = { borderStyle: 'round', borderColor: 'green' }

rerender(<Box borderStyle="round" borderColor="blue"/>);
// Box's style = { borderStyle: 'round', borderColor: 'blue' }
// Ink node's style = { borderColor: 'blue' }

borderStyle hasn't been changed between renders, so it's no longer present in the internal style object. This led to https://github.com/vadimdemedes/ink/pull/345, which actually didn't have a proper solution to the root problem (style being out of sync), but rather a workaround that special-cased props like borderStyle to always be present in the style object.

Solution

This PR does several things to remove this workaround I shipped before:

  1. Make setStyle function responsible for only setting styles and not updating Yoga node.
  2. Change reconciler's prepareUpdate to diff props and props.style prop separately.
  3. Skip update entirely if props or props.style hasn't changed. It's actually unrelated to this PR, but still a nice improvement.
  4. Reconciler is now the one who updates styles of a Yoga node, not setStyle function.

With this update, style is always consistent between an internal representation of an Ink node and what's passed via style prop to a React component. At the same time, reconciler still updates Yoga node with styles that were changed since last render.