You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is the base of all projects and it will include the foundation for all potential react-based projects in Reason.
This base package should include a ReasonReact api to promote collaboration and familiarity with people using a ReasonReact, and for the modern world of React this should also include a Hooks api that currently revery uses.
React module
All blocks in Jsx are of type React.reactElement. This reactElement should represent:
This component mimics the ReasonReact api, and is the closest of the primitives to a class component in React(js).
It provides multiple lifecycles:
didMount
didUpdate
willUnmount
willUpdate
shouldUpdate
A way to store and manipulate state:
initialState
reducer
And of course a render: self => reactElement function.
This function takes a record self (similar to this in js) that contains the state defined by the user and a send func to send actions to the reducer (a small state machine) and return a reactElement
After working and playing with multiple reconcilers, react implementations, etc i've been comparing most of them and looking through their weaknesses and strong points.
The first reconciler in the wild. As far as i know, it was an implementation of ReasonReact to check that the implementation was sound.
Immutable instance tree:
This has great benefits given that two instances can be checked referentially instead of structually and we can bail out reconcilation work as soon as we are sure two instances are equally by using ===. This also has great benefits in a future multicore ocaml implementation, given that immutable data structures can be used by multiple threards and return new instances.
State is in the instance record:
This is a great benefit in terms of creating a hot code reloadable reconciler given that, contrary to Reactjs class component, the state is not inside an oop object hidden and can't be extracted and in this case state is part of an instance tree.
This means that whenever we let instance = React.render(<Component/>) all state is in instance, and from here we can swap state, modify it, seralize it and share it between two computers via wifi, etc. This idea surely has it's pitfalls but is just mind blowing.
Pending updates list:
This has the benefit of aggregating updates and executing them in order.
In this case we will have 2 reconcile computations that are going to be in vain. We can easliy imagine how quickly this can get out of hand, let's imagine how for (i in 1 to 10000) { setValue(i) } can make this computation extremely expensive.
Pending updates will solve this by aggregating a list of updates. This will end up looking like
Let's say we are rendering a tree of elements and we arrive at two components that their subtrees are fairly complex, this will allow us to reconcile those subTrees at the same time (in differen threads) and get two logs of updates ready to be executed by the main thread.
This means that we can create multiple implementations with different types and host components (macOS, iOS, Linux, Android, Windows, etc).
Native Element (Third party elements | modules | components)
This is a brilliant idea given that we can define new elements that have not been defined in the library, but accept future components (custom calendar view created in Cocoa, iOS Image compoonent with extra features, etc).
typenativeElement('state, 'action) = {
make: unit =>Implementation.hostView,
updateInstance: (self('state, 'action),Implementation.hostView) => unit,
shouldReconfigureInstance: (~oldState:'state, ~newState:'state) => bool,
children: reactElement,
}
We can define a nativeElement almost like we define a statless ,stateful ,reducerComponent.
This implementation improves the idea of the UpdateLog in ReactMini with all the same benefits, but improving the list of operations that can be executed.
The use of hooks make them as powerful as their Composite components (ReasonReact records) counterpart.
Algebraic effects (not yet) and Linear Types makes them a novelty as well (still in research).
With the help of a ppx hooks can be safely typed and the compiler will take care of us using hooks properly.
Another excelent helper ppx can help with the creation of functional components in an elegant way.
Containers
A great part of this reconciler. This will allow us to hook into the reconciler lifecycle.
This will allow us to make the container aware about hot code reloading by preserving the instance while reconciling the whole tree with our latests components using (Dynlink for native or Parcel, Webpack HMR, etc).
Context
A great way to use context that has the same benfits as Reactjs context creation.
let ctx = useContext(testContext);
This context type is opaque, so any usage should be via an useContext instead of destructing the context, we still need to find a way of how this context could be use in a composite component (ReasonReact). If you have any ideas around it please let me know!
This uses a fiber record which has features as a list of efffects (ReactMini's UpdateLog), and a linked list of fibers that can be interrupted and resumed at any moment. This is a nice benefit but given how the ocaml multicore project is advancing we can replace most of this behaviour, in a type safe manner without reimplementing the runtime. While i don't see right now how the ReactMini implementation would be able to pause and resume work i know that multiple cores can outweight this feature (Main thread execution of effects and background threads doing reconcilation).
Reconciler composition
This module takes care of creating a parllel tree of Flexbox nodes. While still accepting a module to act as the normal reconciler functor. This attaches to the normal reconciler a layout that can be reused by multiple implementations to manage the layout and don't recreate layout per implementation.
This is a bit hacky right now, any ideas on how to improve this are welcome!
Final thoughts
While i'm really happy to have so many reconcilers/experiments and ideas all around i would love to have one reconciler to unify the project and make it stronger, battle tested and resuable by projects willing to conform to the React module that defines the components, hooks, element types, etc. In my opinion a reconciler following the steps of ReactMini/Brisk will be really great, adding support functional components, hooks, hot code reloading and native elements will make this a great reconciler to use between all projects.
Supporting two models (class-based and hooks-based model) or just one
I think that creating a shared interface between reconcilers will be very challenging on its own, let alone if we try to bake the two models React supports today: the class components, and the functional components + hooks. Both models are pretty much equivalent in their expressiveness, but very different on their API.
It seems that functional+hooks will get more momentum over time so I think I would prefer to bet just on that model for simplicity. I could be wrong though about that momentum.
Interface vs implementation
The Reason type system provides a way to define types interfaces, and we could leave to each reconciler the details on how they want to implement it. For example, the let%reve ppx is a purely implementation detail because Revery decided to use first-class modules to define components. But Brisk or Pure could settle in a totally different approach, if the "component definition" was only limited to the specification of the render function (i.e. one reconciler could wrap the function with a module, but another could wrap it with a record for example).
I tried to start defining in revery-ui/reason-reactify#35 (comment) the different parts of this interface. What would be the minimal interface that we could define so allow individual exploration in the different reconcilers, but still while allowing for a meaningful % of user-created components to be reused across the reconcilers? Does that interface even exist?
Find a balance between sharing too much vs too few
These are some of the areas where I think we could find a shared interface definition, but it's challenging to do so without restricting too much each reconciler evolution:
Dual models (class vs function+hooks), as mentioned above
Shared hooks APIs: supposing we include the function+hooks into the shared interface, maybe we could just follow ReactJS on thisto play it safe
Shared "fixed" props: like style or children: for any meaningful % of components being reused, we probably want to define these
Minimal set of shared component primitives (View, Text, Image,... what else?)
Minimal Layout API (part of fixed prop style): should it be included?
Minimal Event API
Fiber vs non-fiber: Does a fiber implementation have any impact on the minimal shared interface?
@wokalski the nativeElement is a brilliant piece, is a great modular way to handle new components as well. And i think you are right, as you told me a few months ago, the update log can give us so much of the fiber behavior and more, so that's why i think a reconciler based on ReactMini is the way to go.
This might sound like a crazy idea but the fact that is doable totally blows my mind.
Whenever you have a button is common to have a handler that updates state and that makes changes in the UI (let's say selecting a different tab),
so whenever we detect a onHover event from the user we can work the changes needed, and when the user clicks on the button just apply those changes (the result UpdateLog from the onHover event) and swap the current instance tree, as long as we have a way to do background thread reconciling.
This use case might be a micro optimization and kind of silly, but i'm sure there are millons of great use cases here.
For completion purposes, we are discussing something along these lines in revery-ui/reason-reactify#35.
Some thoughts:
Supporting two models (class-based and hooks-based model) or just one
I think that creating a shared interface between reconcilers will be very challenging on its own, let alone if we try to bake the two models React supports today: the class components, and the functional components + hooks. Both models are pretty much equivalent in their expressiveness, but very different on their API.
It seems that functional+hooks will get more momentum over time so I think I would prefer to bet just on that model for simplicity. I could be wrong though about that momentum.
Interface vs implementation
The Reason type system provides a way to define types interfaces, and we could leave to each reconciler the details on how they want to implement it. For example, the
let%reve
ppx is a purely implementation detail because Revery decided to use first-class modules to define components. But Brisk or Pure could settle in a totally different approach, if the "component definition" was only limited to the specification of therender
function (i.e. one reconciler could wrap the function with a module, but another could wrap it with a record for example).I tried to start defining in revery-ui/reason-reactify#35 (comment) the different parts of this interface. What would be the minimal interface that we could define so allow individual exploration in the different reconcilers, but still while allowing for a meaningful % of user-created components to be reused across the reconcilers? Does that interface even exist?
Find a balance between sharing too much vs too few
These are some of the areas where I think we could find a shared interface definition, but it's challenging to do so without restricting too much each reconciler evolution:
style
orchildren
: for any meaningful % of components being reused, we probably want to define theseView
,Text
,Image
,... what else?)Layout
API (part of fixed propstyle
): should it be included?Event
API