用简单的例子说说React之JSX和虚拟DOM还有一些实现思路(非官方)

873 阅读4分钟

今天主要给大家带来的是一篇管理JSX和虚拟DOM的实现。在React出生的时候就伴随JSX的诞生,这部分的内容是由Babel来实现的。

JSX最终转译成什么内容呢?

我们可以去Babel的官网试一试这来看看转译成什么内容呢?

image.png

可以看出我们平时写的JSX其实是转译成右边的代码。这样就能解释了ReactDOM为什么需要React的问题。

由此我么可以发现React.createElement有三个重要的参数

/** 
tag dom 
attrs 属性 
...childrens 子元素 */ 
React.createElement(tag,attrs,...childrens)
{ 
    return { tag, attrs, childrens } 
}

知道这个之后,我们还想需要知道React是怎么进行渲染呢?这个时候就到我们的ReactDOM进行讲解了。

大家是否记得使用ReactDom.render(vnode,document.getElementById('root'))这个方法?

那么ReactDOM.render又给我们做了什么呢?以下代码非官方代码为思路代码,又精简成分。 render接收了两个参数一个为vnode,另一个需要把vnode渲染那个容器里面,这也解释了为什么你在编写jsx的时候需要一个<></>,包含在内。

ReactDOM.render(vnode,container){
    if(vnode===undefined) return;
    
    if(typeof node === 'string'){ 
        const textNode document.createTextNode(vnode); 
        return container.appendChild(textNode); 
    }
    
    //如果以上都不是,那就是我们的createElemenet对象
    const {tag,attrs} = vnode; 
    const dom = document.createElement(tag); 
    if(attrs){ Object.keys(attrs).forEach(key={ 
        const value attrs[key] 
        //设置属性
        setAttribute(dom,key,value) 
        }) 
    }
    
    //渲染子节点
    vnode.childrens.forEach(child=>render(child,dom));
    return container.appendChild(dom);
}
//设置属性的操作
function setAttribute(dom,key,value){ 
    if(key==='className'){ key = 'class' } 
    if(/on\w+/.test(key)){ 
        key = key.toLowerCase(); 
        dom[key] = value || ''; 
    }
    else if(key === 'style'){ 
        if(!value || typeof value==='string'){ 
            dom.style.cssText = value || ''; 
        }
        else if(value && typeof === 'object'){ 
            for(let k in value){ 
                if(typeof value[k] === 'number')
                { dom.style[k]= value[k] + 'px' }
                else{ dom.style[k]= value[k] } } 
            } 
       }
       else{ if(key in dom){ 
            dom[key] = value || '' 
           } 

if(value){ dom.setAttribute(key,value) }else{ dom.removeAttribute(key) } } }

由上面的代码可以看出render在进行渲染的时候,会给vnode判断一下类型,如果是字符串类型,就会直接返回。

如果是一个vnode对象会使用document.createElement把我们的tag进行创建,创建完的BDOM元素在进行attrs的一些设置。这种思路想必大家都知道。

组件化的思路。

在React里组件化的实现有两种一种是类组件,另一种是函数组件。我这里为了简单,函数的实现最终会走向类的实现。


function renderComponent(comp){ 
    const renderer = comp.render(); 
    comp.base = _render(renderer) 
}

class Component { 
    //至于生命周期的执行,你们可以自己想想怎实现调用哦。
    constructor(props={}){ 
        this.props = props; this.state = {}; 
    } 
    
    setState(stateChange){ 
        Object.assign(this.state,stateChange); 
            renderComponent(this); 
        } 
    }
export default Component;

我们在渲染vnode的时候如果判断到他是一个function或者typeof object.prototype.render 存在的话,就会给我们进行实例化类操作。

if(typeof vnode.tag === 'function') { 
    const comp = createComponent(vnode.tag,vnode.attrs);   
    setComponentProps(comp,vnode,attrs); 
    return comp.base; 
}

function renderComponent(comp){ 
    const renderer = comp.render(); 
    comp.base = render(renderer) 
}

function createComponent(comp,props){ 
    let inst; if(comp.prototype && comp.prototype.render )
        { inst = new comp(props) }
        else{ //如果是函数组件 
        inst = new Component(props); 
        inst.constructor = comp; 
        inst.render = function(){ 
            return this 
          } 
        } 
        return inst; 
}

差不多这样吧,当然React的官方实现不是这样的,我这是把思路简单化了。同个一个类组件来管理。

diff的简单实现

diff的主要思路就是对比并更新。

image.png

按照这个思路,如果你想最简单的实现方式,就是使用BDOM和你的VNODE进行比较,如果操作更新,就使用JS来更新的你的BDOM里面的值。但是这种做法时间如果的你的tree节点过多时间复杂度就会很高。这这是思路啦。当然实际React的diff算法做的比较复杂。

我么在使用的时候需要注意以下几点。

每当根元素有不同的类型时,React就会拆除旧的树,从头开始构建新的树。

当拆除树时,旧的Dom节点将被破坏。组件实例接收WillUnmout()。使用DidMount()构建新树。与老树相关的任何状态都将丢失。

尽量减少对根元素的更改。

key尽量不要使用index,在排序时,性能可能会下降

因为React依赖于启发式,如果它们背后的假设没有得到满足,性能就会受到影响。

  1. 该算法不会尝试匹配不同组件类型的子树。如果您看到自己在两种输出非常相似的组件类型之间交替,您可能希望将其设置为相同的类型。在实践中,我们没有发现这是一个问题。

  2. 键应该是稳定的、可预测的和唯一的。不稳定的键(比如' Math.random() '生成的键)会导致不必要地重新创建许多组件实例和DOM节点,这会导致性能下降和丢失子组件的状态。