feat: v2dom attr bind,event delete

v3
veypi 1 month ago
parent 9f789b78e7
commit 40d27cb9e4

@ -18,6 +18,7 @@ let cfg = proxy.Watch({
nickname: '', nickname: '',
email: '', email: '',
phone: '', phone: '',
id: 1,
icon: 'https://public.veypi.com/img/avatar/0001.jpg' icon: 'https://public.veypi.com/img/avatar/0001.jpg'
}, },
myapps: [ myapps: [

@ -10,12 +10,18 @@ import cfg from '../cfg'
export default v('voa-account', [ export default v('voa-account', [
v('voa-account-header', [v('voa-ah-1', '我的账户'), v('voa-ah-2')]), v('voa-account-header', [v('voa-ah-1', '我的账户'), v('voa-ah-2')]),
v('voa-account-body', [ 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-ab-info', [
v('voa-abi-1', [v('span', '昵称:'), v('span', '', (d) => { d.innerHTML = cfg.user.nickname })]), v('voa-abi-1', [v('span', '昵称:'), v('span', () => cfg.user.nickname)]),
v('voa-abi-2', [v('span', '账户:'), v('span', '', (d) => { d.innerHTML = cfg.user.username })]), v('voa-abi-2', [v('span', '账户:'), v('span', () => cfg.user.username)]),
v('voa-abi-3', [v('span', '邮箱:'), v('span', '', (d) => { d.innerHTML = cfg.user.email })]), v('voa-abi-3', [v('span', '邮箱:'), v('span', () => cfg.user.email)]),
v('voa-abi-4', [v('span', '手机:'), v('span', '', (d) => { d.innerHTML = cfg.user.phone })]), v('voa-abi-4', [v('span', '手机:'), v('span', () => cfg.user.phone)]),
v({
typ: 'input',
attrs: { 'type': 'number' },
vbind: [cfg.user, 'id']
})
]) ])
]) ])
]) ])

@ -9,16 +9,23 @@ import cfg from "../cfg";
import v from "./v2dom"; import v from "./v2dom";
export default v({ export default () => v({
class: 'voa-apps', class: 'voa-apps',
children: [ children: [
v({ class: 'voa-apps-header', children: [v('voa-apps-title', '我的应用')] }), v('voa-apps-header', [v('voa-apps-title', '我的应用')]),
v('voa-apps-body', [ v('voa-apps-body', [
[cfg.myapps, [cfg.myapps,
(data) => v('voa-app-box', '', (d) => d.innerHTML = data.name)], (data) => v('voa-app-box', [v('span', () => data.name)])],
v('div', '222'), v('div', '222'),
[cfg.myapps, [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() })
}
})
]) ])
] ]
}) })

@ -6,16 +6,40 @@
*/ */
type voidFn = () => void type voidFn = () => void
// TODO: 没有删除机制 const callbackCache: (voidFn | undefined)[] = []
const callbackCache: voidFn[] = []
const cacheUpdateList: number[] = [] const cacheUpdateList: number[] = []
// 界面响应频率40hz // 界面更新响应频率40hz
setInterval(() => { setInterval(() => {
let list = new Set(cacheUpdateList.splice(0)) let list = new Set(cacheUpdateList.splice(0))
for (let l of list) { for (let l of list) {
if (callbackCache[l]) {
callbackCache[l]() callbackCache[l]()
} }
}
}, 25) }, 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() { function generateUniqueId() {
const timestamp = performance.now().toString(36); const timestamp = performance.now().toString(36);
@ -25,16 +49,20 @@ function generateUniqueId() {
function ForceUpdate() { function ForceUpdate() {
for (let c of callbackCache) { for (let c of callbackCache) {
if (c) {
c() c()
} }
} }
}
var listen_tags: number[] = [] var listen_tags: number[] = []
function Listen(callback: voidFn) { function Listen(callback: voidFn): number {
listen_tags.push(callbackCache.length) let idx = callbackCache.length
listen_tags.push(idx)
callbackCache.push(callback) callbackCache.push(callback)
callback() callback()
listen_tags.pop() listen_tags.pop()
return idx
} }
const isProxy = Symbol("isProxy") const isProxy = Symbol("isProxy")

@ -41,7 +41,7 @@ export default class {
children: [ children: [
account, account,
v({ class: 'voa-sm-separate' }), v({ class: 'voa-sm-separate' }),
app, app(),
v({ class: 'voa-sm-separate' }) v({ class: 'voa-sm-separate' })
] ]
}) })

@ -12,18 +12,22 @@ interface buildOpts<T> {
id?: string id?: string
typ?: string typ?: string
class?: string class?: string
attrs?: attrs
style?: string style?: string
innerHtml?: string innerHtml?: string
onclick?: any onclick?: any
children?: childTyp<T>[] children?: childTyp<T>[]
updator?: (p: HTMLElement) => void 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> = [T[], (d: T) => HTMLElement] type iterChild<T> = [T[], (d: T) => HTMLElement]
type childTyp<T> = HTMLElement | iterChild<T> type childTyp<T> = HTMLElement | iterChild<T>
export default <T>(opts: buildOpts<T> | string, inner?: string | childTyp<T>[], updator?: (p: HTMLElement) => void) => { type computedFn = () => any
type attrs = { [key: string]: string | number | computedFn }
export default <T>(opts: buildOpts<T> | string, inner?: string | computedFn | childTyp<T>[], attrs?: attrs) => {
if (typeof opts === 'string') { if (typeof opts === 'string') {
if (typs.indexOf(opts) >= 0) { if (typs.indexOf(opts) >= 0) {
opts = { typ: opts } opts = { typ: opts }
@ -31,17 +35,27 @@ export default <T>(opts: buildOpts<T> | string, inner?: string | childTyp<T>[],
opts = { class: opts } opts = { class: opts }
} }
} }
let dom = document.createElement(opts.typ || 'div')
if (inner) { if (inner) {
if (typeof inner == 'string') { if (typeof inner == 'string') {
opts.innerHtml = inner 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 { } else {
opts.children = inner opts.children = inner
} }
} }
if (opts.updator) { if (attrs) {
updator = opts.updator opts.attrs = attrs
} }
let dom = document.createElement(opts.typ || 'div')
if (opts.id) { if (opts.id) {
dom.id = opts.id dom.id = opts.id
} }
@ -51,6 +65,18 @@ export default <T>(opts: buildOpts<T> | string, inner?: string | childTyp<T>[],
if (opts.innerHtml) { if (opts.innerHtml) {
dom.innerHTML = 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) { if (opts.onclick) {
dom.onclick = opts.onclick dom.onclick = opts.onclick
} }
@ -60,7 +86,7 @@ export default <T>(opts: buildOpts<T> | string, inner?: string | childTyp<T>[],
if (Object.prototype.toString.call(c) === '[object Array]') { if (Object.prototype.toString.call(c) === '[object Array]') {
const iterID = proxy.generateUniqueId() const iterID = proxy.generateUniqueId()
const iterLast = tmpID const iterLast = tmpID
proxy.Listen(() => { addListener(dom, () => {
c = c as iterChild<T> c = c as iterChild<T>
let itemIDs: string[] = [] let itemIDs: string[] = []
for (let i = 0; i < c[0].length; i++) { for (let i = 0; i < c[0].length; i++) {
@ -144,11 +170,29 @@ export default <T>(opts: buildOpts<T> | string, inner?: string | childTyp<T>[],
} }
} }
dom.setAttribute('voa', '1') dom.setAttribute('voa', '1')
if (updator) { if (opts.updator) {
proxy.Listen(() => { addListener(dom, () => {
updator(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 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)
}

Loading…
Cancel
Save