写在开首
重新完成一个浅易版React(一)地点:https://segmentfault.com/a/11…
上一节,我们细致引见了完成一个浅易React的思绪以及团体的构造,然则关于衬着和更新的道理,却还没有说起,因而,本节我们将重点放在vDom的衬着上。
进入正题
我们把React元素分为text,basic,custom三种,并离别封装了三种vDom的ReactComponent,用来处置惩罚各自的衬着和更新,在这里,我们将重心放在各自ReactComponet的mount要领上。
ReactTextComponent
ReactTextComponent用来处置惩罚文本节点,为了标识轻易,在返回的内容上加了span标签。
// 用来示意文本节点在衬着,更新,删除时应当做的事变
class ReactTextComponent extends ReactComponent {
// 衬着
mountComponent(rootId) {
this._rootNodeId = rootId
return `<span data-reactid="${rootId}">${this._vDom}</span>`
}
}
//代码地点:src/react/component/ReactTextComponent.js
ReactTextComponent的mount要领异常简朴,打上标识符,将内容插进去标签内,并把标签内容返回就能够了。
ReactDomComponent
这个类用来处置惩罚原生节点的vDom,在将vDom衬着为原生DOM时,要斟酌3点:
- 元素范例
- 拼集属性,包括一般属性及事宜的处置惩罚
- 子节点的递归衬着
代码以下:
// 用来示意原生节点在衬着,更新,删除时应当做的事变
class ReactDomComponent extends ReactComponent {
constructor(vDom) {
super(vDom)
this._renderedChildComponents = null
}
// 衬着
mountComponent(rootId) {
this._rootNodeId = rootId
const { props, type, props: { children = [] } } = this._vDom,
childComponents = []
// 设置tag,加上标识
let tagOpen = `${type} data-reactid=${this._rootNodeId}`,
tagClose = `/${type}`,
content = ''
// 拼集属性
for (let propKey in props) {
// 事宜
if (/^on[A-Za-z]/.test(propKey)) {
const eventType = propKey.replace('on', '')
$(document).delegate(`[data-reactid="${this._rootNodeId}"]`, `${eventType}.${this._rootNodeId}`, props[propKey])
}
// 一般属性,消除children与事宜
if (props[propKey] && propKey !== 'children' && !/^on[A-Za-z]/.test(propKey)) {
tagOpen += ` ${propKey}=${props[propKey]}`
}
}
// 猎取子节点衬着出的内容
children.forEach((item, index) => {
// 再次运用工场要领实例化子节点的component,拼接好返回
const childComponent = instantiateReactComponent(item)
childComponent._mountIndex = index
childComponents.push(childComponent)
// 子节点的rootId是父节点的rootId加上索引拼接的值
const curRootId = `${this._rootNodeId}.${index}`
// 获得子节点的衬着内容
const childMarkup = childComponent.mountComponent(curRootId)
// 拼接
content += childMarkup
// 保存一切子节点的component
this._renderedChildComponents = childComponents
})
return `<${tagOpen}>${content}<${tagClose}>`
}
}
//代码地点:src/react/component/ReactDomComponent.js
在React的官方完成中,本身完成了一套事宜体系,这里用了jQuery的事宜替代。
在款式上,须要基于传入的style对象建立款式,这里也临时疏忽了。
ReactCompositComponent
在建立自定义组件时,通常会如许建立
import React from 'react'
class App extends React.Component {
render() {
return (
)
}
}
所以,第一步,我们先完成Component这个父类
// 一切自定义组件的父类
class Component {
constructor(props) {
this.props = props
}
setState(newState) {
this._reactInternalInstance.updateComponent(null, newState)
}
}
//代码地点:src/react/Component.js
Component类上我们重要完成了setState要领,至于有什么用,我们放在更新里说。
在自定义组件的vDom中,type保存的是我们建立的Component的援用,所以在ReactCompositeComponent的mount要领中。我们起首依据vDom的type建立组件的实例,在以此挪用它初始衬着的生命周期要领,render要领。
在render要领中,返回了组件衬着内容的vDom,我们依据这个vDom建立它的ReactComponent并挪用mount(),就获得了实在的衬着内容。
贴代码:
export default class extends ReactComponent {
constructor(element) {
super(element)
// 寄存对应的组件实例
this._instance = null
this._renderedComponent = null
}
// 衬着
mountComponent(rootId) {
this._rootNodeId = rootId
const { type: Component, props } = this._vDom
// 猎取自定义组件的实例
const inst = new Component(props)
this._instance = inst
// 保存对当前component的援用,下面更新时会用到
inst._reactInternalInstance = this
inst.componentWillMount && inst.componentWillMount()
// 挪用自定义组件的render要领,返回一个Vdom
const renderedVdom = inst.render()
// 猎取renderedComponent的component
const renderedComponent = instantiateReactComponent(renderedVdom)
this._renderedComponent = renderedComponent
// 获得衬着以后的内容
const renderMarkup = renderedComponent.mountComponent(this._rootNodeId)
// 在React.render要领末了触发了mountReady事宜,地点在这里监听,在衬着完成后触发
$(document).on('mountReady', () => {
inst.componentDidMount && inst.componentDidMount()
})
return renderMarkup
}
}
// 代码地点:src/react/component/ReactCompositeComponent.js
从这里能够看出,自定义组件的mount要领并不担任详细的衬着,这些都交给了它的render,它把重心放在了建立对象和挪用生命周期上。
总结
文章到这,我们的浅易版react已开端完成了假造DOM的建立,生命周期的挪用,假造DOM的递归衬着和事宜处置惩罚。
总结一下,每一个vDom都有ReactComponent相对应,递归衬着的实质不过就是猎取每一个vDom的ReactComponent,并挪用它的mount要领。
以上就是全部衬着的思绪,下节我们将完成它的diff算法以及更新。
下一节地点:https://segmentfault.com/a/11…
参考资料,谢谢几位先辈的分享:
https://www.cnblogs.com/sven3…
https://github.com/purplebamb…
陈屹 《深切React手艺栈》