Skip to content

Instantly share code, notes, and snippets.

@riteshgurung
Forked from lpalmes/react.md
Created February 23, 2021 09:52
Show Gist options
  • Save riteshgurung/a34439a18f24f15bf9842dea74e8319e to your computer and use it in GitHub Desktop.
Save riteshgurung/a34439a18f24f15bf9842dea74e8319e to your computer and use it in GitHub Desktop.

Revisions

  1. @lpalmes lpalmes revised this gist Jan 1, 2019. 1 changed file with 17 additions and 19 deletions.
    36 changes: 17 additions & 19 deletions reconcilers.md
    Original file line number Diff line number Diff line change
    @@ -12,7 +12,7 @@ This has great benefits given that two instances can be checked referentially in

    #### 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, in this case state is part of an instance tree.
    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:
    @@ -36,7 +36,7 @@ let Counter = () => {
    React.render(<Counter />);
    ```

    If we reconcile immediatly after calling `setValue` we will have this workflow:
    If we reconcile immediatly after calling `setValue` we will have this computation:

    `setValue(1) -> reconcile -> setValue(2) -> reconcile -> setValue(3) -> reconcile`

    @@ -71,9 +71,9 @@ module UpdateLog = {
    ```

    This list of effects have great benefits, e.g:
    This list of effects has great benefits, e.g:

    Let's say we are rendering a tree of elements, and we arrive at two components that the subtree is fairly complex, this will allow us to reconcile those subTrees at the same time and get two logs of updates ready to be executed by the main thread.
    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.

    #### [Remote Actions](https://github.com/reasonml/reason-react/blob/master/ReactMini/src/React.re#L870)

    @@ -99,11 +99,11 @@ Just a nice module to have when testing a component.

    # [Brisk](https://github.com/briskml/brisk)

    A child of ReactMini with a lot of extra benefits.
    A child of ReactMini with lots of extra benefits.

    #### Host Implementation Functor

    This is similar to a [host config](https://github.com/facebook/react/tree/master/packages/react-reconciler#practical-examples) in react. Is defined by this module type:
    This is similar to a [host config](https://github.com/facebook/react/tree/master/packages/react-reconciler#practical-examples) in react. It's defined by this module type:

    ```reason
    module type HostImplementation = {
    @@ -126,11 +126,11 @@ module type HostImplementation = {
    };
    ```

    This means that we can create multiple implementations with different types, (macOS, iOS, Linux, Android, Windows, etc).
    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)
    #### Native Element (Third party elements | modules | components)

    This is a brilliant idea given that we can define new elements that have not been implemented (custom calendar view created in Cocoa, iOS Image compoonent with extra features, etc).
    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).

    ```reason
    type nativeElement('state, 'action) = {
    @@ -141,7 +141,7 @@ type nativeElement('state, 'action) = {
    }
    ```

    We can define a nativeElement almost like we define a statless,stateful,reducerComponent
    We can define a nativeElement almost like we define a statless ,stateful ,reducerComponent.

    ```reason
    module View = {
    @@ -173,7 +173,7 @@ More components in this [file](https://github.com/briskml/brisk/blob/master/rend

    #### Improved UpdateLog

    This implementation improves the idea of the UpdateLog in ReactMini with all the same benefits, but improving [the operations](https://github.com/briskml/brisk/blob/master/core/lib/ReactCore_Internal.re#L252) that can be executed.
    This implementation improves the idea of the UpdateLog in ReactMini with all the same benefits, but improving [the list of operations](https://github.com/briskml/brisk/blob/master/core/lib/ReactCore_Internal.re#L252) that can be executed.

    ```reason
    type subtreeChangeReact = [
    @@ -191,8 +191,6 @@ This implementation stands out for having Functional Components and Hooks as wel

    #### Functional Components and Hooks

    functional components are a very practical and simpler than ReasonReact components.

    ```reason
    let renderCounter = () => {
    let (count, dispatch) = useReducer(reducer, 0);
    @@ -209,7 +207,7 @@ module CounterButtons = (
    );
    ```

    The use of hooks make them as powerful as their Composite components (ReasonReact) counterpart.
    The use of hooks make them as powerful as their Composite components (ReasonReact records) counterpart.
    Algebraic effects (not yet) and [Linear Types](https://gist.github.com/cristianoc/cef37bcfc0446da482da4723dc3319a8) 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.
    @@ -220,7 +218,7 @@ Another excelent helper ppx can help with the creation of functional components

    #### Containers

    A great part of this reconciler, this will allow us to hook into the reconciler lifecycle.
    A great part of this reconciler. This will allow us to hook into the reconciler lifecycle.

    ```reason
    /*
    @@ -237,15 +235,15 @@ A great part of this reconciler, this will allow us to hook into the reconciler
    let updateContainer: (t, component) => unit;
    ```

    This will allow us to make the container aware about hot code reloading preserving the instance while re rendering the whole tree with our latests components using (Dynlink or Parcel).
    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 to have the same benfits as Reactjs context creation.
    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 style). If you have any ideas around it please let me know!
    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!

    # [Pure](https://github.com/lpalmes/pure)

    @@ -270,7 +268,7 @@ type fiber('state) = {
    }
    ```

    This uses a fiber record which has features as a list of efffects (ReactMini's UpdateLog), and a 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).
    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

  2. @lpalmes lpalmes created this gist Jan 1, 2019.
    140 changes: 140 additions & 0 deletions react.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,140 @@
    # React

    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:

    - Strings (`React.string("Hello friend")`)
    - nil/empty/null (`React.nil` | `React.null` | `React.empty`)
    - List/Array (`React.list([...])` | `React.array([|...|])`)
    - ReasonReact components (`let component = React.statelessComponent("Header")`)
    - Functional components with Hooks (`let header = () => <div>{React.string("I'm the Header")}</div>`)

    Both this expressions are equal:

    ```reason
    <div>{React.string("Hello")</div>
    Nested(div, [ Flat([String("Hello")]) ])
    ```

    ### ReasonReact components (Composite components)

    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`

    #### Stateless Component

    ```reason
    module Greeter = {
    let component = React.statelessComponent("Greeter");
    let createElement = (~name="", ~children, ()) => {
    ...component,
    render: _self =>
    <View>
    <Text>
    {React.string("Hello " ++ name ++ "!")}
    </Text>
    </View>,
    };
    };
    ```

    #### Reducer Component

    This example was taken from the ReasonReact documentation

    ```reason
    type state = {
    count: int,
    show: bool,
    };
    type action =
    | Click
    | Toggle;
    let component = React.reducerComponent("Example");
    let make = (~greeting, ~children, ()) => {
    initialState: () => {count: 0, show: true},
    reducer: (action, state) =>
    switch (action) {
    | Click => React.Update({...state, count: state.count + 1})
    | Toggle => React.Update({...state, show: !state.show})
    },
    render: self => {
    let message =
    "You've clicked this " ++ string_of_int(self.state.count) ++ " times(s)";
    <div>
    <button onClick=(_event => self.send(Click))>
    (React.string(message))
    </button>
    <button onClick=(_event => self.send(Toggle))>
    (React.string("Toggle greeting"))
    </button>
    (
    self.state.show
    ? React.string(greeting)
    : React.null
    )
    </div>;
    },
    };
    ```

    ### Functional Components (Hooks)

    So finally we are on the bleeding edge. Let's see how this components could work starting with a stateless component.

    ```reason
    let header = (~name, ~children, ()) => {
    print_endline("I'm hooked");
    <View>
    <Text>
    {React.string("Hello " ++ name ++ "!")}
    </Text>
    </View>
    };
    /* This ends up like */
    <Header name="Lorenzo" />
    ```

    What about some state?

    ```reason
    let Calculator3000 = (~children, ()) => {
    let (value, setValue) = useState(0);
    <View>
    <Button onClick={_ => setValue(value + 1)}>
    {React.string("Increment")}
    </Button>
    <Text>
    {React.string("Value: " ++ string_of_int(value))}
    </Text>
    <Button onClick={_ => setValue(value - 1)}>
    {React.string("Decrement")}
    </Button>
    </View>
    };
    ```
    285 changes: 285 additions & 0 deletions reconcilers.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,285 @@
    # Reconcilers

    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.

    # [ReactMini](https://github.com/reasonml/reason-react/tree/master/ReactMini/)

    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, 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.

    Let's say we have this function.

    ```reason
    let Counter = () => {
    let (value, setValue) = useState(0);
    <Button onClick={_ => {
    setValue(1);
    setValue(2);
    setValue(3);
    }}>
    {React.string(string_of_int(value))}
    </Button>
    };
    React.render(<Counter />);
    ```

    If we reconcile immediatly after calling `setValue` we will have this workflow:

    `setValue(1) -> reconcile -> setValue(2) -> reconcile -> setValue(3) -> reconcile`

    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

    `setValue(1) -> setValue(2) -> setValue(3) -> reconcile`

    #### Update Log

    This reconciler uses an UpdateLog (in React fiber this is more or less a [list of effects](https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiber.js#L154) that is produced by perfomUnitOfWork).

    ```reason
    module UpdateLog = {
    type update = {
    oldId: int,
    newId: int,
    oldOpaqueInstance: opaqueInstance,
    newOpaqueInstance: opaqueInstance,
    componentChanged: bool,
    stateChanged: bool,
    subTreeChanged: bool
    };
    type entry =
    | UpdateInstance(update)
    | NewRenderedElement(renderedElement);
    type t = ref(list(entry));
    let create = () => ref([]);
    let add = (updateLog, x) => updateLog := [x, ...updateLog^];
    };
    ```

    This list of effects have great benefits, e.g:

    Let's say we are rendering a tree of elements, and we arrive at two components that the subtree is fairly complex, this will allow us to reconcile those subTrees at the same time and get two logs of updates ready to be executed by the main thread.

    #### [Remote Actions](https://github.com/reasonml/reason-react/blob/master/ReactMini/src/React.re#L870)

    This is a little pearl of this implementation. We can test components by executing remote actions
    like in this [test](https://github.com/reasonml/reason-react/blob/master/ReactMini/src/Test.re#L328):

    ```reason
    startTest(~msg="Test Update on Alternate Clicks");
    let rAction = RemoteAction.create();
    let rendered0 = renderAndPrint(~msg="Initial", <UpdateAlternateClicks rAction />);
    RemoteAction.send(rAction, ~action=Click);
    let rendered1 = flushAndPrint(~msg="First click then flush", rendered0);
    RemoteAction.send(rAction, ~action=Click);
    let rendered2 = flushAndPrint(~msg="Second click then flush", rendered1);
    RemoteAction.send(rAction, ~action=Click);
    let rendered3 = flushAndPrint(~msg="Third click then flush", rendered2);
    RemoteAction.send(rAction, ~action=Click);
    let rendered4 = flushAndPrint(~msg="Fourth click then flush", rendered3);
    printAll([rendered0, rendered1, rendered2, rendered3, rendered4]);
    ```

    Just a nice module to have when testing a component.

    # [Brisk](https://github.com/briskml/brisk)

    A child of ReactMini with a lot of extra benefits.

    #### Host Implementation Functor

    This is similar to a [host config](https://github.com/facebook/react/tree/master/packages/react-reconciler#practical-examples) in react. Is defined by this module type:

    ```reason
    module type HostImplementation = {
    type hostView;
    let getInstance: int => option(hostView);
    let memoizeInstance: (int, hostView) => unit;
    let markAsDirty: unit => unit;
    let beginChanges: unit => unit;
    let mountChild:
    (~parent: hostView, ~child: hostView, ~position: int) => unit;
    let unmountChild: (~parent: hostView, ~child: hostView) => unit;
    let remountChild:
    (~parent: hostView, ~child: hostView, ~position: int) => unit;
    let commitChanges: unit => unit;
    };
    ```

    This means that we can create multiple implementations with different types, (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 implemented (custom calendar view created in Cocoa, iOS Image compoonent with extra features, etc).

    ```reason
    type nativeElement('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

    ```reason
    module View = {
    let component = statelessNativeComponent("NSView");
    let make = (~layout, ~style, children) => {
    ...component,
    render: _ => {
    make: () => {
    let view = NSView.make();
    {view, layoutNode: makeLayoutNode(~layout, view)};
    },
    shouldReconfigureInstance: (~oldState as _, ~newState as _) => true,
    updateInstance: (_self, {view}) => {
    let {red, green, blue, alpha} = style.backgroundColor;
    NSView.setBackgroundColor(view, red, green, blue, alpha);
    NSView.setBorderWidth(view, style.borderWidth);
    let {red, green, blue, alpha} = style.borderColor;
    NSView.setBorderColor(view, red, green, blue, alpha);
    },
    children,
    },
    };
    let createElement = (~layout, ~style, ~children, ()) =>
    element(make(~layout, ~style, listToElement(children)));
    };
    ```

    More components in this [file](https://github.com/briskml/brisk/blob/master/renderer_cocoa/lib/React_Components.re)

    #### Improved UpdateLog

    This implementation improves the idea of the UpdateLog in ReactMini with all the same benefits, but improving [the operations](https://github.com/briskml/brisk/blob/master/core/lib/ReactCore_Internal.re#L252) that can be executed.

    ```reason
    type subtreeChangeReact = [
    | `Nested
    | `NoChange
    | `Reordered
    | `PrependElement(renderedElement)
    | `ReplaceElements(renderedElement, renderedElement)
    ];
    ```

    # [Revery](https://github.com/revery-ui/reason-reactify)

    This implementation stands out for having Functional Components and Hooks as well as context that i have not seen in any other implementation.

    #### Functional Components and Hooks

    functional components are a very practical and simpler than ReasonReact components.

    ```reason
    let renderCounter = () => {
    let (count, dispatch) = useReducer(reducer, 0);
    <view>
    <button title="Decrement" onPress={() => dispatch(Decrement)} />
    <text> {"Counter: " ++ str(count)} </text>
    <button title="Increment" onPress={() => dispatch(Increment)} />
    </view>;
    };
    module CounterButtons = (
    val component((render, ~children, ()) => render(renderCounter, ~children))
    );
    ```

    The use of hooks make them as powerful as their Composite components (ReasonReact) counterpart.
    Algebraic effects (not yet) and [Linear Types](https://gist.github.com/cristianoc/cef37bcfc0446da482da4723dc3319a8) 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.
    ![Hooks ppx](https://user-images.githubusercontent.com/220424/50554391-ddbaca00-0cb9-11e9-98f8-78e34a501911.gif)

    Another excelent helper ppx can help with the creation of functional components in an elegant way.
    ![Functional component ppx](https://cdn.discordapp.com/attachments/526117811668647956/528166518341500931/letreve.gif)

    #### Containers

    A great part of this reconciler, this will allow us to hook into the reconciler lifecycle.

    ```reason
    /*
    Container API
    */
    type reconcileNotification = node => unit;
    let createContainer:
    (
    ~onBeginReconcile: reconcileNotification=?,
    ~onEndReconcile: reconcileNotification=?,
    node
    ) =>
    t;
    let updateContainer: (t, component) => unit;
    ```

    This will allow us to make the container aware about hot code reloading preserving the instance while re rendering the whole tree with our latests components using (Dynlink or Parcel).

    #### Context

    A great way to use context to have 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 style). If you have any ideas around it please let me know!

    # [Pure](https://github.com/lpalmes/pure)

    This project is an implementation of a reconciler a la fiber in Reason.

    This has some benefits considering that we can use a lot of the fiber features, but it comes at the cost of mutations.

    #### Fiber reconciler

    ```reason
    type fiber('state) = {
    tag: fiberTag,
    fiberType: option(Pure.Types.pureElement),
    parent: option(opaqueFiber),
    mutable state: option('state),
    mutable child: option(opaqueFiber),
    mutable sibling: option(opaqueFiber),
    alternate: option(opaqueFiber),
    mutable effectTag: option(effectTag),
    mutable stateNode: option(Config.hostNode),
    mutable effects: list(opaqueFiber),
    }
    ```

    This uses a fiber record which has features as a list of efffects (ReactMini's UpdateLog), and a 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](https://github.com/lpalmes/pure/blob/master/reconciler_plus_layout/reconciler_plus_layout.re) 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.

    Any thougths or ideas please share them here!