Adam Solove
Blog/UI engineering
14 Mar 2017

Preact internals #2: the component model

What makes React’s component model — composable, stateful, declarative — actually work, and how Preact implements both class-based and functional components

By Adam Solove · Published 14 Mar 2017 · Reading time 10 min

In the first post in this series, we started to build a mental model of the Preact codebase, explored some fringe code for utilities and global options, and saw how JSX becomes virtual dom trees. In this second post, we’ll look at the (P)react component model: what it is, how functional and class-based components work, and how the implementation is structured.

What (P)react is really about

Let’s start by dismissing a major misconception about the React UI model (and the parts of it implemented by Preact). I still frequently read that the main benefit of React is the performance benefit of using virtual DOM diffing to render HTML. Now, virtual DOM diffing is neat, but it’s just an enabling feature for React’s core idea, which is its component model.

In any view library, the component model sets the rules for how one programmer can build an encapsulated component and how someone else can use it from their code. In the React model, components are composable, stateful, and declarative. Let’s look at these three properties in turn:

Notice that if we want a composable component model, there is a tension between making it stateful and making it declarative. The trade-off is between the power given to a component’s author and the complexity demanded from its users. As the user of a stateful component, we have to instantiate it at the right time, manage its events and updates, and remember to destroy it. On the other hand, if we want to use a child component declaratively, avoiding all of that stateful bookkeeping, there doesn’t seem to be any way for it to store state or manage its lifecycle.

The React component model side-steps this apparent trade-off and gives us all the power of authoring components statefully and all the convenience of using them declaratively.

How is that possible? Remember that a component’s render method doesn’t build DOM nodes or create component instances, but instead only returns a vnode that describe how they could be made. The React runtime takes this description and the previously-rendered component instances and determines which vnodes represent new components to be instantiated, which map to existing components that should be updated, and which existing components don’t map to anything in the vnode and should be destroyed. The runtime manages this stateful process in a general and thoroughly-tested way, so our code doesn’t have to worry about it. The result is that a component’s author can write it statefully but its users can take advantage of it declaratively.

This is the big idea of React: composable components with stateful implementations but declarative usage. And all that complicated stateful bookkeeping code that our applications don’t need? It’s all been moved to the runtime, which coincidentally is the codebase that we’re trying to understand. It’s going to be fun.

Component types: class-based and functional components

The React component model has two types of components: objects that inherit from the base Component class and simple functions. As of now, functional components are always stateless, just receiving props and context as arguments and returning a virtual DOM node:

let Person = (props, _context) =>   <div>{props.name}</div>

We could build the same stateless behavior with a class:

class Person extends Component {
  render(props, _state, _context) {
    // In React, props/state/context are not passed as arguments,
    // but accessed on `this`. Preact adds them as arguments too.
    return <div>{props.name}</div>;
  }
}

We want these two components to have (mostly) the same behavior, but they need to be called in very different ways. How does the runtime code know which components are which?

Remember that each vnode has a nodeName, which can be either a string representing an HTML element (like “div”) or a function designating a component. If it’s a function, we need to know whether that function is a constructor for a Component class instance, or just a plain old pure functional component. To find out, src/vdom/functional-component.js defines isFunctionalComponent, which looks for a render method on the prototype of the function. For a class-based component, that method will exist. For functional components, it won’t.

The Component base class

The component above was stateless, just returning a vdom tree for its props. But we can also use class-based components to add local state and handle lifecycle callbacks. (The best guide to the full Component API is the React docs.) In this section, we’ll read through the declaration of the Preact Component class that your stateful components can extend.

The base Component is defined as an ES5-style constructor in src/component.js. This file contains the interface and a few required methods, but leaves out many optional lifecycle methods.

And that’s it for the Component class that you can extend. Most of the interesting component implementation lives in a separate file, src/vdom/component.js, which is responsible for managing component instances and calling their lifecycle methods at the right times.

Implementing the Component lifecycle

So far, we’ve described what the runtime does for us, and seen the stateful class that our components extend. But how does the runtime work? There is a lot of complexity to the implementation, so before reading the code let’s build a picture of the functions involved and what each of them does.

Throughout this implementation overview, we’re going to be dealing with DOM nodes, vnodes, and actual component instances, often representing the same abstract thing. To avoid confusion, I’ve added some color-coding to help us keep them straight: blue for DOM nodes, green for vnodes, and orange for component instances.

The process starts when we have a vnode tree that we want to diff with a dom node and put inside a parent node.

diff(dom, vnode, domParent)
diff(dom, vnode, domParent)

diff takes care of some bookkeeping and then hands control to a helper function idiff to actually do the diffing. After idiff is done, diff will make sure the updated node gets put into the right parent.

idiff(dom, vnode) mutates the DOM

idiff’s behavior depends on the type of vnode it’s given. If the vnode describes a regular HTML element, then idiff updates the DOM to have the right element and attributes. Once the current level of the DOM matches the current level of the vnode, idiff calls innerDiffNode to diff the DOM node’s children against the vnode’s children.

innerDiffNode(dom, vnodes) pairs & diffs children

For each of the child virtual nodes, innerDiffNode finds or creates a matching child node of the current DOM node. The matching process is a bit complex, and this is where the use of key on child components can make things a lot faster. Once the DOM and vnode children are paired up, the function calls idiff on them to change the child DOM to match the description.

Recursive calls between idiff and innerDiffNode can therefore change a whole tree of DOM nodes to match a whole tree of vnodes describing HTML elements. But what happens when idiff reaches a vnode that represents a component? It needs to know what content the component wants to render. To do that, it calls buildComponentFromVNode.

buildComponentFromVNode looks for an existing component instance that corresponds to the vnode or creates a new one. Once it has a component instance, it needs to set the vnode’s attributes as the new props on that instance, by calling setComponentProps.

setComponentProps sets instance props

setComponentProps does some bookkeeping to update the instance’s props while also keeping the old ones around. Now that the component instance has new data, it might want to update what it renders, so it calls renderComponent to actually manage the lifecycle callbacks and rendering.

renderComponent sets component content & calls lifecycle methods

renderComponent calls the lifecycle methods on the component instance to tell it that it will receive new props and ask if it wants to update. If so, renderComponent calls the instance’s render method to get the content it wants to display.

Now it has a new vnode of content to render into the DOM. How can it do that? Fortunately, we have a function for exactly that purpose: diff.

And that’s all. The circle of Preact is complete!

It looks like it might go on forever. But eventually all your components bottom out at rendering regular html elements, so eventually idiff will bottom out at making DOM changes rather than needing to get a component’s content.

I hope that this map gives you a solid high-level understanding of what happens inside Preact. We’ll regularly refer back to it as we drop into code to see how it works at a deeper level.

Meanwhile, you probably still have lots of questions because I’ve left out a lot of details. For example:

How Preact handles these complexities is precisely where we will pick up in the next post.

In the mean time, if you enjoyed this walkthrough of the React component model and would like to learn more about different models of UI programming, enjoy the appendex with links to lots more reading material:

Appendix: further reading in UI programming

In my description of the React component model, I may have misled you into thinking that it is an absolutely new and singular idea. This is completely untrue. If you are interested in other approaches to UI programming, you might be interested in:

Prefer a different component model I forgot to mention? Want to share other UI programming theory I ought to be aware of? Please leave a comment!


Originally published on Medium.