Doorgaan naar hoofdinhoud

Patches

[Onofficiële Beta-vertaling]

Deze pagina is vertaald door PageTurner AI (beta). Niet officieel goedgekeurd door het project. Een fout gevonden? Probleem melden →

egghead.io lesson 14: Capture patches using _produceWithPatches_
egghead.io lesson 16: Apply Patches using _applyPatches_

âš  Vanaf versie 6 moet ondersteuning voor Patches expliciet worden ingeschakeld door enablePatches() eenmaal aan te roepen bij het opstarten van je applicatie.

Tijdens het uitvoeren van een producer kan Immer alle patches vastleggen die de wijzigingen van de reducer zouden herhalen. Dit is een krachtig hulpmiddel als je tijdelijk een kopie van je state wilt maken en later de wijzigingen terug wilt toepassen op het origineel.

Patches zijn nuttig in verschillende scenario's:

  • Voor het uitwisselen van incrementele updates met andere partijen, bijvoorbeeld via websockets

  • Voor debugging/tracering om precies te zien hoe de state in de tijd verandert

  • Als basis voor undo/redo-functionaliteit of om wijzigingen op een licht afwijkende state-structuur toe te passen

Voor het toepassen van patches is applyPatches beschikbaar. Hier is een voorbeeld hoe patches kunnen worden gebruikt om incrementele updates vast te leggen en (omgekeerd) toe te passen:

import {produce, applyPatches} from "immer"

// version 6
import {enablePatches} from "immer"
enablePatches()

let state = {
name: "Micheal",
age: 32
}

// Let's assume the user is in a wizard, and we don't know whether
// his changes should end up in the base state ultimately or not...
let fork = state
// all the changes the user made in the wizard
let changes = []
// the inverse of all the changes made in the wizard
let inverseChanges = []

fork = produce(
fork,
draft => {
draft.age = 33
},
// The third argument to produce is a callback to which the patches will be fed
(patches, inversePatches) => {
changes.push(...patches)
inverseChanges.push(...inversePatches)
}
)

// In the meantime, our original state is replaced, as, for example,
// some changes were received from the server
state = produce(state, draft => {
draft.name = "Michel"
})

// When the wizard finishes (successfully) we can replay the changes that were in the fork onto the *new* state!
state = applyPatches(state, changes)

// state now contains the changes from both code paths!
expect(state).toEqual({
name: "Michel", // changed by the server
age: 33 // changed by the wizard
})

// Finally, even after finishing the wizard, the user might change his mind and undo his changes...
state = applyPatches(state, inverseChanges)
expect(state).toEqual({
name: "Michel", // Not reverted
age: 32 // Reverted
})

De gegenereerde patches lijken op (maar zijn niet identiek aan) de RFC-6902 JSON patch-standaard, met als verschil dat de path-eigenschap een array is in plaats van een string. Dit vereenvoudigt de verwerking van patches. Wil je normaliseren naar de officiële specificatie, dan volstaat patch.path = patch.path.join("/"). Dit is hoe een reeks patches en hun inverse eruit zou kunnen zien:

[
{
"op": "replace",
"path": ["profile"],
"value": {"name": "Veria", "age": 5}
},
{"op": "remove", "path": ["tags", 3]}
]
[
{"op": "replace", "path": ["profile"], "value": {"name": "Noa", "age": 6}},
{"op": "add", "path": ["tags", 3], "value": "kiddo"}
]

âš  Let op: De gegenereerde patches door Immer zijn correct, wat betekent dat toepassen op een identiek basisobject tot dezelfde eindstate leidt. Immer garandeert echter niet dat de gegenereerde patches optimaal zijn, dus de minimaal benodigde set patches. Wat 'optimaal' is, hangt vaak af van de use case, en het genereren van optimale patches kan rekenintensief zijn. Mogelijk wil je daarom gegenereerde patches nabewerken of comprimeren zoals hieronder uitgelegd.

produceWithPatches​

egghead.io lesson 19: Using inverse patches to build undo functionality
egghead.io lesson 20: Use patches to build redo functionality

In plaats van een patch-listener in te stellen, is een eenvoudigere manier om patches te verkrijgen via produceWithPatches. Deze heeft hetzelfde signatuur als produce, maar retourneert niet alleen de volgende state, maar een tuple bestaande uit [nextState, patches, inversePatches]. Net als produce ondersteunt produceWithPatches ook currying.

import {produceWithPatches} from "immer"

const [nextState, patches, inversePatches] = produceWithPatches(
{
age: 33
},
draft => {
draft.age++
}
)

Wat het volgende oplevert:

[
{
age: 34
},
[
{
op: "replace",
path: ["age"],
value: 34
}
],
[
{
op: "replace",
path: ["age"],
value: 33
}
]
]

Voor een diepgaande studie, zie Distributing patches and rebasing actions using Immer

Tip: Bekijk deze truc om patches te comprimeren die in de loop van de tijd worden gegenereerd.