从头实现一个简易版React(二)
写在开头从头实现一个简易版React(一)地址:https://segmentfault.com/a/11... 进入正题我们把React元素分为text,basic,custom三种,并分别封装了三种vDom的ReactComponent,用来处理各自的渲染和更新,在这里,我们将重心放在各自ReactComponet的mount方法上。 ReactTextComponentReactTextComponent用来处理文本节点,为了标识方便,在返回的内容上加了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的事件代替。 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方法,至于有什么用,我们放在更新里说。 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的递归渲染和事件处理。 参考资料,感谢几位前辈的分享: (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |