-
Notifications
You must be signed in to change notification settings - Fork 403
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: move logic for tracking signal subscription
- Loading branch information
Showing
15 changed files
with
276 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
packages/@lwc/engine-core/src/libs/signal-tracker/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/* | ||
* Copyright (c) 2024, salesforce.com, inc. | ||
* All rights reserved. | ||
* SPDX-License-Identifier: MIT | ||
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT | ||
*/ | ||
import { isFalse, isUndefined } from '@lwc/shared'; | ||
import { Signal } from '@lwc/signals'; | ||
import { logWarnOnce } from '../../shared/logger'; | ||
|
||
/** | ||
* This map keeps track of objects to signals. There is an assumption that the signal is strongly referenced | ||
* on the object which allows the SignalTracker to be garbage collected along with the object. | ||
*/ | ||
const TargetToSignalTrackerMap: WeakMap<Object, SignalTracker> = new WeakMap(); | ||
|
||
function getSignalTracker(target: Object) { | ||
let signalTracker = TargetToSignalTrackerMap.get(target); | ||
if (isUndefined(signalTracker)) { | ||
signalTracker = new SignalTracker(); | ||
TargetToSignalTrackerMap.set(target, signalTracker); | ||
} | ||
return signalTracker; | ||
} | ||
|
||
export function subscribeToSignal( | ||
target: Object, | ||
signal: Signal<unknown>, | ||
update: CallbackFunction | ||
) { | ||
const signalTracker = getSignalTracker(target); | ||
if (isFalse(signalTracker.seen(signal))) { | ||
signalTracker.subscribeToSignal(signal, update); | ||
} | ||
} | ||
|
||
export function unsubscribeFromSignals(target: Object) { | ||
if (TargetToSignalTrackerMap.has(target)) { | ||
const signalTracker = getSignalTracker(target); | ||
signalTracker.unsubscribeFromSignals(); | ||
signalTracker.reset(); | ||
} | ||
} | ||
|
||
type CallbackFunction = () => void; | ||
|
||
/** | ||
* This class is used to keep track of the signals associated to a given object. | ||
* It is used to prevent the LWC engine from subscribing duplicate callbacks multiple times | ||
* to the same signal. Additionally, it keeps track of all signal unsubscribe callbacks, handles invoking | ||
* them when necessary and discarding them. | ||
*/ | ||
class SignalTracker { | ||
private signalToUnsubscribeMap: Map<Signal<unknown>, CallbackFunction> = new Map(); | ||
|
||
seen(signal: Signal<unknown>) { | ||
return this.signalToUnsubscribeMap.has(signal); | ||
} | ||
|
||
subscribeToSignal(signal: Signal<unknown>, update: CallbackFunction) { | ||
try { | ||
const unsubscribe = signal.subscribe(update); | ||
this.signalToUnsubscribeMap.set(signal, unsubscribe); | ||
} catch (err) { | ||
logWarnOnce( | ||
`Attempted to subscribe to an object that has the shape of a signal but received the following error: ${err}` | ||
); | ||
} | ||
} | ||
|
||
unsubscribeFromSignals() { | ||
try { | ||
this.signalToUnsubscribeMap.forEach((unsubscribe) => unsubscribe()); | ||
} catch (err) { | ||
logWarnOnce( | ||
`Attempted to call a signal's unsubscribe callback but received the following error: ${err}` | ||
); | ||
} | ||
} | ||
|
||
reset() { | ||
this.signalToUnsubscribeMap.clear(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# @lwc/signals | ||
|
||
This is an experimental package containing the interface expected for signals. | ||
|
||
A key point to note is that when a signal is both bound to an LWC class member variable and used on a template, | ||
the LWC engine will attempt to subscribe a callback to rerender the template. | ||
|
||
## Reactivity with Signals | ||
|
||
A Signal is an object that holds a value and allows components to react to changes to that value. | ||
It exposes a `.value` property for accessing the current value, and `.subscribe` methods for responding to changes. | ||
|
||
```js | ||
import { signal } from 'some/signals'; | ||
|
||
export default class ExampleComponent extends LightningElement { | ||
count = signal(0); | ||
|
||
increment() { | ||
this.count.value++; | ||
} | ||
} | ||
``` | ||
|
||
In the template, we can bind directly to the `.value` property: | ||
|
||
```html | ||
<template> | ||
<button onclick="{increment}">Increment</button> | ||
<p>{count.value}</p> | ||
</template> | ||
``` | ||
|
||
## Supported APIs | ||
|
||
This package supports the following APIs. | ||
|
||
### Signal | ||
|
||
This is the shape of the signal that the LWC engine expects. | ||
|
||
```js | ||
export type OnUpdate = () => void; | ||
export type Unsubscribe = () => void; | ||
|
||
export interface Signal<T> { | ||
get value(): T; | ||
subscribe(onUpdate: OnUpdate): Unsubscribe; | ||
} | ||
``` | ||
|
||
### SignalBaseClass | ||
|
||
A base class is provided as a starting point for implementation. | ||
|
||
```js | ||
export abstract class SignalBaseClass<T> implements Signal<T> { | ||
abstract get value(): T; | ||
|
||
private subscribers: Set<OnUpdate> = new Set(); | ||
|
||
subscribe(onUpdate: OnUpdate) { | ||
this.subscribers.add(onUpdate); | ||
return () => { | ||
this.subscribers.delete(onUpdate); | ||
}; | ||
} | ||
|
||
protected notify() { | ||
for (const subscriber of this.subscribers) { | ||
subscriber(); | ||
} | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/* | ||
* Copyright (c) 2018, salesforce.com, inc. | ||
* All rights reserved. | ||
* SPDX-License-Identifier: MIT | ||
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT | ||
*/ | ||
const BASE_CONFIG = require('../../../scripts/jest/base.config'); | ||
|
||
module.exports = { | ||
...BASE_CONFIG, | ||
displayName: 'lwc-signals', | ||
roots: ['<rootDir>/src'], | ||
testEnvironment: 'jsdom', | ||
}; |
Oops, something went wrong.