MiniReact系列之渲染函数组件和类组件(三)

311 阅读5分钟

MiniReact系列之实现一个简易createElement(一)

MiniReact系列之实现一个简易render(二)

完整代码git地址:github.com/nocodig/min…

之前写了两篇文章有点水,大家别介意,原谅菜逼~今天这个菜逼又开始瞎胡扯了,可能继续很水,这个菜逼也在努力提高水平不是,大家没事点个赞,给点鼓励(✧◡✧),废话不多说,开整

一、判断是否是组件

组件与浏览器中原生节点差别在生成VirtualDOM中的type属性,组件的type属性是一个function,而浏览器原生的type属性是字符串

之前已经说到组件与浏览器的原生元素差别是VirtualDOMtype属性,所以isFunction只需要判断VirtualDOMtype属性是不是一个方法,函数组件的type就是一个方法,而类组件的type是一个类的构造函数

// src/MiniReact/isFunction.js

export default function isFunction(virtualDOM) {
  return virtualDOM && typeof virtualDOM.type === 'function'
}

在之前的文章中,写到mountElement方法,在里面需要判断是组件还是原生的元素,代码需要就需要进行修改,如果是组件的话,通过mountComponet进行解析,具体实现的话我们接着往下看

// src/MiniReact/mountElement.js

export default function mountElement(virtualDOM, container) {
  if (isFunction(virtualDOM)) {
    // 渲染Component,代码如何实现在后面
    mountComponent(virtualDOM, container)
  } else {
    // 渲染NativeElement
    mountNativeElement(virtualDOM, container);
  }
}

二、区分类组件和函数组件

渲染一个组件可能是类组件或者函数组件,二者存在差异,类组件是通过原型上调用render方法进行渲染,而函数组件是直接掉调用即可,所以需要在mountComponent方法中进一步区分是通过函数组件调用,还是通过类组件的render方法,需要实现一个isFunctionComponent用来辨别组件是否是函数组件,判断virtualDOMtype属性,是不是一个方法,并且它的原型原型上没有render方法

// src/MiniReact/isFunctionComponent.js

import isFunction from "./isFunction"

export default function isFunctionComponent(virtualDOM) {
  const type = virtualDOM.type
  
  return (
    type && isFunction(virtualDOM) && !(type.prototype && type.prototype.render)
  )
}

三、渲染函数组件

通过上面的方法,我们已经能够区分出来渲染是浏览器DOM元素还是组件,而且也能够区分出来是函数组件还是类组件,接下来就需要考虑一点,如何将函数组件或者类组件渲染出来,这一部分我们先只考虑如何实现函数组件的渲染,类组件的渲染我们在后面进行处理

组件的type是一个方法,那么我们直接调用type方法即可转化成我们的virtualDOM

// src/MiniReact/mountComponent.js

....

function buildFunctionComponent(virtualDom) {
  return virtualDom.type()
}

....

但是,这个时候我们可能会向组件里面传递props,上面的写法是没有办法传递props的,所以我们需要将props传入到virtualDOM.type方法里面,还需要考虑的是有是props参数有时为空,我们需要做下兼容处理

// src/MiniReact/mountComponent.js

...

function buildFunctionComponent(virtualDom) {
  return virtualDom.type(virtualDom.props)
}

...

处理好组件的编译,下面我们需要考虑实现mountComponet,用来渲染组件。

首先我们需要将调用buildFunctionComponent方法,转化一下virtualDOM,并且用一个局部变量保存它,此时可能存在一个问题,我们转换后的virturlDOM可能还是个组件,我们需要递归调用mountComponent方法,将转化后的virtualDOM传进去,直到所有的virtualDOM都转化成浏览器DOM元素,最后执行mountNativeElement,挂载到页面上去。

// src/MiniReact/mountComponent.js

....

export default function mountComponent(virtualDOM, container) {
  // 临时存储virtual
  let nextVirtualDOM = null;
  if (isFunctionComponent(virtualDOM)) {
    // 编译函数组件转化成virtualDOM
    nextVirtualDOM = buildFunctionComponent(virtualDOM);
  } else {
    // 类组件
  }

  if (isFunction(nextVirtualDOM)) {
    // 如果virtualDOM里面存在组件
    mountComponent(nextVirtualDOM, container)
  } else {
    // 确保virtualDOM将所有的组件都转换完成,进行页面挂载
    mountNativeElement(nextVirtualDOM, container)
  }
}

....

四、渲染类组件

我们现在写一个类组件,用于我们后面来对我们方法进行验证

// src/index.js

class Title extends MiniReact.Component {
  render() {
    return <div>Hello Class</div>
  }
}

上面代码中存在一个问题,就是MiniReact.Component在哪里,我们写一个Componet类,Component类后面我们在写具体内容。

我们将Component类在MiniReact导出即可

// src/MiniReact/component.js

export default class Component {
  constructor() {
    
  }
}


// src/MiniReact/index.js
import createElement from "./createElement";
import render from "./render";
import Component from "./component";

export default {
  createElement,
  render,
  Component
};

准备工作都尊卑好,下面我们实现下类组件的渲染,

上面我们已经把渲染函数组件方法实现了,在mountComponent中,我们首先判断了组件是函数组件还是类组件,显然,此时我们走到了else里面。

export default function mountComponent(virtualDOM, container) {
  // 临时存储virtual
  let nextVirtualDOM = null;
  if (isFunctionComponent(virtualDOM)) {
    // 编译函数组件转化成virtualDOM
    nextVirtualDOM = buildFunctionComponent(virtualDOM);
  } else {
    // 类组件
  }

  .....
}

我需要一个函数来编译类组件,需要注意的是,此时的type是一个我们类组件的构造函数,我们需要先对类组件进行实例化,然后调用实例的render方法。

function buildClassComponent(virtualDOM) {
  const component = new virtualDOM.type()
  return component.render()
}

和函数组件一样,我们调用组件时,会传递参数,参数都存在props属性中,而我们的组件又是继承了Componet类,当Componet这个父类上存在props属性时,我们能组件也能拿到props,所以我们组件的构造函数调用super时,将props传入进去,在Componet中接收props即可。我们改造下Component类。

// src/MiniReact/component.js

export default class Component {
  constructor(props) {
    this.props = props
  }
}

我们在实例化组件时,将virtualDOM.props传入即可。

function buildClassComponent(virtualDOM) {
  const component = new virtualDOM.type()
  return component.render(virtualDOM.props)
}

我们在判断组件是否是函数组件的判断里面的调用上面的方法即可,代码如下

import isFunction from "./isFunction";
import isFunctionComponent from "./isFunctionComponent";
import mountNativeElement from "./mountNativeElement";

export default function mountComponent(virtualDOM, container) {
  let nextVirtualDOM = null;
  if (isFunctionComponent(virtualDOM)) {
    // 函数组件
    nextVirtualDOM = buildFunctionComponent(virtualDOM)
  } else {
    // 类组件
    nextVirtualDOM = buildClassComponent(virtualDOM)
  }

  if (isFunction(nextVirtualDOM)) {
    mountComponent(nextVirtualDOM, container)
  } else {
    mountNativeElement(nextVirtualDOM, container)
  }
}

function buildFunctionComponent(virtualDom) {
  return virtualDom.type()
}

function buildClassComponent(virtualDom) {
  // 由于virtualDom的type是一个类,我们需要先对其进行实例化,然后调用render方法
  const component = new virtualDom.type()
  return component.render()
}

使用buildClassComponent将类组件转换成virtualDOM,接下来的会判断nextVirtualDOM是不是一个组件,如果是递归调用mountComponent,知道将所以的组件都转换成不是组件的virtualDOM,最后挂载到页面上。

好了,水完了,感谢大家的捧场,欢迎大家多提提意见,后面改正