react组件的渲染
1,生成虚拟dom对象:
function createElement(type, config, children) {//children永远都是数组
let ref, key;
if (config) {
delete config.__source;
delete config.__self;
ref = config.ref;//后面会讲用来引用这个真实DOM元素
key = config.key;//后面会讲是用来进行DOM-DIFF优化的,是用来唯一标识某个子元素的
delete config.ref;
delete config.key;
}
let props = { ...config };
if (arguments.length > 3) {//如果参数的长度大于3,说明有多个儿子
props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
} else if (arguments.length === 3) {
props.children = wrapToVdom(children);//字符串 数字 React元素
}
return {//这就是虚拟DOM,也就是React元素
$$typeof: REACT_ELEMENT,//指的是元素的类型 现在没有什么作用
type,//dom标签的类型 h1 h2 span div
ref,
key,
props//className style children
}
}
通过createElement封装一个虚拟dom,虚拟dom,有type,prop等属性
2,将虚拟dom更新到真实dom上:
function render(vdom, container) {
mount(vdom, container);
}
//把虚拟dom转换为真实dom并且更新到对应的父节点上。
function mount(vdom, parentDOM) {
//把虚拟DOM变成真实DOM
let newDOM = createDOM(vdom);
//把真实DOM追加到容器上
if (newDOM) parentDOM.appendChild(newDOM);
if (newDOM && newDOM.componentDidMount) newDOM.componentDidMount();
}
//根据虚拟dom创建真实的dom,并将虚拟dom的属性更新到真实dom上。
function createDOM(vdom) {
let { type, props, ref } = vdom;
let dom; //真实DOM
if (type && type.$$typeof === REACT_MEMO) {
return mountMemoComponent(vdom);
} else if (type && type.$$typeof === REACT_PROVIDER) {
return mountProviderComponent(vdom);
} else if (type && type.$$typeof === REACT_CONTEXT) {
return mountContextComponent(vdom);
} else if (type && type.$$typeof === REACT_FORWARD_REF) {
return mountForwardComponent(vdom);
} else if (type === REACT_TEXT) {
//创建文件节点
dom = document.createTextNode(props.content);
} else if (typeof type === 'function') {
if (type.isReactComponent) {
//说明这是一个类组件
return mountClassComponent(vdom);
} else {
return mountFunctionComponent(vdom);
}
} else if (typeof type === 'string') {
//创建DOM节点 span div p
dom = document.createElement(type);
}
if (props) {
//更新DOM的属性 后面我们会实现组件和页面的更新。
updateProps(dom, {}, props);
let children = props.children;
//如果说children是一个React元素,也就是说也是个虚拟DOM
if (typeof children === 'object' && children.type) {
//把这个儿子这个虚拟DOM挂载到父节点DOM上
props.children.mountIndex = 0;
mount(children, dom);
} else if (Array.isArray(children)) {
reconcileChildren(children, dom);
}
}
vdom.dom = dom; //在虚拟DOM挂载或者说放置一个dom属性指向此虚拟DOM对应的真实DOM
if (ref) ref.current = dom;
return dom;
}
function reconcileChildren(children, parentDOM) {
children.forEach((childVdom, index) => {
childVdom.mountIndex = index;
mount(childVdom, parentDOM);
});
}
不同类型的虚拟dom渲染为真实dom:
function mountMemoComponent(vdom) {
let { type, props } = vdom; //type={$$typeof: REACT_MEMO,compare,type:FunctionCounter}
let renderVdom = type.type(props);
vdom.prevProps = props; //记录一下老的属性对象,方便更新的时候进行对比
vdom.oldRenderVdom = renderVdom;
if (!renderVdom) return null;
return createDOM(renderVdom);
}
function mountProviderComponent(vdom) {
let { type, props } = vdom; //type={$$typeof: REACT_PROVIDER, _context: context}
let context = type._context; //{ $$typeof: REACT_CONTEXT, _currentValue: undefined }
context._currentValue = props.value; //把属性中的value值赋给context._currentValue
let renderVdom = props.children; //Provider而言它要渲染的其实 是它的儿子
vdom.oldRenderVdom = renderVdom; //这一步是为了后面更新用的
if (!renderVdom) return null;
return createDOM(renderVdom);
}
function mountContextComponent(vdom) {
let { type, props } = vdom; //type = {$$typeof: REACT_CONTEXT,_context: context}
let context = type._context;
let renderVdom = props.children(context._currentValue);
vdom.oldRenderVdom = renderVdom;
if (!renderVdom) return null;
return createDOM(renderVdom);
}
function mountForwardComponent(vdom) {
let { type, props, ref } = vdom;
let renderVdom = type.render(props, ref);
if (!renderVdom) return null;
return createDOM(renderVdom);
}
//类组件的虚拟dom渲染为真实dom
function mountClassComponent(vdom) {
let { type: ClassComponent, props, ref } = vdom;
//把类组件的属性传递给了类组件的构造函数,
let classInstance = new ClassComponent(props); //创建类组件的实例
if (ClassComponent.contextType) {
//把value值赋给classInstance的context属性
classInstance.context = ClassComponent.contextType._currentValue;
}
vdom.classInstance = classInstance; //在虚拟DOm上挂载一个属性,指向类组件的实例
if (ref) ref.current = classInstance;
if (classInstance.componentWillMount) {
classInstance.componentWillMount();
}
//可能是原生组件的虚拟DOM,也可能是类组件的的虚拟DOM,也可能是函数组件的虚拟DOM
let renderVdom = classInstance.render();
//在第一次挂载类组件的时候让类实例上添加一个oldRenderVdom=renderVdom
vdom.oldRenderVdom = classInstance.oldRenderVdom = renderVdom;
if (!renderVdom) return null;
let dom = createDOM(renderVdom); //TODO
if (classInstance.componentDidMount) {
dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);
}
return dom;
}
//函数组件的虚拟dom渲染为真实dom
function mountFunctionComponent(vdom) {
let { type: functionComponent, props } = vdom;
let renderVdom = functionComponent(props); //获取组件将要渲染的虚拟DOM
vdom.oldRenderVdom = renderVdom;
if (!renderVdom) return null;
return createDOM(renderVdom);
}
更新真实dom的属性:
function updateProps(dom, oldProps, newProps) {
for (let key in newProps) {
if (key === 'children') {
//此处不处理子节点
continue;
} else if (key === 'style') {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else if (/^on[A-Z].*/.test(key)) {
///说明是一个事件处理函数
//dom.onclick = 函数
//dom[key.toLowerCase()]=newProps[key];
//以后我们不再把事件函数绑定到对应DOM上的,而是会进行事件委托,全部委托到document上
addEvent(dom, key.toLowerCase(), newProps[key]);
} else {
dom[key] = newProps[key];
}
}
for (let key in oldProps) {
//如果说一个属性老的属性对象里有,新的属性没有,就需要删除
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
react组件的setState更新渲染:
React.Component实现:
class Component {
static isReactComponent = true;
constructor(props) {
this.props = props;
this.state = {};
this.updater = new Updater(this);
}
setState(partialState) {
this.updater.addState(partialState);
}
forceUpdate() {
let oldRenderVdom = this.oldRenderVdom; //获取老的虚拟DOM
let oldDOM = findDOM(oldRenderVdom); //获取老的真实DOM
if (this.constructor.contextType) {
this.context = this.constructor.contextType._currentValue;
}
if (this.constructor.getDerivedStateFromProps) {
let newState = this.constructor.getDerivedStateFromProps(
this.props,
this.state
);
if (newState) {
this.state = { ...this.state, ...newState };
}
}
let newRenderVdom = this.render();
let snapshot =
this.getSnapshotBeforeUpdate && this.getSnapshotBeforeUpdate();
//此处的逻辑其实就是DOM-DIFF的逻辑
compareTwoVdom(oldDOM.parentNode, oldRenderVdom, newRenderVdom);
this.oldRenderVdom = newRenderVdom;
if (this.componentDidUpdate) {
this.componentDidUpdate(this.props, this.state, snapshot);
}
}
}
调用组件实例的setState会触发组件的更新,会调用Updater的addState进行对应的操作。forceUpdate是组件根据当前的属性state和prop进行强制更新。 Updater实现:
class Updater {
constructor(classInstance) {
this.classInstance = classInstance;
this.pendingStates = [];
}
addState(partialState) {
//将所有的新状态放到一个待处理状态数组中。
this.pendingStates.push(partialState);
this.emitUpdate();
}
//判断当前是否是批处理模式,然后根据具体模式去决定是否更新。
emitUpdate(nextProps) {
this.nextProps = nextProps;
if (updateQueue.isBatchingUpdate) {
//说明当前处于批量列新模式
updateQueue.updaters.add(this); //会将update实例放进来
} else {
this.updateComponent(); //立即更新
}
}
updateComponent() {
let { nextProps, classInstance, pendingStates } = this;
//如果属性更新的了,或者说状态更新了都会进行更新
if (nextProps || pendingStates.length > 0) {
shouldUpdate(classInstance, nextProps, this.getState());
}
}
//基于老状态和pendingStates获取新状态
getState() {
let { classInstance, pendingStates } = this;
let { state } = classInstance; //老状态
pendingStates.forEach((nextState) => {
if (typeof nextState === 'function') {
nextState = nextState(state);
}
state = { ...state, ...nextState };
});
pendingStates.length = 0;
return state;
}
}
updateQueue存放的是updater实例。 判断更新是否拦截:
function shouldUpdate(classInstance, nextProps, nextState) {
let willUpdate = true;
//如果有shouldComponentUpdate方法,并且shouldComponentUpdate执行结果是false的话才会willUpdate=false
if (
classInstance.shouldComponentUpdate &&
!classInstance.shouldComponentUpdate(nextProps, nextState)
) {
willUpdate = false;
}
if (willUpdate && classInstance.componentWillUpdate) {
classInstance.componentWillUpdate();
}
if (nextProps) {
classInstance.props = nextProps;
}
classInstance.state = nextState; //不管要不要更新,赋值都 会执行
if (willUpdate) {
//如果要更新,就会更新界面,否则 不更新
classInstance.forceUpdate();
}
}
setState不是批量更新的情况下:首先将最新的状态存放在一个数组中,然后合并更新的状态和组件的老状态为一个最新的状态,然后根据组件的shouldComponentUpdate的返回值,去决定是否进行组件更新。不管组件是否重新渲染,组件的状态都要更新为最新状态。
合成事件与批量更新
import { updateQueue } from './component';
/**
*
* @param {*} dom 要绑定事件的DOM元素 button
* @param {*} eventType 事件类型 onclick
* @param {*} handler 事件处理函数 handleClick
*/
export function addEvent(dom, eventType, handler) {
//保证dom有store属性,值初始化是一个空对象 store是一个自定义属性
/* let store;
if(dom.store){
store = dom.store;
}else{
store = dom.store = {};
} */
let store = dom.store || (dom.store = {})
//dom.store['onclick']=handleClick
store[eventType] = handler;//虽然没有给每个子DOM绑定事件,但是事件处理函数还是保存在子DOM上的
//从17开始,我们不再把事件委托给document.而是委托给容器了 div#root
if (!document[eventType]) {
document[eventType] = dispatchEvent;
}
}
//合成事件的统一代理处理函数
function dispatchEvent(event) {
let { target, type } = event;//target=button type =click
let eventType = `on${type}`;//onclick
let syntheticEvent = createSyntheticEvent(event);
updateQueue.isBatchingUpdate = true;//事件函数执行前先设置批量更新模式为true
//在此我们要模拟React事件的冒泡
while (target) {
let { store } = target;
let handler = store && store[eventType]
handler && handler(syntheticEvent);
//在执行handler的过程中有可能会阻止冒泡
if (syntheticEvent.isPropagationStopped) {
break;
}
target = target.parentNode;
}
updateQueue.isBatchingUpdate = false;
updateQueue.batchUpdate();
}
/**
* 为什么React不会原生事件对象直接传给事件处理函数
* 1.为了兼容性 像jquery一样,抹平浏览器的差异
* @param {*} nativeEvent
* @returns
*/
function createSyntheticEvent(nativeEvent) {
let syntheticEvent = {};
for (let key in nativeEvent) {//把原生事件上的属性拷贝到合成事件对象上去
syntheticEvent[key] = nativeEvent[key];
}
syntheticEvent.nativeEvent = nativeEvent;
syntheticEvent.isDefaultPrevented = false;
syntheticEvent.isPropagationStopped = false;
syntheticEvent.preventDefault = preventDefault;
syntheticEvent.stopPropagation = stopPropagation;
return syntheticEvent;
}
function preventDefault() {
this.defaultPrevented = true;
const event = this.nativeEvent;
if (event.preventDefault) {
event.preventDefault();
} else {//IE
event.returnValue = false;
}
this.isDefaultPrevented = true;
}
function stopPropagation() {
const event = this.nativeEvent;
if (event.stopPropagation) {
event.stopPropagation();
} else {//IE
event.cancelBubble = true;
}
this.isPropagationStopped = true;
}
dispatchEvent是将原生事件进行封装成合成事件对象,然后传给对应的事件处理函数,这样我们拿到的事件对象就是合成事件对象,在合成事件触发前后,可以设置批量模式,并进行批量更新。