什么是jsx
jsx其实也是js,react jsx经过babel编译后得到需要递归调用的React.createElement函数。
jsx = (<div className="container">
<p>这是第一个子节点</p>
<p>这是第二个子节点</p>
</div>)
// babel将上面的jsx编译后得到如下的函数调用
React.createElement(
'div',
{ className: 'container'},
React.createElement('p', null, '这是第一个子节点'),
React.createElement('p', null, '这是第二个子节点')
)
// React.createElement(type, attr, ...children)这个函数将出传入的转换为virtualDOM
// 那virtualDOM是上面样的呢?如下
virtualDom = {
// type可以为dom标签名称,或组件(可以是react函数式组件,也可以是react类组件)
type: 'div',
props: {
children: [{
type: 'p',
props: null,
children: [
type: 'text',
props: {
textContainer: '这是第一个子节点'
}
]
},{
type: 'p',
props: null,
children: [
type: 'text',
props: {
textContainer: '这是第二个子节点'
}
]
}],
className: 'container',
...等等,
},
children: [{
type: 'p',
props: null,
children: [
type: 'text',
props: {
textContainer: '这是第一个子节点'
}
]
},{
type: 'p',
props: null,
children: [
type: 'text',
props: {
textContainer: '这是第二个子节点'
}
]
}]
}
// 下面是createElement如何生成virtualDOM
React.createElement = function(type, props, ...children) {
const childElements = [].concat(...children).map(child => {
if (child instanceof Object) {
// 对象类型则返回
return child
} else {
// 普通文本节点,处理为{type: 'text', props: { textContent: text }}
return createElement("text", { textContent: child }) }
})
return {
type,
props: Object.assign({ children: childElements }, props),
children: childElements
}
}
virtualDOM生成后如何diff
- 1:当调用setState后,React.component会调用当前实例的render方法,获取最新的virtualDOM,newVirtualDOM = this.render();
- 2: 那如何获取oldVirtualDOM呢?
- 1:react在初始化第一次生成virtualDOM时,会将virtualDOM挂载在对应的dom节点对象下,只要拿到对应的dom节点对象就能获取到oldVirtualDOM,
- 2 那如何获取对应的dom节点对象,在初始化oldVirtualDOM时,react内部会调用new 我们当前的class组件,并调用了组件实例的setDom方法,将dom节点对象挂载在当前的class实例下
function diff(newVirtualDOM, oldVirtualDOM, oldDOM) {
// 第一步:判断newVirtualDOM和oldVirtualDOM的type
if (newVirtualDOM.type !== oldVirtualDOM.type) {
cnost newDom = creatDom(newVirtualDOM)
// 移除旧的节点
unMount(oldVirtualDOM);
mount(newVirtualDOM, oldDOM);
} else { // 若是同一个组件
/** 以下是props的diff算法
* 1:先遍历新的props时,对比新的prop和旧的prop是否相等,不相等则替换prop
* 2:新的props添加完成后,判断旧的props的长度是否大于旧的props长度,
* 2:是的话,就说明需要删除后面(oldpropsLength - newpropsLength)个prop。
*/
// 第一步:依据newVirtualDOM.props和oldVirtualDOM.props进行更新props
const newpropsLength = Object.keys(newVirtualDOM.props).length;
const oldpropsLength = Object.keys(oldVirtualDOM.props).length;
for (let i = 0; i < newpropsLength.length; i ++) {
// 新props的属性和旧pros属性名和值都相等
if (newVirtualDOM[i].type !== oldVirtualDOM[i].type){
updateProps(newVirtualDOM[i], oldDOM)
}
}
// 若旧的oldVirtualDOM.props大于新的newVirtualDOM.props,则之后旧的props要删除
for (let i = oldpropsLength.length - 1; i > newpropsLength.length - 1; i --) {
delProps(oldVirtualDOM[i]);
}
}
//接下来diff children
/**
* 1:先判断children是否包含key
* 包含了:走包含key的diff算法
* 不包含:走不包含key的算法
*/
let keyElements = {};
if (oldVirtualDOM && newVirtualDOM.type === oldVirtualDOM.type) {
for (let i = 0, len = oldDOM.childNodes.length; i < len; i++) {
let domElement = oldDOM.childNodes[i]
if (domElement.nodeType === 1) {
let key = domElement.getAttribute("key")
if (key) {
keyElements[key] = domElement
}
}
}
}
const isHasKey = Object.keys(keyElements).length;
if (isHasKey) {
// 1: 遍历newVirtualDOM下children,获取key
// 2: 根据key到keyElements寻找对应节点
// 3: 没找到,则在当前索引下插入child节点
// 这样只处理了新增和修改的
for (let i = 0; i < newVirtualDOM.children.length - 1; i ++) {
const key = newVirtualDOM.children[i].key;
if (key) {
let domElement = keyElements[key]
// 若存在相同key的子元素,还需要判断新旧元素的顺序是否一致
if (domElement) {
// 若顺序不相等,则插入新的dom节点
if (oldDOM.childNode[i] && oldDOM.childNode[i] !=== domElement) {
oldDOM.insertBefore(domElement, oldDOM.childNode[i]);
}
}
}
}
// 接下来处理删除的
// 1: 遍历旧的oldDOM.childNodes
// 2: 判断oldVirtualDOM.children中的key是否在newVirtualDOM下children中存在,
// 如果不存在,则需要删除对应的childNode。调用umMount(childNode);
// let oldChildKey = oldChild._virtualDOM.props.key
const
for (let i = oldChild._virtualDOM, i < oldDOM._virtualDOM.children.length - 1; i ++) {
const key = oldDOM._virtualDOM.children[i].props[key];
// 寻找新的newVirtualDOM中是否包含旧的oldVirtualDOM中的key
// 不包含则删除该节点
cnost isFind = newVirtualDOM.children.find((child) => {
return key === child.props.key;
})
if (!isFind) {
// 卸载对应的节点,
// unMount里还要地柜处理childNodes下的所有子组件的ref绑定,和所有子组件的事件监听,放在内存泄漏
// 所有自组件的ref引用移除,dom事件监听解绑,则调用oldDOM.childNodes[i].remove();删除自身dom节点
unMount(oldDOM.childNodes[i])
}
}
} else {
}
}
React.component = class Component {
constructor(props) {
this.props = props
};
setState(options){
this.state = options;
const newVirtualDOM = this.render();
// 获取当前组件对应的dom节点
const dom = this.getDom();
// 通过dom获取oldVirtualDOM
const oldVirtualDOM = dom._virtualDOM
/**
* 新的virtualDOM: newVirtualDOM
* 旧的virtualDOM: oldVirtualDOM
* 组件挂载的dom节点:this.dom
*/
diff(newVirtualDOM, oldVirtualDOM, this.dom);
}
setDom(dom) {
this.dom = dom;
}
getDom() {
return this.dom;
}
}
class childClass extends React.component {
state = {
cont = 0;
}
onClick() {
this.setState({ count: 1 })
}
render() {
return (
const count = this.state.count
<div>
<div> {count}</div>
<button onClick={this.onClick.bind(this)}></button>
</div>
)
}
}
// 遍历virtualDOM时如果判断当前virtualDOM的type时class组件,
// 则会实例化组件new virtualDOM.type(virtualDOM.props);
// 我们上面有提到virtualDOM.type可以是标签名称,函数式组件的函数,或者是具有render方法的calss
// 如果判断到virtualDOM.type是函数且原型上具有render方法,则说明是class组件,就会调用
// buildStatefulComponent实例化这个组件,得到Virtual DOM
function buildStatefulComponent(virtualDOM) {
// 实例化react的class组件
const component = new virtualDOM.type(virtualDOM.props);
// 调用组件的render方法,获取最新的virtualDOM
const nextVirtualDOM = component.render()
// 将component挂载在virtualDOM下,之后可以通过virtualDOM获取对应的组件实例
nextVirtualDOM.component = component
// 返回virtualDOM
return nextVirtualDOM
}
function mountComponent(virtualDOM, container) {
let nextVirtualDOM = null
// 是否函数组件
if (isFunctionalComponent(virtualDOM)) {
// 调用函数组件获取最新virtualDOM
nextVirtualDOM = buildFunctionalComponent(virtualDOM)
} else {
// 调用class组件获取最新virtualDOM
nextVirtualDOM = buildStatefulComponent(virtualDOM)
}
if (isFunction(nextVirtualDOM)) {
mountComponent(nextVirtualDOM, container)
} else {
mountNativeElement(nextVirtualDOM, container)
}
}
function mountNativeElement(virtualDOM, container) {
const component = virtualDOM.component
if (component) {
// 将组件对应的com阶段,调用setDom挂载到实例的this.dom下。
component.setDOM(container)
}
}