阅读 171

React学习第四天---Virtual DOM 及 Diff 算法(三)

这是我参与更文挑战的第8天

前面我们已经将virtualDOM转化为真实DOM了,但是这些真实DOM是没有任何属性的,接下来我们就为真实DOM添加属性

为真实DOM添加属性

分析: 这些属性全部存储在虚拟DOM的props里面,我们只需要在创建元素的时候找到virtualDOM的属性props,然后遍历props属性,在遍历的过程当中就可以将这些属性添加给真实DOM元素了,但是在添加属性的时候我们还需要考虑一些不同的情况,因为不同情况我们要去做不同的处理,如果是事件属性,那么我们就要为该元素去添加事件;还要判断这个属性是否是value属性或者是checked属性,因为这两个属性是无法使用setAttribute方法去设置它的,我们还需要看看这个属性是否是children属性,children不是属性是子元素但是也存在props中;还需要看这个属性是否是className属性,如果是className的话我们就给元素添加class属性;如果是普通属性就可以用setAttribute方法给真实DOM去设置就行了。

代码实现:

export default function updateNodeElement(newElement, virtualDOM) {
  // 获取节点对应的属性对象
  const newProps = virtualDOM.props

  Object.keys(newProps).forEach(propName => {
    // 获取属性值
    const newPropsValue = newProps[propName]

    // 判断属性是否是事件属性 onClick => click

    if(propName.slice(0, 2) === "on") {
      const eventName = propName.toLowerCase().slice(2)
      // 为元素添加事件
      newElement.addEventListener(eventName, newPropsValue)
    } else if(propName === "value" || propName === "checked") {
      newElement[propName] = newPropsValue
    } else if (propName !== "children") {
      if(propName === "className") {
        newElement.setAttribute('class', newPropsValue)
      } else {
        newElement.setAttribute(propName, newPropsValue)
      }
    }
  })
}

复制代码

在生成真实DOM文件夹引用这个方法:

import updateNodeElement from "./updateNodeElement"

export default function mountNativeElement (virtualDOM, container)  {
  let newElement = null
  ···
  else {
    // 元素节点
    newElement = document.createElement(virtualDOM.type);
    updateNodeElement(newElement, virtualDOM);

复制代码

小结: 当我们创建真实元素节点的时候,调用updateNodeElement给这个元素添加属性,属性存在virtualDOM的props中,所以这个updateNodeElement传两个参数,一个是当前的真实DOM,newElement(给谁设置属性),一个是virtualDOM(这些属性藏在哪),在把props里面的属性全部拿出来,属性为事件使用addEventListener添加监听事件,value和checked使用newElement[propName] = newPropsValue,接下来除children这个节点,其他节点为className转化为class,其他的直接setAttribute。

组件渲染之区分函数组件和类组件

组件在转化为virtualDOM之前,会是什么样子,因为无论我们在渲染组件还是渲染DOM元素我们都要调用mountElement这个方法。在这个方法中我们要把组件和普通virtualDOM元素区分开,因为在普通virtualDOM元素他的处理方式和组件是不一样的

// 原始组件
const Heart = () => <span>&hearts;</span>
复制代码

调用组件方式

<Heart />
复制代码

下面是组件的VirtualDOM的

type: f function() {},
props: {}
children: []
复制代码

分析: 我们发现组件type是一个函数,而普通VirtualDOM是一个字符串。所以在调用mountElement分开处理,组件执行方法是mountComponent(virtualDOM, container),普通virtualDOM执行的方法是mountNativeElement(virtualDOM, container),无论类组件还是函数组件,其实本质都是函数,如果virtualDOM的type属性值为函数,就说明当前这个VirtualDOM为组件。 代码:

import isFunction from './isFunction';
import mountComponent from './mountComponent';
import mountNativeElement from './mountNativeElement';
export default function mountElement(virtualDOM, container) {
  // Component VS NativeElement
  if(isFunction(virtualDOM)) {
    // 处理 组件的方法 mountComponent
    mountComponent()
  } else {
    // 处理原生virtualDOM方法mountNativeElement
    mountNativeElement(virtualDOM, container);
  }
  
}

//  isFunction.js
// 区分virtualDOM的type是否是函数
export default function isFunction(virtualDOM) {
  return virtualDOM && typeof virtualDOM.type === "function"
}
复制代码

上面我们区分了组件virtualDOM和普通virtualDOM,我们前面已经写完普通VirtualDOM处理方法,现在编写组件VirtualDOM处理方法,组件分两种类组件和函数组件,虽然本质都是函数组件,但是还是要分开处理更为清晰一些。于是在创建一个文件 isFuntionComponent.js创建同名函数用来判断这个组件Virtual是函数还是类,区分开之后函数组件调用函数组件处理方法,类组件调用类组件处理方法

import isFunctionComponent from "./isFunctionComponent";

export default function mountComponent(virtualDOM, container) {
  // 判断组件是类组件还是函数组件
  if(isFunctionComponent(virtualDOM)) {
    console.log('函数组件。。。')
  } else {
    console.log('类组件')
  }
}

// isFunctionComponent.js
import isFunction from "./isFunction"

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

函数组件走函数组件处理方法buildFunctionComponent, 类组件走类组件处理方法buildClassComponent,在buildFunctionComponent 方法中直接调用virtualDOM.type()就可以拿到对应的虚拟DOM,因为函数组件 type就是一个方法,

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

export default function mountComponent(virtualDOM, container) {
  let nextVirtualDOM = null
  // 判断组件是类组件还是函数组件
  if(isFunctionComponent(virtualDOM)) {
    nextVirtualDOM = buildFunctionComponent(virtualDOM);
    console.log("nextVirtualDOM", nextVirtualDOM);
  }
  mountNativeElement(nextVirtualDOM, container)
}

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

// src/index.js
function Heart() {
  return <div>&hearts;</div>
}
TinyReact.render(<Heart />, root)
复制代码

得到结果是:

image.png 函数组件已经初步成功渲染成功,但是我们还是有一个问题没有解决,如果这个函数组件里面调用的不是原始标签是组件的话,我们还不能直接调用mountNativeElement进行处理,如果是组件的话还是要递归调用mountComponent方法,知道不是isFunction,type不是函数为止即为普通VirtualDOM

// mountComponent.js
export default function mountComponent(virtualDOM, container) {
  ···
  if(isFunction(nextVirtualDOM)) {
    mountComponent(nextVirtualDOM, container);
  } else {
    mountNativeElement(nextVirtualDOM, container);
  }
}

function Dome(){
  return <div>Hello</div>
}
function Heart() {
  return <Demo />
}
TinyReact.render(<Heart />, root)
复制代码

效果:

image.png

好我们上面已经处理完了函数组件,接下来处理下函数组件的props属性. 只需要在buildFunctionComponent方法里面的virtualDOM.type这个函数里面将virtualDOM.props传递进去即可,当这个函数接收的时候在JSX中使用就会转化成相应的节点,渲染出来

function buildFunctionComponent(virtualDOM) {
  return virtualDOM.type(virtualDOM.props || {});
}

function Demo(){
  return <div>Hello</div>
}
function Heart(props) {
  return <div>{props.title}&hearts;<Demo /></div>
}
TinyReact.render(<Heart title="Hello React" />, root)

复制代码

image.png

上面讲完了函数组件的渲染,接下来我们来处理类组件的渲染

类组件渲染

分析: 我们上面说了,组件处理两种处理如果是函数组件我们调用buildFunctionComponent, 如果是类组件我们用类组件处理函数buildClassComponent,我们会创建一个基类Component类属于TinyReact, 使所有声明的类组件继承该Component类。buildClassComponent和函数组件一样,babel处理的时候类组件type也是一个函数,但是这个函数是构造函数,new来及时组件virtualDOM,然后调用这个实例render即可

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

export default function mountComponent(virtualDOM, container) {
  let nextVirtualDOM = null
  ···
  else {
    // 类组件
    nextVirtualDOM = buildClassComponent(virtualDOM)
  }

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

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

// Component.js
export default class Component {
  
}

// TinyReact/index.js
mport createElement from "./createElement"
import render from "./render"
import Component from "./Component"

export default {
  createElement,
  Component,
  render
}

// src/index.js 调用Demo
class Alert extends TinyReact.Component {
  render () {
    return <div> Hello TinyReact</div>
  }
}

TinyReact.render(<Alert />, root)

复制代码

处理类组件的Props属性

我们使用react类组件应该是这样的

class Alert extends TinyReact.Component {
  render () {
    return <div> 
      {this.props.name}
      {this.props.age}
    </div>
  }
}

TinyReact.render(<Alert name="张三" age={20}/>, root)
复制代码

这个props明显是类的属性,但是我们在使用react类组件的时候会给类组件添加props组件么,很明显是没有的对吧,那实现思路是啥呢,props这个属性可以放到Component中去完成初始化,在子组件构造器里面去调用即可

// Component.js
export default class Component {
  constructor (props) {
    this.props = props
  }
}
class Alert extends TinyReact.Component {
    constructor (props) {
        super(props)
      }
  render () {
    return <div> 
      {this.props.name}
      {this.props.age}
    </div>
  }
}
TinyReact.render(<Alert name="张三" age={20}/>, root)
复制代码

所有每个类组件的构造器都接受一个props参数我们应该在buildClassComponent 将其传递过来即可

function buildClassComponent(virtualDOM) {
  const component = new virtualDOM.type(virtualDOM.props || {});
  const nextVirtualDOM = component.render()
  return nextVirtualDOM
}
复制代码

好!今天学习到这,我们今天主要学习了虚拟DOM到真实DOM的渲染, 也可以分为两大类普通VirtualDOM和组件virtualDOM,我们真正处理呢还是要分为三大类,普通VirtualDOM, 函数组件和类组件VirtualDOM,三种虚拟DOM需要处理,这三种处理都有彼此的不同之处,但是都要明确的步骤先处理基本结构完成渲染,然后再处理他们各自情况下的props,这里比较麻烦和基础的是普通virtualDOM的props的处理,因为类组件和函数组件经过转化之后都用到它,普通VirtualDOM 的props处理三种情况,事件,处理掉true,false, null不显示的文本节点,表单控件的value和checked的处理。完成VirtualDOM的基本机构和props的处理,就完成了我们今天主要的目标,VirtualDOM到RealDOM的转化和渲染

接下来我们会学习更新DOM元素,敬请期待!!!欢迎点赞与关注,加油

文章分类
前端
文章标签