diff --git a/apps/demo/src/components/blench/button/index.html b/apps/demo/src/components/blench/button/index.html index 8b37a63..bf8dc80 100644 --- a/apps/demo/src/components/blench/button/index.html +++ b/apps/demo/src/components/blench/button/index.html @@ -1,3 +1,3 @@
- +
diff --git a/apps/demo/src/components/blench/button/index.ts b/apps/demo/src/components/blench/button/index.ts index 609857e..8dd543d 100644 --- a/apps/demo/src/components/blench/button/index.ts +++ b/apps/demo/src/components/blench/button/index.ts @@ -1,17 +1,11 @@ import template from "./index.html?raw"; -import { createComponent, useMount } from "@sourcebug/extreme/dev"; +import { createComponent } from "@sourcebug/extreme/dev"; export const MyButton = createComponent<{ sid: string; cb: () => void; title: string; }>("MyButton", ({ sid, cb, title }) => { - useMount(() => { - document.getElementById(sid)?.addEventListener("click", cb); - return () => { - document.getElementById(sid)?.removeEventListener("click", cb); - }; - }); return { template, diff --git a/apps/demo/src/components/blench/row/index.ts b/apps/demo/src/components/blench/row/index.ts index 26c6763..de7c9b8 100644 --- a/apps/demo/src/components/blench/row/index.ts +++ b/apps/demo/src/components/blench/row/index.ts @@ -3,20 +3,20 @@ import template from "./index.html?raw"; export const Row = createComponent<{ selected: () => number; - item: { id: number; label: string }; + item: () => { id: number; label: string }; dispatch: (action: { type: string; id: number }) => void; }>("Row", ({ selected, item, dispatch }) => { return { template, state: { item, - containerClass: () => (selected() === item.id ? "danger" : ""), + containerClass: () => (selected() === item().id ? "danger" : ""), }, methods: { handleSelect: () => { - dispatch({ type: "SELECT", id: item.id }); + dispatch({ type: "SELECT", id: item().id }); }, - handleRemove: () => dispatch({ type: "REMOVE", id: item.id }), + handleRemove: () => dispatch({ type: "REMOVE", id: item().id }), }, }; }); diff --git a/packages/extreme/package.json b/packages/extreme/package.json index f65e4cb..31b751a 100644 --- a/packages/extreme/package.json +++ b/packages/extreme/package.json @@ -1,6 +1,6 @@ { "name": "@sourcebug/extreme", - "version": "1.0.13", + "version": "1.0.15", "description": "", "main": "./dist/extreme.umd.js", "module": "./dist/extreme.mjs", diff --git a/packages/extreme/src/core/dom-str.ts b/packages/extreme/src/core/dom-str.ts index 6f50853..9993439 100644 --- a/packages/extreme/src/core/dom-str.ts +++ b/packages/extreme/src/core/dom-str.ts @@ -1,6 +1,12 @@ export const getRandomID = () => "W" + Math.random().toString(36).slice(2, 10); +const domStrCache = new Map(); export const findDomStr = (index: number, htmlText: string) => { + const key = `${index}#-${htmlText}`; + if (domStrCache.has(key)) { + return domStrCache.get(key)!; + } + let firstDOMIndex = htmlText.lastIndexOf("<", index); let tag = ""; @@ -30,7 +36,9 @@ export const findDomStr = (index: number, htmlText: string) => { break; } } - return htmlText.slice(firstDOMIndex, lastIndex); + const result = htmlText.slice(firstDOMIndex, lastIndex); + domStrCache.set(key, result); + return result; }; export const getDomAttr = (domStr: string, attr: string) => { @@ -65,7 +73,7 @@ export const addDomID = (domStr: string, newID: string | (() => string)) => { return [domStrWithID, id || getRandomID()]; }; -export const getHash = (str: string) => "W" + btoa(str).replace(/=/g, "ace"); +export const getHash = (str: string) => "W" + btoa(str).replace(/=/g, "~"); const analyzeDomCache = new Map(); diff --git a/packages/extreme/src/core/render.ts b/packages/extreme/src/core/render.ts index b94159c..c44953d 100644 --- a/packages/extreme/src/core/render.ts +++ b/packages/extreme/src/core/render.ts @@ -1,4 +1,5 @@ -import { type Ref } from "../hooks"; +import { useState, type Ref } from "../hooks"; +import { markIdHandler } from "../worker/render"; import { findDomStr, getDomID, @@ -26,6 +27,10 @@ const getValue = (state: Record, key: string) => { let value = state; for (let i = 0; i < keys.length; i++) { if (!value) return; + if (typeof value === "function") { + // @ts-ignore + return (...rest) => value(...rest)[keys[i]]; + } value = value[keys[i]]; } return value; @@ -73,30 +78,7 @@ export async function render( }; // 第一阶段,为所有使用了{{}}的dom添加id,或者对id="{{ref}}"的dom进行替换 - { - const usageDomSet = new Set(); - template.replace(/{{(.*?)}}/g, (_, _key, start) => { - usageDomSet.add(findDomStr(start, template)); - return _; - }); - usageDomSet.forEach((dom) => { - if (dom.indexOf("id=") === -1) { - const [newDom] = addDomID(dom, getRandomID); - template = template.replace(dom, newDom); - } - }); - template = template.replace(/id="{{(.*?)}}"/g, (_, key) => { - const _key = key.trim(); - if (ref && _key in ref) { - return `id="${ref[_key]}"`; - } - if (state && _key in state && typeof state[_key] === "string") { - // TODO: 增加对state function的支持 - return `id="${state[_key]}"`; - } - return _; - }); - } + template = markIdHandler(template, { ref, state }); // 第二阶段,收集所有methods和对应的DOM节点 { @@ -194,6 +176,7 @@ export async function render( testForResult.push([_, key, start]); return _; }); + for (const [_, key, start] of testForResult) { const [itemName, listName] = key .trim() @@ -211,8 +194,16 @@ export async function render( const list = getValue(state, listName); + const signalCache = new Map(); const renderItem = async (item: any, index: number) => { - const listID = getHash(String(item[keyIndex] ?? index)); + const curKey = String(item[keyIndex] ?? index); + if (signalCache.has(curKey)) { + const fn = signalCache.get(curKey)!; + fn(item); + return ""; + } + + const listID = getHash(curKey); let [newDom] = addDomID(dom, listID); newDom = newDom.replace(/id="(.*?)"/g, (source, key) => { if (key === listID) { @@ -232,11 +223,14 @@ export async function render( }); const template = document.createElement("template"); const cloneProps = { ...props }; + const [sign, setSign] = useState(item); + signalCache.set(curKey, setSign); + if (cloneProps.state && typeof cloneProps.state === "object") { cloneProps.state = { ...cloneProps.state, - // TODO:通过设置state()的render,可以在item改变时触发render快速得到新的dom,不用重计算 - ...{ [itemName]: item }, + // TODO:通过设置state()的render,可以在item改变时触发render快速得到新的dom,不用重计算item + ...{ [itemName]: sign }, key: item[keyIndex] ?? index, }; } @@ -301,6 +295,7 @@ export async function render( const oldIndex = oldKeyToIndex.get(oldKey) ?? -1; const dom = parent.children[oldIndex]; dom && toRemove.push(dom); + signalCache.delete(oldKey); } } for (const dom of toRemove) { @@ -326,10 +321,10 @@ export async function render( if (index === oldIndex) { // 位置正确,不需要移动 if (oldData !== newData && newList[index]) { - const renderDom = await renderItem(newList[index], index); - if (renderDom !== childNode.outerHTML) { - childNode.outerHTML = renderDom; - } + await renderItem(newList[index], index); + // if (renderDom !== childNode.outerHTML) { + // childNode.outerHTML = renderDom; + // } } } else { if (index > oldIndex) { @@ -346,10 +341,10 @@ export async function render( } } if (oldData !== newData && newList[index]) { - const renderDom = await renderItem(newList[index], index); - if (renderDom !== childNode.outerHTML) { - childNode.outerHTML = renderDom; - } + await renderItem(newList[index], index); + // if (renderDom !== childNode.outerHTML) { + // childNode.outerHTML = renderDom; + // } } } usagKeyList.delete(curKey); @@ -494,14 +489,25 @@ export async function render( /{{(.*?)}}/g, (source, key, start) => { const value = getValue(state, key); + // debugger; if (typeof value === "function") { - const analyzeUpdateKey = analyzeKey(baseDomStr, source, start); + const updateDom = findDomStr(start, baseDomStr); + const updateDomId = getDomID(updateDom); + const analyzeUpdateKey = analyzeKey( + updateDom, + source, + updateDom !== baseDomStr ? updateDom.indexOf(source) : start + ); if (analyzeUpdateKey === null) { + // debugger; console.error(`[extreme] ${source} is not a valid UpdateKey`); return source; } const rerenderDom = () => { - const dom = document.getElementById(ele?.id || id); + const dom = + document.getElementById(updateDomId ?? ele?.id ?? id) || + document.getElementById(ele?.id ?? id); + // debugger if (!dom) return; const newValue = encodeValue(value()); switch (analyzeUpdateKey.type) { diff --git a/packages/extreme/src/hooks/useEffect.ts b/packages/extreme/src/hooks/useEffect.ts index 800bcdf..d9ae4e0 100644 --- a/packages/extreme/src/hooks/useEffect.ts +++ b/packages/extreme/src/hooks/useEffect.ts @@ -1,7 +1,10 @@ import type { GetState } from "./useState"; - import { idleCallback } from "./useState"; export const useEffect = (fn: Function, deps: GetState[]) => { - for (const dep of deps) dep((v) => idleCallback(() => fn(v))) + for (const dep of deps) + dep((v) => { + idleCallback(() => fn(v)); + return; + }); }; diff --git a/packages/extreme/src/utils/index.ts b/packages/extreme/src/utils/index.ts new file mode 100644 index 0000000..fd87a10 --- /dev/null +++ b/packages/extreme/src/utils/index.ts @@ -0,0 +1 @@ +export * from './worker' \ No newline at end of file diff --git a/packages/extreme/src/utils/worker.ts b/packages/extreme/src/utils/worker.ts new file mode 100644 index 0000000..2ea4c54 --- /dev/null +++ b/packages/extreme/src/utils/worker.ts @@ -0,0 +1 @@ +export const MAX_WORKER_COUNT = navigator.hardwareConcurrency / 2 + 1; diff --git a/packages/extreme/src/worker/render.ts b/packages/extreme/src/worker/render.ts new file mode 100644 index 0000000..89f1427 --- /dev/null +++ b/packages/extreme/src/worker/render.ts @@ -0,0 +1,44 @@ +import { getRandomID, findDomStr, addDomID } from "../core/dom-str"; +import type { Ref } from "../hooks"; + +export type PropsRef = Record | null; +export type PropsState = Record string)> | null; + +export const markIdHandler = ( + template: string, + { + ref, + state, + }: { + ref?: PropsRef; + state?: PropsState; + } = {} +) => { + const usageDomSet = new Set(); + template.replace(/{{(.*?)}}/g, (_, _key, start) => { + usageDomSet.add(findDomStr(start, template)); + return _; + }); + usageDomSet.forEach((dom) => { + if (dom.indexOf("id=") === -1) { + const [newDom] = addDomID(dom, getRandomID); + template = template.replace(dom, newDom); + } + }); + template = template.replace(/id="{{(.*?)}}"/g, (_, key) => { + const _key = key.trim(); + if (ref && _key in ref) { + return `id="${ref[_key]}"`; + } + if (state && _key in state && typeof state[_key] === "string") { + // TODO: 增加对state function的支持 + return `id="${state[_key]}"`; + } + return _; + }); + return template; +}; + +export const collectMethodHanlder=()=>{ + +} \ No newline at end of file