From 29d30dfef3815ffd37eae371289d5cce9acd3ed7 Mon Sep 17 00:00:00 2001 From: "Randal L. Schwartz" Date: Sat, 23 Aug 2025 18:11:11 -0700 Subject: [PATCH] docs: remove duplicated documentation from classes The documentation for the Signal, Computed, and Effect classes was duplicated from the top-level functions. This commit removes the duplicated documentation and uses the {@macro} tag to reference the documentation from the top-level functions. This makes the code cleaner and easier to maintain, as the documentation will only need to be updated in one place. --- .../signals_core/lib/src/core/computed.dart | 170 +----------------- .../signals_core/lib/src/core/effect.dart | 98 +--------- .../signals_core/lib/src/core/signal.dart | 9 +- 3 files changed, 3 insertions(+), 274 deletions(-) diff --git a/packages/signals_core/lib/src/core/computed.dart b/packages/signals_core/lib/src/core/computed.dart index a5c8c127..ec51b44d 100644 --- a/packages/signals_core/lib/src/core/computed.dart +++ b/packages/signals_core/lib/src/core/computed.dart @@ -172,175 +172,7 @@ part of 'signals.dart'; class Computed extends signals.Computed with ReadonlySignalMixin implements ReadonlySignal { - /// {@template computed} - /// Data is often derived from other pieces of existing data. The `computed` function lets you combine the values of multiple signals into a new signal that can be reacted to, or even used by additional computeds. When the signals accessed from within a computed callback change, the computed callback is re-executed and its new return value becomes the computed signal's value. - /// - /// > `Computed` class extends the [`Signal`](/core/signal/) class, so you can use it anywhere you would use a signal. - /// - /// ```dart - /// import 'package:signals/signals.dart'; - /// - /// final name = signal("Jane"); - /// final surname = signal("Doe"); - /// - /// final fullName = computed(() => name.value + " " + surname.value); - /// - /// // Logs: "Jane Doe" - /// print(fullName.value); - /// - /// // Updates flow through computed, but only if someone - /// // subscribes to it. More on that later. - /// name.value = "John"; - /// // Logs: "John Doe" - /// print(fullName.value); - /// ``` - /// - /// Any signal that is accessed inside the `computed`'s callback function will be automatically subscribed to and tracked as a dependency of the computed signal. - /// - /// > Computed signals are both lazily evaluated and memoized - /// - /// ## Force Re-evaluation - /// - /// You can force a computed signal to re-evaluate by calling its `.recompute` method. This will re-run the computed callback and update the computed signal's value. - /// - /// ```dart - /// final name = signal("Jane"); - /// final surname = signal("Doe"); - /// final fullName = computed(() => name.value + " " + surname.value); - /// - /// fullName.recompute(); // Re-runs the computed callback - /// ``` - /// - /// ## Disposing - /// - /// ### Auto Dispose - /// - /// If a computed signal is created with autoDispose set to true, it will automatically dispose itself when there are no more listeners. - /// - /// ```dart - /// final s = computed(() => 0, autoDispose: true); - /// s.onDispose(() => print('Signal destroyed')); - /// final dispose = s.subscribe((_) {}); - /// dispose(); - /// final value = s.value; // 0 - /// // prints: Signal destroyed - /// ``` - /// - /// A auto disposing signal does not require its dependencies to be auto disposing. When it is disposed it will freeze its value and stop tracking its dependencies. - /// - /// This means that it will no longer react to changes in its dependencies. - /// - /// ```dart - /// final s = computed(() => 0); - /// s.dispose(); - /// final value = s.value; // 0 - /// final b = computed(() => s.value); // 0 - /// // b will not react to changes in s - /// ``` - /// - /// You can check if a signal is disposed by calling the `.disposed` method. - /// - /// ```dart - /// final s = computed(() => 0); - /// print(s.disposed); // false - /// s.dispose(); - /// print(s.disposed); // true - /// ``` - /// - /// ### On Dispose Callback - /// - /// You can attach a callback to a signal that will be called when the signal is destroyed. - /// - /// ```dart - /// final s = computed(() => 0); - /// s.onDispose(() => print('Signal destroyed')); - /// s.dispose(); - /// ``` - /// - /// - /// ## Flutter - /// - /// In Flutter if you want to create a signal that automatically disposes itself when the widget is removed from the widget tree and rebuilds the widget when the signal changes, you can use the `createComputed` inside a stateful widget. - /// - /// ```dart - /// import 'package:flutter/material.dart'; - /// import 'package:signals/signals_flutter.dart'; - /// - /// class CounterWidget extends StatefulWidget { - /// @override - /// _CounterWidgetState createState() => _CounterWidgetState(); - /// } - /// - /// class _CounterWidgetState extends State with SignalsMixin { - /// late final counter = createSignal(0); - /// late final isEven = createComputed(() => counter.value.isEven); - /// late final isOdd = createComputed(() => counter.value.isOdd); - /// - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: Column( - /// mainAxisAlignment: MainAxisAlignment.center, - /// children: [ - /// Text('Counter: even=$isEven, odd=$isOdd'), - /// ElevatedButton( - /// onPressed: () => counter.value++, - /// child: Text('Increment'), - /// ), - /// ], - /// ), - /// ), - /// ); - /// } - /// } - /// ``` - /// - /// No `Watch` widget or extension is needed, the signal will automatically dispose itself when the widget is removed from the widget tree. - /// - /// The `SignalsMixin` is a mixin that automatically disposes all signals created in the state when the widget is removed from the widget tree. - /// - /// ## Testing - /// - /// Testing computed signals is possible by converting a computed to a stream and testing it like any other stream in Dart. - /// - /// ```dart - /// test('test as stream', () { - /// final a = signal(0); - /// final s = computed(() => a()); - /// final stream = s.toStream(); - /// - /// a.value = 1; - /// a.value = 2; - /// a.value = 3; - /// - /// expect(stream, emitsInOrder([0, 1, 2, 3])); - /// }); - /// ``` - /// - /// `emitsInOrder` is a matcher that will check if the stream emits the values in the correct order which in this case is each value after a signal is updated. - /// - /// You can also override the initial value of a computed signal when testing. This is is useful for mocking and testing specific value implementations. - /// - /// ```dart - /// test('test with override', () { - /// final a = signal(0); - /// final s = computed(() => a()).overrideWith(-1); - /// - /// final stream = s.toStream(); - /// - /// a.value = 1; - /// a.value = 2; - /// a.value = 2; // check if skipped - /// a.value = 3; - /// - /// expect(stream, emitsInOrder([-1, 1, 2, 3])); - /// }); - /// ``` - /// - /// `overrideWith` returns a new computed signal with the same global id sets the value as if the computed callback returned it. - /// @link https://dartsignals.dev/core/computed - /// {@endtemplate} + /// {@macro computed} Computed( super.fn, { this.debugLabel, diff --git a/packages/signals_core/lib/src/core/effect.dart b/packages/signals_core/lib/src/core/effect.dart index 828d9c82..b3b2ef6a 100644 --- a/packages/signals_core/lib/src/core/effect.dart +++ b/packages/signals_core/lib/src/core/effect.dart @@ -109,103 +109,7 @@ class Effect extends signals.Effect { /// Label used for debugging final String? debugLabel; - /// {@template effect} - /// The `effect` function is the last piece that makes everything reactive. When you access a signal inside its callback function, that signal and every dependency of said signal will be activated and subscribed to. In that regard it is very similar to [`computed(fn)`](/core/computed). By default all updates are lazy, so nothing will update until you access a signal inside `effect`. - /// - /// ```dart - /// import 'package:signals/signals.dart'; - /// - /// final name = signal("Jane"); - /// final surname = signal("Doe"); - /// final fullName = computed(() => name.value + " " + surname.value); - /// - /// // Logs: "Jane Doe" - /// effect(() => print(fullName.value)); - /// - /// // Updating one of its dependencies will automatically trigger - /// // the effect above, and will print "John Doe" to the console. - /// name.value = "John"; - /// ``` - /// - /// You can destroy an effect and unsubscribe from all signals it was subscribed to, by calling the returned function. - /// - /// ```dart - /// import 'package:signals/signals.dart'; - /// - /// final name = signal("Jane"); - /// final surname = signal("Doe"); - /// final fullName = computed(() => name.value + " " + surname.value); - /// - /// // Logs: "Jane Doe" - /// final dispose = effect(() => print(fullName.value)); - /// - /// // Destroy effect and subscriptions - /// dispose(); - /// - /// // Update does nothing, because no one is subscribed anymore. - /// // Even the computed `fullName` signal won't change, because it knows - /// // that no one listens to it. - /// surname.value = "Doe 2"; - /// ``` - /// - /// ## Cleanup Callback - /// - /// You can also return a cleanup function from an effect. This function will be called when the effect is destroyed. - /// - /// ```dart - /// import 'package:signals/signals.dart'; - /// - /// final s = signal(0); - /// - /// final dispose = effect(() { - /// print(s.value); - /// return () => print('Effect destroyed'); - /// }); - /// - /// // Destroy effect and subscriptions - /// dispose(); - /// ``` - /// - /// ## On Dispose Callback - /// - /// You can also pass a callback to `effect` that will be called when the effect is destroyed. - /// - /// ```dart - /// import 'package:signals/signals.dart'; - /// - /// final s = signal(0); - /// - /// final dispose = effect(() { - /// print(s.value); - /// }, onDispose: () => print('Effect destroyed')); - /// - /// // Destroy effect and subscriptions - /// dispose(); - /// ``` - /// - /// ## Warning About Cycles - /// - /// Mutating a signal inside an effect will cause an infinite loop, because the effect will be triggered again. To prevent this, you can use [`untracked(fn)`](/core/untracked) to read a signal without subscribing to it. - /// - /// ```dart - /// import 'dart:async'; - /// - /// import 'package:signals/signals.dart'; - /// - /// Future main() async { - /// final completer = Completer(); - /// final age = signal(0); - /// - /// effect(() { - /// print('You are ${age.value} years old'); - /// age.value++; // <-- This will throw a cycle error - /// }); - /// - /// await completer.future; - /// } - /// ``` - /// @link https://dartsignals.dev/core/effect - /// {@endtemplate} + /// {@macro effect} Effect( super.fn, { this.debugLabel, diff --git a/packages/signals_core/lib/src/core/signal.dart b/packages/signals_core/lib/src/core/signal.dart index 7efcaddd..ee163064 100644 --- a/packages/signals_core/lib/src/core/signal.dart +++ b/packages/signals_core/lib/src/core/signal.dart @@ -4,14 +4,7 @@ part of 'signals.dart'; class Signal extends signals.Signal with ReadonlySignalMixin implements ReadonlySignal { - /// Simple writeable signal. - /// - /// ```dart - /// final count = signal(0); - /// print(count.value); // 0 - /// count.value++; - /// print(count.value); // 1 - /// ``` + /// {@macro signal} Signal( super.internalValue, { this.debugLabel,