برنامه نویسی

Angular LAB: حفظ حالت مولفه در سراسر مسیر انتقال

بیایید به یک تمرین جدید سرگرم کننده شیرجه بزنیم!

تصور کنید یک برنامه کاربردی با دو مسیر دارید:

  • یکی لیستی از کاربران را نمایش می دهد.
  • دیگری جزئیات را برای یک کاربر خاص نشان می دهد.

می‌توانید با کلیک کردن روی پیوندها بین این مسیرها حرکت کنید و همچنین می‌توانید با استفاده از دکمه‌های «قبلی» و «بعدی» بین جزئیات کاربر جابه‌جا شوید.

در اینجا مثالی از آنچه ممکن است به نظر برسد آورده شده است:

نسخه ی نمایشی 1

چالش اول

هنگام بازگشت به مسیری که قبلاً بازدید کرده اید، برنامه دوباره داده ها را از سرور واکشی می کند.

در حالی که می توان به چندین روش به این موضوع پرداخت – مانند اجرای یک حافظه پنهان یا استفاده از یک فروشگاه جهانی برای حفظ وضعیت در سراسر انتقال مسیرها – یک مسئله ظریف دیگر وجود دارد.

چالش دوم: از دست دادن وضعیت DOM

هنگام دور شدن از یک مسیر، تمام حالت DOM را از دست می دهید. این به ویژه در مورد فرم ها مشکل ساز است. وقتی به فرم باز می‌گردید، همه داده‌های وارد شده، وضعیت‌های اعتبارسنجی و تغییرات رابط کاربری از بین می‌روند:

نسخه ی نمایشی 2

اگرچه مدیریت صحیح دولتی می تواند این را حل کند، اما ساده نیست. شما باید:

  • حالت جزء را ذخیره کنید.
  • برای بازیابی ظاهر و رفتار قبلی، همه تغییرات DOM را دوباره اعمال کنید.

این شامل اصلاح مقادیر فرم، حالت ها، خطاها و موارد دیگر است. و این به فرم ها محدود نمی شود – سایر عناصر رابط کاربری مانند آکاردئون نیز نیاز به درمان مشابه دارند.

خوشبختانه، Angular ابزار قدرتمندی برای رفع این مشکل ارائه می‌کند: RouteReuseStrategy کلاس

RouteReuseStrategy: چه کاری انجام می دهد؟

شما مقالات عمیق زیادی در این زمینه خواهید یافت RouteReuseStrategy، اما اصل ماجرا اینجاست: به شما این امکان را می دهد که کنترل کنید که Angular چه زمانی باید یک جزء را در حین پیمایش دفع یا حفظ کند.

در سناریوی ما، ما می‌خواهیم Angular به‌جای از بین بردن مؤلفه‌ها هنگام خروج از مسیر، آن‌ها را کنار بگذارد (پنهان کند)، و هنگام بازگشت به عقب، آن‌ها را همانطور که هست بازیابی کند.

به طور پیش فرض، Angular از یک استراتژی داخلی استفاده می کند، اما ما می توانیم آن را با یک پیاده سازی سفارشی لغو کنیم.

ایجاد یک استراتژی استفاده مجدد سفارشی

در اینجا نحوه تعریف سفارشی آمده است RouteReuseStrategy:

@Injectable({
  providedIn: 'root',
})
export class CustomReuseStrategy implements RouteReuseStrategy {}

// main.ts
bootstrapApplication(AppComponent, {
  providers: [
    // ...
    {
      provide: RouteReuseStrategy,
      useExisting: CustomReuseStrategy,
    },
  ],
});
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

توجه داشته باشید: علامت گذاری آن به عنوان Injectable به شدت مورد نیاز نیست زیرا Angular آن را به صورت داخلی مدیریت می کند، اما این رویکرد انعطاف پذیری را ارائه می دهد – بیشتر در این مورد در یک لحظه.

تعریف رفتار استفاده مجدد از مسیر

بیایید یک کنوانسیون برای مدیریت مسیرها ایجاد کنیم:

  • استفاده کنید storeRoute: true برای مسیرهایی که نباید تخریب شوند.
  • استفاده کنید noReuse: true برای مسیرهایی که نباید از اجزاء استفاده مجدد کنند، حتی اگر مسیرها یکسان باشند.

را noReuse پرچم برای مسیرهایی با پارامترها حیاتی است (به عنوان مثال، /users/1 در مقابل /users/2). بدون آن، این مسیرها نمونه یک مولفه را به اشتراک می‌گذارند که منجر به اشتراک‌گذاری حالت ناخواسته می‌شود.

در اینجا ممکن است مسیرهای شما چگونه به نظر برسند:

const routes: Routes = [
  {
    path: 'users',
    component: UsersComponent,
    data: {
      storeRoute: true,
    },
  },
  {
    path: 'users/:id',
    component: UserComponent,
    data: {
      noReuse: true,
      storeRoute: true,
    },
  },
  {
    path: '**',
    redirectTo: '/users',
  },
];
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

چند تا یاری

برای پیاده سازی ما به چند کمک کننده نیاز داریم:

  • تابعی که مسیر کامل یک مسیر را به منظور شناسایی آن می سازد
  • تابعی که دو شی را برای مقایسه پارامترها و پارامترهای پرس و جو مقایسه می کند

در اینجا یک مثال برای هر دو:

function compareObjects(a: any, b: any): boolean {
  return Object.keys(a).every(prop => 
    b.hasOwnProperty(prop) &&
    (typeof a[prop] === typeof b[prop]) &&
    (
      (typeof a[prop] === "object" && compareObjects(a[prop], b[prop])) ||
      (typeof a[prop] === "function" && a[prop].toString() === b[prop].toString()) ||
      a[prop] == b[prop]
    )
  );
}

// Returns the full path of a route, as a string
 export function getFullPath(route: ActivatedRouteSnapshot): string {
  return route.pathFromRoot
    .map(v => v.url.map(segment => segment.toString()).join("/"))
    .join("/")
    .trim()
    .replace(/\/$/, ""); // Remove trailing slash
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در حال اجرا RouteReuseStrategy

بیایید کلاس را پیاده سازی کنیم. ابتدا باید یک شی بسازیم تا کامپوننت های خود را کش کند:

import {
  ActivatedRouteSnapshot,
  DetachedRouteHandle,
  RouteReuseStrategy
} from "@angular/router";

interface StoredRoute {
  route: ActivatedRouteSnapshot;
  handle: DetachedRouteHandle;
}

@Injectable({
  providedIn: 'root'
})
export class CustomReuseStrategy implements RouteReuseStrategy {
  storedRoutes: Record<string, StoredRoute | null> = {};
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

من وارد جزئیات نمی شوم DetachedRouteHandle، اما این چیزی است که وقتی مسیر دوباره بازدید می شود، مؤلفه ما را برمی گرداند.

حال باید روش های مورد نیاز را پیاده سازی کنیم RouteReuseStrategy.


الف shouldDetach روش تصمیم می گیرد که آیا یک مسیر را جدا کنیم یا نه: ما بر اساس آن تصمیم خواهیم گرفت storeRoute ویژگی در پیکربندی مسیر ما.

  // Should we store the route? Defaults to false.
  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return !!route.data['storeRoute'];
  }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

الف store روش به ما امکان می دهد مسیر خود را هنگام دور شدن از آن ذخیره کنیم. ما آن را با مسیر کامل با کمک خود از قبل ذخیره خواهیم کرد:

  // Store the route
  store(
    route: ActivatedRouteSnapshot,
    handle: DetachedRouteHandle
  ): void {
    // Ex. users/1, users/2, users/3, ...
    const key = getFullPath(route);
    this.storedRoutes[key] = { route, handle };
  }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

الف shouldAttach متد تصمیم می‌گیرد که مسیر را از حافظه پنهان خود بگیرد یا خیر. در اینجا، ما تمام پارامترهای مسیر را با هم مقایسه می کنیم queryParams.

  // Should we retrieve a route from the store?
  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    const key = getFullPath(route);
    const isStored = !!route.routeConfig && !!this.storedRoutes[key];

    if (isStored) {
      // Compare params and queryParams.
      // Params, however, have already been compared because the key includes them.
      const paramsMatch = compareObjects(
        route.params,
        this.storedRoutes[key]!.route.params
      );
      const queryParamsMatch = compareObjects(
        route.queryParams,
        this.storedRoutes[key]!.route.queryParams
      );

      return paramsMatch && queryParamsMatch;
    }
    return false;
  }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

الف retrieve متد برای گرفتن مسیر از کش ما استفاده می شود:

  // Retrieve from the store (it only needs the handle)
  retrieve(route: ActivatedRouteSnapshot) {
    const key = getFullPath(route);
    if (!route.routeConfig || !this.storedRoutes[key]) return null;
    return this.storedRoutes[key].handle;
  }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در نهایت، shouldReuseRoute تصمیم می گیرد که آیا مسیری با همان مسیر قبلی باید دوباره مورد استفاده قرار گیرد یا خیر. باز هم به کنوانسیون خود اشاره می کنیم و true پیش فرض خواهد بود:

  // Should the route be reused?
  shouldReuseRoute(
    previous: ActivatedRouteSnapshot,
    next: ActivatedRouteSnapshot
  ): boolean {
    const isSameConfig = previous.routeConfig === next.routeConfig;
    const shouldReuse = !next.data['noReuse'];
    return isSameConfig && shouldReuse;
  }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

روش های راحت برای پاکسازی حافظه پنهان

درک این نکته مهم است که وقتی یک مسیر در کش ذخیره می شود، مؤلفه مرتبط هرگز از بین نمی رود. این نیز به این معنی است ngOnInit هنگامی که کاربر به مسیر برگشت دوباره فعال نخواهد شد. در حالی که این می تواند برای حفظ وضعیت مفید باشد، اما در صورت عدم مدیریت صحیح، خطر نشت حافظه را نیز به همراه دارد.

برای کاهش این مشکل، من توصیه می‌کنم روش‌های راحت را برای پاک‌سازی دستی حافظه پنهان پیاده‌سازی کنید: یکی برای پاک کردن یک مسیر و دیگری برای پاک کردن همه مسیرها.

  // Destroys the components of all stored routes
  clearAllRoutes() {
    for (const key in this.storedRoutes) {
      if (this.storedRoutes[key]!.handle) {
        this.destroyComponent(this.storedRoutes[key]!.handle);
      }
    }
    this.storedRoutes = {};
  }

  // Destroys the component of a particular route.
  clearRoute(fullPath: string) {
    if (this.storedRoutes[fullPath]?.handle) {
      this.destroyComponent(this.storedRoutes[fullPath].handle);
      this.storedRoutes[fullPath] = null;
    }
  }

  // A bit of a hack: manually destroy a particular component.
  private destroyComponent(handle: DetachedRouteHandle): void {
    const componentRef: ComponentRef<any> = (handle as any).componentRef;
    if (componentRef) {
      componentRef.destroy();
    }
  }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نتیجه گیری

در اینجا نتیجه نهایی است:

نسخه ی نمایشی 3

هنگام بازگشت به یک مسیر ذخیره شده، کامپوننت تمام حالت خود را یکپارچه حفظ می کند. علاوه بر این، هر زمان که نیاز بود، می توانید کش را به صورت دستی پاک کنید.

این راه حل بسیار “plug-and-play” است و می تواند به راحتی در پروژه های شما ادغام شود. با این حال، از آن عاقلانه استفاده کنید—استفاده نادرست می تواند برنامه شما را مستعد نشت حافظه کند.

لینک نسخه ی نمایشی کامل در اینجا.


AccademiaDev

AccademiaDev: دوره های توسعه وب مبتنی بر متن!

من معتقدم به ارائه محتوای مختصر و ارزشمند بدون طولانی شدن و پرکننده های غیرضروری موجود در کتاب های سنتی. این منابع که به‌عنوان دوره‌های آنلاین تعاملی طراحی شده‌اند، برگرفته از سال‌های من به عنوان مشاور و مربی، بینش‌های عملی را از طریق متن، تکه‌های کد و آزمون‌ها ارائه می‌کنند و یک تجربه یادگیری کارآمد و جذاب را ارائه می‌دهند.

دوره های موجود

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا