From 4148d162e47a775784c2eb6d45f11045015790ec Mon Sep 17 00:00:00 2001 From: xion Date: Sat, 30 Nov 2024 02:21:33 +0800 Subject: [PATCH] fix: add page perfect --- demo/index.html | 16 ++++++ demo/package.json | 16 ++++++ demo/src/index.ts | 17 ++++++ package.json | 2 +- src/page.ts | 134 +++++++++++++++++++++++++++++++++++++++++++--- src/store.ts | 1 + 6 files changed, 177 insertions(+), 9 deletions(-) create mode 100644 demo/index.html create mode 100644 demo/package.json create mode 100644 demo/src/index.ts diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..ceb31fe --- /dev/null +++ b/demo/index.html @@ -0,0 +1,16 @@ + + + + + + + Demo Page + + + +

Welcome to the Demo Page

+

This is a basic HTML template.

+ + + + \ No newline at end of file diff --git a/demo/package.json b/demo/package.json new file mode 100644 index 0000000..195ee1e --- /dev/null +++ b/demo/package.json @@ -0,0 +1,16 @@ +{ + "name": "demo", + "version": "0.0.1", + "description": "", + "main": "index.js", + "scripts": { + "dev": "vite" + }, + "keywords": [], + "author": "abearxiong ", + "license": "MIT", + "type": "module", + "dependencies": { + "@kevisual/store": "link:.." + } +} \ No newline at end of file diff --git a/demo/src/index.ts b/demo/src/index.ts new file mode 100644 index 0000000..4f38a5e --- /dev/null +++ b/demo/src/index.ts @@ -0,0 +1,17 @@ +console.log('index.js'); +import { Page } from '@kevisual/store/page'; + +const page = new Page({ + isListen: true, +}); + +page.addPage('/:id', 'user'); + +page.subscribe('user', (params, state) => { + console.log('user', params, 'state', state); + return; +}); + +// page.navigate('/c', { id: 3 }); +// page.navigate('/c', { id: 2 }); +// page.refresh(); diff --git a/package.json b/package.json index 0b4e61b..3d818d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kevisual/store", - "version": "0.0.1-alpha.3", + "version": "0.0.1-alpha.4", "main": "dist/store.js", "module": "dist/store.js", "types": "dist/store.d.ts", diff --git a/src/page.ts b/src/page.ts index bcb299f..55c5879 100644 --- a/src/page.ts +++ b/src/page.ts @@ -1,10 +1,12 @@ import { getPathKey } from '@/utils/path-key.ts'; import { match } from 'path-to-regexp'; +import deepEqual from 'fast-deep-equal'; type PageOptions = { path?: string; key?: string; basename?: string; + isListen?: boolean; }; type PageModule = { // 模块的key,如 user,定义模块的唯一标识 @@ -12,31 +14,80 @@ type PageModule = { // 模块的path路径,如 /user/:id,match的时候会用到 path: string; }; +type CallFn = (params: Record, state?: any, e?: any) => any; +type CallbackInfo = { + fn: CallFn; + id: string; +} & PageModule; export class Page { pageModule = new Map(); + // pathname的第一个路径 path: string; + // pathname的第二个路径 key: string; basename: string; + isListen: boolean; + callbacks = [] as CallbackInfo[]; constructor(opts?: PageOptions) { const pathKey = getPathKey(); this.path = opts?.path ?? pathKey.path; this.key = opts?.key ?? pathKey.key; this.basename = opts?.basename ?? `/${this.path}/${this.key}`; + const isListen = opts?.isListen ?? true; + if (isListen) { + this.listen(); + } } + popstate(event?: PopStateEvent, manual?: boolean) { + const pathname = window.location.pathname; + // manual 为true时,表示手动调用,不需要检查是否相等 + this.callbacks.forEach((item) => { + const result = this.check(item.key, pathname); + result && item.fn?.(result.params, event.state, { event, manual }); + }); + } + listen() { + this.isListen = true; + window.addEventListener('popstate', this.popstate.bind(this), false); + } + unlisten() { + this.isListen = false; + window.removeEventListener('popstate', this.popstate.bind(this), false); + } + /** + * + * @param path 路径 + * @param key + */ addPage(path: string, key?: string) { this.pageModule.set(key, { path, key }); } - check(key: string, pathname?: string): boolean | Record { - if (!key) { - console.error('key is required'); - return; - } + addObjectPages(pages: Record) { + Object.keys(pages).forEach((key) => { + this.addPage(pages[key], key); + }); + } + addPages(pages: { path: string; key?: string }[]) { + pages.forEach((item) => { + this.addPage(item.path, item.key); + }); + } + /** + * 检查当前路径是否匹配 + * @param key + * @param pathname + * @returns + */ + check(key: string, pathname?: string): false | { pathname: string; params: Record } { const has = this.pageModule.has(key); if (!has) { console.error(`PageModule ${key} not found`); return; } const pageModule = this.pageModule.get(key); + if (!pageModule) { + return false; + } const location = window.location; const _pathname = pathname || location.pathname; const cur = _pathname.replace(this.basename, ''); @@ -46,23 +97,90 @@ export class Page { if (result) { result.path; params = result.params; - return params; + return { pathname: _pathname, params }; } return false; } /** - * - * @returns 返回当前路径 + * 订阅path监听事件 + * @param key + * @param fn + * @param opts + * @returns + */ + subscribe(key: string, fn?: CallFn, opts?: { pathname?: string; runImmediately?: boolean; id?: string }) { + const runImmediately = opts?.runImmediately ?? true; // 默认立即执行 + const id = opts?.id ?? Math.random().toString(36).slice(2); + const path = this.pageModule.get(key)?.path; + if (!path) { + console.error(`PageModule ${key} not found`); + return () => {}; + } + this.callbacks.push({ key, fn, id: id, path }); + if (runImmediately) { + this.popstate({ state: window.history.state } as any, true); + } + return () => { + this.callbacks = this.callbacks.filter((item) => item.id !== id); + }; + } + /** + * 返回当前路径,不包含basename + * @returns */ getPath() { const location = window.location; const pathname = location.pathname; return pathname.replace(this.basename, ''); } + getAppPath() { + return this.path; + } + getAppKey() { + return this.key; + } decodePath(path: string) { return decodeURIComponent(path); } encodePath(path: string) { return encodeURIComponent(path); } + /** + * 如果state 和 pathname都相等,则不执行popstate + * @param path + * @param state + * @param check + * @returns + */ + navigate(path: string | number, state?: any, check?: boolean) { + if (typeof path === 'number') { + window.history.go(path); + return; + } + const location = window.location; + const pathname = location.pathname; + + const newPathname = new URL(this.basename + path, location.href).pathname; + if (pathname === newPathname) { + if (deepEqual(state, window.history.state)) { + return; + } + } + window.history.pushState(state, '', this.basename + path); + let _check = check ?? true; + if (_check) { + this.popstate({ state } as any, true); + } + } + replace(path: string, state?: any, check?: boolean) { + let _check = check ?? true; + window.history.replaceState(state, '', this.basename + path); + if (_check) { + this.popstate({ state } as any, true); + } + } + refresh() { + const state = window.history.state; + this.popstate({ state } as any, true); + } } diff --git a/src/store.ts b/src/store.ts index c6f52b5..45261f8 100644 --- a/src/store.ts +++ b/src/store.ts @@ -62,4 +62,5 @@ export class StoreManager { export const store = new StoreManager(); + export { shallow, deepEqual };