Build your own React, part 2: Class and Functional Component
Index:
2. Part 2: Class and Functional Component
In this second part of the series, I will add support for Class and Functional Component. We will start with the simpler one:
Functional component
Functional component in React is very simple. It’s a function that received a props
variable and return a React element, it has no state.
Therefore, in our createInstance
function, we just need to add another condition to check if the type is a function or not.
If it is, we will call it.
let createInstance = (type, props) => {
// ... (omitted)
if (typeof type == 'function') {
let chilElement = type(props);
return createInstance(chilElement.type, chilElement.props);
}
// ... (omitted)
}
this enables us to write code like this
let Form = (props) => {
return <div><label><input name="answer" type="checkbox"/></label></div>;
}
next, let’s implement Class Component, a slightly more complicated type.
Class component
If you have used React, you would be familiar with class component. Its usage can be like
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render(){
return <div><label><input type="checkbox" name="selection"/>Check</label></div>;
}
}
so in my own React library, I also need to implement a Component class so that it can be inherited.
let React = (function(){
class Component {
constructor(props) {
this.props = props;
this.state = {};
}
}
return { render, createElement, Component }
})();
then in our createInstance
, we just need to add another condition to check if it’s a Class Component.
However,
we cannot use the usual typeof
to check because class and function both have the same type, which is function
.
So, to differ between these two component, we will set a variable of React.Component something like:
Component.prototype.isReactComponent = true;
// Helper function to check for React Class
let isReactClass = (type) => type.prototype && type.prototype.isReactComponent;
Simple as that, we add another condition in our createInstance
let createInstance = (type, props) => {
// omitted
if (typeof type == 'object') {
if (isReactClass(type)) {
let instance = new type(props);
let childElement = instance.render();
let childNode = createInstance(childElement.type, childElement.props);
instance.dom = childNode; // explanation below
return childNode;
} else { // functional component
// ... omitted
}
}
}
Re-render when state change
One missing piece of class component is state and re-render after a state change.
Let’s add setState
function.
class Component {
constructor(props) {
this.props = props;
this.state = {};
}
setState(partialState) {
// we don't want to mutate current state,
// so create a new state and assign it to this.state
this.state = Object.assign({}, this.state, partialState);
// re-render
const element = this.render();
const node = createInstance(element.type, element.props);
const parent = this.dom.parentNode; // this.dom is the one assigned in createInstance
parent.replaceChild(node, this.dom)
this.dom = node;
}
}
Our re-render mechanism is very simple, we call createInstance
to create a new DOM then use replaceChild
to replace it to the old one. We also need to keep track of the DOM node that was rendered; hence, the assignment this.dom = node
.
So, our React works but not quite performant because it will create a new DOM everytime we change state. In reality, we need another step to decide which DOM we can reuse and which needed to create given current props and new props. This step is called reconciliation and is what will be implemented in my next post.
Anyway, the full code of this part is at codepen