From 4dbc5ab1d5700313ed302aa2665987a8c94efcf2 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Sun, 18 Jun 2023 14:37:13 -0400 Subject: [PATCH] documentation/clarification --- _lib_/hmr.tsx | 34 ++++++++++++++++++++++++--- _lib_/react.tsx | 61 ++++++++++++++++++------------------------------- example/app.tsx | 13 ++++------- 3 files changed, 58 insertions(+), 50 deletions(-) diff --git a/_lib_/hmr.tsx b/_lib_/hmr.tsx index 854d503..3abd95f 100644 --- a/_lib_/hmr.tsx +++ b/_lib_/hmr.tsx @@ -14,14 +14,42 @@ Socket.addEventListener('message', async(event:{data:string})=> { // When a file changes, dynamically re-import it to get the updated members // send the updated members to any listeners for that file - const reImport = await import(event.data+"?reload="+HMR.reloads); - const handlers = FileListeners.get(event.data)??[]; - handlers.forEach(handler=>handler(reImport)); + const reImport = await import(event.data+"?reload="+Math.random()); + FileListeners.get(event.data)?.forEach(reExport=>reExport(reImport)); HMR.update(); }); Socket.addEventListener("error", ()=>{clearInterval(SocketTimer); console.log("HMR socket lost")}) 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 with whatever is in statesOld +- statesNew is moved into *statesOld* +- statesNew is cleared. + + +*/ + + const HMR = { reloads:1, diff --git a/_lib_/react.tsx b/_lib_/react.tsx index 4a2fffc..c899811 100644 --- a/_lib_/react.tsx +++ b/_lib_/react.tsx @@ -5,6 +5,7 @@ export type StateType = boolean|number|string|Record export type StateCapture = {state:StateType, set:ReactParts.StateUpdater, reload:number}; type FuncArgs = [element:keyof ReactParts.JSX.IntrinsicElements, props:Record, children:ReactParts.JSX.Element[]]; + const H = ReactParts.createElement; const MapIndex =(inMap:Map, inIndex:number)=> { @@ -20,18 +21,7 @@ const MapIndex =(inMap:Map, inIndex:number)=> return false; }; - -const ProxyCreate =(...args:FuncArgs)=> -{ - if(typeof args[0] == "string") - { - return H(...args) - } - else - { - return H(ProxyElement, {__args:args, ...args[1]}); - } -}; +const ProxyCreate =(...args:FuncArgs)=> (typeof args[0] == "string") ? H(...args) : H(ProxyElement, {__args:args, ...args[1]}); const ProxyElement = (props:{__args:FuncArgs})=> { @@ -54,51 +44,44 @@ const ProxyElement = (props:{__args:FuncArgs})=> } }; - -const ProxyState =(arg:StateType)=> +const ProxyState =(argNew:StateType)=> { - const id = ReactParts.useId(); - //const argOriginal = arg; // does statesOld have an entry for this state? use that instead of the passed arg const check = MapIndex(HMR.statesOld, HMR.statesNew.size); - const argOld = check ? check[1].state : arg; + 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 [stateGet, stateSet] = ReactParts.useState(argOld); ReactParts.useEffect(()=>{ - - - - /* - i have no idea what this does 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 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") } - else - { - console.log("check: hmr-invoked") - } + HMR.statesNew.delete(id); } - */ }, []); - if(!HMR.statesNew.has(id)) - { - HMR.statesNew.set(id, {state:argOld, set:stateSet, reload:HMR.reloads}); - } - + + // do we need to account for the function set? function proxySetter (inArg:StateType) { - //console.log("state spy update", id, arg); - HMR.statesNew.set(id, {state:inArg, set:stateSet, reload:HMR.reloads}); - return stateSet(inArg); + const stateUser = {state:inArg, set:stateSet, reload:HMR.reloads}; + HMR.statesNew.set(id, stateUser); + stateSet(inArg); } return [stateGet, proxySetter]; diff --git a/example/app.tsx b/example/app.tsx index 43850ac..f86ad81 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -3,12 +3,6 @@ import React from "react"; const CTXString = React.createContext("lol"); -const Butt =(props:{label:string})=> -{ - const [countGet, countSet] = React.useState(3); - return ; -}; - type StateBinding = [get:T, set:React.StateUpdater]; const CTXState = React.createContext(null) as React.Context|null>; const Outer =(props:{children:VNode})=> @@ -21,14 +15,17 @@ const Outer =(props:{children:VNode})=> const Inner =()=> { const binding = React.useContext(CTXState); - return + return }; export default ()=> { return -

Title

+
+

Title!

+

subtitle

+