Skip to content

URL에 저장되는 상태

드래그·휠로 보는 구간을 바꾸면 주소창의 쿼리(?view=…)가 따라온다. 새로고침해도 그대로다 — getState/applyState/serializeChartState/parseChartState 네 계약 위다. (갤러리 자체가 해시로 라우팅하므로 여기서는 해시가 아니라 쿼리에 저장한다 — 상태의 자리만 다르고 계약은 README의 예제와 같다.)

소스

apps/examples/src/cases/state-url.ts — CI가 타입체크하는 실물이다.

ts
import { PlotBuilder, browserDeps } from "@finchart/dom";
import type { OHLC } from "@finchart/core";
import { candleSeries, parseChartState, priceFormat, serializeChartState, timeTicks } from "@finchart/core";
import { fixtureCandles } from "./fixture";
import { chartHost } from "./stage";

export const title = "URL에 저장되는 상태";
export const description =
  "드래그·휠로 보는 구간을 바꾸면 주소창의 쿼리(?view=…)가 따라온다. 새로고침해도 그대로다 — getState/applyState/serializeChartState/parseChartState 네 계약 위다. " +
  "(갤러리 자체가 해시로 라우팅하므로 여기서는 해시가 아니라 쿼리에 저장한다 — 상태의 자리만 다르고 계약은 README의 예제와 같다.)";

const PARAM = "view";

function readFromUrl(): string | null {
  return new URLSearchParams(location.search).get(PARAM);
}

function writeToUrl(serialized: string): void {
  const url = new URL(location.href);
  url.searchParams.set(PARAM, serialized);
  history.replaceState(null, "", url);
}

export function mount(container: HTMLElement): () => void {
  const status = document.createElement("p");
  status.style.cssText = "font-size: 13px; color: #64748b; margin: 0 0 8px";
  container.append(status);

  const host = chartHost(container, 420);
  const plot = PlotBuilder.create<OHLC>(browserDeps({ autoSize: true }))
    .setSize(container.clientWidth || 900, 420)
    .setAxis({
      x: { ticks: timeTicks({ timeZone: "UTC", locale: "ko" }) },
      y: { position: "right", format: priceFormat({ compact: true, locale: "ko" }) },
    })
    .build(host);

  plot.mainPane.addSeries({
    series: candleSeries(),
    data: fixtureCandles(),
    name: "가격",
  });

  // 데이터보다 먼저 와도 되는 것이 applyState의 계약이다 — 여기서는
  // addSeries 뒤에 부르지만 순서를 요구하지 않는다는 걸 보여주려는
  // 것이지 순서가 중요해서가 아니다.
  const saved = readFromUrl();
  const restored = saved ? parseChartState(saved) : null;
  if (restored) plot.applyState(restored);
  status.textContent = restored
    ? "URL의 상태를 복원했다 — 새로고침해도 유지된다."
    : "드래그하거나 휠로 확대해 보라 — URL이 따라온다.";

  const unsubscribe = plot.on("stateChange", (state) => {
    writeToUrl(serializeChartState(state));
    status.textContent = "URL의 상태를 복원했다 — 새로고침해도 유지된다.";
  });

  return Object.assign(
    () => {
      unsubscribe();
      plot.destroy();
      host.remove();
      status.remove();
      // 케이스를 떠나면 쿼리도 치운다 — 다음 케이스 진입에 유령 상태를
      // 안 남긴다(해시 라우팅과 같은 결의 청소).
      const url = new URL(location.href);
      url.searchParams.delete(PARAM);
      history.replaceState(null, "", url);
    },
    { requestRender: () => plot.requestRender() },
  );
}