Last active
December 12, 2023 06:41
-
-
Save romgrk/6c09f15d98327ea43074d9bba086b529 to your computer and use it in GitHub Desktop.
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
/* | |
* Skipping over internal reactivity but this would be the gist of how to build a component that doesn't | |
* use react but still allows customization via user-supplied react-based render functions. | |
* | |
* Why? Because react is way to slow for use-cases like virtualization+scrolling. The whole cycle (state-update | |
* then re-render then vdom diffing then dom update) has too many unavoidable costs. Being able to go from | |
* state-update to dom-update directly is unbeatable. | |
* | |
* It uses react portals, so even though it manages its own HTML structure, it contains inside itself parts that | |
* are rendered & managed by react. And portals ensure there is full react compatibility, including the Context API, | |
* event-bubbling, and useEffect unmount callbacks. Portals are efficient enough to create large numbers of them. | |
*/ | |
type Target = { | |
node: HTMLElement | |
jsx: React.ReactNode | |
} | |
/* the component itself */ | |
class Grid { | |
constructor(root, setTargets) { | |
this.root = root | |
this.setTargets = setTargets | |
} | |
setProps(props) { | |
this.props = props | |
this.render() | |
} | |
render() { | |
/* this is just the initial render but the component would manage its own internal reactivity & updates | |
* in whichever way it sees fit to do it */ | |
const targets = [] | |
props.rows.forEach((row) => { | |
const rowNode = document.createElement('div') | |
this.root.appendChild(rowNode) | |
props.cols.forEach((col) => { | |
const cellNode = document.createElement('div') | |
row.appendChild(cellNode) | |
if (col.render) { | |
/* external rendering, let react render whenever it finds time to do it */ | |
targets.push({ node: cellNode, jsx: col.render(/* ... */) }) | |
} else { | |
/* internal rendering, use DOM API directly */ | |
cellNode.innerText = row[col.field] | |
} | |
}) | |
}) | |
this.setTargets(targets) | |
} | |
dispose = () => { /* ... */ } | |
} | |
function Wrapper /* memoized ideally */ (props) { | |
const [targets, setTargets] = useState([]) | |
let component | |
useEffect(() => (component = new Grid(ref.current, setTargets)).dispose, []) | |
useEffect(() => component.setProps(props), [props]) | |
return ( | |
<> | |
<div ref={ref} /> | |
<> | |
{targets.map(({ node, jsx }) => | |
<React.Fragment> | |
{createPortal(jsx, node)} | |
</React.Fragment> | |
} | |
</> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment