Skip to content

azl397985856/mono-react

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

从零开始开发一个 React

这个是从零开始开发一个 React 系列的第四篇。

先行知识

学习这个课程之前呢,需要各位了解 React 的 api,以及做了什么事情。

如果完全不了解的话,不建议您继续往下看。

如果你已经具备了相关 React 的知识,那么就让我们开始吧。

本章要实现的效果

上一节我们已经完整实现了 React 官网给的最简单的例子。

上节实现的效果是这样的:

class HelloMessage extends React.Component {
  // render函数和react的render略有不同
  // 这里借鉴了preact的思想,将props和state通过参数传到render函数中去
  render({ name }) {
    return <div>Hello {name}</div>;
  }
}

// render to dom
ReactDOM.render(
  <HelloMessage name="Taylor" />,
  document.getElementById("root")
);

我们这个 APP 如果需要更新,则需要我们手动调用 ReactDOM.render

有三个问题:

  1. 不方便,每次更新都需要调用 TOP_LEVEL API 更新。如果是子组件,则需要在子组件拿到整个 VDOM(我们会在本节修复)

  2. 似乎我们写的代码有bug,每次 ReactDOM.render 都会生成新的元素,之前的元素不会删除。(我们会在本节修复)

  3. 每次都会重新渲染,浪费性能(我们会在调和算法一节修复)

为此我们引入一个新的概念,就是 State,并且引入一个新的 API - setState 用于改变组件的 state。

我们先给 Component 增加 state 属性用于保存状态,并 setState 方法用于修改 state 并更新界面。

// component.js
+import ReactDOM from "./mini-react-dom";
+
+function reRender(rootReactElement, rootDOMElement) {
+  // 移除之前的节点(之后引入调和算法后进行优化)
+  while (rootDOMElement.hasChildNodes()) {
+    rootDOMElement.removeChild(rootDOMElement.lastChild);
+  }
+  ReactDOM.render(rootReactElement, rootDOMElement);
+}
+
 class Component {
   constructor(props) {
     this.props = props;
+    this.state = this.state || {};
+  }
+  setState(partialState) {
+    this.state = Object.assign({}, this.state, partialState);
+    // 这个render只是渲染生成vdom,界面不会真正刷新
+    // this.render(this.props, this.state);
+    // 在后面的章节(调和算法)我们进行优化
+    reRender(vdom,el);
   }
 }

setState 方法接受一个对象,用于将其合并到 this.state,然后调用reRender重新渲染页面,

reRender逻辑很简单,就是删除当前节点下的所有节点(暂时没有引入调和算法,所以就是这么粗暴),然后重新 调用 ReactDOM.render。

问题的关键在于

// 这一行代码的vdom,el哪来的?
+reRender(vdom, el);

vdom 和 el 其实就是 ReactDOM.render(vdom, el), 我们只需要将 ReactDOM.render 的 vdom 和 el 存起来,在这里使用就 ok。

代码是这样:

 const ReactDOM = {
   render(vdom, el) {

+    if (!window.vdom) {
+      window.vdom = vdom;
+    }
+    if (!window.el) {
+      window.el = el;
+    }
     ...
    }

getDOM 之前每次调用都会生成一个新的实例,因此这里需要修改:

 function getDOM(type, props, el) {
   const isTextElement = type === "TEXT";
   if (isTextElement) {
     return document.createTextNode("");
   } else if (isClass(type)) {
-    return ReactDOM.render(new type(props).render(props), el);	+    // 组件实例只创建一次,创建成功之后挂在到type上
+    // 以便下次可以访问
+    if (!type.instance) {
+      type.instance = new type(props);
+    }
+    return ReactDOM.render(
+      type.instance.render(props, type.instance.state || {}),
+      el
+    );
   }
   return document.createElement(type);
 }

将 instance 挂在到 type 上,如果下次 type 上有 instance 就表示已经实例话了,不需要重新实例化。

后面讲生命周期和调和算法的时候会往 instance 添加更多的东西

最终实现的效果:

class Counter extends React.Component {
  constructor(props) {
    super(props);
  }
  handleClick() {
    this.setState({
      count: this.state.count + 1
    });
  }
  render({ name }, { count }) {
    return <div onClick={this.handleClick.bind(this)}>Count: {count}</div>;
  }
}

// render to dom
ReactDOM.render(<Counter count={0} />, document.getElementById("root"));

感谢你的阅读, 下一节我们增加生命周期

About

Create React from zero series

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published