今天主要给大家带来的是一篇管理JSX和虚拟DOM的实现。在React出生的时候就伴随JSX的诞生,这部分的内容是由Babel来实现的。
JSX最终转译成什么内容呢?
我们可以去Babel的官网试一试这来看看转译成什么内容呢?
可以看出我们平时写的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的主要思路就是对比并更新。
按照这个思路,如果你想最简单的实现方式,就是使用BDOM和你的VNODE进行比较,如果操作更新,就使用JS来更新的你的BDOM里面的值。但是这种做法时间如果的你的tree节点过多时间复杂度就会很高。这这是思路啦。当然实际React的diff算法做的比较复杂。
我么在使用的时候需要注意以下几点。
每当根元素有不同的类型时,React就会拆除旧的树,从头开始构建新的树。
当拆除树时,旧的Dom节点将被破坏。组件实例接收WillUnmout()。使用DidMount()构建新树。与老树相关的任何状态都将丢失。
尽量减少对根元素的更改。
key尽量不要使用index,在排序时,性能可能会下降
因为React依赖于启发式,如果它们背后的假设没有得到满足,性能就会受到影响。
-
该算法不会尝试匹配不同组件类型的子树。如果您看到自己在两种输出非常相似的组件类型之间交替,您可能希望将其设置为相同的类型。在实践中,我们没有发现这是一个问题。
-
键应该是稳定的、可预测的和唯一的。不稳定的键(比如' Math.random() '生成的键)会导致不必要地重新创建许多组件实例和DOM节点,这会导致性能下降和丢失子组件的状态。