├── components
├── funcComponent.js
├── pureComponent.js
└── component.js
├── .gitignore
├── .babelrc
├── index.js
├── package.json
├── react
└── index.js
├── README.md
├── index.html
├── reactDom
├── handleAttrs.js
└── index.js
├── app.js
└── test.js
/components/funcComponent.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/pureComponent.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /dist
3 | /.cache
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env"],
3 | "plugins": [
4 | ["transform-react-jsx", {
5 | "pragma": "React.createElement"
6 | }]
7 | ]
8 | }
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import React from './react';
2 | import ReactDom from './reactDom';
3 | import App from './app'
4 | ReactDom.render(
5 | ,
6 | document.querySelector('#root')
7 | );
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spa",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "devDependencies": {
7 | "@babel/core": "^7.5.5",
8 | "babel-plugin-transform-react-jsx": "^6.24.1",
9 | "babel-preset-env": "^1.7.0"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/react/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from '../components/component';
2 | const React = {};
3 | React.Component = Component;
4 | React.createElement = function(tag, attrs, ...children) {
5 | return {
6 | tag,
7 | attrs,
8 | children
9 | };
10 | };
11 | export default React;
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mini-react
2 | 这是一个迷你版 react,This is a mini-react
3 |
4 | # 欢迎来到`mini-react`
5 |
6 | 使用步骤 :
7 |
8 | * `npm install -g parcel-bundler`
9 |
10 | * `parcel index.html`
11 |
12 | 本仓库一共三个分支:
13 |
14 | * `master` - 最简单的版本
15 |
16 | * `diff` - 加入`diff`算法版本
17 |
18 | * `diff-async` 异步`state`和`diff`算法
19 |
20 |
21 |
22 | 如果有任何问题欢迎联系 `453089136@qq.com`
23 |
24 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
14 |
15 |
17 |
18 |
--------------------------------------------------------------------------------
/reactDom/handleAttrs.js:
--------------------------------------------------------------------------------
1 | export default function setAttribute(dom, name, value) {
2 | if (name === 'className') name = 'class';
3 | if (/on\w+/.test(name)) {
4 | name = name.toLowerCase();
5 | dom[name] = value || '';
6 | } else if (name === 'style') {
7 | if (!value || typeof value === 'string') {
8 | dom.style.cssText = value || '';
9 | } else if (value && typeof value === 'object') {
10 | for (let name in value) {
11 | dom.style[name] =
12 | typeof value[name] === 'number' ? value[name] + 'px' : value[name];
13 | }
14 | }
15 | } else {
16 | if (name in dom) {
17 | dom[name] = value || '';
18 | }
19 | if (value) {
20 | dom.setAttribute(name, value);
21 | } else {
22 | dom.removeAttribute(name);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | import React from './react/index';
2 | export default class App extends React.Component {
3 | constructor(props) {
4 | super(props);
5 | this.state = {
6 | test: 'init'
7 | };
8 | this.test=[1,2,3]
9 | }
10 |
11 | handleClick(e){
12 | this.setState({
13 | test:'test'
14 | })
15 | }
16 | componentDidMount(){
17 | console.log('mount')
18 | this.setState({
19 | test:'mount'
20 | })
21 | }
22 | componentWillMount(){
23 | console.log('willMount')
24 | }
25 | componentWillUpdate(){
26 | console.log('willupdate')
27 | }
28 | componentDidUpdate(){
29 | console.log('didupdate')
30 | }
31 | render() {
32 | return
33 | {this.state.test}
34 |
35 |
;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/reactDom/index.js:
--------------------------------------------------------------------------------
1 | import handleAttrs from './handleAttrs';
2 | import { createComponent, setComponentProps } from '../components/component';
3 | const ReactDom = {};
4 |
5 | const render = function(vnode, container) {
6 | return container.appendChild(_render(vnode));
7 | };
8 |
9 | ReactDom.render = render;
10 | export function _render(vnode) {
11 | console.log('_render');
12 | if (vnode === undefined || vnode === null || typeof vnode === 'boolean')
13 | vnode = '';
14 |
15 | if (typeof vnode === 'number') vnode = String(vnode);
16 |
17 | if (typeof vnode === 'string') {
18 | let textNode = document.createTextNode(vnode);
19 | return textNode;
20 | }
21 | if (typeof vnode.tag === 'function') {
22 | const component = createComponent(vnode.tag, vnode.attrs);
23 | setComponentProps(component, vnode.attrs);
24 | return component.base;
25 | }
26 |
27 | const dom = document.createElement(vnode.tag);
28 |
29 | if (vnode.attrs) {
30 | Object.keys(vnode.attrs).forEach(key => {
31 | const value = vnode.attrs[key];
32 | handleAttrs(dom, key, value);
33 | });
34 | }
35 |
36 | vnode.children && vnode.children.forEach(child => render(child, dom)); // 递归渲染子节点
37 |
38 | return dom;
39 | }
40 |
41 |
42 |
43 |
44 | export default ReactDom;
45 |
--------------------------------------------------------------------------------
/components/component.js:
--------------------------------------------------------------------------------
1 | import { _render } from '../reactDom/index';
2 | export class Component {
3 | constuctor(props = {}) {
4 | this.state = {};
5 | this.props = props;
6 | }
7 | setState(stateChange) {
8 | // 将修改合并到state
9 | console.log('setstate');
10 | const result = Object.assign(this.state, stateChange);
11 | console.log('state:', result);
12 | renderComponent(this);
13 | }
14 | }
15 | export function createComponent(component, props) {
16 | let inst;
17 | // 如果是类定义组件,则直接返回实例
18 | if (component.prototype && component.prototype.render) {
19 | inst = new component(props);
20 | // 如果是函数定义组件,则将其扩展为类定义组件
21 | } else {
22 | inst = new Component(props);
23 | inst.constructor = component;
24 | inst.render = function() {
25 | return this.constructor(props);
26 | };
27 | }
28 |
29 | return inst;
30 | }
31 | export function setComponentProps(component, props) {
32 | if (!component.base) {
33 | if (component.componentWillMount) component.componentWillMount();
34 | } else if (component.base && component.componentWillReceiveProps) {
35 | component.componentWillReceiveProps(props);
36 | }
37 |
38 | component.props = props;
39 |
40 | renderComponent(component);
41 | }
42 | export function renderComponent(component) {
43 | console.log('renderComponent');
44 | let base;
45 |
46 | const renderer = component.render();
47 |
48 | if (component.base && component.componentWillUpdate) {
49 | component.componentWillUpdate();
50 | }
51 |
52 | base = _render(renderer);
53 |
54 | if (component.base) {
55 | if (component.componentDidUpdate) component.componentDidUpdate();
56 | } else {
57 | component.base = base;
58 | component.componentDidMount && component.componentDidMount();
59 | if (component.base && component.base.parentNode) {
60 | component.base.parentNode.replaceChild(base, component.base);
61 | }
62 | return;
63 | }
64 | if (component.base && component.base.parentNode) {
65 | component.base.parentNode.replaceChild(base, component.base);
66 | }
67 |
68 | component.base = base;
69 | base._component = component;
70 | }
71 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | import { Componet } from '../react';
2 | import { setAttribute } from './dom';
3 |
4 | /*
5 | * @param {HTMLElement} dom 真实DOM
6 | * @param {vnode} vnode 虚拟DOM
7 | * @param {HTMLElement} container 容器
8 | * @returns {HTMLElement} 更新后的DOM
9 | */
10 | export function diff(dom, vnode, container) {
11 | const ret = diffNode(dom, vnode);
12 |
13 | if (container && ret.parentNode !== container) {
14 | container.appendChild(ret);
15 | }
16 |
17 | return ret;
18 | }
19 |
20 | function diffNode(dom, vnode) {
21 | let out = dom;
22 |
23 | if (vnode === undefined || vnode === null || typeof vnode === 'boolean')
24 | vnode = '';
25 |
26 | if (typeof vnode === 'number') vnode = String(vnode);
27 |
28 | // diff text node
29 | if (typeof vnode === 'string') {
30 | // 如果当前的DOM就是文本节点,则直接更新内容
31 | if (dom && dom.nodeType === 3) {
32 | // nodeType: https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType
33 | if (dom.textContent !== vnode) {
34 | dom.textContent = vnode;
35 | }
36 | // 如果DOM不是文本节点,则新建一个文本节点DOM,并移除掉原来的
37 | } else {
38 | out = document.createTextNode(vnode);
39 | if (dom && dom.parentNode) {
40 | dom.parentNode.replaceChild(out, dom);
41 | }
42 | }
43 | return out;
44 | }
45 |
46 | if (typeof vnode.tag === 'function') {
47 | return diffComponent(dom, vnode);
48 | }
49 |
50 | //
51 | if (!dom || !isSameNodeType(dom, vnode)) {
52 | out = document.createElement(vnode.tag);
53 |
54 | if (dom) {
55 | [...dom.childNodes].map(out.appendChild); // 将原来的子节点移到新节点下
56 |
57 | if (dom.parentNode) {
58 | dom.parentNode.replaceChild(out, dom); // 移除掉原来的DOM对象
59 | }
60 | }
61 | }
62 |
63 | if (
64 | (vnode.children && vnode.children.length > 0) ||
65 | (out.childNodes && out.childNodes.length > 0)
66 | ) {
67 | diffChildren(out, vnode.children);
68 | }
69 |
70 | diffAttributes(out, vnode);
71 |
72 | return out;
73 | }
74 |
75 | function diffChildren(dom, vchildren) {
76 | const domChildren = dom.childNodes;
77 | const children = [];
78 |
79 | const keyed = {};
80 |
81 | if (domChildren.length > 0) {
82 | for (let i = 0; i < domChildren.length; i++) {
83 | const child = domChildren[i];
84 | const key = child.key;
85 | if (key) {
86 | keyedLen++;
87 | keyed[key] = child;
88 | } else {
89 | children.push(child);
90 | }
91 | }
92 | }
93 |
94 | if (vchildren && vchildren.length > 0) {
95 | let min = 0;
96 | let childrenLen = children.length;
97 |
98 | for (let i = 0; i < vchildren.length; i++) {
99 | const vchild = vchildren[i];
100 | const key = vchild.key;
101 | let child;
102 |
103 | if (key) {
104 | if (keyed[key]) {
105 | child = keyed[key];
106 | keyed[key] = undefined;
107 | }
108 | } else if (min < childrenLen) {
109 | for (let j = min; j < childrenLen; j++) {
110 | let c = children[j];
111 |
112 | if (c && isSameNodeType(c, vchild)) {
113 | child = c;
114 | children[j] = undefined;
115 |
116 | if (j === childrenLen - 1) childrenLen--;
117 | if (j === min) min++;
118 | break;
119 | }
120 | }
121 | }
122 |
123 | child = diffNode(child, vchild);
124 |
125 | const f = domChildren[i];
126 | if (child && child !== dom && child !== f) {
127 | if (!f) {
128 | dom.appendChild(child);
129 | } else if (child === f.nextSibling) {
130 | removeNode(f);
131 | } else {
132 | dom.insertBefore(child, f);
133 | }
134 | }
135 | }
136 | }
137 | }
138 |
139 | function diffComponent(dom, vnode) {
140 | let c = dom && dom._component;
141 | let oldDom = dom;
142 |
143 | // 如果组件类型没有变化,则重新set props
144 | if (c && c.constructor === vnode.tag) {
145 | setComponentProps(c, vnode.attrs);
146 | dom = c.base;
147 | // 如果组件类型变化,则移除掉原来组件,并渲染新的组件
148 | } else {
149 | if (c) {
150 | unmountComponent(c);
151 | oldDom = null;
152 | }
153 |
154 | c = createComponent(vnode.tag, vnode.attrs);
155 |
156 | setComponentProps(c, vnode.attrs);
157 | dom = c.base;
158 |
159 | if (oldDom && dom !== oldDom) {
160 | oldDom._component = null;
161 | removeNode(oldDom);
162 | }
163 | }
164 |
165 | return dom;
166 | }
167 |
168 | function setComponentProps(component, props) {
169 | if (!component.base) {
170 | if (component.componentWillMount) component.componentWillMount();
171 | } else if (component.componentWillReceiveProps) {
172 | component.componentWillReceiveProps(props);
173 | }
174 |
175 | component.props = props;
176 |
177 | renderComponent(component);
178 | }
179 |
180 | export function renderComponent(component) {
181 | let base;
182 |
183 | const renderer = component.render();
184 |
185 | if (component.base && component.componentWillUpdate) {
186 | component.componentWillUpdate();
187 | }
188 |
189 | base = diffNode(component.base, renderer);
190 |
191 | component.base = base;
192 | base._component = component;
193 |
194 | if (component.base) {
195 | if (component.componentDidUpdate) component.componentDidUpdate();
196 | } else if (component.componentDidMount) {
197 | component.componentDidMount();
198 | }
199 |
200 | component.base = base;
201 | base._component = component;
202 | }
203 |
204 | function createComponent(component, props) {
205 | let inst;
206 |
207 | if (component.prototype && component.prototype.render) {
208 | inst = new component(props);
209 | } else {
210 | inst = new Component(props);
211 | inst.constructor = component;
212 | inst.render = function() {
213 | return this.constructor(props);
214 | };
215 | }
216 |
217 | return inst;
218 | }
219 |
220 | function unmountComponent(component) {
221 | if (component.componentWillUnmount) component.componentWillUnmount();
222 | removeNode(component.base);
223 | }
224 |
225 | function isSameNodeType(dom, vnode) {
226 | if (typeof vnode === 'string' || typeof vnode === 'number') {
227 | return dom.nodeType === 3;
228 | }
229 |
230 | if (typeof vnode.tag === 'string') {
231 | return dom.nodeName.toLowerCase() === vnode.tag.toLowerCase();
232 | }
233 |
234 | return dom && dom._component && dom._component.constructor === vnode.tag;
235 | }
236 |
237 | function diffAttributes(dom, vnode) {
238 | const old = {}; // 当前DOM的属性
239 | const attrs = vnode.attrs; // 虚拟DOM的属性
240 |
241 | for (let i = 0; i < dom.attributes.length; i++) {
242 | const attr = dom.attributes[i];
243 | old[attr.name] = attr.value;
244 | }
245 |
246 | // 如果原来的属性不在新的属性当中,则将其移除掉(属性值设为undefined)
247 | for (let name in old) {
248 | if (!(name in attrs)) {
249 | setAttribute(dom, name, undefined);
250 | }
251 | }
252 |
253 | // 更新新的属性值
254 | for (let name in attrs) {
255 | if (old[name] !== attrs[name]) {
256 | setAttribute(dom, name, attrs[name]);
257 | }
258 | }
259 | }
260 |
261 | function removeNode(dom) {
262 | if (dom && dom.parentNode) {
263 | dom.parentNode.removeChild(dom);
264 | }
265 | }
266 |
--------------------------------------------------------------------------------