只考虑class组件 setState更新情况,暂不考虑function组件
一、简单暴力全部替换实现更新
class Component {
....
setState(nextState) {
this.state = Object.assign(this.state, nextState);
const newDom = render(this.render()); // 注意区分全局render(生成真实dom) 和 本组件的render(生成vdom)
this.dom.replaceWith(newDom);
this.dom = newDom;
}
render () {
return <div>我是jsx</div>
}
}
二、patch 按需更新
patch 功能是把要渲染的 vdom 和已有的 dom 做下 diff,只更新需要更新的 dom,也就是按需更新。
class Component {
....
setState(nextState) {
this.state = Object.assign(this.state, nextState);
if(this.dom && this.shouldComponentUpdate(this.props, nextState)) {
patch(this.dom, this.render());
}
}
shouldComponentUpdate(nextProps, nextState) { // 加一个 shouldComponentUpdate 来控制,如果 props 和 state 都没变就不用 patch 了。
return nextProps != this.props || nextState != this.state;
}
render () {
return <div>我是jsx</div>
}
}
(1)已有的dom是文本,怎么patch?
- 如果 vdom 不是文本节点,直接替换
- 如果 vdom 也是文本节点,那就对比下内容,内容不一样就替换
if (dom instanceof Text) {
if (typeof vdom === 'object') {
return replace(render(vdom, parent));
} else {
return dom.textContent != vdom ? replace(render(vdom, parent)) : dom;
}
}
使用replaceChild更新developer.mozilla.org/en-US/docs/…
const replace = parent ? el => {
parent.replaceChild(el, dom);
return el;
} : (el => el);
(2)已有的dom是元素,怎么patch?
- 不同类型的元素,直接替换
- 同一类型的元素,更新子节点和属性
// patch 函数中对比
if (dom.nodeName !== vdom.type.toUpperCase() && typeof vdom === 'object') {
return replace(render(vdom, parent));
} else if(dom.nodeName === vdom.type.toUpperCase() && typeof vdom === 'object'){
const active = document.activeElement;
const oldDoms = {};
[].concat(...dom.childNodes).map((child, index) => {
const key = child.__key || `__index_${index}`;
oldDoms[key] = child;
});
[].concat(...vdom.children).map((child, index) => {
const key = child.props && child.props.key || `__index_${index}`;
dom.appendChild(oldDoms[key] ? patch(oldDoms[key], child) : render(child, dom));
delete oldDoms[key];
});
for (const key in oldDoms) {
const instance = oldDoms[key].__instance;
if (instance) instance.componentWillUnmount();
oldDoms[key].remove();
}
for (const attr of dom.attributes) dom.removeAttribute(attr.name);
for (const prop in vdom.props) setAttribute(dom, prop, vdom.props[prop]);
active.focus();
return dom;
}
// 全局render函数,也就是reconcile
const instance = new vdom.type(props);
instance.componentWillMount();
const componentVdom = instance.render();
instance.dom = render(componentVdom, parent);
instance.dom.__instance = instance;
instance.dom.__key = vdom.props.key;
instance.componentDidMount();
return instance.dom;
(3)已有的dom是组件,怎么patch?
不同组件,直接替换。
同一个组件,更新children。
怎么知道dom是什么组件渲染的呢?
全局render生成dom的时候,instance.dom
// 全局render函数,也就是reconcile
const functionProps = Object.assign({}, vdom.props, {
children: vdom.children // 注意这里将children也合并入props中,传给组件参数
});
if (Component.isPrototypeOf(vdom.type)) {
const instance = new vdom.type(functionProps);
instance.componentWillMount();
const componentVdom = instance.render();
instance.dom = render(componentVdom, parent);
instance.dom.__instance = instance;
instance.dom.__key = vdom.props.key;
instance.componentDidMount();
return instance.dom;
} else {
const componentVdom = vdom.type(functionProps);
return render(componentVdom, parent);
}
// 然后更新的时候就可以对比下 constructor 是否一样,如果一样说明是同一个组件,那 dom 是差不多的,再 patch 子元素:
// patch 函数中对比
const props = Object.assign({}, vdom.props, {children: vdom.children});
if (dom.__instance && dom.__instance.constructor == vdom.type) { // 同一个类组件
dom.__instance.componentWillReceiveProps(props);
dom.__instance.props = props;
return patch(dom, dom.__instance.render(), parent);
} else if (Component.isPrototypeOf(vdom.type)) { // 不同组件类型,vdom为class类
const componentDom = renderComponent(vdom, parent);
if (parent){
parent.replaceChild(componentDom, dom);
return componentDom;
} else {
return componentDom
}
} else if (!Component.isPrototypeOf(vdom.type)) { // 不同组件类型,vom为class类
return patch(dom, vdom.type(props), parent);
}