Skip to content

Commit

Permalink
Add components and setState
Browse files Browse the repository at this point in the history
  • Loading branch information
pomber committed May 22, 2017
1 parent 35619a0 commit 2e290ff
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 33 deletions.
27 changes: 26 additions & 1 deletion src/component.js
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@
export default class Component {}
import { reconcile } from "./reconciler";

export class Component {
constructor(props) {
this.props = props;
this.state = this.state || {};
}

setState(partialState) {
this.state = Object.assign({}, this.state, partialState);
updateInstance(this.__internalInstance);
}
}

function updateInstance(internalInstance) {
const parentDom = internalInstance.dom.parentNode;
const element = internalInstance.element;
reconcile(parentDom, internalInstance, element);
}

export function createPublicInstance(element, internalInstance) {
const { type, props } = element;
const publicInstance = new type(props);
publicInstance.__internalInstance = internalInstance;
return publicInstance;
}
18 changes: 7 additions & 11 deletions src/didact.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { createElement } from './element';
import Component from './component';
import { render } from './reconciler';
import { createElement } from "./element";
import { Component } from "./component";
import { render } from "./reconciler";

export default {
createElement,
Component,
render
createElement,
Component,
render
};

export {
createElement,
Component,
render
};
export { createElement, Component, render };
2 changes: 1 addition & 1 deletion src/element.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const TEXT_ELEMENT = "TEXT ELEMENT";
export const TEXT_ELEMENT = "TEXT ELEMENT";

export function createElement(type, config, ...args) {
const props = Object.assign({}, config);
Expand Down
64 changes: 44 additions & 20 deletions src/reconciler.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { updateDomProperties } from "./dom-utils";
import { TEXT_ELEMENT } from "./element";
import { createPublicInstance } from "./component";

let rootInstance = null;

Expand All @@ -8,7 +10,7 @@ export function render(element, container) {
rootInstance = nextInstance;
}

function reconcile(parentDom, instance, element) {
export function reconcile(parentDom, instance, element) {
if (instance == null) {
// Create instance
const newInstance = instantiate(element);
Expand All @@ -18,17 +20,27 @@ function reconcile(parentDom, instance, element) {
// Remove instance
parentDom.removeChild(instance.dom);
return null;
} else if (instance.element.type === element.type) {
// Update instance
} else if (instance.element.type !== element.type) {
// Replace instance
const newInstance = instantiate(element);
parentDom.replaceChild(newInstance.dom, instance.dom);
return newInstance;
} else if (typeof element.type === "string") {
// Update dom instance
updateDomProperties(instance.dom, instance.element.props, element.props);
instance.childInstances = reconcileChildren(instance, element);
instance.element = element;
return instance;
} else {
// Replace instance
const newInstance = instantiate(element);
parentDom.replaceChild(newInstance.dom, instance.dom);
return newInstance;
//Update composite instance
instance.publicInstance.props = element.props;
const childElement = instance.publicInstance.render();
const oldChildInstance = instance.childInstance;
const childInstance = reconcile(parentDom, oldChildInstance, childElement);
instance.dom = childInstance.dom;
instance.childInstance = childInstance;
instance.element = element;
return instance;
}
}

Expand All @@ -49,21 +61,33 @@ function reconcileChildren(instance, element) {

function instantiate(element) {
const { type, props } = element;
const isDomElement = typeof type === "string";

// Create DOM element
const isTextElement = type === "TEXT ELEMENT";
const dom = isTextElement
? document.createTextNode("")
: document.createElement(type);
if (isDomElement) {
// Instantiate DOM element
const isTextElement = type === TEXT_ELEMENT;
const dom = isTextElement
? document.createTextNode("")
: document.createElement(type);

updateDomProperties(dom, [], props);
updateDomProperties(dom, [], props);

// Instantiate and append children
const childElements = props.children || [];
const childInstances = childElements.map(instantiate);
const childDoms = childInstances.map(childInstance => childInstance.dom);
childDoms.forEach(childDom => dom.appendChild(childDom));
const childElements = props.children || [];
const childInstances = childElements.map(instantiate);
const childDoms = childInstances.map(childInstance => childInstance.dom);
childDoms.forEach(childDom => dom.appendChild(childDom));

const instance = { dom, element, childInstances };
return instance;
const instance = { dom, element, childInstances };
return instance;
} else {
// Instantiate component element
const instance = {};
const publicInstance = createPublicInstance(element, instance);
const childElement = publicInstance.render();
const childInstance = instantiate(childElement);
const dom = childInstance.dom;

Object.assign(instance, { dom, element, childInstance, publicInstance });
return instance;
}
}
39 changes: 39 additions & 0 deletions test/04.components.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import test from "ava";
import browserEnv from "browser-env";
/** @jsx createElement */
import { render, createElement, Component } from "../src/didact";

// Create document global var
browserEnv(["document"]);

test.beforeEach(t => {
let root = document.getElementById("root");
if (!root) {
root = document.createElement("div");
root.id = "root";
document.body.appendChild(root);
}
t.context.root = root;
});

test("render component", t => {
const root = t.context.root;
class FooComponent extends Component {
render() {
return <div><b /><a href="foo" /></div>;
}
}
render(<FooComponent />, root);
t.is(root.innerHTML, '<div><b></b><a href="foo"></a></div>');
});

test("render component with props", t => {
const root = t.context.root;
class FooComponent extends Component {
render() {
return <div><b>{this.props.name}</b><a href="foo" /></div>;
}
}
render(<FooComponent name="Bar" />, root);
t.is(root.innerHTML, '<div><b>Bar</b><a href="foo"></a></div>');
});
49 changes: 49 additions & 0 deletions test/05.set-state.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import test from "ava";
import browserEnv from "browser-env";
/** @jsx createElement */
import { render, createElement, Component } from "../src/didact";

// Create document global var
browserEnv(["document"]);

test.beforeEach(t => {
let root = document.getElementById("root");
if (!root) {
root = document.createElement("div");
root.id = "root";
document.body.appendChild(root);
}
t.context.root = root;
});

test("change state on click", t => {
const root = t.context.root;
class FooComponent extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}

handleClick() {
this.setState({
count: this.state.count + 1
});
}

render() {
return <div onClick={e => this.handleClick()}>{this.state.count}</div>;
}
}
render(<FooComponent />, root);
t.is(root.innerHTML, "<div>0</div>");
click(root.firstChild);
t.is(root.innerHTML, "<div>1</div>");
});

function click(dom) {
var evt = document.createEvent("MouseEvent");
evt.initEvent("click", false, true);
dom.dispatchEvent(evt);
}

0 comments on commit 2e290ff

Please sign in to comment.