-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
253 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,51 +1,267 @@ | ||
import { h, createComponent, useState, useMemo, useDeferredValue } from '@dark-engine/core'; | ||
import { render } from '@dark-engine/platform-browser'; | ||
import { | ||
h, | ||
View, | ||
Text, | ||
Fragment, | ||
createComponent, | ||
memo, | ||
useCallback, | ||
SplitUpdate, | ||
useSplitUpdate, | ||
} from '@dark-engine/core'; | ||
import { createRoot } from '@dark-engine/platform-browser'; | ||
|
||
function generateProducts() { | ||
const products: Array<string> = []; | ||
const div = (props = {}) => View({ ...props, as: 'div' }); | ||
const button = (props = {}) => View({ ...props, as: 'button' }); | ||
|
||
for (let i = 0; i < 10000; i++) { | ||
products.push(`Product ${i + 1}`); | ||
} | ||
return products; | ||
} | ||
const createMeasurer = () => { | ||
let startTime; | ||
let lastMeasureName: string; | ||
const start = (name: string) => { | ||
startTime = performance.now(); | ||
lastMeasureName = name; | ||
}; | ||
const stop = () => { | ||
const last = lastMeasureName; | ||
|
||
const dummyProducts = generateProducts(); | ||
if (lastMeasureName) { | ||
setTimeout(() => { | ||
lastMeasureName = null; | ||
const stopTime = performance.now(); | ||
const diff = stopTime - startTime; | ||
|
||
function filterProducts(filterTerm) { | ||
if (!filterTerm) { | ||
return dummyProducts; | ||
} | ||
console.log(`${last}: ${diff}`); | ||
}); | ||
} | ||
}; | ||
|
||
return dummyProducts.filter(product => product.toLowerCase().indexOf(filterTerm.toLowerCase()) !== -1); | ||
} | ||
return { | ||
start, | ||
stop, | ||
}; | ||
}; | ||
|
||
const measurer = createMeasurer(); | ||
|
||
let nextId = 0; | ||
const buildData = (count, prefix = '') => { | ||
return Array(count) | ||
.fill(0) | ||
.map(() => ({ | ||
id: ++nextId, | ||
name: `item: ${nextId} ${prefix}`, | ||
selected: false, | ||
})); | ||
}; | ||
|
||
type ListItem = { id: number; name: string; selected: boolean }; | ||
|
||
type List = Array<ListItem>; | ||
|
||
type ProductListProps = { | ||
products: Array<string>; | ||
type State = { | ||
list: List; | ||
}; | ||
|
||
const ProductList = createComponent<ProductListProps>(({ products }) => { | ||
const deferredProducts = useDeferredValue(products); | ||
const items = useMemo(() => { | ||
return deferredProducts.map(product => <li key={product}>{product}</li>); | ||
}, [deferredProducts]); | ||
const state: State = { | ||
list: [], | ||
}; | ||
|
||
type HeaderProps = { | ||
onCreate: () => void; | ||
onPrepend: () => void; | ||
onAppend: () => void; | ||
onInsertDifferent: () => void; | ||
onUpdateAll: () => void; | ||
onSwap: () => void; | ||
onClear: () => void; | ||
}; | ||
|
||
const Header = createComponent<HeaderProps>( | ||
({ onCreate, onPrepend, onAppend, onInsertDifferent, onUpdateAll, onSwap, onClear }) => { | ||
return div({ | ||
style: | ||
'width: 100%; height: 64px; background-color: blueviolet; display: flex; align-items: center; padding: 16px;', | ||
slot: [ | ||
button({ | ||
slot: Text('create 10000 rows'), | ||
onClick: onCreate, | ||
}), | ||
button({ | ||
slot: Text('Prepend 1000 rows'), | ||
onClick: onPrepend, | ||
}), | ||
button({ | ||
slot: Text('Append 1000 rows'), | ||
onClick: onAppend, | ||
}), | ||
button({ | ||
slot: Text('insert different'), | ||
onClick: onInsertDifferent, | ||
}), | ||
button({ | ||
slot: Text('update every 10th row'), | ||
onClick: onUpdateAll, | ||
}), | ||
button({ | ||
slot: Text('swap rows'), | ||
onClick: onSwap, | ||
}), | ||
button({ | ||
slot: Text('clear rows'), | ||
onClick: onClear, | ||
}), | ||
button({ | ||
slot: Text('unmount app'), | ||
onClick: () => { | ||
root.unmount(); | ||
}, | ||
}), | ||
], | ||
}); | ||
}, | ||
); | ||
|
||
return <ul>{items}</ul>; | ||
const MemoHeader = memo<HeaderProps>(Header); | ||
|
||
type RowProps = { | ||
id: number; | ||
onRemove: (id: number) => void; | ||
onHighlight: (id: number) => void; | ||
}; | ||
|
||
const Row = createComponent<RowProps>(({ id, onRemove, onHighlight }) => { | ||
const { name, selected } = useSplitUpdate<ListItem>( | ||
map => map[id], | ||
x => `${x.name}:${x.selected}`, | ||
); | ||
const handleRemove = useCallback(() => onRemove(id), []); | ||
const handleHighlight = useCallback(() => onHighlight(id), []); | ||
|
||
return ( | ||
<tr class={selected ? 'selected' : undefined}> | ||
<td class='cell'>{name}</td> | ||
<td class='cell'>qqq</td> | ||
<td class='cell'>xxx</td> | ||
<td class='cell'> | ||
<button onClick={handleRemove}>remove</button> | ||
<button onClick={handleHighlight}>highlight</button> | ||
</td> | ||
</tr> | ||
); | ||
}); | ||
|
||
const App = createComponent(() => { | ||
const [name, setName] = useState(''); | ||
const deferredName = useDeferredValue(name); | ||
const filteredProducts = useMemo(() => filterProducts(deferredName), [deferredName]); | ||
const MemoRow = memo<RowProps>(Row); | ||
|
||
const handleInput = e => setName(e.target.value); | ||
type ListProps = { | ||
items: List; | ||
onRemove: (id: number) => void; | ||
onHighlight: (id: number) => void; | ||
}; | ||
|
||
const List = createComponent<ListProps>(({ items, onRemove, onHighlight }) => { | ||
return ( | ||
<div> | ||
<input value={name} autoFocus onInput={handleInput} /> | ||
<ProductList products={filteredProducts} /> | ||
</div> | ||
<table class='table'> | ||
<tbody> | ||
{items.map(item => { | ||
return <MemoRow key={item.id} id={item.id} onRemove={onRemove} onHighlight={onHighlight} />; | ||
})} | ||
</tbody> | ||
</table> | ||
); | ||
}); | ||
|
||
render(<App />, document.getElementById('root')); | ||
const MemoList = memo(List); | ||
|
||
const Bench = createComponent(() => { | ||
const handleCreate = useCallback(() => { | ||
state.list = buildData(10000); | ||
measurer.start('create'); | ||
forceUpdate(); | ||
measurer.stop(); | ||
}, []); | ||
const handlePrepend = useCallback(() => { | ||
state.list.unshift(...buildData(1000, '!!!')); | ||
state.list = [...state.list]; | ||
measurer.start('prepend'); | ||
forceUpdate(); | ||
measurer.stop(); | ||
}, []); | ||
const handleAppend = useCallback(() => { | ||
state.list.push(...buildData(1000, '!!!')); | ||
state.list = [...state.list]; | ||
measurer.start('append'); | ||
forceUpdate(); | ||
measurer.stop(); | ||
}, []); | ||
const handleInsertDifferent = useCallback(() => { | ||
const [item1, item2, item3, ...rest] = state.list; | ||
|
||
state.list = [...buildData(5, '***'), item1, item2, item3, ...buildData(2, '***'), ...rest].filter(Boolean); | ||
measurer.start('insert different'); | ||
forceUpdate(); | ||
measurer.stop(); | ||
}, []); | ||
const handleUpdateAll = useCallback(() => { | ||
state.list = state.list.map((x, idx) => ({ ...x, name: (idx + 1) % 10 === 0 ? x.name + '!!!' : x.name })); | ||
measurer.start('update every 10th'); | ||
forceUpdate(); | ||
measurer.stop(); | ||
}, []); | ||
const handleRemove = useCallback(id => { | ||
state.list = state.list.filter(x => x.id !== id); | ||
measurer.start('remove'); | ||
forceUpdate(); | ||
measurer.stop(); | ||
}, []); | ||
const handleHightlight = useCallback(id => { | ||
const idx = state.list.findIndex(x => x.id === id); | ||
state.list[idx].selected = !state.list[idx].selected; | ||
state.list = [...state.list]; | ||
measurer.start('highlight'); | ||
forceUpdate(); | ||
measurer.stop(); | ||
}, []); | ||
const handleSwap = useCallback(() => { | ||
if (state.list.length === 0) return; | ||
const temp = state.list[1]; | ||
state.list[1] = state.list[state.list.length - 2]; | ||
state.list[state.list.length - 2] = temp; | ||
state.list = [...state.list]; | ||
measurer.start('swap'); | ||
forceUpdate(); | ||
measurer.stop(); | ||
}, []); | ||
const handleClear = useCallback(() => { | ||
state.list = []; | ||
measurer.start('clear'); | ||
forceUpdate(); | ||
measurer.stop(); | ||
}, []); | ||
|
||
return ( | ||
<> | ||
<MemoHeader | ||
onCreate={handleCreate} | ||
onPrepend={handlePrepend} | ||
onAppend={handleAppend} | ||
onInsertDifferent={handleInsertDifferent} | ||
onUpdateAll={handleUpdateAll} | ||
onSwap={handleSwap} | ||
onClear={handleClear} | ||
/> | ||
<SplitUpdate list={state.list} getKey={getKey}> | ||
<MemoList items={state.list} onRemove={handleRemove} onHighlight={handleHightlight} /> | ||
</SplitUpdate> | ||
</> | ||
); | ||
}); | ||
|
||
const getKey = (x: ListItem) => x.id; | ||
|
||
const root = createRoot(document.getElementById('root')); | ||
|
||
function forceUpdate() { | ||
root.render(<Bench />); | ||
} | ||
|
||
forceUpdate(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters