Ever since I pulled open the hood of Mithril.js in 2017, I have developed an interest in framework and library architecture. Poking around the source made me realise that the authors of these tools use the same language features I used daily, albeit at a much more advanced level. In the early days, when digging into codebases I followed a very passive approach of reading the code, inserting breakpoints here and there, and moving on. This has its benefits in that you can learn a lot by reading well written code. However, there comes a stage when that approach becomes boring or evolves into something more dynamic. So, in this short article I will share some of the ways I actively dig into source code.
The background to this post is that it came from me doing this tutorial. After completing it, I was curious to see how much code was needed to ensure class App extends React.Component
worked.
class App extends React.Component {
state = {
text: Date.now()
}
onButtonClick = () => {
this.setState(() => ({ text: Date.now() }))
}
render() {
// ...
}
}
ReactExperimentalRenderer.render(
<App />,
document.getElementById('root')
);
Using the simple app above, I went about my new adventure. Previously, I would have jumped straight into the code but I began by asking myself: What kind of object am I extending when I write class App extends React.Component
?. After jotting down some thoughts about expecting to find the setState
method and references to the createElement
function which turns JSX into React elements, I dove in.
The base class
In the aptly named ReactBaseClasses
file you will find the function below. The original function has a lot more comments but I've only left the ones relevant for this article:
function Component (props, context, updater) {
this.props = props;
this.context = context;
this.refs = {};
// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue ;
}
Component.prototype.isReactComponent = {};
Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState')
}
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
}
//...
export {Component, PureComponent};
You can use this code to come up with a list of questions. The aim is not to create an exhaustive list or to even answer all the questions. Instead, focus on learning how to ask good questions. The questions I came up with were:
- Why is
Component
a function and not an ES6 class? -
this.setState
calls another function which, according to the comments, is injected by the renderer. How does this happen and how is the updater implemented? -
function Component (props, context, updater) { /* ... */ }
is what we're extending when we writeclass App extends React.Component
. How doesextends
work? - Which precedence rule applies to the way we are passing
this.updater.enqueueSetState
athis
binding?
Why is React.Component a function and not an ES6 class?
I could not think of an answer for this so I asked Stack Overflow. The general consensus is that it was done to cater for environments which do not support ES6 classes. I expected a fancier reason but the responses reminded me that every piece of code you comes across does not have to be complicated.
What is the updater
?
This is the updater and this is where it is set. Unlike our previous question, this one requires some context. Normally, whenever I come across a rabbit hole I tend to jump in. However, that is not always fruitful because not every rabbit hole needs investigation. What you can do, though, is take a cursory glance at the code in the aforementioned hole and note down topics for future inquiry.
In this instance, you will come across linked lists. This can lead you to articles which explain React's usage of linked lists. You might also come across interesting tidbits whilst researching the usefulness of noop functions like ReactNoopUpdateQueue
.
How does the extends
keyword work?
In short, the extends
keyword is used to create subclasses. In our case, App
is a subclass of React.Component
. React creates an instance of App
and then the fun begins. And again, asking a question such as this leads you to more excellent writing.
What is this
doing?
Our click handler looks like this:
onButtonClick = () => {
this.setState( () => ({ text: Date.now() }) )
}
The setState
method like this:
Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
}
And this.updater.enqueueSetState
like this:
function enqueueSetState(inst, payload, callback) {
var fiber = get(inst);
// ...
}
Updates on a component are managed via the component's corresponding fiber object. At the time of the setState
call, the updater
property (which is an object) already exists on our App
component but we need to ensure enqueueSetState
is invoked within the context of App
. Fortunately, the this
context passed to enqueueSetState
is our App
component, so React uses it to get a reference to App
's fiber object via the get(inst)
call.
Also, notice that in our onButtonClick
method, we pass an anonymous function as the first argument to this.setState
. How is it treated by enqueueSetState
? This is how:
partialState = _payload2.call(instance, prevState, nextProps)
Using the .call
method gives React a reference to App
via the first argument passed in. Unlike the previous usage with enqueueSetState
where the this
binding was more implicit, using .call
makes it more explicit.
What next?
Interrogating source code in this manner is one of the best ways to improve your programming skills. How so? Well, let us list some of things learnt during the process above:
- The importance of thinking about which environment your code will run in and how this impacts the language features you choose
- A real life example of linked lists in action
- Noop functions
- In-depth reference material on ES6 classes
- Implicit and explicit ways of binding
this
In addition to increase your knowledge, reading the source code of a framework or library you use frequently also helps with things such as debugging or contributing to open source. For example, my first (and only commits) to the React source code have been typo fixes.
Top comments (2)
Nice! Would like to see the comparison with functional component.
Yes, I never looked at that but just this week I've been adding components to the virtual dom framework I am building. This is how I am handling the components and I imagine React probably does something similar (with more complexity, of course)