Canvas Immediate mode rendering using React
import type { HostConfig, Reconciler } from 'react-fiber-types'; | |
import type { ReactNodeList } from 'react-fiber-types/ReactTypes'; | |
import DOMPropertyOperations from './DOMPropertyOperations'; | |
import type { | |
Props, | |
Container, | |
Instance, | |
TextInstance, | |
OpaqueHandle, | |
HostContext, | |
} from './ReactTinyTypes'; | |
const CHILDREN = 'children'; | |
const ReactFiberReconciler : ( | |
hostConfig: HostConfig<*, *, *, *, *, *, *, *> | |
) => Reconciler<*, *, *> = require('react-dom/lib/ReactFiberReconciler'); | |
const LOG_STEPS = false; | |
const log = (...args) => { | |
if (LOG_STEPS) { | |
console.log(...args); | |
} | |
}; | |
const TinyRenderer = ReactFiberReconciler({ | |
createInstance( | |
type : string, | |
props : Props, | |
rootContainerInstance : Container, | |
hostContext : HostContext, | |
internalInstanceHandle : Object | |
) { | |
const DIV = 'div'; | |
const SPAN = 'span'; | |
const supportedTags = [ | |
DIV, | |
SPAN | |
]; | |
const createElement = (tag, props) => { | |
return rootContainerInstance; | |
}; | |
if (supportedTags.indexOf(type) === -1) { | |
throw new Error('Unindentitfied type', type); | |
} else { | |
return createElement(type, props); | |
} | |
}, | |
appendInitialChild( | |
parentInstance : Instance, | |
child : Instance | TextInstance | |
) : void { | |
// Office | |
/* log('appendInitialChild', child, parentInstance);*/ | |
}, | |
appendChild( | |
parentInstance : Instance | Container, | |
child : Instance | TextInstance | |
) : void { | |
/* log('appendChild', child, parentInstance);*/ | |
}, | |
removeChild( | |
parentInstance : Instance | Container, | |
child : Instance | TextInstance | |
) : void { | |
log('removeChild', child); | |
}, | |
insertBefore( | |
parentInstance : Instance | Container, | |
child : Instance | TextInstance, | |
beforeChild : Instance | TextInstance | |
) : void { | |
log('insertBefore'); | |
}, | |
finalizeInitialChildren( | |
domElement : Instance, | |
type : string, | |
props : Props, | |
rootContainerInstance : Container | |
) : boolean { | |
const setTextContent = function(node, text) { | |
// needs a retained mode api | |
const ctx = node.getContext("2d"); | |
return ctx.fillText(text, 80, 50); | |
}; | |
for (var propKey in props) { | |
var nextProp = props[propKey]; | |
if (!props.hasOwnProperty(propKey)) { | |
continue; | |
} | |
if (propKey === CHILDREN) { | |
if (typeof nextProp === 'string') { | |
setTextContent(domElement, nextProp); | |
} else if (typeof nextProp === 'number') { | |
setTextContent(domElement, '' + nextProp); | |
} | |
} | |
} | |
return false; | |
}, | |
// prepare update is where you compute the diff for an instance. This is done | |
// here to separate computation of the diff to the applying of the diff. Fiber | |
// can reuse this work even if it pauses or aborts rendering a subset of the | |
// tree. | |
prepareUpdate( | |
instance : Instance, | |
type : string, | |
oldProps : Props, | |
newProps : Props, | |
rootContainerInstance : Container, | |
hostContext : HostContext | |
) : null | Array<mixed> { | |
log('In .prepareUpdate(): ', oldProps, newProps); | |
var updatePayload: Array<any> = []; | |
for (let propKey in oldProps) { | |
if ( | |
newProps.hasOwnProperty(propKey) || | |
!oldProps.hasOwnProperty(propKey) || | |
oldProps[propKey] == null | |
) { | |
continue; | |
} | |
(updatePayload = updatePayload || []).push(propKey, null); | |
} | |
for (let propKey in newProps) { | |
const newProp = newProps[propKey]; | |
const oldProp = oldProps != null ? oldProps[propKey] : undefined; | |
if ( | |
!newProps.hasOwnProperty(propKey) || | |
newProp === oldProp || | |
(newProp == null && oldProp == null) | |
) { | |
continue; | |
} | |
if (propKey === CHILDREN) { | |
if ( | |
oldProp !== newProp && | |
(typeof newProp === 'string' || typeof newProp === 'number') | |
) { | |
(updatePayload = updatePayload || []).push(propKey, '' + newProp); | |
} | |
} else { | |
// For any other property we always add it to the queue and then we | |
// filter it out using the whitelist during the commit. | |
(updatePayload = updatePayload || []).push(propKey, newProp); | |
} | |
} | |
return updatePayload; | |
// return diffProperties(instance, type, oldProps, newProps, rootContainerInstance, hostContext); | |
}, | |
commitUpdate( | |
instance : Instance, | |
updatePayload : Array<mixed>, | |
type : string, | |
oldProps : Props, | |
newProps : Props, | |
internalInstanceHandle : Object, | |
) : void { | |
// Apply the diff to the DOM node. | |
// updateProperties(instance, updatePayload, type, oldProps, newProps); | |
/* for (var i = 0; i < updatePayload.length; i += 2) { | |
* var propKey = updatePayload[i]; | |
* var propValue = updatePayload[i + 1]; | |
* if (propKey === CHILDREN) { | |
* setTextContent(instance, propValue); | |
* } else { | |
* DOMPropertyOperations.setValueForProperty( | |
* domElement, | |
* propKey, | |
* propValue, | |
* ); | |
* } | |
* }*/ | |
}, | |
// commitMount is called after initializeFinalChildren *if* | |
// `initializeFinalChildren` returns true. | |
commitMount( | |
instance : Instance, | |
type : string, | |
newProps : Props, | |
internalInstanceHandle : Object | |
) { | |
log('commitMount'); | |
// noop | |
}, | |
getRootHostContext(rootContainerInstance : Container) : HostContext { | |
log('getRootHostContext'); | |
return emptyObject; | |
}, | |
getChildHostContext(parentHostContext : HostContext, type: string) : HostContext { | |
log('getChildHostContext'); | |
return emptyObject; | |
}, | |
getPublicInstance(instance : Instance | TextInstance) { | |
log('getPublicInstance', instance); | |
if (instance == null) { | |
return null; | |
} | |
return instance; | |
}, | |
prepareForCommit() : void { | |
log('prepareForCommit'); | |
// noop | |
}, | |
resetAfterCommit() : void { | |
log('resetAfterCommit'); | |
// noop | |
}, | |
shouldSetTextContent(props : Props): boolean { | |
const criteria = typeof props.children === 'string' || | |
typeof props.children === 'number' | |
return criteria; | |
}, | |
resetTextContent(instance : Instance) : void { | |
log('resetTextContent'); | |
// noop | |
}, | |
createTextInstance( | |
text : string, | |
rootContainerInstance : Container, | |
hostContext : HostContext, | |
internalInstanceHandle : OpaqueHandle | |
) : TextInstance { | |
const ctx = rootContainerInstance.getContext("2d"); | |
return ctx.fillText(text, 10, 50); | |
}, | |
commitTextUpdate( | |
textInstance : TextInstance, | |
oldText : string, | |
newText : string | |
) : void { | |
log('commitTextUpdate'); | |
// noop | |
throw new Error('commitTextUpdate should not be called'); | |
}, | |
scheduleAnimationCallback() { | |
log('scheduleAnimationCallback'); | |
}, | |
scheduleDeferredCallback() { | |
log('scheduleDeferredCallback'); | |
}, | |
useSyncScheduling: true, | |
}); | |
/** | |
* Our public renderer. When someone requires your renderer, this is all they | |
* should have access to. `render` and `unmountComponentAtNode` methods should | |
* be considered required, though that isn’t strictly true. | |
*/ | |
const defaultContainer = {}; | |
const Tiny = { | |
render( | |
element : React$Element<any>, | |
container : any, | |
callback : ?Function, | |
) { | |
const containerKey = typeof container === 'undefined' ? defaultContainer : container; | |
let root = roots.get(containerKey); | |
if (!root) { | |
root = TinyRenderer.createContainer(containerKey); | |
roots.set(container, root); | |
} | |
TinyRenderer.updateContainer((element : any), root, null, callback); | |
return TinyRenderer.getPublicRootInstance(root); | |
}, | |
unmountComponentAtNode(container : any) { | |
const containerKey = typeof container === 'undefined' ? defaultContainer : container; | |
const root = roots.get(containerKey); | |
if (root) { | |
TinyRenderer.updateContainer(null, root, null, () => { | |
roots.delete(container); | |
}); | |
} | |
}, | |
// other API methods you may support, such as `renderPortal()` | |
}; | |
const roots = new Map(); | |
const emptyObject = {}; | |
export default Tiny; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment