Compare commits

...

2 Commits

Author SHA1 Message Date
4dbc5ab1d5 documentation/clarification 2023-06-18 14:37:13 -04:00
a781b3300c arg new/old 2023-06-18 07:36:55 -04:00
3 changed files with 63 additions and 48 deletions

View File

@ -14,14 +14,42 @@ Socket.addEventListener('message', async(event:{data:string})=>
{ {
// When a file changes, dynamically re-import it to get the updated members // When a file changes, dynamically re-import it to get the updated members
// send the updated members to any listeners for that file // send the updated members to any listeners for that file
const reImport = await import(event.data+"?reload="+HMR.reloads); const reImport = await import(event.data+"?reload="+Math.random());
const handlers = FileListeners.get(event.data)??[]; FileListeners.get(event.data)?.forEach(reExport=>reExport(reImport));
handlers.forEach(handler=>handler(reImport));
HMR.update(); HMR.update();
}); });
Socket.addEventListener("error", ()=>{clearInterval(SocketTimer); console.log("HRM socket lost")}) Socket.addEventListener("error", ()=>{clearInterval(SocketTimer); console.log("HMR socket lost")})
const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000); const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000);
/*
Each custom component is secretly modified to have an extra state and id.
When there is an HMR update, this state is changed, forcing it to re-render.
Each *user-created* React.useState is secretly modified and accompanied by an ID.
Every time its state is set, the HMR.statesNew map for this ID is set to contain the new state and updater.
When a component is removed, any of it's states in HMR.statesNew are also removed.
(HMR.statesNew is the "running total" of all states currently at play).
---
When a state is interacted with:
- statesNew for this id is set
- the internal state is also set in the traditional way
When there is an HMR update:
- All custom components are re-rendered...
for each useState(value) call that then happens in the re-render:
- if there is a "statesOld" value for this state, use that and ignore the passed value, otherwise use the passed value
- if this state has not been interacted with since the last reload (statesNew is empty at this id), set statesNew<id> with whatever is in statesOld<id>
- statesNew is moved into *statesOld*
- statesNew is cleared.
*/
const HMR = const HMR =
{ {
reloads:1, reloads:1,

View File

@ -5,8 +5,9 @@ export type StateType = boolean|number|string|Record<string, string>
export type StateCapture = {state:StateType, set:ReactParts.StateUpdater<StateType>, reload:number}; export type StateCapture = {state:StateType, set:ReactParts.StateUpdater<StateType>, reload:number};
type FuncArgs = [element:keyof ReactParts.JSX.IntrinsicElements, props:Record<string, string>, children:ReactParts.JSX.Element[]]; type FuncArgs = [element:keyof ReactParts.JSX.IntrinsicElements, props:Record<string, string>, children:ReactParts.JSX.Element[]];
const H = ReactParts.createElement; const H = ReactParts.createElement;
const MapAt =(inMap:Map<string, StateCapture>, inIndex:number)=> const MapIndex =(inMap:Map<string, StateCapture>, inIndex:number)=>
{ {
let index = 0; let index = 0;
for(const kvp of inMap) for(const kvp of inMap)
@ -20,18 +21,7 @@ const MapAt =(inMap:Map<string, StateCapture>, inIndex:number)=>
return false; return false;
}; };
const ProxyCreate =(...args:FuncArgs)=> (typeof args[0] == "string") ? H(...args) : H(ProxyElement, {__args:args, ...args[1]});
const ProxyCreate =(...args:FuncArgs)=>
{
if(typeof args[0] == "string")
{
return H(...args)
}
else
{
return H(ProxyElement, {__args:args, ...args[1]});
}
};
const ProxyElement = (props:{__args:FuncArgs})=> const ProxyElement = (props:{__args:FuncArgs})=>
{ {
@ -54,44 +44,44 @@ const ProxyElement = (props:{__args:FuncArgs})=>
} }
}; };
const ProxyState =(argNew:StateType)=>
const ProxyState =(arg:StateType)=>
{ {
const id = ReactParts.useId();
// does statesOld have an entry for this state? use that instead of the passed arg // does statesOld have an entry for this state? use that instead of the passed arg
const check = MapAt(HMR.statesOld, HMR.statesNew.size); const check = MapIndex(HMR.statesOld, HMR.statesNew.size);
const argOld = check ? check[1].state : argNew;
const id = ReactParts.useId();
const [stateGet, stateSet] = ReactParts.useState(argOld);
// state updates due to clicks, interactivity, etc. since the last reload may already be in statesNew for this slot.
// DONT overwrite it.
if(!HMR.statesNew.get(id))
{
HMR.statesNew.set(id, {state:stateGet, set:stateSet, reload:HMR.reloads});
}
const lastKnowReloads = HMR.reloads; const lastKnowReloads = HMR.reloads;
const [stateGet, stateSet] = ReactParts.useState(check ? check[1].state : arg);
ReactParts.useEffect(()=>{ ReactParts.useEffect(()=>{
return ()=>{ return ()=>{
if(HMR.reloads == lastKnowReloads) if(HMR.reloads == lastKnowReloads)/*i have no idea what this does. this may have to be re-introduced when routing is added*/
{ {
// this is a switch/ui change, not a HMR reload change // this is a switch/ui change, not a HMR reload change
const oldState = MapAt(HMR.statesOld, HMR.statesNew.size-1); const oldState = MapIndex(HMR.statesOld, HMR.statesNew.size-1);
oldState && HMR.statesOld.set(oldState[0], {...oldState[1], state:arg}); oldState && HMR.statesOld.set(oldState[0], {...oldState[1], state:argNew});
console.log("check: ui-invoked") console.log("check: ui-invoked")
} }
else
{
console.log("check: hmr-invoked")
}
HMR.statesNew.delete(id); HMR.statesNew.delete(id);
} }
}, []); }, []);
if(!HMR.statesNew.has(id))
{
HMR.statesNew.set(id, {state:arg, set:stateSet, reload:HMR.reloads});
}
function proxySetter (arg:StateType) // do we need to account for the function set?
function proxySetter (inArg:StateType)
{ {
//console.log("state spy update", id, arg); const stateUser = {state:inArg, set:stateSet, reload:HMR.reloads};
HMR.statesNew.set(id, {state:arg, set:stateSet, reload:HMR.reloads}); HMR.statesNew.set(id, stateUser);
return stateSet(arg); stateSet(inArg);
} }
return [stateGet, proxySetter]; return [stateGet, proxySetter];

View File

@ -3,12 +3,6 @@ import React from "react";
const CTXString = React.createContext("lol"); const CTXString = React.createContext("lol");
const Butt =(props:{label:string})=>
{
const [countGet, countSet] = React.useState(3);
return <button onClick={e=>countSet(countGet+1)}>{props.label+" -- "+countGet}</button>;
};
type StateBinding<T> = [get:T, set:React.StateUpdater<T>]; type StateBinding<T> = [get:T, set:React.StateUpdater<T>];
const CTXState = React.createContext(null) as React.Context<StateBinding<string>|null>; const CTXState = React.createContext(null) as React.Context<StateBinding<string>|null>;
const Outer =(props:{children:VNode})=> const Outer =(props:{children:VNode})=>
@ -21,14 +15,17 @@ const Outer =(props:{children:VNode})=>
const Inner =()=> const Inner =()=>
{ {
const binding = React.useContext(CTXState); const binding = React.useContext(CTXState);
return <button onClick={e=>binding && binding[1](Math.random().toString())}>{binding?.[0]??"(its null)"}!</button> return <button onClick={e=>binding && binding[1](Math.random().toString())}>{binding?.[0]??"(its null)"} :)</button>
}; };
export default ()=> export default ()=>
{ {
return <CTXString.Provider value="intradestink"> return <CTXString.Provider value="intradestink">
<div><h1>Title</h1></div> <div>
<h1>Title!</h1>
<h2>subtitle</h2>
</div>
<Outer> <Outer>
<Inner/> <Inner/>
</Outer> </Outer>