ampproject / ampproject/amp-react-prototype

Two-way DOM update and mutation observers

Offen
#40 1 Kommentar 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
TBD
Vorherrschende Sprache
JavaScript
Sterne
36
Forks
6
PR-Merge-Kennzahlen
Keine gemergten PRs in 30 T.

Beschreibung

Context: https://github.com/ampproject/amp-react-prototype/pull/29#discussion_r341651128

The goal is to find a clear way to separate DOM mutations from external script and components.

Usually we can separate user DOM updates from component updates using light and shadow trees. However, this is not always possible, such as the case of `amp-selector` component.

Let's consider the `amp-selector` component. For the amp-selector, all mutations are in the main DOM tree and all observable by the main script/css/etc. E.g. if the CE user wants to decorated a selected option within the `amp-selector`, the following CSS can be used:

```
.option[selected] {
border: 1px solid red;
}
```

Currently this is virtually the only option for us to signify that a selection option (as a DOM element) is currently selected. This is definitely not great. It'd be much better from API point of view if we could set a custom state, e.g. `.option:selected {...}`. But [custom states spec](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/custom-states-and-state-pseudo-class.md) is still ways and ways away.

As a result, a subtree-based MutationObserver would see these mutations too. In other words the following are both valid code paths that mutate DOM:

1. In-component: user clicks on an option and the React component sets it as selected. Our `Slot` delegation updates DOM:

```js
// A. React's onClick handler -> state
function AmpSelection.Option() {
...
( setSelectedOptionState(props.option)} ...>)
...
}

// B. Slot's props.selected is set in React:
function AmpSelection.Option() {
...
()
...
}

// C. Slot's side effect sets DOM attribute:

function Slot() {
const domRef = useRef();
useEffect(() => {
const slot = domRef.current;
...
// Update in the main DOM:
const assignedOption = slot.assignedElements()[0];
if (props.selected) {
assignedOption.setAttribute('selected', '');
} else {
assignedOption.removeAttribute('selected');
}
});
...
}
```

2. Out-of-component mutation: a user script in the main document manually writes DOM:

```js
button1.onclick = () => {
option2.setAttribute('selected', '');
};
```

Update an attribute this way by the user script will trigger mutation observer and trigger React component re-rendering with the new `value` prop.

We need the mutation observer to synchronize DOM -> React component in the case /2/. But we don't really need mutation observer for /1/ since we ourselves ensure that DOM/React are in full sync. In general case, incorrectly working /1/ can cause cycles. So far the cycles in such mutations have been easy to work around or ignore. But in general case this is still a dangerous situation. It'd be nice to have a more "automatic" solution for this.

Beitragsleitfaden

Beitragsleitfaden öffnen

Rechercherichtung

Beginne mit der verlinkten Pull-Request-Diskussion und verfolge die beiden für amp-selector beschriebenen Mutationspfade: Slot-Aktualisierungen auf selected und Aktualisierungen desselben DOM durch ein externes Skript. Definiere eine klare Möglichkeit, diese Pfade zu unterscheiden, während DOM und React synchron gehalten und Aktualisierungszyklen verhindert werden; die Aufgabe ist abgeschlossen, wenn dieser Ansatz für beide Fälle spezifiziert und validiert ist.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
javascript, react
Bereich
frontend
Issue-Typ
Feature
Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Aktivitätsstatus
Veraltet
Klarheit
Muss geklärt werden
Anfängerfreundlichkeit
30/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.