Didact: Components and State
[Update] This post is based on the old React architecture, there’s a new self-contained post where we build everything from scratch including hooks, concurrent mode, fibers, etc.
This story is part of a series where we build our own version of React step by step:
The code from the last post has some problems:
- Every change triggers the reconciliation on the full virtual DOM tree
- State is global
- We need to explicitly call the
render
function after changes to the state
Components help us solve these issues and also let us:
- Define our own “tags” for JSX
- Hook to lifecyle events (not included on this post)
First thing we need to do is provide the Component
base class that components will extend. We need a constructor with a props
parameter and a setState
method that receives a partialState
that we will use to update the component state:
The application code will extend this class and then use it in the same way that other element types, like div
or span
, are used: <MyComponent/>
. Note that we don’t need to change anything in our createElement
function, it will keep the component class as the type
of the element and handle props
as usual. We do need a function that creates the component instance (we’ll call them public instances) given an element:
In addition to creating the public instance we are keeping a reference to the internal instance (from the virtual DOM) that triggered the instantiation of the component, we need it to be able to update only the instance sub-tree when the public instance state changes:
We also need to update the instantiate
function. For components we need to create the public instance and call the component’s render
function to get a child element that we’ll pass again to instantiate
:
The internal instances for component elements and dom elements are different. Component internal instances can only have one child (returned from render
) so they have the childInstance
property instead of the childInstances
array that dom instances have. Also, component internal instances need to have a reference to the public instance so the render
function can be called during the reconciliation.
The only thing missing is handling the component instances reconciliation, so we will add one more case to our reconciliation algorithm. Given that component instances can only have one child we don’t need to handle children reconciliation, we just update the props
of the public instance, re-render the child and reconcile it:
And that’s all, we now support components. I’ve updated the codepen from last time to use them. The app code looks like this:
Using component enabled us to create our own “JSX tags”, encapsulate the component state, and run the reconciliation algorithm only on the affected sub-tree:
The last codepen use the full code from the whole series. You can review the changes to Didact from this post on this commit.
For now, this will be the last post on the series, but if there’s interest on other features, like component life cycle methods, reconciliation by key or functional components, please leave a comment and I’ll try to extend the series and Didact to include them.
Update 10/19/2017: the story about incremental reconciliation (a.k.a. Fiber) is out!
Follow us on Medium or Twitter to be notified when new stories are posted.
Thanks for reading.