Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Optional Support For Multiple References to an Object
# What This Does Some state trees may need to reference an object more than once (such as the tree for my [fomod](https://www.npmjs.com/package/fomod) library). In essence, we store existing drafts when an off-by-default Immer class configuration option is enabled. This should be a painless solution. Specifics are described below. ## Implementation Details * Two `WeakMap` are used to keep track of draft states and related data at different parts of the immerification process: * `existingStateMap_` maps a given base object to the first draft state created for it. This state includes a reference to the revokable draft. * If a state is referenced multiple times, it will be given a new `revoke_()` function that, once called the first time, calls the old `revoke_()` function. The result is that the final `revoke_()` must be called once for every requested draft before the Proxy is finally revoked. Since a proxy which has has its `revoke_()` method called should be considered revoked by all code paths, duplicate calls should not be an issue. * During finalization, `encounteredObjects` keeps track of objects we've finalized and doesn't traverse an object if it's already seen it. It prevents infinite recursion when circular references are present. * Introduced the `extraParents_` property to the `ImmerBaseState` interface. This keeps track of additional values that would normally be attached to `parent_` so that functionality such as marking the parent state as modified is retained for objects with multiple parent objects * For Maps and Sets, a proxy is established between the state and DraftMap/DraftSet classes to handle multiple references to these native classes while preserving the idea of having one DraftSet per reference. * For Sets, each child draft has a single symbol value set so that a copy is prepared. (discussion needed; see TODOs below) * During finalization, objects may have drafted children and, thus, even unmodified children are finalized in multi-ref mode * To enable the feature, it is the same as other Immer class configuration options (such as `useStrictShallowCopy`). That is, either specify it in the config object passed to the class's constructor OR call the relevant method, `setAllowMultiRefs()` > [!NOTE] > Because of the extra computation involved with checking every proxied object against a map and traversing every object in a tree, enabling multi-ref will have a significant performance impact—even on trees which contain no repeated references. # Tests The file `__tests__/multiref.ts` contains a number of tests related to this multi-reference support implementation. Such tests seek to verify that: * Direct circular references (which Immer tests for normally) do not throw an error when multi-ref is enabled * When the properties of multiple references are modified, all references are modified * Unmodified references to the same object are kept * The same copy is provided for every reference (new references are strictly equivalent [`===`] just as the references before `produce()` would have been) Tests are performed on all relevant object archetypes where applicable. # Outstanding Discussion TODOs * [ ] What to do regarding documentation * [ ] Possible alternate solution for preparing copies for multi-reference DraftSet children * [ ] Add an error for when WeakMap isn't supported in the current environment? (supported in every noteworthy browser and server environment since late 2015)
- Loading branch information