From 40d27cb9e411711e55aa4cdc65549d36ea150346 Mon Sep 17 00:00:00 2001 From: veypi Date: Thu, 24 Oct 2024 11:30:40 +0800 Subject: [PATCH] feat: v2dom attr bind,event delete --- oaer/lib/cfg.ts | 1 + oaer/lib/components/account.ts | 16 ++++++--- oaer/lib/components/app.ts | 15 +++++--- oaer/lib/components/proxy.ts | 42 +++++++++++++++++++---- oaer/lib/components/slide.ts | 2 +- oaer/lib/components/v2dom.ts | 62 +++++++++++++++++++++++++++++----- 6 files changed, 112 insertions(+), 26 deletions(-) diff --git a/oaer/lib/cfg.ts b/oaer/lib/cfg.ts index 4b95d67..2db1939 100644 --- a/oaer/lib/cfg.ts +++ b/oaer/lib/cfg.ts @@ -18,6 +18,7 @@ let cfg = proxy.Watch({ nickname: '', email: '', phone: '', + id: 1, icon: 'https://public.veypi.com/img/avatar/0001.jpg' }, myapps: [ diff --git a/oaer/lib/components/account.ts b/oaer/lib/components/account.ts index 8d0a48a..f9c88c8 100644 --- a/oaer/lib/components/account.ts +++ b/oaer/lib/components/account.ts @@ -10,12 +10,18 @@ import cfg from '../cfg' export default v('voa-account', [ v('voa-account-header', [v('voa-ah-1', '我的账户'), v('voa-ah-2')]), v('voa-account-body', [ - v('voa-ab-ico', [v('img', '', (d) => { d.setAttribute('src', cfg.user.icon) })]), + // v('voa-ab-ico', [v('img', '', (d) => { d.setAttribute('src', cfg.user.icon) })]), + v('voa-ab-ico', [v({ typ: 'img', attrs: { 'src': () => `https://public.veypi.com/img/avatar/000${cfg.user.id}.jpg`, test: () => cfg.user.id } })]), v('voa-ab-info', [ - v('voa-abi-1', [v('span', '昵称:'), v('span', '', (d) => { d.innerHTML = cfg.user.nickname })]), - v('voa-abi-2', [v('span', '账户:'), v('span', '', (d) => { d.innerHTML = cfg.user.username })]), - v('voa-abi-3', [v('span', '邮箱:'), v('span', '', (d) => { d.innerHTML = cfg.user.email })]), - v('voa-abi-4', [v('span', '手机:'), v('span', '', (d) => { d.innerHTML = cfg.user.phone })]), + v('voa-abi-1', [v('span', '昵称:'), v('span', () => cfg.user.nickname)]), + v('voa-abi-2', [v('span', '账户:'), v('span', () => cfg.user.username)]), + v('voa-abi-3', [v('span', '邮箱:'), v('span', () => cfg.user.email)]), + v('voa-abi-4', [v('span', '手机:'), v('span', () => cfg.user.phone)]), + v({ + typ: 'input', + attrs: { 'type': 'number' }, + vbind: [cfg.user, 'id'] + }) ]) ]) ]) diff --git a/oaer/lib/components/app.ts b/oaer/lib/components/app.ts index f254446..5fec02d 100644 --- a/oaer/lib/components/app.ts +++ b/oaer/lib/components/app.ts @@ -9,16 +9,23 @@ import cfg from "../cfg"; import v from "./v2dom"; -export default v({ +export default () => v({ class: 'voa-apps', children: [ - v({ class: 'voa-apps-header', children: [v('voa-apps-title', '我的应用')] }), + v('voa-apps-header', [v('voa-apps-title', '我的应用')]), v('voa-apps-body', [ [cfg.myapps, - (data) => v('voa-app-box', '', (d) => d.innerHTML = data.name)], + (data) => v('voa-app-box', [v('span', () => data.name)])], v('div', '222'), [cfg.myapps, - (data) => v('voa-app-box', '', (d) => d.innerHTML = data.name)], + (data) => v('voa-app-box', () => data.name)], + v({ + typ: 'div', innerHtml: 'add', vbind: [cfg.myapps[0], 'name'], + onclick: () => { + cfg.myapps.splice(0, 1) + cfg.myapps.push({ name: new Date().toLocaleString() }) + } + }) ]) ] }) diff --git a/oaer/lib/components/proxy.ts b/oaer/lib/components/proxy.ts index 71a2250..87cac77 100644 --- a/oaer/lib/components/proxy.ts +++ b/oaer/lib/components/proxy.ts @@ -6,16 +6,40 @@ */ type voidFn = () => void -// TODO: 没有删除机制 -const callbackCache: voidFn[] = [] +const callbackCache: (voidFn | undefined)[] = [] const cacheUpdateList: number[] = [] -// 界面响应频率40hz +// 界面更新响应频率40hz setInterval(() => { let list = new Set(cacheUpdateList.splice(0)) for (let l of list) { - callbackCache[l]() + if (callbackCache[l]) { + callbackCache[l]() + } } }, 25) +// 绑定事件删除 +setInterval(() => { + let exists = new Set() + let check = (dom: Element) => { + let ids = dom.getAttribute('vbind-fns')?.split(',') + if (ids?.length) { + for (let i of ids) { + exists.add(i) + } + } + for (let child of dom.children) { + check(child) + } + } + check(document.body) + for (let i in callbackCache) { + if (!exists.has(i)) { + // 只删除元素,保留位置 + delete callbackCache[i] + // console.log('remove ' + i) + } + } +}, 1000) function generateUniqueId() { const timestamp = performance.now().toString(36); @@ -25,16 +49,20 @@ function generateUniqueId() { function ForceUpdate() { for (let c of callbackCache) { - c() + if (c) { + c() + } } } var listen_tags: number[] = [] -function Listen(callback: voidFn) { - listen_tags.push(callbackCache.length) +function Listen(callback: voidFn): number { + let idx = callbackCache.length + listen_tags.push(idx) callbackCache.push(callback) callback() listen_tags.pop() + return idx } const isProxy = Symbol("isProxy") diff --git a/oaer/lib/components/slide.ts b/oaer/lib/components/slide.ts index acbbe14..dc3c022 100644 --- a/oaer/lib/components/slide.ts +++ b/oaer/lib/components/slide.ts @@ -41,7 +41,7 @@ export default class { children: [ account, v({ class: 'voa-sm-separate' }), - app, + app(), v({ class: 'voa-sm-separate' }) ] }) diff --git a/oaer/lib/components/v2dom.ts b/oaer/lib/components/v2dom.ts index 8a40589..3a16bcd 100644 --- a/oaer/lib/components/v2dom.ts +++ b/oaer/lib/components/v2dom.ts @@ -12,18 +12,22 @@ interface buildOpts { id?: string typ?: string class?: string + attrs?: attrs style?: string innerHtml?: string onclick?: any children?: childTyp[] updator?: (p: HTMLElement) => void + vbind?: [Object, string] } -const typs = ['div', 'img', 'span', 'p', 'a'] +const typs = ['div', 'img', 'span', 'p', 'a', 'input'] type iterChild = [T[], (d: T) => HTMLElement] type childTyp = HTMLElement | iterChild -export default (opts: buildOpts | string, inner?: string | childTyp[], updator?: (p: HTMLElement) => void) => { +type computedFn = () => any +type attrs = { [key: string]: string | number | computedFn } +export default (opts: buildOpts | string, inner?: string | computedFn | childTyp[], attrs?: attrs) => { if (typeof opts === 'string') { if (typs.indexOf(opts) >= 0) { opts = { typ: opts } @@ -31,17 +35,27 @@ export default (opts: buildOpts | string, inner?: string | childTyp[], opts = { class: opts } } } + let dom = document.createElement(opts.typ || 'div') if (inner) { if (typeof inner == 'string') { opts.innerHtml = inner + } else if (typeof inner == 'function') { + addListener(dom, () => { + let res = inner() + if (typeof res === 'string') { + dom.innerHTML = res + } else { + dom.innerHTML = '' + dom.appendChild(inner()) + } + }) } else { opts.children = inner } } - if (opts.updator) { - updator = opts.updator + if (attrs) { + opts.attrs = attrs } - let dom = document.createElement(opts.typ || 'div') if (opts.id) { dom.id = opts.id } @@ -51,6 +65,18 @@ export default (opts: buildOpts | string, inner?: string | childTyp[], if (opts.innerHtml) { dom.innerHTML = opts.innerHtml } + if (opts.attrs) { + for (let a in opts.attrs) { + let attr = opts.attrs[a] + if (typeof attr === 'function') { + addListener(dom, () => { + dom.setAttribute(a, attr()) + }) + } else { + dom.setAttribute(a, String(attr)) + } + } + } if (opts.onclick) { dom.onclick = opts.onclick } @@ -60,7 +86,7 @@ export default (opts: buildOpts | string, inner?: string | childTyp[], if (Object.prototype.toString.call(c) === '[object Array]') { const iterID = proxy.generateUniqueId() const iterLast = tmpID - proxy.Listen(() => { + addListener(dom, () => { c = c as iterChild let itemIDs: string[] = [] for (let i = 0; i < c[0].length; i++) { @@ -144,11 +170,29 @@ export default (opts: buildOpts | string, inner?: string | childTyp[], } } dom.setAttribute('voa', '1') - if (updator) { - proxy.Listen(() => { - updator(dom) + if (opts.updator) { + addListener(dom, () => { + opts.updator!(dom) + }) + } + if (opts.vbind && opts.typ === 'input') { + dom.setAttribute('value', Reflect.get(opts.vbind[0], opts.vbind[1])) + dom.addEventListener('input', (e) => { + // @ts-ignore + Reflect.set(opts.vbind[0], opts.vbind[1], e.target.value) }) } return dom } + +function addListener(dom: HTMLElement, fn: () => void) { + let fns = dom.getAttribute('vbind-fns') + let fnid = proxy.Listen(fn) + if (fns) { + fns += ',' + fnid + } else { + fns = String(fnid) + } + dom.setAttribute('vbind-fns', fns) +}