Skip to content
This repository was archived by the owner on Apr 14, 2026. It is now read-only.
This repository was archived by the owner on Apr 14, 2026. It is now read-only.

[FR] Signal idea #285

Description

@alimd

چند ایده در مورد سیگنال

۱. سیگنال‌های مبتنی بر زمان (Time-Based Signals)

این دسته از قابلیت‌ها در مدیریت تعاملات کاربر (UI Events) بسیار حیاتی هستند و مستقیماً از اپراتورهای RxJS الهام گرفته شده‌اند.

الف) debounceSignal (مشابه debounceTime)

این یکی از پرکاربردترین و ضروری‌ترین قابلیت‌هاست.

  • هدف: ایجاد یک سیگنال جدید که تنها زمانی مقدار خود را منتشر می‌کند که یک دوره زمانی مشخص از آخرین انتشار مقدار گذشته باشد.
  • کاربرد: جلوگیری از فراخوانی‌های مکرر در هنگام تایپ کردن کاربر در یک فیلد جستجو. کاربر تایپ می‌کند و درخواست به سرور تنها زمانی ارسال می‌شود که کاربر برای مثلاً ۳۰۰ میلی‌ثانیه دست از تایپ کردن بردارد.
  • پیاده‌سازی پیشنهادی: می‌توان یک تابع سازنده (Factory Function) نوشت که یک سیگنال ورودی و یک زمان تأخیر می‌گیرد و یک ComputedSignal جدید برمی‌گرداند.

نمونه کد مفهومی:

import { StateSignal, ComputedSignal } from './';
import { debounce } from '@alwatr/delay';

// Factory function
export function debounceSignal<T>(
  sourceSignal: StateSignal<T>,
  debounceTimeMs: number,
): ComputedSignal<T> {
  const debouncedSet = debounce(
    (value: T) => internalSignal.set(value),
    debounceTimeMs,
  );

  const internalSignal = new StateSignal<T>({
    signalId: `${sourceSignal.signalId}-debounced`,
    initialValue: sourceSignal.value,
  });

  // Listen to the source and call the debounced setter
  sourceSignal.subscribe(newValue => {
    debouncedSet(newValue);
  });

  // The public API is a readonly computed signal
  return new ComputedSignal({
    signalId: `${sourceSignal.signalId}-debounced-computed`,
    deps: [internalSignal],
    get: () => internalSignal.value,
  });
}

// --- نحوه استفاده ---
const searchInput = new StateSignal({ initialValue: '', signalId: 'searchInput' });
const debouncedSearch = debounceSignal(searchInput, 300);

new EffectSignal({
  deps: [debouncedSearch],
  run: () => {
    console.log(`Sending request for: ${debouncedSearch.value}`);
  }
});

searchInput.set('alwatr');
searchInput.set('alwatr signal'); // درخواست قبلی لغو و تایمر ریست می‌شود

۲. اپراتورهای تبدیل و فیلتر (Transformation & Filtering Operators)

این اپراتورها به شما اجازه می‌دهند سیگنال‌های جدیدی را بر اساس مقادیر سیگنال‌های دیگر بسازید. اگرچه ComputedSignal این کار را به صورت کلی انجام می‌دهد، اما ارائه توابع کمکی (Helpers) مشخص، کد را خواناتر می‌کند.

الف) mapSignal (مشابه map)

  • هدف: ایجاد یک سیگنال جدید که مقادیر آن حاصل اعمال یک تابع بر روی مقادیر یک سیگنال دیگر است.
  • کاربرد: شما یک StateSignal<User> دارید و می‌خواهید یک ComputedSignal<string> برای نمایش نام کامل کاربر داشته باشید.

پیاده‌سازی پیشنهادی: این در واقع یک ساده‌سازی برای ComputedSignal است.

// Factory function
export function mapSignal<T, R>(
  sourceSignal: IReadonlySignal<T>,
  project: (value: T) => R,
): IReadonlySignal<R> {
  return new ComputedSignal({
    signalId: `${sourceSignal.signalId}-mapped`,
    deps: [sourceSignal],
    get: () => project(sourceSignal.value),
  });
}

// --- نحوه استفاده ---
const userSignal = new StateSignal({ initialValue: { firstName: 'John', lastName: 'Doe' } });
const fullNameSignal = mapSignal(userSignal, user => `${user.firstName} ${user.lastName}`);

console.log(fullNameSignal.value); // "John Doe"

ب) filterSignal (مشابه filter)

  • هدف: ایجاد یک سیگنال جدید که تنها مقادیر سیگنال منبع را منتشر می‌کند که از یک شرط (Predicate) خاص عبور کنند.
  • کاربرد: فرض کنید یک سیگنال برای اعداد دارید و فقط می‌خواهید به اعداد زوج واکنش نشان دهید.

پیاده‌سازی پیشنهادی:

// Factory function
export function filterSignal<T>(
  sourceSignal: StateSignal<T>,
  predicate: (value: T) => boolean,
): StateSignal<T | undefined> {
  const filteredSignal = new StateSignal<T | undefined>({
    signalId: `${sourceSignal.signalId}-filtered`,
    initialValue: predicate(sourceSignal.value) ? sourceSignal.value : undefined,
  });

  sourceSignal.subscribe(newValue => {
    if (predicate(newValue)) {
      filteredSignal.set(newValue);
    }
  });

  return filteredSignal;
}

// --- نحوه استفاده ---
const numberSignal = new StateSignal({ initialValue: 0 });
const evenNumberSignal = filterSignal(numberSignal, num => num % 2 === 0);

evenNumberSignal.subscribe(num => {
  if(num !== undefined) console.log(`Even number detected: ${num}`);
});

numberSignal.set(1); // اتفاقی نمی‌افتد
numberSignal.set(2); // "Even number detected: 2"

۳. ترکیب سیگنال‌ها (Combination Signals)

این بخش برای هماهنگ کردن چندین منبع داده بسیار قدرتمند است.

الف) mergeSignals (مشابه merge)

  • هدف: ایجاد یک EventSignal جدید که هرگاه هر یک از EventSignal های ورودی یک رویداد منتشر کنند، آن رویداد را منتشر می‌کند.
  • کاربرد: شما چندین دکمه (onSaveClick, onEnterPress) دارید و می‌خواهید هر دوی آن‌ها یک عمل یکسان (مثلاً ذخیره فرم) را تحریک کنند.

پیاده‌سازی پیشنهادی:

// Factory function
export function mergeSignals<T>(
  signalId: string,
  signals: EventSignal<T>[],
): EventSignal<T> {
  const mergedSignal = new EventSignal<T>({ signalId });

  signals.forEach(signal => {
    signal.subscribe(value => {
      mergedSignal.dispatch(value);
    });
  });

  // Handle destruction
  const originalDestroy = mergedSignal.destroy;
  mergedSignal.destroy = () => {
    // Unsubscribe from all sources (memory leak prevention)
    // ... logic to unsubscribe ...
    originalDestroy.call(mergedSignal);
  };

  return mergedSignal;
}

// --- نحوه استفاده ---
const onSaveClick = new EventSignal<{data: string}>({ signalId: 'onSaveClick' });
const onEnterPress = new EventSignal<{data: string}>({ signalId: 'onEnterPress' });

const onFormSubmit = mergeSignals('onFormSubmit', [onSaveClick, onEnterPress]);

onFormSubmit.subscribe(payload => console.log('Form submitted:', payload.data));

onSaveClick.dispatch({data: 'from click'}); // "Form submitted: from click"
onEnterPress.dispatch({data: 'from enter'}); // "Form submitted: from enter"

۴. ایده پیشرفته: ResourceSignal برای عملیات ناهمزمان

این یک مفهوم سطح بالاتر برای مدیریت حالت‌های رایج در واکشی داده (Data Fetching) است.

  • هدف: کپسوله کردن کامل یک عملیات ناهمزمان (مانند یک درخواست API) و مدیریت حالت‌های آن (initial, loading, success, error).
  • الهام از: اپراتورهای switchMap و exhaustMap در RxJS که برای مدیریت درخواست‌های شبکه استفاده می‌شوند.
  • پیاده‌سازی: این می‌تواند یک کلاس جدید باشد که یک StateSignal داخلی برای نگهداری وضعیت فعلی ({state: 'loading' | 'success' | 'error', data?: T, error?: E}) دارد و یک متد trigger برای شروع عملیات.

کد مفهومی:

type ResourceState<T, E = Error> =
  | { state: 'initial' }
  | { state: 'loading' }
  | { state: 'success', data: T }
  | { state: 'error', error: E };

class ResourceSignal<T, P> {
  private readonly stateSignal: StateSignal<ResourceState<T>>;
  private readonly asyncAction: (params: P) => Promise<T>;

  constructor(config: { signalId: string, action: (params: P) => Promise<T> }) {
    this.stateSignal = new StateSignal({
      signalId: `${config.signalId}-state`,
      initialValue: { state: 'initial' },
    });
    this.asyncAction = config.action;
  }

  get value() {
    return this.stateSignal.value;
  }

  subscribe(callback) {
    return this.stateSignal.subscribe(callback);
  }

  async trigger(params: P): Promise<void> {
    if (this.stateSignal.value.state === 'loading') return; // Prevent concurrent requests

    this.stateSignal.set({ state: 'loading' });
    try {
      const data = await this.asyncAction(params);
      this.stateSignal.set({ state: 'success', data });
    } catch (err) {
      this.stateSignal.set({ state: 'error', error: err as Error });
    }
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature-requestRequest new features or options.julesIssues related to the Google Jules AI agent.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions