前言
带你从0~1手写React源码----原生组件的渲染中我们只是实现了原始组件的渲染和更新。我们知道,React中有函数组件和类组件,他们是可独立的,可复用的部件,他们类似于函数,可接受任意入参(props),同时也返回React元素。
我们从上一节中可以知道JSX会通过babel转换工具转换成createElement函数,执行createElement函数会返回一个对象{type,props},通过babel转换器可以知道,如果是函数组件或者类组件,type是该组件的函数名。如下图:
从带你从0~1手写React源码----原生组件的渲染中,通过 render 转换成真实 dom是我们只是判断type是字符串的情况,并没有判断函数组件以及类组件的情况,代码如下:
/**
* 把虚拟dom变成真实dom
* @param {*} vdom 虚拟dom
*/
function createDOM(vdom) {
//如果是字符串或者数字 直接返回一个真实的文本节点
if (typeof vdom === 'string' || typeof vdom === 'number') {
return document.createTextNode(vdom)
}
}
所以这里我们需要判断函数组件和类组件的情况:
/**
* 把虚拟dom变成真实dom
* @param {*} vdom 虚拟dom
*/
export const createDOM=function createDOM(vdom){
//如果是字符串或者数字 直接返回一个真实的文本节点
if(typeof vdom==='string'||typeof vdom==='number'){
return document.createTextNode(vdom);
}
let {type,props,key,ref}=vdom;
//创建dom元素
let dom;
if(typeof type==='function'){ //自定义的函数组件
if(type.isReactComponent){//说明是类组件
return mountClassComponent(vdom)
}else{
return mountFunctionCompinent(vdom)
}
}else{
dom=document.createElement(type);
}
//使用虚拟dom属性更新刚刚创建出来的真实DOM属性
updateProps(dom,props);
//在这里处理children
if(typeof props.children==='object'&&props.children.type){
//把此虚拟dom的儿子也都变成真实dom挂载到自己的dom上
mount(props.children,dom)
}else if(Array.isArray(props.children)){
reconcileChildren(props.children,dom);
}
//把真实dom放在虚拟dom属性上,为以后更新做准备
vdom.dom=dom;
if(ref){
ref.current=dom
}
return dom
}
实现类组件的渲染和更新
这里类组件和函数组件的区别是类组件有isReactComponent,所以我们可以通过isReactComponent区分类组件和函数组件。这里我们自定义的Component类,所有的类组件都需要继承这个Component类。在Component类中我们需要定义一个静态属性isReactComponent,所以所有的类组件都会继承这个isReactComponent。
class Component {
static isReactComponent = true
constructor(props) {
this.props = props
this.state = {}
}
render() {
throw new Error('此方法为抽象方法,需要子类实现')
}
}
Couter类需要继承Component类,注意:constructorf方法中必须写上super(props)。
class Couter extends React.Component{
constructor(props){
super(props)
}
render(){
return (
<div></div>
)
}
}
在mountClassComponent方法中:将虚拟DOM变成真实DOM。
/**
* 1.创建类组件实例
* 2.执行实例的render方法返回虚拟DOM
* 3.把返回的虚拟DOM变成真实的DOM进行挂载
*/
function mountClassComponent(vdom) {
let { type, props } = vdom
//创建类组件实例
let classInstance = new type(props)
//执行render方法
let renderVdom = classInstance.render()
//返回真实DOM
let dom = createDOM(renderVdom)
//为以后类组件的更新,把真实DOM挂载到类的实例上
classInstance.dom = dom
return dom
}
在类组件中需要保证state状态的更新和事件方法的执行:我们之前写了一个updateProps方法,但是没有对事件方法进行处理。
/**
* 使用虚拟dom属性更新刚刚创建出来的真实DOM属性
* @param {*} vdom 真实DOM
* @param {*} props 新属性对象
*/
function updateProps(vdom, props) {
for (let key in props) {
if (key === 'children') continue //单独处理
if (key === 'style') {
let styleObj = props[key]
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr]
}
} else if (key.startsWith('on')) { //这里对事件方法进行处理
dom[key.toLocaleLowerCase()] = props[key]
} else {
dom[key] = porps[key]
}
}
}
这里我们在Component类中添加setState方法:
class Component {
static isReactComponent = true
constructor(props) {
this.props = props
this.state = {}
}
setState(partialState) {
let state = this.state
this.state = { ...state, ...partialState }
//state改变组件更新 得到新的虚拟DOM
let newVdom = this.render()
//挂载到真实DOM去
updateClassComponent(this, newVdom)
}
render() {
throw new Error('此方法为抽象方法,需要子类实现')
}
}
function updateClassComponent(classInstance, newVdom) {
let oldDom = classInstance.dom //取出上次类组件渲染出来的真实DOM
let newDom = createDOM(newVdom)
oldDom.parentNode.replaceChild(newDom, oldDom)
classInstance.dom = newDom
}
实现函数组件的渲染和更新
在mountFunctionCompinent方法中:将虚拟DOM变成真实DOM。
/**
* 把类型为自定义函数组件的虚拟dom转换为真实dom并返回
* 处理函数组件
*/
function mountFunctionCompinent(vdom){
let {type,props}=vdom;
let renderVdom=type(props);
vdom.oldRenderVdom=renderVdom;
return createDOM(renderVdom)
}