- Toughts and doubts
- Reactivity
Current implementationComponent based implementation
-
displayName
-
defaultProps
-
memo
-
Ref's
Components created with composition will not have a displayName
to use on dev tools, making it harder to debug.
I guess a babel plugin could be done to make it add a MyComponent.displayName='MyComponent';
automaticly
To make defaultProps to work on composition components we have to use plain old .defaultProps
like we use in class Component's:
const MyComponent = createComponent(c => {
console.log(c.props);
return props => <div>{props.hello}</div>;
});
MyComponent.defaultProps = {
hello: 'Hey there'
};
Probably we could also accept optionally an object on createComponent
with some options like:
const MyComponent = createComponent({
defaultProps: {
hello: 'Hey there'
},
displayName: 'Mycomponent',
setup: c => {
console.log(c.props);
return props => <div>{props.hello}</div>;
}
});
There is an implementation of memo
that can make a composition component skip renders if nothing changes. By default it checks props only using shallowDiffers
, we can pass a function to specify what you really want to check, kinda like how memo
in React works. For that reason, probably it should be named differently to not make confusion with memo
from preact/compat
maybe?
const MyComponent = createComponent(c => {
memo();
return props => <div>{props.hello}</div>;
});
Or if we accept the options object on createComponent
maybe we can pass memo: true
or a function to custom check props?
const MyComponent = createComponent({
memo: true,
setup: c => {
return props => <div>{props.hello}</div>;
}
});
Either way, memo
will only check props, there is no way currently to skip render on state change. Actually exactly how memo
in React works.
ref's in React always seems like a second class citizen, in fact we should avoid it when we can, but when you are defining a component library, you never know if the other developer will need to use ref's on your FancyButton
so we end up doing forwardRef
on every component. Because of that i decided to implement the components in Composition API to always pass ref
as a second parameter on the render function.
The problem is if that ref is not assigned to any child, and you pass a ref to this component, that ref will be lost in space and will never be assigned on an error be trowed. So something must be done to make this more clear. Either some way to know that the ref if going to be needed and if not fallback to the default (pass the instance of the Component):
const MyComponent = createComponent(c => {
const ref = refForwarded();
return (props, ref) => <div ref={ref}>{props.hello}</div>;
});
Or add an option to it like:
const MyComponent = createComponent({
ref: true,
setup: c => {
return (props, ref) => <div ref={ref}>{props.hello}</div>;
}
});
Apart from that, there is a something i dislike of the current implementation of the ref forwarding system.
Ref is removed from props on vnode
https://github.com/preactjs/preact/blob/master/src/create-element.js#L15
Then added back again on composition vnode hook:
https://github.com/preactjs/preact/pull/1923/files#diff-23369f01c5ea98c3654a47ca1abd2de7R7-R17
And extracted again before calling render:
https://github.com/preactjs/preact/pull/1923/files#diff-23369f01c5ea98c3654a47ca1abd2de7R33-R39
This looks a bit hackish to me and i wish to find a way to simplify this in a way that makes this more direct. Maybe a tiny concept of forward ref on core like this:
https://github.com/preactjs/preact/pull/2156/files#diff-9722053b2275277f66f76c1671e478bfR18
Or have some way deal with ref's, like some hook or something...
My first implementation didn't add ref back on the props, used a different variable, but then there was no way to check in on shouldComponentUpdate
and i needed to check if ref changed on memo
, maybe if shouldComponentUpdate
could know what is the nextVNode to be able to compare the ref with the oldVNode, that i can acces in this._vnode
Now either effect
or watch
will have it's sources evaluated before each render to see if they need to update/execute the callback.
That needs to be done because one of its sources may be a function that return a diferent value based on props. So if the component rerender because the prop change, the watch/effect
will be executed, or not if it didn't.
But watch/effect
may not have props watcher, it may be watching some value
or reactive
or even the return of another watch
! So i tought about creating a more observable approach. The return of value
, reactive
and watch
will return an object that can be observable so any watch/effect
using it as a source will only be executed when they actually change and not on every render. It also need to keep track of every watcher (it already does so...) and on unmount unobserve all sources being watched to avoid memory leaks.
But that keeps 2 things out of this approach: functions that return prop and context (from createContext
)
To fix this, we need a way to make them reactive, it can be done keeping the same API and do that behind, or separate it and create a specific API for this:
const SomeContext = createContext('context');
const MyComponent = createComponent(c => {
const someReactiveProp = watchProp(() => c.props.hello);
const someReactiveContext = watchContext(SomeContext);
effect(
[someReactiveProp, someReactiveContext],
([prop, ctx]) => prop + ' ' + ctx
);
return props => <div>{props.hello}</div>;
});
Maybe at some point we can also add watchObjervable
to make this work with RxJS observables.
At the moment functions passed to watch/effect
receive the current props, thats nice but makes it impossible to play nice with TypeScript, so i think it might be simpler to get the props from c
arg passed to createComponent
function, that can be easly typed