feat: v2dom attr bind,event delete

v3
veypi 1 month ago
parent 9f789b78e7
commit 40d27cb9e4

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

@ -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']
})
])
])
])

@ -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() })
}
})
])
]
})

@ -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) {
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) {
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")

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

@ -12,18 +12,22 @@ interface buildOpts<T> {
id?: string
typ?: string
class?: string
attrs?: attrs
style?: string
innerHtml?: string
onclick?: any
children?: childTyp<T>[]
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 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 (typs.indexOf(opts) >= 0) {
opts = { typ: opts }
@ -31,17 +35,27 @@ export default <T>(opts: buildOpts<T> | string, inner?: string | childTyp<T>[],
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 <T>(opts: buildOpts<T> | string, inner?: string | childTyp<T>[],
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 <T>(opts: buildOpts<T> | string, inner?: string | childTyp<T>[],
if (Object.prototype.toString.call(c) === '[object Array]') {
const iterID = proxy.generateUniqueId()
const iterLast = tmpID
proxy.Listen(() => {
addListener(dom, () => {
c = c as iterChild<T>
let itemIDs: string[] = []
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')
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)
}

Loading…
Cancel
Save