Closed wavesoft closed 7 years ago
Is this just onComponentUpdate or does it include all of React's life cycle methods?
Hmm good point.. onComponentUpdate
is actually a misleading name. I changed it.
Due to the severe lack of space, this is going to be a multi-functional function. The react equivalents would be:
React Function | .dom Equivalent |
---|---|
componentDidMount |
onDOMUpdate( domInstance ) |
componentWillUnmount |
onDOMUpdate( undefined ) |
I am working also on an alternative syntax, but unfortunately it's taking too much space.
React Function | .dom Equivalent |
---|---|
componentDidMount |
onDOMUpdate( newElement, 1 ) |
componentWillUnmount |
onDOMUpdate( previousElement, 0 ) |
componentDidUpdate |
onDOMUpdate( newElement, previousElement ) |
I am really open for discussion. This is not concrete yet and I would really like some feedback from all of you out there 😄
Instead of using a bunch of parameters for each lifecycle method, could you instead do something like the following:
const MyComponent = (props, state, setState, hook) {
// Hook into life cycle events
hook.componentDidMount(() => ready());
hook.componentWillMount(() => almostReady());
hook.componentWillReceiveProps(nextProps => changing(nextProps));
hook.shouldComponentUpdate((nextProps, nextState) => false);
// This is still your basic render
return div( ... )
}
This would allow you to gradually implement life cycle hooks and better match React's lifecycle.
The goal of this project is different then React obviously but I think trying to maintain a similar API might make it gain more traction.
I like the hook
idea. Perhaps a Proxy
element can do it's magic there. I will investigate 👍
But from the looks of it, lifecycle methods would most probably land on ~0.2.3
~ 0.3.0
@wavesoft Very cool! Keep up the great work! 💯 👍
I eventually ended up going with your idea @styfle . Unfortunately due to size limitations the naming had to be changed, so I used mnemonic letters instead:
.dom | React.js equivalent |
---|---|
.m | componentDidMount |
.u | componentWillUnmount |
.d | componentDidUpdate |
Very cool. I was thinking of making a thin React Compatibility layer so that existing React components could be utilized in DotDom projects. I haven't given it much thought beyond assigning window.React = { createElement: H }
I would say it's more or less what you said:
window.React = { createElement: H };
window.ReactDOM = { render: R };
Assuming that no other core API from React.js is used, nor class components 🤔
But I wouldn't spend too much time there. I would focus a bit more on writing a JSX transformation for .dom
, or figuring out other ways to reduce the size footprint of an application 😄
JSX transformation would be good if your app is only using .dom
and don't expect to use 3rd party components, because 3rd party components are likely already compiled from JSX to JS.
For comparison, there is a lib called Preact which has a similar API to React, They have an optional preact-compat lib to convert an existing React project to Preact. It might be nice to do the same for .dom
because optional doesn't have to be included in the size of the core lib.
Ah. I see your point. Yeah, that makes sense. Let's do this dot-dom-compat
! We might need to implement some trickery if we want to convert classes into methods if we want to support Component
instances.
Can you open an issue with your ideas so we can track the progress there? It's getting a bit out of the scope of this PR.
Makes sense. I created #26 👍
A problem that I see with this implementation of lifecycle methods is that it's not easy to create higher order components easily, if they also have to listen for lifecycle events.
For instance:
const A = (props, state, setState, hooks) => {
hooks.m = () => alert ( 'mounted' );
return div ('hello');
}
const AlertHi = (wrapComponent) =>
(props, state, setState, hooks) => {
hooks.m = () => alert ('hi');
return wrapComponent(props, state, setState, hooks);
}
const B = AlertHi(A);
// This will alert 'mounted'
R (H (B), document.body);
Note to self: unmount is not triggered when components change yet they return the same first-level vnode
@wavesoft what is the status on lifecycle methods? I want to say that while higher order components are important, basic onload and unload is probably even more important and pressing for the module. That would make it very useful for me. If these hooks are functional, I would say, we should push those and figure out higher order problems later.
Also I would prefer this notation, but I realize it may add boilerplate:
const MyComponent = (props, { state, setState, hook }) {
// Hook into life cycle events
hook.componentDidMount(() => ready());
hook.componentWillMount(() => almostReady());
hook.componentWillReceiveProps(nextProps => changing(nextProps));
hook.shouldComponentUpdate((nextProps, nextState) => false);
// This is still your basic render
return div( ... )
}
Same effect, but I can only grab what I need from the injected object. What if I dont need state or setState. I find this notation far better.
I really need the onload and unload lifecycle hooks lol.
I also tried to create a playground project so I can try this in a real world example and yeah, I felt the pain of higher order components @SilentCicero 😄
This branch contains a functional state of the lifecycle method implementation, so you can have a look, but I am still looking for a good solution to help creating higher-order components. If you have any idea, I am all ears 😄
@wavesoft I'll think about the higher order component design. How can I reach you btw, gitter?
The only other way would be to have a register method for the hooks, then when the hooks are triggerend all registered hooks are fired. Probably more bytes though..
So you just make m an array, m.push(() => {}), then it fires all m methods during life cycle changes.
Also, this comparison seems sketchy.. have you fully tested this, comparing objects with a single comparison is not necessarily a good idea: _child.a != _hooks.a..
You could also just fix the problem by policy:
const A = (props, state, setState, hooks) => {
hooks.m = hooks.m || (() => alert ( 'mounted' ));
return div ('hello');
}
const AlertHi = (wrapComponent) =>
(props, state, setState, hooks) => {
hooks.m = () => alert ('hi');
return wrapComponent(props, state, setState, hooks);
}
const B = AlertHi(A);
// This will alert 'mounted'
R (H (B), document.body);
That fixes the problem of overrides. You just make a policy of.. this is how you should use lifecycle methods. However, not all hooks get fired then.
You can also try this:
let createElement...
,z=(a=[],b,c)=>a.map(e=>e(b,c))
**approx. 31 byte increase above
_hooks={a:vnode.$,m:[],u:[],d:[]},
**approx. 15 byte increase above
z(
_child
? _child.a != _hooks.a
? (z(_child.u), _hooks.m)
: _hooks.d
: _hooks.m
,_new_dom, _child);
**approx. 9-12 byte savings above..
while (_children[_c]) { // The _c property keeps track of the number of
// elements in the VDom. If there are more child
// nodes in the DOM, we remove them.
z(_children[_c].u)
// elements that will be removed
render( // Remove child an trigger a recursive child removal
[], // in order to call the correct lifecycle methods in our
dom.removeChild(_children[_c]) // deep children too.
)
}
**approx. 4-5 byte savings above..
Here we assume the life cycle hooks are actually arrays of methods to be fired. This way, when we set the methods, we can set a push
potentially. However, the problem becomes, pushing the same hook multiple times. Although, I suppose that wouldn't happen as the function should only be called on re-render any way. But you would have to remove something like Proxy to make enough space for this (which I would be in favor of).
it would result in something like this, which would work I believe (in theory lol):
const A = (props, state, setState, hooks) => {
hooks.m.push(() => alert ( 'mounted' ));
return div ('hello');
}
const AlertHi = (wrapComponent) =>
(props, state, setState, hooks) => {
hooks.m.push(() => alert ('hi'));
return wrapComponent(props, state, setState, hooks);
}
const B = AlertHi(A);
// This will alert 'mounted'
R (H (B), document.body);
Yep.. just tested this.. works like a charm =D Both console logs fire. @wavesoft my solution above.
@SilentCicero what about making the hooks callable? That can spare a few client-side bytes. Like:
hooks.m(() => console.log('mounted'));
But I am not sure how much we can fit in 6 bytes that we have left 😄
I think I am satisfied with this. We are going to have lifecycle methods in 0.3.0
🎉
@wavesoft wooo!
This PR introduces a lifecycle method support that can be used to handle DOM mounts, unmounts and updates. This tries to be as close as possible to the react.js equivalent lifecycle methods, but be aware that they are more DOM-oriented in their arguments.
ADDED: An object as a fourth argument to the functional component that can be used to handle lifecycle events through callbacks. For example:
REMOVED:
globalState
. We are now using the object ofwrapClassProxy
as the global state. Since all paths are prefixed with a whitespace character no collisions should ever occur with theFunction
class methods and properties.REMOVED:
vnodeFlag
symbol. Instead we are using the symbol$
to identify virtual nodes. This should not cause any trouble to the users assuming they follow the guidelines.REMOVED:
$
from the string prototype. Now theH
function tests for.trim
to detect strings.CHANGED: Props in VNode from
.P
to.a
for size optimisationCHANGED: Children in VNode props from
.C
to.c
for size optimisationCHANGED: Local variables from being function arguments to locally-defined
let
variables for readability and size optimisation.