/**
* 递归查找真实dom节点
* @param vdom
* @returns {*}
*/
function findDom(vdom){
const { type } = vdom;
let dom;
/**
* 比如render(){return <Demo />}这里面render出来的还不是最终的虚拟dom;
*/
if(typeof type === 'function'){
// vdom可能是一个函数或者类组件,需要继续递归查找真实的DOM节点.
dom = findDom(vdom.oldVdom);
}else{
dom = vdom.dom;
}
return dom;
}
将前面的compareTwoVdoms方法修改如下
export function compareTwoVdoms(oldVdom, newVdom){
let oldDom = findDom(oldVdom);
let newDom = createDom(newVdom);
oldDom.parentNode.replaceChild(newDom, oldDom);
}
组件渲染出来了,也能响应事件了,接下来一个重点就是组件的更新了,毕竟React是数据驱动UI的,那么数据变了,我们怎么是需要去进行UI的更新的呢?
触发组件的更新有以下三种情况:
React采用的是
Dom-diff
来更新UI,也就是通过新的虚拟DOM和旧的虚拟DOM进行对比,找出差异点,然后将对应的改变在真实的DOM上进行替换操作.我们先来解决第一个场景: state变更了,自动调用组件的forceUpdate方法来更新组件.
在
src/react/Componennt.js
里面的Component
父类增加两个API,一个setState
,一个forceUpdate
,在接收到新的state的时候,更新组件的State,并调用forceUpdate来更新组件.在
forceUpdate
中,要实现组件的更新,我们要拿到旧的虚拟DOM,以及state更新之后的新的虚拟DOM,然后将它对比之后,将差异替换到真实的DOM节点上面去.新的虚拟DOM和之前的createDom
里面一样,调用render
方法即可取到,那旧的虚拟DOM呢?以及如何找其对应的真实DOM呢?在最开始生成虚拟DOM的时候,我们可以将把挂载到我们的实例(或者方法,如果是函数组件)上面.以及把生成出来的真实DOM挂载到它的虚拟DOM上面.这样就可以在每次重新渲染的时候,找到旧的虚拟DOM和旧的真实DOM了.
对原来的
createDom
和mountClassComponennt
方法进行修改接下来既可来实现
forceUpdate
方法.从实例上获取到上一步挂载的oldVdom
,以及调用render
获取到的新的虚拟DOM.对比两颗虚拟DOM,将差异来更新到真实DOM上面.这里先不考虑
Dom-diff
这个难点,如果直接进行全量更新,那么可以直接用新的真实DOM替换掉老的真实DOM即可.在
src/react-dom/index.js
增加compareTwoVdom
方法注意,这里有一个小坑,
oldVdom.dom
就一定是真实节点吗?在前面的createDom
里面的增加的vdom.dom = dom
知道, Dom是挂在虚拟Dom上面的,但在这个方法里面传入的oldVdom
是实例上面的,可以看到,它来源于实例的
render
方法,但注意,render
返回的可能会另外一个组件,那它会继续调用createDom
方法,所以最后真实的Dom是挂在最后渲染它的虚拟Dom上面的.如下图这样,真实Dom是挂在Child这个组件上面的.完了,Child如果再返回的是别外一个组件,组件套组件,组件何其多,那如何定位最终的真实dom?
还好,每个组件上面都挂了一个
oldVdom
,而这个oldVdom
要么挂有真实dom,要么就指向下一个oldVdom
,于是可以增加一个函数来递归查找最终的真实dom即可.在
src/react-dom/index.js
增加一个findDom
的方法将前面的
compareTwoVdoms
方法修改如下修改
src/index.js
文件如下运行发现,点击按钮,我们的类组件通过State, 函数组件通过props都可以实现更新了.