Skip to content

Commit

Permalink
perf: 批量更新时做细粒度更新
Browse files Browse the repository at this point in the history
  • Loading branch information
GrinZero committed Mar 2, 2024
1 parent 8d5238a commit 847ecd5
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 55 deletions.
2 changes: 1 addition & 1 deletion apps/demo/src/components/blench/button/index.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<div className="col-sm-6 smallpad">
<button type="button" className="btn btn-primary btn-block" id="{{id}}" @click="{{cb}}">{{title}}</button>
<button type="button" className="btn btn-primary btn-block" id="{{sid}}" @click="{{cb}}">{{title}}</button>
</div>
8 changes: 1 addition & 7 deletions apps/demo/src/components/blench/button/index.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
8 changes: 4 additions & 4 deletions apps/demo/src/components/blench/row/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }),
},
};
});
2 changes: 1 addition & 1 deletion packages/extreme/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
12 changes: 10 additions & 2 deletions packages/extreme/src/core/dom-str.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
export const getRandomID = () => "W" + Math.random().toString(36).slice(2, 10);

const domStrCache = new Map<string, string>();
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 = "";
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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<string, HTMLDivElement>();

Expand Down
82 changes: 44 additions & 38 deletions packages/extreme/src/core/render.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type Ref } from "../hooks";
import { useState, type Ref } from "../hooks";
import { markIdHandler } from "../worker/render";
import {
findDomStr,
getDomID,
Expand Down Expand Up @@ -26,6 +27,10 @@ const getValue = (state: Record<string, any>, 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;
Expand Down Expand Up @@ -73,30 +78,7 @@ export async function render<T extends HTMLElement | HTMLTemplateElement>(
};

// 第一阶段,为所有使用了{{}}的dom添加id,或者对id="{{ref}}"的dom进行替换
{
const usageDomSet = new Set<string>();
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节点
{
Expand Down Expand Up @@ -194,6 +176,7 @@ export async function render<T extends HTMLElement | HTMLTemplateElement>(
testForResult.push([_, key, start]);
return _;
});

for (const [_, key, start] of testForResult) {
const [itemName, listName] = key
.trim()
Expand All @@ -211,8 +194,16 @@ export async function render<T extends HTMLElement | HTMLTemplateElement>(

const list = getValue(state, listName);

const signalCache = new Map<string, Function>();
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) {
Expand All @@ -232,11 +223,14 @@ export async function render<T extends HTMLElement | HTMLTemplateElement>(
});
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,
};
}
Expand Down Expand Up @@ -301,6 +295,7 @@ export async function render<T extends HTMLElement | HTMLTemplateElement>(
const oldIndex = oldKeyToIndex.get(oldKey) ?? -1;
const dom = parent.children[oldIndex];
dom && toRemove.push(dom);
signalCache.delete(oldKey);
}
}
for (const dom of toRemove) {
Expand All @@ -326,10 +321,10 @@ export async function render<T extends HTMLElement | HTMLTemplateElement>(
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) {
Expand All @@ -346,10 +341,10 @@ export async function render<T extends HTMLElement | HTMLTemplateElement>(
}
}
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);
Expand Down Expand Up @@ -494,14 +489,25 @@ export async function render<T extends HTMLElement | HTMLTemplateElement>(
/{{(.*?)}}/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) {
Expand Down
7 changes: 5 additions & 2 deletions packages/extreme/src/hooks/useEffect.ts
Original file line number Diff line number Diff line change
@@ -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;
});
};
1 change: 1 addition & 0 deletions packages/extreme/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './worker'
1 change: 1 addition & 0 deletions packages/extreme/src/utils/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const MAX_WORKER_COUNT = navigator.hardwareConcurrency / 2 + 1;
44 changes: 44 additions & 0 deletions packages/extreme/src/worker/render.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { getRandomID, findDomStr, addDomID } from "../core/dom-str";
import type { Ref } from "../hooks";

export type PropsRef = Record<string, Ref> | null;
export type PropsState = Record<string, string | (() => string)> | null;

export const markIdHandler = (
template: string,
{
ref,
state,
}: {
ref?: PropsRef;
state?: PropsState;
} = {}
) => {
const usageDomSet = new Set<string>();
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=()=>{

}

0 comments on commit 847ecd5

Please sign in to comment.