Skip to content

Commit 2e290ff

Browse files
committed
Add components and setState
1 parent 35619a0 commit 2e290ff

File tree

6 files changed

+166
-33
lines changed

6 files changed

+166
-33
lines changed

src/component.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,26 @@
1-
export default class Component {}
1+
import { reconcile } from "./reconciler";
2+
3+
export class Component {
4+
constructor(props) {
5+
this.props = props;
6+
this.state = this.state || {};
7+
}
8+
9+
setState(partialState) {
10+
this.state = Object.assign({}, this.state, partialState);
11+
updateInstance(this.__internalInstance);
12+
}
13+
}
14+
15+
function updateInstance(internalInstance) {
16+
const parentDom = internalInstance.dom.parentNode;
17+
const element = internalInstance.element;
18+
reconcile(parentDom, internalInstance, element);
19+
}
20+
21+
export function createPublicInstance(element, internalInstance) {
22+
const { type, props } = element;
23+
const publicInstance = new type(props);
24+
publicInstance.__internalInstance = internalInstance;
25+
return publicInstance;
26+
}

src/didact.js

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
import { createElement } from './element';
2-
import Component from './component';
3-
import { render } from './reconciler';
1+
import { createElement } from "./element";
2+
import { Component } from "./component";
3+
import { render } from "./reconciler";
44

55
export default {
6-
createElement,
7-
Component,
8-
render
6+
createElement,
7+
Component,
8+
render
99
};
1010

11-
export {
12-
createElement,
13-
Component,
14-
render
15-
};
11+
export { createElement, Component, render };

src/element.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const TEXT_ELEMENT = "TEXT ELEMENT";
1+
export const TEXT_ELEMENT = "TEXT ELEMENT";
22

33
export function createElement(type, config, ...args) {
44
const props = Object.assign({}, config);

src/reconciler.js

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { updateDomProperties } from "./dom-utils";
2+
import { TEXT_ELEMENT } from "./element";
3+
import { createPublicInstance } from "./component";
24

35
let rootInstance = null;
46

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

11-
function reconcile(parentDom, instance, element) {
13+
export function reconcile(parentDom, instance, element) {
1214
if (instance == null) {
1315
// Create instance
1416
const newInstance = instantiate(element);
@@ -18,17 +20,27 @@ function reconcile(parentDom, instance, element) {
1820
// Remove instance
1921
parentDom.removeChild(instance.dom);
2022
return null;
21-
} else if (instance.element.type === element.type) {
22-
// Update instance
23+
} else if (instance.element.type !== element.type) {
24+
// Replace instance
25+
const newInstance = instantiate(element);
26+
parentDom.replaceChild(newInstance.dom, instance.dom);
27+
return newInstance;
28+
} else if (typeof element.type === "string") {
29+
// Update dom instance
2330
updateDomProperties(instance.dom, instance.element.props, element.props);
2431
instance.childInstances = reconcileChildren(instance, element);
2532
instance.element = element;
2633
return instance;
2734
} else {
28-
// Replace instance
29-
const newInstance = instantiate(element);
30-
parentDom.replaceChild(newInstance.dom, instance.dom);
31-
return newInstance;
35+
//Update composite instance
36+
instance.publicInstance.props = element.props;
37+
const childElement = instance.publicInstance.render();
38+
const oldChildInstance = instance.childInstance;
39+
const childInstance = reconcile(parentDom, oldChildInstance, childElement);
40+
instance.dom = childInstance.dom;
41+
instance.childInstance = childInstance;
42+
instance.element = element;
43+
return instance;
3244
}
3345
}
3446

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

5062
function instantiate(element) {
5163
const { type, props } = element;
64+
const isDomElement = typeof type === "string";
5265

53-
// Create DOM element
54-
const isTextElement = type === "TEXT ELEMENT";
55-
const dom = isTextElement
56-
? document.createTextNode("")
57-
: document.createElement(type);
66+
if (isDomElement) {
67+
// Instantiate DOM element
68+
const isTextElement = type === TEXT_ELEMENT;
69+
const dom = isTextElement
70+
? document.createTextNode("")
71+
: document.createElement(type);
5872

59-
updateDomProperties(dom, [], props);
73+
updateDomProperties(dom, [], props);
6074

61-
// Instantiate and append children
62-
const childElements = props.children || [];
63-
const childInstances = childElements.map(instantiate);
64-
const childDoms = childInstances.map(childInstance => childInstance.dom);
65-
childDoms.forEach(childDom => dom.appendChild(childDom));
75+
const childElements = props.children || [];
76+
const childInstances = childElements.map(instantiate);
77+
const childDoms = childInstances.map(childInstance => childInstance.dom);
78+
childDoms.forEach(childDom => dom.appendChild(childDom));
6679

67-
const instance = { dom, element, childInstances };
68-
return instance;
80+
const instance = { dom, element, childInstances };
81+
return instance;
82+
} else {
83+
// Instantiate component element
84+
const instance = {};
85+
const publicInstance = createPublicInstance(element, instance);
86+
const childElement = publicInstance.render();
87+
const childInstance = instantiate(childElement);
88+
const dom = childInstance.dom;
89+
90+
Object.assign(instance, { dom, element, childInstance, publicInstance });
91+
return instance;
92+
}
6993
}

test/04.components.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import test from "ava";
2+
import browserEnv from "browser-env";
3+
/** @jsx createElement */
4+
import { render, createElement, Component } from "../src/didact";
5+
6+
// Create document global var
7+
browserEnv(["document"]);
8+
9+
test.beforeEach(t => {
10+
let root = document.getElementById("root");
11+
if (!root) {
12+
root = document.createElement("div");
13+
root.id = "root";
14+
document.body.appendChild(root);
15+
}
16+
t.context.root = root;
17+
});
18+
19+
test("render component", t => {
20+
const root = t.context.root;
21+
class FooComponent extends Component {
22+
render() {
23+
return <div><b /><a href="foo" /></div>;
24+
}
25+
}
26+
render(<FooComponent />, root);
27+
t.is(root.innerHTML, '<div><b></b><a href="foo"></a></div>');
28+
});
29+
30+
test("render component with props", t => {
31+
const root = t.context.root;
32+
class FooComponent extends Component {
33+
render() {
34+
return <div><b>{this.props.name}</b><a href="foo" /></div>;
35+
}
36+
}
37+
render(<FooComponent name="Bar" />, root);
38+
t.is(root.innerHTML, '<div><b>Bar</b><a href="foo"></a></div>');
39+
});

test/05.set-state.test.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import test from "ava";
2+
import browserEnv from "browser-env";
3+
/** @jsx createElement */
4+
import { render, createElement, Component } from "../src/didact";
5+
6+
// Create document global var
7+
browserEnv(["document"]);
8+
9+
test.beforeEach(t => {
10+
let root = document.getElementById("root");
11+
if (!root) {
12+
root = document.createElement("div");
13+
root.id = "root";
14+
document.body.appendChild(root);
15+
}
16+
t.context.root = root;
17+
});
18+
19+
test("change state on click", t => {
20+
const root = t.context.root;
21+
class FooComponent extends Component {
22+
constructor(props) {
23+
super(props);
24+
this.state = {
25+
count: 0
26+
};
27+
}
28+
29+
handleClick() {
30+
this.setState({
31+
count: this.state.count + 1
32+
});
33+
}
34+
35+
render() {
36+
return <div onClick={e => this.handleClick()}>{this.state.count}</div>;
37+
}
38+
}
39+
render(<FooComponent />, root);
40+
t.is(root.innerHTML, "<div>0</div>");
41+
click(root.firstChild);
42+
t.is(root.innerHTML, "<div>1</div>");
43+
});
44+
45+
function click(dom) {
46+
var evt = document.createEvent("MouseEvent");
47+
evt.initEvent("click", false, true);
48+
dom.dispatchEvent(evt);
49+
}

0 commit comments

Comments
 (0)