Temel kavramlar

Yaşam Döngüsü

Kanca sırası, sınıf metodu ve JSX prop'u olarak kancalar, initializeComponent, görünürlük, bekletme, serbest bırakma ve uygulama yaşam döngüsü; JSX ile ve JSX'siz.

Her MotifJS bileşeni, oluşturulmasından DOM'a bağlanmasına ve serbest bırakılmasına kadar belirli bir yaşam döngüsünden geçer. Bu aşamalarda çalışan kancaları (hooks) sınıf metodu, JSX prop'u ya da JSX'siz yazımda motifComponent'in nesne biçiminde anahtar olarak tanımlarsınız.

Örnekler bir not uygulamasının kesitleridir. Note ve NotesService Bileşenler sayfasındaki src/services/notes.ts dosyasındakilerdir; notları sunucudan çeken servis şudur:

import { Injectable } from "@motifx/core";
import type { Note } from "./notes";

@Injectable({ lifetime: "singleton" })
export class SyncService {
  async pull(): Promise<Note[]> {
    const response = await fetch("/api/notes");
    return response.json();
  }
}
import { Injectable } from "@motifx/core";

@Injectable({ lifetime: "singleton" })
export class SyncService {
  async pull() {
    const response = await fetch("/api/notes");
    return response.json();
  }
}

Kancalar ve çalışma sırası

Sıra Kanca Ne zaman çalışır
1 onInitializing Yapıcıda, element atanıp props işlendikten sonra (this.props hazır; alt sınıfın state = reactive(...) gibi alanları henüz ilklenmemiştir).
2 onInitialized Yapıcıda, onInitializing'den hemen sonra.
3 onConfig Yapılandırma aşaması; props hazır, DOM'a henüz bağlı değil. Veri çekmek için uygun yer. Alt sınıflarda build() başında (sınıf alanları ilklendikten sonra) çalışır.
4 onConfigured Yapılandırma tamamlandığında (build başlangıcı).
5 onBuilding DOM inşası başlarken.
6 initializeComponent view() çağrılmadan hemen önce; bileşenin kurulum kodu (JSX'ten derleyici üretir, JSX'siz yazımda siz yazarsınız).
7 oninitializeComponent Bütün initializeComponent kodu (sınıf metodu, etiketteki değer, derleyicinin ürettiği) çalıştıktan hemen sonra, view()'den önce.
8 onBuilt Bileşen ve çocukları inşa edildikten sonra. Element bu anda hâlâ detached bir fragment içinde olabilir (ternary/liste dalları).
9 onMounted Bileşen, sayfadaki bir köke bağlı ağaca yerleştikten sonraki görevde, bir kez; ekran bu sırada çizilmiş olabilir. Kök, app.run'ın hedefi ya da belgedeki hazır bir elemanı saran bileşendir; yol üzerindeki her bileşenin görünür olması gerekir. Yalnız elemanın gerçekten sayfada ve görünür olması gereken işler içindir: focus(), ölçüm, elemanı belgede isteyen kütüphaneler. Diğer kurulum işleri onBuilt'a yazılır.
— onActivated / onDeactivated Bileşen DOM'dan sökülüp (serbest bırakılmadan) yeniden yerleştiğinde: keepAlive rota dönüşü, controls.detach/silentDetach sonrası controls.add (taşıma dahil), motif.hide()/motif.show() (x-wait/x-display dahil), Virtualization satırının pencereden çıkıp geri gelmesi. Ayrılırken onDeactivated, geri gelince onActivated. İlk gösterimde çalışmaz. Görünür alt ağaca da iletilir (önce ebeveyn); gizli bir çocuk ebeveyniyle etkinleşmez, kendi motif.show()'unda etkinleşir.
— onVisibilityChanged motif.show()/motif.hide()/motif.toggle() (x-wait/x-display dahil) görünürlüğü değiştirirken; isVisible güncellenmeden önce çalışır (gizlemede çıkış animasyonu bittikten sonra; hideStrategy: "detach" ile animasyondan önce), kanca içinde isVisible önceki değeri, e.visible yeni değeri gösterir.
— onDisposing Serbest bırakma başlarken.
— onDisposed Serbest bırakma tamamlandığında.

onInitializing ve onInitialized bileşen oluşturulurken (constructor akışında) çalışır. onConfig, düz new Component(tag, { onConfig }) örneklerinde yapıcıda; alt sınıflarda (class X extends Component) ise state = reactive(...) gibi sınıf alanları ilklendikten sonra, build() başında onConfigured'dan hemen önce çalışır (hiç build edilmeyen bir alt sınıf örneği onConfig almaz). onConfigured, onBuilding, initializeComponent, oninitializeComponent ve onBuilt build() sırasında; onMounted ise bileşen köke bağlı ağaca yerleştikten sonraki görevde çalışır (yerleştiği anda ağaç zaten bağlıysa da sonraki görevde). isInitialized onInitializing sırasında false, onInitialized'dan itibaren true'dur (yapıcıdaki ilkleme bitince atanır).

onMounted kancaları sıraya alınır ve kurulumu yapan görevden sonra, kayıt sırasıyla çalışır. Sıra 8 ms'lik dilimler hâlinde işlenir: süre dolunca kalan kancalar bir sonraki göreve kalır, arada tarayıcı ekranı çizer ve kullanıcı girdisini işler. Böylece bir grafik ya da tablo kütüphanesini saran çok sayıda bileşenin kurulumu sayfayı kilitlemez. Alt bileşenin kancası üst bileşeninkinden önce çalışır; sırası gelmeden serbest bırakılan bileşenin kancası çalışmaz. Yerleşmeyi çekirdek izler: controls.add, x-wait'in bitmesi, motif.show() ve Transporter kancayı sıraya sokar; bir elemanı appendChild ile belgeye kendiniz koyarsanız kanca çalışmaz. Hazır bir elemanın içine içerik vermek için elemanı new Component(eleman) ile sarıp çocukları controls.add ile eklersiniz; window.open ile açılan pencerenin gövdesi de böyle sarılır ve o pencereye verilen bileşenler orada bağlanır. Sıra, kökün bulunduğu pencerenin zamanlayıcısıyla işler.

Durum bayrakları: isInitialized, isConfigured, isBuilt, isVisible, isWait, isDisposed ilgili evre geçildiğinde güncellenir ve bileşen üzerinden okunur.

Options API nesnesine yazılan onInitializing, onInitialized ve onConfig çalışmaz (bu evreler alanlar nesneden kopyalanmadan önce biter); kurulum için ctor ya da onConfigured kullanın (bkz. Options API).

Kancaları tanımlama

Sınıfta kanca bir metottur; fonksiyon bileşeninde döndürülen köke yazılan bir prop, Options API'de nesnenin bir üyesidir. JSX'siz yazımda sınıf metodu aynı kalır, fonksiyon ve düz etiketlerde kanca motifComponent'in nesne biçiminde bir anahtardır. NoteBoard yapılandırılırken notları çeker (onConfig), DOM'a girince arama kutusuna odaklanır (onMounted) ve dakikada bir yeniler; zamanlayıcı motif.setDisposable ile bileşene bağlanır, serbest bırakılırken kendiliğinden durur:

import { Component, reactive } from "@motifx/core";
import { NotesService } from "../services/notes";
import { SyncService } from "../services/sync";

export class NoteBoard extends Component {
  notes = this.getService(NotesService)!;
  sync = this.getService(SyncService)!;
  state = reactive({ loading: true });
  search!: Component<HTMLInputElement>;

  override async onConfig() {
    this.notes.state.items = await this.sync.pull();
    this.state.loading = false;
    const timer = setInterval(async () => (this.notes.state.items = await this.sync.pull()), 60_000);
    this.motif.setDisposable(() => clearInterval(timer));
  }

  override onMounted() {
    this.search.element.focus();
  }

  override view() {
    return (
      <section class="board">
        <input type="search" placeholder="Notlarda ara" ref={this.search} />
        <p class="loading" x-display={() => this.state.loading}>Yükleniyor…</p>
        <ul>{this.notes.list("notes").map((note) => <li key={note.id}>{note.title}</li>)}</ul>
      </section>
    );
  }
}
import { Component, FromService, reactive } from "@motifx/core";
import { NotesService } from "../services/notes";
import { SyncService } from "../services/sync";

export function NoteBoard() {
  const notes = FromService(NotesService)!;
  const sync = FromService(SyncService)!;
  const state = reactive({ loading: true });
  let search!: Component<HTMLInputElement>;

  return (
    <section
      class="board"
      onConfig={async (board) => {
        notes.state.items = await sync.pull();
        state.loading = false;
        const timer = setInterval(async () => (notes.state.items = await sync.pull()), 60_000);
        board.motif.setDisposable(() => clearInterval(timer));
      }}
      onMounted={() => search.element.focus()}
    >
      <input type="search" placeholder="Notlarda ara" ref={(field) => (search = field)} />
      <p class="loading" x-display={() => state.loading}>Yükleniyor…</p>
      <ul>{notes.list("notes").map((note) => <li key={note.id}>{note.title}</li>)}</ul>
    </section>
  );
}
import { Component, FromService, motifOptions, reactive } from "@motifx/core";
import { NotesService } from "../services/notes";
import { SyncService } from "../services/sync";

export const NoteBoard = () => motifOptions({
  el: "section",
  data: reactive({ loading: true }),
  search: undefined as Component<HTMLInputElement> | undefined,

  async onConfigured() {
    const notes = FromService(NotesService)!;
    const sync = FromService(SyncService)!;
    this.class.add("board");
    notes.state.items = await sync.pull();
    this.data.loading = false;
    const timer = setInterval(async () => (notes.state.items = await sync.pull()), 60_000);
    this.motif.setDisposable(() => clearInterval(timer));
  },

  onMounted() {
    this.search!.element.focus();
  },

  view() {
    const notes = FromService(NotesService)!;
    return (
      <>
        <input type="search" placeholder="Notlarda ara" ref={(field: Component<HTMLInputElement>) => (this.search = field)} />
        <p class="loading" x-display={() => this.data.loading}>Yükleniyor…</p>
        <ul>{notes.list("notes").map((note) => <li key={note.id}>{note.title}</li>)}</ul>
      </>
    );
  },
});
import { Component, ComponentBase, motifComponent, reactive } from "@motifx/core";
import { Note, NotesService } from "../services/notes";
import { SyncService } from "../services/sync";

export class NoteBoard extends Component {
  notes = this.getService(NotesService)!;
  sync = this.getService(SyncService)!;
  state = reactive({ loading: true });
  search!: Component<HTMLInputElement>;

  override async onConfig() {
    this.notes.state.items = await this.sync.pull();
    this.state.loading = false;
    const timer = setInterval(async () => (this.notes.state.items = await this.sync.pull()), 60_000);
    this.motif.setDisposable(() => clearInterval(timer));
  }

  override onMounted() {
    this.search.element.focus();
  }

  override view() {
    return motifComponent("section", (board: ComponentBase) => {
      board.class.add("board");
      const search = motifComponent("input", {
        ref: (field: Component<HTMLInputElement>) => (this.search = field),
        initializeComponent: (field: ComponentBase) => field.attr.add({ type: "search", placeholder: "Notlarda ara" }),
      });
      const loading = motifComponent("p", {
        preconfig: (p: ComponentBase) => p.bindings.display(() => this.state.loading),
        initializeComponent: (p: ComponentBase) => {
          p.class.add("loading");
          p.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Yükleniyor…")));
        },
      });
      const list = motifComponent("ul", (ul: ComponentBase) => ul.controls.add(motifComponent({
        initializeComponent: (holder: ComponentBase) => holder.bindings.list(() => this.notes.list("notes"), (note: Note) => motifComponent("li", {
          indexkey: () => note.id,
          initializeComponent: (li: ComponentBase) => li.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", note, "title"))),
        })),
      })));
      board.controls.add(search, loading, list);
    });
  }
}
import { Component, ComponentBase, FromService, motifComponent, reactive } from "@motifx/core";
import { Note, NotesService } from "../services/notes";
import { SyncService } from "../services/sync";

export function NoteBoard() {
  const notes = FromService(NotesService)!;
  const sync = FromService(SyncService)!;
  const state = reactive({ loading: true });
  let search!: Component<HTMLInputElement>;

  return motifComponent("section", {
    onConfig: async (board: ComponentBase) => {
      notes.state.items = await sync.pull();
      state.loading = false;
      const timer = setInterval(async () => (notes.state.items = await sync.pull()), 60_000);
      board.motif.setDisposable(() => clearInterval(timer));
    },
    onMounted: () => search.element.focus(),
    initializeComponent: (board: ComponentBase) => {
      board.class.add("board");
      const field = motifComponent("input", {
        ref: (input: Component<HTMLInputElement>) => (search = input),
        initializeComponent: (input: ComponentBase) => input.attr.add({ type: "search", placeholder: "Notlarda ara" }),
      });
      const loading = motifComponent("p", {
        preconfig: (p: ComponentBase) => p.bindings.display(() => state.loading),
        initializeComponent: (p: ComponentBase) => {
          p.class.add("loading");
          p.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Yükleniyor…")));
        },
      });
      const list = motifComponent("ul", (ul: ComponentBase) => ul.controls.add(motifComponent({
        initializeComponent: (holder: ComponentBase) => holder.bindings.list(() => notes.list("notes"), (note: Note) => motifComponent("li", {
          indexkey: () => note.id,
          initializeComponent: (li: ComponentBase) => li.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", note, "title"))),
        })),
      })));
      board.controls.add(field, loading, list);
    },
  });
}
import { Component, ComponentBase, FromService, motifComponent, motifOptions, reactive } from "@motifx/core";
import { Note, NotesService } from "../services/notes";
import { SyncService } from "../services/sync";

export const NoteBoard = () => motifOptions({
  el: "section",
  data: reactive({ loading: true }),
  search: undefined as Component<HTMLInputElement> | undefined,

  async onConfigured() {
    const notes = FromService(NotesService)!;
    const sync = FromService(SyncService)!;
    this.class.add("board");
    notes.state.items = await sync.pull();
    this.data.loading = false;
    const timer = setInterval(async () => (notes.state.items = await sync.pull()), 60_000);
    this.motif.setDisposable(() => clearInterval(timer));
  },

  onMounted() {
    this.search!.element.focus();
  },

  view() {
    const notes = FromService(NotesService)!;
    const search = motifComponent("input", {
      ref: (field: Component<HTMLInputElement>) => (this.search = field),
      initializeComponent: (field: ComponentBase) => field.attr.add({ type: "search", placeholder: "Notlarda ara" }),
    });
    const loading = motifComponent("p", {
      preconfig: (p: ComponentBase) => p.bindings.display(() => this.data.loading),
      initializeComponent: (p: ComponentBase) => {
        p.class.add("loading");
        p.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Yükleniyor…")));
      },
    });
    const list = motifComponent("ul", (ul: ComponentBase) => ul.controls.add(motifComponent({
      initializeComponent: (holder: ComponentBase) => holder.bindings.list(() => notes.list("notes"), (note: Note) => motifComponent("li", {
        indexkey: () => note.id,
        initializeComponent: (li: ComponentBase) => li.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", note, "title"))),
      })),
    })));
    return [search, loading, list];
  },
});
import { Component, reactive } from "@motifx/core";
import { NotesService } from "../services/notes";
import { SyncService } from "../services/sync";

export class NoteBoard extends Component {
  notes = this.getService(NotesService);
  sync = this.getService(SyncService);
  state = reactive({ loading: true });
  search;

  async onConfig() {
    this.notes.state.items = await this.sync.pull();
    this.state.loading = false;
    const timer = setInterval(async () => (this.notes.state.items = await this.sync.pull()), 60_000);
    this.motif.setDisposable(() => clearInterval(timer));
  }

  onMounted() {
    this.search.element.focus();
  }

  view() {
    return (
      <section class="board">
        <input type="search" placeholder="Notlarda ara" ref={this.search} />
        <p class="loading" x-display={() => this.state.loading}>Yükleniyor…</p>
        <ul>{this.notes.list("notes").map((note) => <li key={note.id}>{note.title}</li>)}</ul>
      </section>
    );
  }
}
import { FromService, reactive } from "@motifx/core";
import { NotesService } from "../services/notes";
import { SyncService } from "../services/sync";

export function NoteBoard() {
  const notes = FromService(NotesService);
  const sync = FromService(SyncService);
  const state = reactive({ loading: true });
  let search;

  return (
    <section
      class="board"
      onConfig={async (board) => {
        notes.state.items = await sync.pull();
        state.loading = false;
        const timer = setInterval(async () => (notes.state.items = await sync.pull()), 60_000);
        board.motif.setDisposable(() => clearInterval(timer));
      }}
      onMounted={() => search.element.focus()}
    >
      <input type="search" placeholder="Notlarda ara" ref={(field) => (search = field)} />
      <p class="loading" x-display={() => state.loading}>Yükleniyor…</p>
      <ul>{notes.list("notes").map((note) => <li key={note.id}>{note.title}</li>)}</ul>
    </section>
  );
}
import { FromService, motifOptions, reactive } from "@motifx/core";
import { NotesService } from "../services/notes";
import { SyncService } from "../services/sync";

export const NoteBoard = () => motifOptions({
  el: "section",
  data: reactive({ loading: true }),
  search: undefined,

  async onConfigured() {
    const notes = FromService(NotesService);
    const sync = FromService(SyncService);
    this.class.add("board");
    notes.state.items = await sync.pull();
    this.data.loading = false;
    const timer = setInterval(async () => (notes.state.items = await sync.pull()), 60_000);
    this.motif.setDisposable(() => clearInterval(timer));
  },

  onMounted() {
    this.search.element.focus();
  },

  view() {
    const notes = FromService(NotesService);
    return (
      <>
        <input type="search" placeholder="Notlarda ara" ref={(field) => (this.search = field)} />
        <p class="loading" x-display={() => this.data.loading}>Yükleniyor…</p>
        <ul>{notes.list("notes").map((note) => <li key={note.id}>{note.title}</li>)}</ul>
      </>
    );
  },
});
import { Component, motifComponent, reactive } from "@motifx/core";
import { NotesService } from "../services/notes";
import { SyncService } from "../services/sync";

export class NoteBoard extends Component {
  notes = this.getService(NotesService);
  sync = this.getService(SyncService);
  state = reactive({ loading: true });
  search;

  async onConfig() {
    this.notes.state.items = await this.sync.pull();
    this.state.loading = false;
    const timer = setInterval(async () => (this.notes.state.items = await this.sync.pull()), 60_000);
    this.motif.setDisposable(() => clearInterval(timer));
  }

  onMounted() {
    this.search.element.focus();
  }

  view() {
    return motifComponent("section", (board) => {
      board.class.add("board");
      const search = motifComponent("input", {
        ref: (field) => (this.search = field),
        initializeComponent: (field) => field.attr.add({ type: "search", placeholder: "Notlarda ara" }),
      });
      const loading = motifComponent("p", {
        preconfig: (p) => p.bindings.display(() => this.state.loading),
        initializeComponent: (p) => {
          p.class.add("loading");
          p.controls.add(motifComponent("text", (node) => node.setText("Yükleniyor…")));
        },
      });
      const list = motifComponent("ul", (ul) => ul.controls.add(motifComponent({
        initializeComponent: (holder) => holder.bindings.list(() => this.notes.list("notes"), (note) => motifComponent("li", {
          indexkey: () => note.id,
          initializeComponent: (li) => li.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", note, "title"))),
        })),
      })));
      board.controls.add(search, loading, list);
    });
  }
}
import { FromService, motifComponent, reactive } from "@motifx/core";
import { NotesService } from "../services/notes";
import { SyncService } from "../services/sync";

export function NoteBoard() {
  const notes = FromService(NotesService);
  const sync = FromService(SyncService);
  const state = reactive({ loading: true });
  let search;

  return motifComponent("section", {
    onConfig: async (board) => {
      notes.state.items = await sync.pull();
      state.loading = false;
      const timer = setInterval(async () => (notes.state.items = await sync.pull()), 60_000);
      board.motif.setDisposable(() => clearInterval(timer));
    },
    onMounted: () => search.element.focus(),
    initializeComponent: (board) => {
      board.class.add("board");
      const field = motifComponent("input", {
        ref: (input) => (search = input),
        initializeComponent: (input) => input.attr.add({ type: "search", placeholder: "Notlarda ara" }),
      });
      const loading = motifComponent("p", {
        preconfig: (p) => p.bindings.display(() => state.loading),
        initializeComponent: (p) => {
          p.class.add("loading");
          p.controls.add(motifComponent("text", (node) => node.setText("Yükleniyor…")));
        },
      });
      const list = motifComponent("ul", (ul) => ul.controls.add(motifComponent({
        initializeComponent: (holder) => holder.bindings.list(() => notes.list("notes"), (note) => motifComponent("li", {
          indexkey: () => note.id,
          initializeComponent: (li) => li.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", note, "title"))),
        })),
      })));
      board.controls.add(field, loading, list);
    },
  });
}
import { FromService, motifComponent, motifOptions, reactive } from "@motifx/core";
import { NotesService } from "../services/notes";
import { SyncService } from "../services/sync";

export const NoteBoard = () => motifOptions({
  el: "section",
  data: reactive({ loading: true }),
  search: undefined,

  async onConfigured() {
    const notes = FromService(NotesService);
    const sync = FromService(SyncService);
    this.class.add("board");
    notes.state.items = await sync.pull();
    this.data.loading = false;
    const timer = setInterval(async () => (notes.state.items = await sync.pull()), 60_000);
    this.motif.setDisposable(() => clearInterval(timer));
  },

  onMounted() {
    this.search.element.focus();
  },

  view() {
    const notes = FromService(NotesService);
    const search = motifComponent("input", {
      ref: (field) => (this.search = field),
      initializeComponent: (field) => field.attr.add({ type: "search", placeholder: "Notlarda ara" }),
    });
    const loading = motifComponent("p", {
      preconfig: (p) => p.bindings.display(() => this.data.loading),
      initializeComponent: (p) => {
        p.class.add("loading");
        p.controls.add(motifComponent("text", (node) => node.setText("Yükleniyor…")));
      },
    });
    const list = motifComponent("ul", (ul) => ul.controls.add(motifComponent({
      initializeComponent: (holder) => holder.bindings.list(() => notes.list("notes"), (note) => motifComponent("li", {
        indexkey: () => note.id,
        initializeComponent: (li) => li.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", note, "title"))),
      })),
    })));
    return [search, loading, list];
  },
});

Her kanca (sender, e) imzasıyla çağrılır: sender bileşenin kendisi, e EventArgs ({ cancel: boolean }). Fonksiyon bileşeninde sender döndürülen köktür; fonksiyonla yazılan NoteBoard zamanlayıcıyı bu kökün motif.setDisposable'ına bağlar.

Bir kancada fırlatılan hata ya da kancanın döndürdüğü Promise'in reddedilmesi (async kancalar dahil) MotifError MJX122 olarak raporlanır: console.error'a yazılır (app.useLogging(false) değilse) ve errorHandler.addListener(fn) dinleyicilerine iletilir; geliştirme modu gerekmez, üretimde de çalışır. Bileşen kurulmaya devam eder. Bu kural initializeComponent, oninitializeComponent, Options API nesnesinin ctor'u, ref geri çağrısı (düz etikette ve sınıf bileşeni etiketinde; bileşen yine oluşturulur) ve kodla motif.on("x:built", fn) biçiminde eklenen yaşam döngüsü dinleyicileri (x:mounted dahil) için de geçerlidir.

Etikette kanca

Her etikete kanca prop olarak yazılabilir; kanca o etiketin bileşenine bağlanır. JSX'siz yazımda aynı adlar nesne biçiminin anahtarlarıdır. Örnekte düz bir etikete onMounted, bir bileşen etiketine (aşağıda anlatılan TitleSync) onDisposing verilir:

<input type="search" placeholder="Notlarda ara" onMounted={(field) => field.element.focus()} />
<TitleSync onDisposing={() => (document.title = "Notlar")} />
const search = motifComponent("input", {
  onMounted: (field: ComponentBase) => field.element.focus(),
  initializeComponent: (field: ComponentBase) => field.attr.add({ type: "search", placeholder: "Notlarda ara" }),
});
const title = motifComponent(TitleSync, { onDisposing: () => (document.title = "Notlar") });
<input type="search" placeholder="Notlarda ara" onMounted={(field) => field.element.focus()} />
<TitleSync onDisposing={() => (document.title = "Notlar")} />
const search = motifComponent("input", {
  onMounted: (field) => field.element.focus(),
  initializeComponent: (field) => field.attr.add({ type: "search", placeholder: "Notlarda ara" }),
});
const title = motifComponent(TitleSync, { onDisposing: () => (document.title = "Notlar") });

x- ve x: biçimleri de kabul edilir: <ul x-config={…} x:built={…}>. JSX'siz nesnede x- biçimi anahtar olarak çalışır ({ "x-config": … }); x: biçimini yalnız JSX tanır.

Önemli: Prop olarak verdiğiniz yaşam döngüsü işleyicileri, sınıf metotlarının yerini almaz; ikisi de çalışır (prop işleyicileri toplanır ve sınıf metoduna ek olarak çağrılır).

Aynı etikette bir kancanın birden çok yazımı (on<kanca>, x-<kanca>, x:<kanca>; büyük/küçük harf farkı dahil, örn. onBuilt ile onbuilt) birleşir: hepsi kaynaktaki sırayla birer kez çalışır. JSX'siz yazımda bir anahtara fonksiyon dizisi de verilebilir (onBuilt: [a, b]); dizideki sırayla çalışırlar. Bu, düz DOM etiketinde ve bileşen etiketinde aynıdır; fonksiyon bileşeni etiketinde kancalar, tek yazımla da birleşik yazımla da, fonksiyonun döndürdüğü köke uygulanır. initializing/initialized kancaları fonksiyon bileşeni etiketinde kök kurulur kurulmaz, sınıf bileşeni etiketindeki sırayla çalışır: ref → initializing → initialized → config → … → building → initializeComponent → built. Bu kural built, building, mounted, config, configured, initializing, initialized, disposing, disposed, visibilitychanged, activated, deactivated kancalarının hepsi için geçerlidir:

<ul onBuilt={() => console.log("önce")} x-built={() => console.log("sonra")} />
const list = motifComponent("ul", { onBuilt: [() => console.log("önce"), () => console.log("sonra")] });
<ul onBuilt={() => console.log("önce")} x-built={() => console.log("sonra")} />
const list = motifComponent("ul", { onBuilt: [() => console.log("önce"), () => console.log("sonra")] });

initializeComponent — bileşenin kurulum kodu

initializeComponent, view()'den hemen önce, bileşen başına bir kez çalışır. Derleyici JSX'ten ürettiği kurulum kodunu (etikete yazılan öznitelikler, olaylar, direktifler, çocukların yerleştirilmesi) bu adla verir; JSX'siz yazımda motifComponent(etiket, kurulum) çağrısının ikinci argümanı tam olarak budur. JSX kullanan bir bileşende bağ eklemek ya da çocukları programatik hazırlamak için de kullanılır. TitleSync sekme başlığını not sayısıyla eşler:

import { Component, ComponentBase } from "@motifx/core";
import { NotesService } from "../services/notes";

export class TitleSync extends Component<HTMLSpanElement> {
  notes = this.getService(NotesService)!;

  override initializeComponent(sender: ComponentBase) {
    sender.bindings.watch(() => (document.title = `${this.notes.count("notes")} not · Notlar`));
  }
}
import { FromService } from "@motifx/core";
import { NotesService } from "../services/notes";

export function TitleSync() {
  const notes = FromService(NotesService)!;
  return <span initializeComponent={(sender) => sender.bindings.watch(() => (document.title = `${notes.count("notes")} not · Notlar`))} />;
}
import { FromService, motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";

export const TitleSync = () => motifOptions({
  el: "span",

  initializeComponent() {
    const notes = FromService(NotesService)!;
    this.bindings.watch(() => (document.title = `${notes.count("notes")} not · Notlar`));
  },
});
import { Component, ComponentBase } from "@motifx/core";
import { NotesService } from "../services/notes";

export class TitleSync extends Component<HTMLSpanElement> {
  static override elementTag = "span";
  notes = this.getService(NotesService)!;

  override initializeComponent(sender: ComponentBase) {
    sender.bindings.watch(() => (document.title = `${this.notes.count("notes")} not · Notlar`));
  }
}
import { ComponentBase, FromService, motifComponent } from "@motifx/core";
import { NotesService } from "../services/notes";

export function TitleSync() {
  const notes = FromService(NotesService)!;
  return motifComponent("span", (sender: ComponentBase) => sender.bindings.watch(() => (document.title = `${notes.count("notes")} not · Notlar`)));
}
import { FromService, motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";

export const TitleSync = () => motifOptions({
  el: "span",

  initializeComponent() {
    const notes = FromService(NotesService)!;
    this.bindings.watch(() => (document.title = `${notes.count("notes")} not · Notlar`));
  },
});
import { Component } from "@motifx/core";
import { NotesService } from "../services/notes";

export class TitleSync extends Component {
  static elementTag = "span";
  notes = this.getService(NotesService);

  initializeComponent(sender) {
    sender.bindings.watch(() => (document.title = `${this.notes.count("notes")} not · Notlar`));
  }
}
import { FromService } from "@motifx/core";
import { NotesService } from "../services/notes";

export function TitleSync() {
  const notes = FromService(NotesService);
  return <span initializeComponent={(sender) => sender.bindings.watch(() => (document.title = `${notes.count("notes")} not · Notlar`))} />;
}
import { FromService, motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";

export const TitleSync = () => motifOptions({
  el: "span",

  initializeComponent() {
    const notes = FromService(NotesService);
    this.bindings.watch(() => (document.title = `${notes.count("notes")} not · Notlar`));
  },
});
import { Component } from "@motifx/core";
import { NotesService } from "../services/notes";

export class TitleSync extends Component {
  static elementTag = "span";
  notes = this.getService(NotesService);

  initializeComponent(sender) {
    sender.bindings.watch(() => (document.title = `${this.notes.count("notes")} not · Notlar`));
  }
}
import { FromService, motifComponent } from "@motifx/core";
import { NotesService } from "../services/notes";

export function TitleSync() {
  const notes = FromService(NotesService);
  return motifComponent("span", (sender) => sender.bindings.watch(() => (document.title = `${notes.count("notes")} not · Notlar`)));
}
import { FromService, motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";

export const TitleSync = () => motifOptions({
  el: "span",

  initializeComponent() {
    const notes = FromService(NotesService);
    this.bindings.watch(() => (document.title = `${notes.count("notes")} not · Notlar`));
  },
});

initializeComponent bir etikete prop olarak da yazılabilir; etiketin bileşeni sender olarak verilir ve kurulumda bir kez çağrılır. Düz DOM etiketinde, sınıf bileşeni etiketinde ve fonksiyon bileşeni etiketinde (fonksiyonun döndürdüğü kökte) anlamı ve zamanlaması aynıdır:

<p class="count" initializeComponent={(count) => count.bindings.text(() => `${notes.count("notes")} not`)} />
<TitleSync initializeComponent={(title) => title.motif.setDisposable(() => (document.title = "Notlar"))} />
const count = motifComponent("p", (count: ComponentBase) => {
  count.class.add("count");
  count.bindings.text(() => `${notes.count("notes")} not`);
});
const title = motifComponent(TitleSync, { initializeComponent: (title: ComponentBase) => title.motif.setDisposable(() => (document.title = "Notlar")) });
<p class="count" initializeComponent={(count) => count.bindings.text(() => `${notes.count("notes")} not`)} />
<TitleSync initializeComponent={(title) => title.motif.setDisposable(() => (document.title = "Notlar"))} />
const count = motifComponent("p", (count) => {
  count.class.add("count");
  count.bindings.text(() => `${notes.count("notes")} not`);
});
const title = motifComponent(TitleSync, { initializeComponent: (title) => title.motif.setDisposable(() => (document.title = "Notlar")) });

Kullanıcının verdiği ve derleyicinin ürettiği ikisi de çalışır; sıra: bileşenin initializeComponent metodu, etikette verilen, derleyicinin ürettiği.

oninitializeComponent — kurulum kodundan hemen sonra

JSX kullanan bir bileşende initializeComponent aşamasını derleyicinin kurulum kodu doldurur. oninitializeComponent, aynı aşamada bu kod bittikten hemen sonra çalışan kancadır. Öznitelikler, olaylar, bağlar ve çocuklar bu anda tanımlanmış olur; view() henüz çağrılmamıştır. Derleyicinin ürettiği kurulumun üzerine kendi kurulumunu eklemek isteyen kod için yeri burasıdır.

build() içindeki sıra:

  1. Sınıfın initializeComponent metodu.
  2. Etiketten ya da derleyiciden gelen initializeComponent değerleri.
  3. Sınıfın oninitializeComponent metodu.
  4. Etiketten gelen oninitializeComponent değerleri.
  5. view().

Sınıf metodu olarak (Options API nesnesinde aynı adlı üye de aynı anda çalışır):

import { Component, ComponentBase, EventArgs } from "@motifx/core";

export class TagList extends Component<HTMLUListElement> {
  oninitializeComponent(sender: ComponentBase, e: EventArgs) {
    sender.class.add(sender.controls.items.length > 5 ? "tags tags--many" : "tags");
  }
}
import { Component, ComponentBase, EventArgs } from "@motifx/core";

export class TagList extends Component<HTMLUListElement> {
  static override elementTag = "ul";

  oninitializeComponent(sender: ComponentBase, e: EventArgs) {
    sender.class.add(sender.controls.items.length > 5 ? "tags tags--many" : "tags");
  }
}
import { Component } from "@motifx/core";

export class TagList extends Component {
  static elementTag = "ul";

  oninitializeComponent(sender, e) {
    sender.class.add(sender.controls.items.length > 5 ? "tags tags--many" : "tags");
  }
}
import { Component } from "@motifx/core";

export class TagList extends Component {
  static elementTag = "ul";

  oninitializeComponent(sender, e) {
    sender.class.add(sender.controls.items.length > 5 ? "tags tags--many" : "tags");
  }
}

Etikette prop olarak (düz DOM etiketi, sınıf bileşeni etiketi ve fonksiyon bileşeni etiketi; fonksiyon bileşeninde fonksiyonun döndürdüğü köke uygulanır). Bu kanca çalıştığında etikete yazılan çocuklar controls içine eklenmiştir; JSX'siz yazımda nesne biçiminin anahtarıdır ve kurulum fonksiyonunun eklediği ya da childs ile verilen çocuklardan sonra çalışır:

<ul oninitializeComponent={(list) => list.class.add(list.controls.items.length > 5 ? "tags tags--many" : "tags")}>
  <li>iş</li>
  <li>ev</li>
</ul>
<TagList oninitializeComponent={(tags) => tags.attr.add({ "aria-label": `${tags.controls.items.length} etiket` })}>
  <li>iş</li>
  <li>ev</li>
</TagList>
const plain = motifComponent("ul", {
  oninitializeComponent: (list: ComponentBase) => list.class.add(list.controls.items.length > 5 ? "tags tags--many" : "tags"),
  initializeComponent: (list: ComponentBase) => {
    for (const tag of ["iş", "ev"]) {
      list.controls.add(motifComponent("li", (li: ComponentBase) => li.controls.add(motifComponent("text", (node: ComponentBase) => node.setText(tag)))));
    }
  },
});
const labelled = motifComponent(TagList, {
  oninitializeComponent: (tags: ComponentBase) => tags.attr.add({ "aria-label": `${tags.controls.items.length} etiket` }),
  childs: ["iş", "ev"].map((tag) => motifComponent("li", (li: ComponentBase) => li.controls.add(motifComponent("text", (node: ComponentBase) => node.setText(tag))))),
});
<ul oninitializeComponent={(list) => list.class.add(list.controls.items.length > 5 ? "tags tags--many" : "tags")}>
  <li>iş</li>
  <li>ev</li>
</ul>
<TagList oninitializeComponent={(tags) => tags.attr.add({ "aria-label": `${tags.controls.items.length} etiket` })}>
  <li>iş</li>
  <li>ev</li>
</TagList>
const plain = motifComponent("ul", {
  oninitializeComponent: (list) => list.class.add(list.controls.items.length > 5 ? "tags tags--many" : "tags"),
  initializeComponent: (list) => {
    for (const tag of ["iş", "ev"]) {
      list.controls.add(motifComponent("li", (li) => li.controls.add(motifComponent("text", (node) => node.setText(tag)))));
    }
  },
});
const labelled = motifComponent(TagList, {
  oninitializeComponent: (tags) => tags.attr.add({ "aria-label": `${tags.controls.items.length} etiket` }),
  childs: ["iş", "ev"].map((tag) => motifComponent("li", (li) => li.controls.add(motifComponent("text", (node) => node.setText(tag))))),
});

Görünürlük — motif.show / motif.hide / motif.toggle

Bir bileşeni serbest bırakmadan gizleyip gösterebilirsiniz:

await this.motif.hide();     // gizle (varsa çıkış animasyonu oynatılır)
await this.motif.show();     // göster (varsa giriş animasyonu)
this.motif.toggle();         // tersine çevir
this.isVisible;              // mevcut görünürlük
await panel.motif.hide();    // gizle (varsa çıkış animasyonu oynatılır)
await panel.motif.show();    // göster (varsa giriş animasyonu)
panel.motif.toggle();        // tersine çevir
panel.isVisible;             // mevcut görünürlük
await this.motif.hide();     // gizle (varsa çıkış animasyonu oynatılır)
await this.motif.show();     // göster (varsa giriş animasyonu)
this.motif.toggle();         // tersine çevir
this.isVisible;              // mevcut görünürlük
await panel.motif.hide();    // gizle (varsa çıkış animasyonu oynatılır)
await panel.motif.show();    // göster (varsa giriş animasyonu)
panel.motif.toggle();        // tersine çevir
panel.isVisible;             // mevcut görünürlük

Fonksiyon bileşeninde bu çağrılar bileşenin kendisi üzerindedir (örnekte panel): JSX'te kökün onConfig kancasına ya da ref'ine gelen bileşen, JSX'siz yazımda kökü kuran motifComponent fonksiyonunun aldığı bileşen.

Görünürlük değişince onVisibilityChanged(sender, e) çalışır; kanca isVisible güncellenmeden önce çağrılır. Yeni durum olay argümanındadır: e.visible gösterilirken true, gizlenirken false; isVisible o an önceki durumu verir. Aynı argüman onVisibilityChanged prop'una ve motif.on("x:visibilityChanged", …) dinleyicisine de gelir. Gizlenen bileşen ve alt ağacı onDeactivated, yeniden gösterilince onActivated alır. Bağlar gizliyken de çalışmayı sürdürür; bileşen gösterildiğinde güncel değerle görünür. Elemanı olmayan (fragment) bileşende de aynı sıra geçerlidir: çocukların elemanları kendi izleriyle (<!--h-->) yer değiştirir ve kendi çıkış/giriş geçişlerini oynatır, bildirim önce fragment'a sonra görünür çocuklara gider; çocukların kendi isVisible'ı değişmez, ayrıca gizlenmiş bir çocuk fragment gösterilince gizli kalır; fragment gizliyken eklenen çocuk (yeni, önceden kurulmuş ya da başka bir ebeveynden taşınmış) da gizli kalır; yeri controls içindeki sırasıyla bellidir ve fragment gösterilince o yere yerleşir.

Gizleme stratejisi — hideStrategy

Gizlenen bir bileşenin DOM'da nasıl ele alınacağını hideStrategy belirler:

Değer Davranış
'placeholder' Eleman DOM'dan çıkar, yerine <!--h--> yorum izi konur; gösterirken izle yer değiştirir. controls.move ve liste sıralaması izi bileşenle birlikte taşır.
'detach' DOM'dan iz bırakmadan çıkarılır; gösterilince ebeveyndeki sırasına göre yeniden eklenir.
'auto' (varsayılan) placeholder gibi davranır; .map satırları ve key verilmiş öğeler dahil her bileşen yerinde iz bırakır.

Strateji options ile verilir; JSX'te prop, JSX'siz yazımda nesne biçiminin anahtarıdır. Programatik olarak motif.options.hideStrategy alanına yazılır:

<p class="loading" options={{ hideStrategy: "detach" }} x-display={() => this.state.loading}>Yükleniyor…</p>
<p class="loading" options={{ hideStrategy: "detach" }} x-display={() => state.loading}>Yükleniyor…</p>
const loading = motifComponent("p", {
  options: { hideStrategy: "detach" },
  preconfig: (p: ComponentBase) => p.bindings.display(() => this.state.loading),
  initializeComponent: (p: ComponentBase) => {
    p.class.add("loading");
    p.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Yükleniyor…")));
  },
});
const loading = motifComponent("p", {
  options: { hideStrategy: "detach" },
  preconfig: (p: ComponentBase) => p.bindings.display(() => state.loading),
  initializeComponent: (p: ComponentBase) => {
    p.class.add("loading");
    p.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Yükleniyor…")));
  },
});
<p class="loading" options={{ hideStrategy: "detach" }} x-display={() => this.state.loading}>Yükleniyor…</p>
<p class="loading" options={{ hideStrategy: "detach" }} x-display={() => state.loading}>Yükleniyor…</p>
const loading = motifComponent("p", {
  options: { hideStrategy: "detach" },
  preconfig: (p) => p.bindings.display(() => this.state.loading),
  initializeComponent: (p) => {
    p.class.add("loading");
    p.controls.add(motifComponent("text", (node) => node.setText("Yükleniyor…")));
  },
});
const loading = motifComponent("p", {
  options: { hideStrategy: "detach" },
  preconfig: (p) => p.bindings.display(() => state.loading),
  initializeComponent: (p) => {
    p.class.add("loading");
    p.controls.add(motifComponent("text", (node) => node.setText("Yükleniyor…")));
  },
});
this.motif.options.hideStrategy = "detach";
panel.motif.options.hideStrategy = "detach";

Fonksiyon bileşeninde state bileşenin reactive ile kurduğu yerel durumdur.

options bileşenin kendi ayar nesnesidir; çerçevenin tanıdığı anahtarlar okunur: hideStrategy ve disableDisposal motif.options'a kopyalanır, isSvg elemanın ad alanını belirler (bkz. Kök eleman); diğer anahtarlar yok sayılır. Bu adı veri prop'u olarak kullanmayın. Fonksiyon bileşeni etiketine yazılan options döndürülen köke uygulanır.

Bekletme — isWait / x-wait

x-wait direktifi bir bileşenin DOM'a eklenmesini bir koşula bağlar; programatik karşılığı isWait özelliğidir (isWait bir JSX prop'u değildir). Koşul true iken bileşen bekletilir (gizli/eklenmemiş), false olunca eklenir. Koşullu gösterimde birincil yöntem budur — bkz. Koşullu Gösterim ve Listeler. Örnekte bileşen etiketi x-wait koşulu true olduğu sürece kurulmaz:

x-chunk ile dilimlenen bir çocuk da sırası gelene kadar isWait ile bekler ve izini bırakır; sırası geldiğinde çerçeve bekletmeyi kaldırır. Siz daha önce isWait = false yazarsanız çocuk hemen kurulur ve dilim sırasından çıkar — bkz. Büyük listeler.

<TitleSync x-wait={() => this.state.loading} />
<TitleSync x-wait={() => state.loading} />
const title = motifComponent(TitleSync, { preconfig: (title: ComponentBase) => title.bindings.wait(() => this.state.loading) });
const title = motifComponent(TitleSync, { preconfig: (title: ComponentBase) => title.bindings.wait(() => state.loading) });
<TitleSync x-wait={() => this.state.loading} />
<TitleSync x-wait={() => state.loading} />
const title = motifComponent(TitleSync, { preconfig: (title) => title.bindings.wait(() => this.state.loading) });
const title = motifComponent(TitleSync, { preconfig: (title) => title.bindings.wait(() => state.loading) });

Programatik:

this.isWait = true;   // beklet
this.isWait = false;  // devam et (gerekirse DOM'a eklenir)
panel.isWait = true;   // beklet
panel.isWait = false;  // devam et (gerekirse DOM'a eklenir)
this.isWait = true;   // beklet
this.isWait = false;  // devam et (gerekirse DOM'a eklenir)
panel.isWait = true;   // beklet
panel.isWait = false;  // devam et (gerekirse DOM'a eklenir)

Yaşam döngüsü açısından önemli iki nokta:

Serbest bırakma (dispose)

Gerekmeyen bir bileşen serbest bırakılır. Bu; olay dinleyicilerini kaldırır, bağları deaktive eder, çocukları özyinelemeli serbest bırakır ve DOM'dan söker:

await this.dispose();                               // varsayılan: { deep: true } + çıkış animasyonu
await this.dispose({ skipLeaveTransition: true });  // animasyonsuz
await this.dispose({ deep: false });                // çocuklarda başvuru temizliğini atla
await this.disposeAsync();                          // çocukların serbest bırakılması: çıkış geçişi oynatılmaz, süren animasyonlar durdurulur
await panel.dispose();                              // varsayılan: { deep: true } + çıkış animasyonu
await panel.dispose({ skipLeaveTransition: true }); // animasyonsuz
await panel.dispose({ deep: false });               // çocuklarda başvuru temizliğini atla
await panel.disposeAsync();                         // çocukların serbest bırakılması: çıkış geçişi oynatılmaz, süren animasyonlar durdurulur
await this.dispose();                               // varsayılan: { deep: true } + çıkış animasyonu
await this.dispose({ skipLeaveTransition: true });  // animasyonsuz
await this.dispose({ deep: false });                // çocuklarda başvuru temizliğini atla
await this.disposeAsync();                          // çocukların serbest bırakılması: çıkış geçişi oynatılmaz, süren animasyonlar durdurulur
await panel.dispose();                              // varsayılan: { deep: true } + çıkış animasyonu
await panel.dispose({ skipLeaveTransition: true }); // animasyonsuz
await panel.dispose({ deep: false });               // çocuklarda başvuru temizliğini atla
await panel.disposeAsync();                         // çocukların serbest bırakılması: çıkış geçişi oynatılmaz, süren animasyonlar durdurulur

deep (varsayılan true) çocuk bileşenlerde başvuru temizliğini (element, props, childs, olay haritaları) de yapar; kök bileşen her zaman temizlenir. disposeAsync, çerçevenin bir ebeveyn serbest bırakılırken çocuklar için kullandığı yoldur; dışarıdan çağrılırsa bileşen çıkış geçişi beklemeden söner. Süren bir serbest bırakma varken ikinci dispose/disposeAsync çağrısı yenisini başlatmaz, aynı Promise'i döndürür.

controls.remove(child) veya controls.clear() da ilgili çocukları serbest bırakır. JSX ile ya da motifComponent ile kurduğunuz her şey (dinleyiciler, bağlar, çocuklar, context.on abonelikleri) kendiliğinden temizlenir; zamanlayıcı, gözlemci, soket gibi dış kaynakları motif.setDisposable ile kaydedersiniz (NoteBoard'daki setInterval gibi). Ayrıntı için Bellek Yönetimi ve Serbest Bırakma.

Uygulama yaşam döngüsü — app.onLifecycle

Bileşen kancalarından ayrı olarak, uygulamanın bütünü için de bir yaşam döngüsü vardır: sekme arka plana alınır, mobil işletim sistemi sayfayı dondurur, bağlantı kopar. Yoklama, websocket ve zamanlayıcıları duraklatıp sürdürmek, dönüşte bayat veriyi yenilemek için:

const off = app.onLifecycle(async ({ state }) => {
  switch (state) {
    case "hidden":   poller.pause(); break;           // sekme/uygulama arka planda
    case "visible":  poller.resume(); break;
    case "frozen":   socket.close(); break;           // tarayıcı sayfayı dondurdu
    case "resumed":  socket.open(); break;
    case "restored": notes.state.items = await sync.pull(); break;  // geri/ileri önbelleğinden (bfcache) döndü
    case "offline":  offlineBanner.motif.show(); break;
    case "online":   offlineBanner.motif.hide(); break;
  }
});
off(); // abonelikten çık
const off = app.onLifecycle(async ({ state }) => {
  switch (state) {
    case "hidden":   poller.pause(); break;           // sekme/uygulama arka planda
    case "visible":  poller.resume(); break;
    case "frozen":   socket.close(); break;           // tarayıcı sayfayı dondurdu
    case "resumed":  socket.open(); break;
    case "restored": notes.state.items = await sync.pull(); break;  // geri/ileri önbelleğinden (bfcache) döndü
    case "offline":  offlineBanner.motif.show(); break;
    case "online":   offlineBanner.motif.hide(); break;
  }
});
off(); // abonelikten çık

app.isVisible ve app.isOnline anlık durumu verir. Tarayıcı dinleyicileri yalnızca ilk abonelikte kurulur (kullanılmıyorsa maliyeti sıfırdır) ve app.dispose() ile kaldırılır. Abonelik bir bileşenin ömrüne bağlı değildir; bileşen içinden açıyorsanız off'u bileşenin motif.setDisposable(off)'u ile kaydedin (sınıfta ve Options'ta this.motif, fonksiyon bileşeninde kök bileşenin motif'i).

Sonraki adım

Stil ve animasyon sistemini öğrenmek için Stil ve Animasyon bölümüne geçin.