±à¼ÍƼö: |
±¾ÎÄÖØµã½éÉÜÁËpreactÊÇʲô?ÐéÄâDOM¡¢×é¼þ¡¢diffËã·¨¡¢Òì²½setStateµÈÏà¹ØÄÚÈÝ¡£
±¾ÎÄÀ´×ÔÓÚ˼·ñ,ÓÉ»ðÁú¹ûÈí¼þAnna±à¼ÍƼö¡£ |
|
ǰÑÔ
preactËäÈ»ÊÇreactµÄ×îСʵÏÖ, ºÜ¶àreactµÄÌØÐÔpreactÀïÒ»µã¶¼Ã»ÓÐÉÙ, ±ÈÈçcontextAPI,
FragmentµÈ¡£ÎÒÃÇ·ÖÎöʱ¸ü×¢ÖØÊµÏÖ¹ý³Ì£¬»á¶ÔһЩAPIµÄʵÏÖ½øÐкöÂÔ¡£Çë¼ûÁÂ
preactÊÇʲô?
Fast 3kB React alternative with the
same modern API. Components & Virtual DOM
preact¿ÉÒÔ˵ÊÇÀàreact¿ò¼ÜµÄ×îСʵÏÖ
ÐéÄâDOM
¹ØÓÚjsx
ÎÒÃÇÊ×ÏÈ¿´ÏÂpreact¹ÙÍøÉϵÄdemo¡£
import { h, render
} from 'preact';
render((
<h1 id="title" >Hello, world!</h1>
), document.body); |
ÆäʵÉÏÃæµÄjsx´úÂ룬±¾ÖÊÊÇÏÂÃæ´úÂëµÄÓï·¨ÌÇ
h(
'h1',
{ id: 'title' },
'Hello, world!'
) |
preactÊÇÈçºÎ×öµ½µÄÄØ£¿preact±¾Éí²¢Ã»ÓÐʵÏÖÕâ¸öÓ﷨ת»»µÄ¹¦ÄÜ£¬preactÊÇÒÀÀµtransform-react-jsxµÄbabel²å¼þ×öµ½µÄ¡£
createElement
Ç°ÃæÎÒÃÇ¿´µ½ÁËjsxµÄ´úÂë»á±»×ª»»ÎªÓÃhº¯Êý°ü¹üµÄ´úÂë, ÎÒÃǽÓÏÂÀ´¿´ÏÂhº¯ÊýÊÇÈçºÎʵÏֵġ£createElementº¯ÊýλÓÚcreate-element.jsÕâ¸öÎļþÖС£
ÎļþÖÐÖ÷ҪΪ3¸öº¯Êý, createElementºÍcreateVNode, ÒÔ¼°coerceToVNode¡£
createElementºÍcreateVNodeÊÇÒ»¶ÔµÄ, createElement»á½«children¹ÒÔØµ½VNodeµÄpropsÖС£¼Èprops.childrenµÄÊý×éÖС£createVNodeÔò»á½«¸ù¾ÝÕâЩ²ÎÊý·µ»ØÒ»¸ö¶ÔÏó,
Õâ¸ö¶ÔÏó¾ÍÊÇÐéÄâDOM¡£
ÔÚcreateElementÖÐÎÒÃÇ»¹¿ÉÒÔ¿´µ½¶ÔdefaultPropsµÄ´¦Àí, ¶ødefaultProps¿ÉÒÔΪÎÒÃÇÉèÖÃpropsµÄĬÈϵijõʼֵ¡£
export function createElement (type, props, children)
{
if (props==null) props = {};
if (arguments.length>3) {
children = [children];
for (let i=3; i<arguments.length; i++) {
children.push(arguments[i]);
}
}
if (children!=null) {
props.children = children;
}
if (type!=null && type.defaultProps!=null)
{
for (let i in type.defaultProps) {
if (props[i]===undefined) props[i] = type.defaultProps[i];
}
}
let ref = props.ref;
if (ref) delete props.ref;
let key = props.key;
if (key) delete props.key; return createVNode (type, props, null, key,
ref);
} export function createVNode (type, props, text,
key, ref) { const vnode = {
type,
props,
text,
key,
ref,
_children: null,
_dom: null,
_lastDomChild: null,
_component: null
}; return vnode;
}
|
¶øcoerceToVNodeº¯ÊýµÄ×÷ÓÃÔòÊǽ«Ò»Ð©Ã»ÓÐtypeÀàÐ͵Ľڵ㡣±ÈÈçÒ»¶Î×Ö·û´®,
Ò»¸öÊý×ÖÇ¿ÖÆ×ª»»ÎªVNode½Úµã, ÕâЩ½ÚµãµÄtypeֵΪnull, textÊôÐÔÖб£ÁôÁË×Ö·û´®ºÍÊý×ÖµÄÖµ¡£
export function
coerceToVNode (possibleVNode) {
if (possibleVNode == null || typeof possibleVNode
=== 'boolean') return null;
if (typeof possibleVNode === 'string' || typeof
possibleVNode === 'number') {
return createVNode (null, null, possibleVNode,
null, null);
} if (Array.isArray(possibleVNode)) {
return createElement(Fragment, null, possibleVNode);
} if (possibleVNode._dom!=null) {
return createVNode (possibleVNode.type, possibleVNode.props,
possibleVNode.text, possibleVNode.key, null);
} return possibleVNode;
}
|
µ½ÕâÀïcreate-elementµÄÕâ¸öÄ£¿éÎÒÃǾͽéÉÜÍêÁË¡£ÕâÊÇÒ»¸ö·Ç³£¼òµ¥µÄÄ£¿é, ×öµÄ¹¦ÄܾÍÊǸù¾Ý¶ÔÓ¦µÄjsx->ÐéÄâDOM¡£ÎÒÃÇÕâÀﻹûÓÐÉæ¼°ÈçºÎäÖȾ³öÕæÕýµÄDOM½Úµã,
ÕâÊÇÒòΪpreactÖÐäÖȾµÄ¹ý³ÌÊÇÖ±½ÓÔÚdiffËã·¨ÖÐʵÏÖ£¬Ò»±ß±È¶ÔÒ»±ß¸ú¸üÐÂÕæÊµµÄdom¡£
×é¼þ
preactÖÐÓÐÒ»¸öͨÓÃComponentÀà, ×é¼þµÄʵÏÖÐèÒª¼Ì³ÐÕâ¸öͨÓõÄComponentÀà¡£ÎÒÃÇÀ´¿´ÏÂpreactÖÐComponentÀàÊÇÈçºÎʵÏֵġ£ËüλÓÚcomponent.jsÎļþ??ÖС£
ÎÒÃÇÊ×ÏÈ¿´ÏÂComponentÀàµÄ¹¹Ô캯Êý£¬·Ç³£µÄ¼òµ¥¡£Ö»ÓÐÁ½¸öÊôÐÔprops, context¡£ÒòΪͨÓõÄComponentÀàʵÏÖÁËpropsÊôÐÔ£¬ËùÒÔÎÒÃǵÄ×é¼þÀàÔڼ̳ÐComponentÀàºó£¬ÐèÒªÏÔʽµÄʹÓÃsuper×÷Ϊº¯Êýµ÷Ó㬲¢½«props´«Èë¡£
export function Component(props,
context) {
this.props = props
this.context = context
}
|
ComponentÀàÖÐʵÏÖÁËsetState·½·¨, forceUpdate·½·¨£¬render·½·¨£¬ÒÔ¼°ÆäËûµÄһЩ¸¨Öúº¯Êý¡£forceUpdateÉæ¼°µ½ÁËsetStateµÄÒì²½¸üÐÂ,
ÎÒÃǽ«ÔÚsetStateÒ»½ÚÖÐרÃŽéÉÜ¡£ÕâÀïÔݲ»×ö½éÉÜ¡£ÎÒÃǽÓÏÂÀ´¿´¿´setStateµÄʵÏÖ¡£
Component.prototype.setState
= function(update, callback) {
let s = (this._nextState!==this.state &&
this._nextState) || (this._nextState = assign({},
this.state));
if (typeof update!=='function' || (update
= update(s, this.props))) {
assign(s, update);
} if (update==null) return; if (this._vnode) {
if (callback) this._renderCallbacks.push(callback);
enqueueRender (this);
}
}; // src/util.js
export function assign(obj, props) {
for (let i in props) obj[i] = props[i];
return obj;
} |
ÔÚpreactµÄsetState·½·¨, ͬreactÒ»ÑùÖ§³Öº¯Êý»òÕßObjectÁ½ÖÖ·½Ê½¸üÐÂstate,
²¢ÇÒÖ§³ÖsetStateµÄ»Øµ÷¡£ÎÒÃÇÕâÀï¿´µ½ÁËÁ½¸ö¸ö˽ÓÐÊôÐÔ_nextState, _renderCallbacks¡£_renderCallbacksÔòÊÇ´æ´¢ÁËsetState»Øµ÷µÄ¶ÓÁС£
_nextStateÀï´æ´¢ÁË×îеÄstate, ΪʲôÎÒÃDz»È¥Ö±½Ó¸üÐÂstateÄØ£¿ÒòΪÎÒÃÇҪʵÏÖÉúÃüÖÜÆÚ,
±ÈÈçgetDerivedStateFromPropsÉúÃüÖÜÆÚÖÐ×é¼þµÄstate²¢Ã»ÓиüÐÂÄØ¡£ÎÒÃÇÐèҪʹÓÃ_nextState´æ´¢×îеÄstate??¡£enqueueRenderº¯ÊýÉæ¼°µ½ÁËstateµÄÒì²½¸üÐÂ,
ÎÒÃÇÔÚ±¾½ÚÏȲ»½éÉÜ¡£
// src/component.js
export function Fragment() { }
Component.prototype.render = Fragment; |
»ùÀàµÄrender·½·¨±¾ÉíÊÇÒ»¸ö¿Õº¯Êý, ÐèÒª¼Ì³ÐµÄ×ÓÀà×Ô¼º¾ßÌåʵÏÖ¡£
component.jsµÄÄ£¿éµÄ²¿·ÖÄÚÈÝ£¬ÎÒÃÇÒѾ½éÉÜÍê³ÉÁË, ͬÑù²»ÊǺܸ´ÔÓ¡£component.jsµÄÄ£¿éµÄÆäËûµÄÄÚÈÝÒòÎªÉæ¼°ÁËsetStateÒì²½¸üжÓÁУ¬ËùÒÔÎÒÃǽ«ÔÚsetStateÒ»½ÚÖС£»Ø¹ýÍ·À´½éÉÜËü¡£
diffËã·¨

ps:ÎÒÃÇÖ»ÐèÒª±È½Ïͬ¼¶µÄ½Úµã(ÏàͬÑÕÉ«¿òÄÚµÄ), Èç¹ûÁ½¸ö½Úµãtype²»Ò»ÖÂ,
ÎÒÃÇ»áÏú»Ùµ±Ç°µÄ½Úµã¡£²»½øÐбȽÏ×Ó½ÚµãµÄ²Ù×÷¡£
ÔÚpreactÖÐdiffËã·¨ÒÔ¼°ÕæÊµdomµÄ¸üкÍäÖȾÊÇÔÓôÛÔÚÒ»ÆðµÄ¡£ËùÒÔ±¾½ÚÄÚÈÝ»á±È½Ï¶à¡£
preact»á´æ´¢ÉÏÒ»´ÎµÄäÖȾµÄVNode(´æ´¢ÔÚ_prevVNodeµÄ˽ÓÐÊôÐÔÉÏ)¡£¶ø±¾´ÎäÖȾ¹ý³ÌÖÐÎÒÃÇ»á±È½Ï±¾´ÎµÄVNodeÉÏǰһ´ÎµÄ_prevVNode¡£ÅжÏÊÇ·ñÐèÒªÉú³ÉеÄDom,
Ð¶ÔØDomµÄ²Ù×÷, ¸üÐÂÕæÊµdomµÄ²Ù×÷(ÎÒÃǽ«VNode¶ÔÓ¦µÄÕæÊµµÄdom´æ´¢ÔÚVNodeµÄ˽ÓÐÊôÐÔ_dom,
¿ÉÒÔʵÏÖÔÚdiffµÄ¹ý³ÌÖиüÐÂdomµÄ²Ù×÷)¡£
render
¶Ô±ÈÎı¾½Úµã
ÎÒÃÇÊ×ÏÈ»ØÒäÒ»ÏÂÎı¾½ÚµãµÄVNodeµÄ½á¹¹ÊÇÔõôÑùµÄ
// Îı¾½ÚµãVNode
{
type: null,
props: null,
text: 'ÄãµÄÎı¾'
_dom: TextNode
} |
ÎÒÃÇÊ×ÏȽøÈëdiff·½·¨¡£diff·½·¨Öлá¶ÔVNodeÀàÐͽøÐÐÅжÏ,
Èç¹û²»ÊÇfunctionÀàÐÍ(×é¼þÀàÐÍ), ºÍFragmentÀàÐÍ¡£ÎÒÃǵĻáµ÷ÓÃdiffElementNodesº¯Êý¡£
// src/diff/index.js
// func diff
// ²ÎÊýºÜ¶à, ÎÒÃÇÀ´ËµÏ¼¸¸ö²ÎÊýµÄ¾ßÌ庬Òå
// domΪVNode¶ÔÓ¦µÄÕæÊµµÄDom½Úµã
// newVNodeеÄVNode
// oldVNode¾ÉµÄVNode
// mounts´æ´¢¹ÒÔØ×é¼þµÄÁбí
dom = diffElementNodes(dom, newVNode, oldVNode,
context, isSvg, excessDomChildren, mounts, ancestorComponent) |
Èç¹û´Ëʱdom»¹Ã»Óд´½¨¡£³õ´ÎäÖȾ, ÄÇôÎÒÃǸù¾ÝVNodeÀàÐÍ´´½¨¶ÔÓ¦µÄÕæÊµdom½Úµã¡£Îı¾ÀàÐÍ»áʹÓÃcreateTextNode´´½¨Îı¾½Úµã¡£
½ÓÏÂÀ´ÎÒÃÇ»á±êǩ֮ǰVNodeµÄtextµÄÄÚÈÝ, Èç¹ûоɲ»ÏàµÈ¡£ÎÒÃǽ«ÐÂVNodeµÄtextÊôÐÔ£¬¸³Öµ¸ødom½Úµã¡£Íê³É¶ÔdomµÄ¸üвÙ×÷¡£
// src/diff/index.js
// func diffElementNodes
if (dom==null) {
dom = newVNode.type===null ? document.createTextNode(newVNode.text)
: isSvg ? document.createElementNS('http://www.w3.org/2000/svg',
newVNode.type) : document.createElement(newVNode.type); excessDomChildren = null;
} newVNode._dom = dom; if (newVNode.type===null) {
if ((d===null || dom===d) && newVNode.text!==oldVNode.text)
{
dom.data = newVNode.text;
}
} |
¶Ô±È·ÇÎı¾DOM½Úµã
·ÇÎı¾DOM½ÚµãÖ¸µÄÊÇÄÇЩtypeΪdiv, span, h1µÄVNode½Úµã¡£ÕâЩÀàÐ͵ĽڵãÔÚdiff·½·¨ÖÐ,
ÎÒÃÇÒÀ¾É»áµ÷ÓÃdiffElementNodesº¯ÊýÈ¥´¦Àí¡£
// src/diff/index.js
// func diff
dom = diffElementNodes(dom, newVNode, oldVNode,
context, isSvg, excessDomChildren, mounts, ancestorComponent) |
½øÈëdiffElementNodes·½·¨ºó, Èç¹ûÊdzõ´ÎäÖȾÎÒÃÇ»áʹÓÃcreateElement´´½¨ÕæÊµµÄdom½Úµã¹ÒÔØµ½VNodeµÄ_domÊôÐÔÉÏ¡£
½ÓÏÂÀ´ÎÒÃÇ»á±È½ÏоÉVNodeµÄÊôÐÔprops¡£µ«ÊÇ֮ǰ»áµ÷ÓÃdiffChildren·½·¨,
¶Ôµ±Ç°µÄVNode×Ó½Úµã½øÐбȽϡ£ÎÒÃÇÕâÀïÏȲ»½øÈëdiffChildrenº¯ÊýÖС£ÎÒÃÇÖ»ÐèÒªÖªµÀÎÒÃÇÔÚ¸üе±Ç°½ÚµãÊôÐÔµÄʱºò,
ÎÒÃÇÒѾͨ¹ýµÝ¹éÐÎʽ, Íê³ÉÁ˶Ե±Ç°½ÚµãµÄ×Ó½ÚµãµÄ¸üвÙ×÷¡£½ÓÏÂÀ´ÎÒÃǽøÈëdiffPropsº¯ÊýÖС£
// src/diff/index.js
// func diffElementNodes
if (dom==null) {
dom = newVNode.type===null ? document.createTextNode
(newVNode.text) : isSvg ? document.createElementNS
('http://www.w3.org/2000/svg', newVNode.type)
: document.createElement (newVNode.type);
} newVNode._dom = dom; if (newVNode !== oldVNode) {
let oldProps = oldVNode.props;
let newProps = newVNode.props; if (oldProps == null) {
oldProps = {};
}
diffChildren(dom, newVNode, oldVNode, context,
newVNode.type === 'foreignObject' ? false :
isSvg, excessDomChildren, mounts, ancestorComponent);
diffProps (dom, newProps, oldProps, isSvg);
}
|
ÔÚdiffPropsº¯ÊýÖÐÎÒÃÇ»á×öÁ½¼þÊ¡£ÉèÖÃ, ¸üÐÂÊôÐÔ¡£É¾³ýеÄpropsÖв»´æÔÚµÄÊôÐÔ¡£setPropertyÔÚpreactÖеľßÌåʵÏÖ,
ÎÒÃÇÍùÏ¿´¡£
// src/diff/props.js
export function diffProps (dom, newProps, oldProps,
isSvg) {
// ÉèÖûò¸üÐÂÊôÐÔÖµ
for (let i in newProps) {
if (i!=='children' && i!=='key' &&
(!oldProps || ((i==='value' || i==='checked')
? dom : oldProps) [i]!==newProps[i])) {
setProperty(dom, i, newProps[i], oldProps[i],
isSvg);
}
}
// ɾ³ýÊôÐÔ
for (let i in oldProps) {
if (i!=='children' && i!=='key' &&
(!newProps || !(i in newProps))) {
setProperty (dom, i, null, oldProps[i], isSvg);
}
}
} |
ÔÚsetProperty·½·¨ÖÐ, Èç¹ûvalue(еÄÊôÐÔÖµ)Ϊnull, ÎÒÃÇ»áɾ³ý¶ÔÓ¦µÄÊôÐÔ¡£Èç¹û²»Îªnull,
ÎÒÃǽ«»á¸üлòÕßÉèÖÃеÄÊôÐÔ¡£Í¬Ê±»¹»á¶Ôʼþ½øÐд¦Àí, ÀýÈçonClickÊôÐÔ, ÎÒÃÇ»áʹÓÃaddEventListenerÌí¼ÓÔÉúµÄclickʼþ¡£
// src/diff/props.js
function setProperty (dom, name, value, oldValue,
isSvg) {
let v;
// ¶Ôclass´¦Àí
if (name==='class' || name==='className') name
= isSvg ? 'class' : 'className'; // ¶Ôstyle´¦Àí, style´«ÈëObject»òÕß×Ö·û´®¶¼»áµÃµ½¼æÈݵĴ¦Àí
if (name==='style') { let s = dom.style; // Èç¹ûstyleÊÇstringÀàÐÍ
if (typeof value==='string') {
s.cssText = value;
}
else {
// Èç¹ûstyleÊÇobjectÀàÐÍ
if (typeof oldValue==='string') s.cssText =
'';
else {
for (let i in oldValue) {
if (value==null || !(i in value)) s.setProperty (i.replace(CAMEL_REG,
'-'), '');
}
}
for (let i in value) {
v = value[i];
if (oldValue==null || v!==oldValue[i]) {
s.setProperty(i.replace(CAMEL_REG, '-'), typeof
v==='number' && IS_NON_DIMENSIONAL.test(i)===false
? (v + 'px') : v);
}
}
}
}
else if (name==='dangerouslySetInnerHTML') {
return;
}
else if (name[0]==='o' & & name[1]==='n')
{
// ¶Ôʼþ´¦Àí
let useCapture = name !== (name=name.replace(/Capture$/,
''));
let nameLower = name.toLowerCase();
name = (nameLower in dom ? nameLower : name).substring(2); if (value) {
if (!oldValue) dom.addEventListener (name, eventProxy,
useCapture);
}
else {
dom.removeEventListener (name, eventProxy, useCapture);
}
(dom._listeners || (dom._listeners = {}))[name]
= value;
}
else if (name!=='list' && name!=='tagName'
&& !isSvg && (name in dom))
{
dom[name] = value==null ? '' : value;
}
else if (value==null || value===false) {
// ɾ³ýÒÔ¼°ÎªnullµÄÊôÐÔ
if (name!== (name = name.replace(/^xlink:?/,
''))) dom.removeAttributeNS ('http://www.w3.org/1999/xlink',
name.toLowerCase());
else dom.removeAttribute(name);
}
else if (typeof value!=='function') {
// ¸üлòÉèÖÃеÄÊôÐÔ
if (name!==(name = name.replace(/^xlink:?/,
''))) dom.setAttributeNS ('http://www.w3.org/1999/xlink',
name.toLowerCase(), value);
else dom.setAttribute (name, value);
}
} |
¶Ô±È×é¼þ 
Èç¹ûVNodeÊÇ×é¼þÀàÐÍ¡£ÔÚdiffº¯ÊýÖÐ, »áÔÚ²»Í¬µÄʱ¿ÌÖ´ÐÐ×é¼þµÄÉúÃüÖÜÆÚ¡£ÔÚdiffÖÐ, Ö´ÐÐ×é¼þʵÀýµÄrenderº¯Êý¡£ÎÒÃǽ«»áÄõ½×é¼þ·µ»ØµÄVNode,
È»ºóÔÙ½«VNodeÔÙÒ»´Î´øÈëdiff·½·¨ÖнøÐÐdiff±È½Ï¡£´óÖµÄÁ÷³Ì¿ÉÒÔÈçÉÏͼËùʾ¡£
// src/diff/index.js
// func diff
let c, p, isNew = false, oldProps, oldState,
snapshot,
newType = newVNode.type;
let cxType = newType.contextType;
let provider = cxType && context[cxType._id];
let cctx = cxType != null ? (provider ? provider.props.value
: cxType._defaultValue) : context; if (oldVNode._component) {
c = newVNode._component = oldVNode._component;
clearProcessingException = c._processingException;
}
else {
isNew = true; // ´´½¨×é¼þµÄʵÀý
if (newType.prototype && newType.prototype.render)
{
newVNode._component = c = new newType(newVNode.props,
cctx);
}
else {
newVNode._component = c = new Component(newVNode.props,
cctx);
c.constructor = newType;
c.render = doRender;
}
c._ancestorComponent = ancestorComponent;
if (provider) provider.sub(c); // ³õʼ»¯,×é¼þµÄstate, propsµÄÊôÐÔ
c.props = newVNode.props;
if (!c.state) c.state = {};
c.context = cctx;
c._context = context;
c._dirty = true;
c._renderCallbacks = [];
} // ×é¼þµÄʵÀýÉϹÒÔØ×é¼þËù¶ÔÓ¦µÄVNode½Úµã
c._vnode = newVNode; let s = c._nextState || c.state; // Ö´ÐÐgetDerivedStateFromPropsÉúÃüÖÜÆÚº¯Êý, ·µ»ØÖ»»á¸üÐÂ×é¼þµÄstate
if (newType.getDerivedStateFromProps != null)
{
oldState = assign({}, c.state);
if (s === c.state) s = c._nextState = assign({},
s);
assign(s, newType.getDerivedStateFromProps(newVNode.props,
s));
} if (isNew) {
// Ö´ÐÐcomponentWillMountÉúÃüÖÜÆÚ
if (newType.getDerivedStateFromProps == null
&& c.componentWillMount != null) c.componentWillMount();
// ½«ÐèÒªÖ´ÐÐcomponentDidMountÉúÃüÖÜÆÚµÄ×é¼þ, pushµ½mounts¶ÓÁÐÖÐ
if (c.componentDidMount != null) mounts.push(c);
}
else {
// Ö´ÐÐcomponentWillReceivePropsÉúÃüÖÜÆÚ
if (newType.getDerivedStateFromProps == null
&& force == null && c.componentWillReceiveProps
!= null) {
c.componentWillReceiveProps(newVNode.props,
cctx);
s = c._nextState || c.state;
} // Ö´ÐÐshouldComponentUpdateÉúÃüÖÜÆÚ, ²¢½«_dirtyÉèÖÃΪfalse,
µ±_dirty±»ÉèÖÃΪfalseʱ, Ö´ÐеĸüвÙ×÷½«»á±»ÔÝÍ£
if (!force && c.shouldComponentUpdate
!= null && c.shouldComponentUpdate (newVNode.props,
s, cctx) === false) {
c.props = newVNode.props;
c.state = s;
c._dirty = false;
// breakºó£¬²»ÔÚÖ´ÐÐÒÔϵĴúÂë
break outer;
} // Ö´ÐÐcomponentWillUpdateÉúÃüÖÜÆÚ
if (c.componentWillUpdate != null) {
c.componentWillUpdate (newVNode.props, s, cctx);
}
} oldProps = c.props;
if (!oldState) oldState = c.state; c.context = cctx;
c.props = newVNode.props;
// ½«¸üкóµÄstateµÄs£¬¸³Óè×é¼þµÄstate
c.state = s; // prevΪÉÏÒ»´ÎäÖȾʱ¶ÔÓ¦µÄVNode½Úµã
let prev = c._prevVNode;
// µ÷ÓÃ×é¼þµÄrender·½·¨»ñÈ¡×é¼þµÄVNode
let vnode = c._prevVNode = coerceToVNode (c.render(c.props,
c.state, c.context));
c._dirty = false; if (c.getChildContext != null) {
context = assign (assign({}, context), c.getChildContext());
} // Ö´ÐÐgetSnapshotBeforeUpdateÉúÃüÖÜÆÚ
if (!isNew && c.getSnapshotBeforeUpdate
!= null) {
snapshot = c.getSnapshotBeforeUpdate(oldProps,
oldState);
} // ¸üÐÂ×é¼þËù¶ÔÓ¦µÄVNode£¬·µ»Ø¶ÔÓ¦µÄdom
c.base = dom = diff(dom, parentDom, vnode, prev,
context, isSvg, excessDomChildren, mounts, c,
null); if (vnode != null) {
newVNode._lastDomChild = vnode._lastDomChild;
} c._parentDom = parentDom; |
ÔÚdiffº¯ÊýµÄ¶¥²¿ÓÐÕâÑùÒ»¶Î´úÂëÉÏÃæÓÐÒ»¾äÓ¢ÎÄ×¢ÊÍ(If the
previous type doesn't match the new type we drop the
whole subtree), Èç¹ûoldVNodeºÍnewVNodeÀàÐͲ»Í¬£¬ÎÒÃǽ«»áÐ¶ÔØÕû¸ö×ÓÊ÷¡£
if (oldVNode==null || newVNode==null || oldVNode.type!==newVNode.type)
{
// Èç¹ûnewVNodeΪnull, ÎÒÃǽ«»áÐ¶ÔØÕû¸ö×é¼þ, ²¢É¾³ý¶ÔÓ¦µÄdom½Úµã
if (oldVNode!=null) unmount(oldVNode, ancestorComponent);
if (newVNode==null) return null;
dom = null;
oldVNode = EMPTY_OBJ;
} |
¶Ô±È×Ó½Úµã
export function diffChildren (parentDom, newParentVNode,
oldParentVNode, context, isSvg, excessDomChildren,
mounts, ancestorComponent) {
let childVNode, i, j, p, index, oldVNode, newDom,
nextDom, sibDom, focus,
childDom; let newChildren = newParentVNode._children
|| toChildArray (newParentVNode.props.children,
newParentVNode._children=[], coerceToVNode);
let oldChildren = oldParentVNode !=null &&
oldParentVNode!= EMPTY_OBJ && oldParentVNode._children
|| EMPTY_ARR; let oldChildrenLength = oldChildren.length; childDom = oldChildrenLength ? oldChildren[0]
&& oldChildren[0]._dom : null;
for (i=0; i<newChildren.length; i++) {
childVNode = newChildren[i] = coerceToVNode(newChildren[i]);
oldVNode = index = null; p = oldChildren[i];
//
if (p != null && (childVNode.key==null
&& p.key==null ? (childVNode.type ===
p.type) : (childVNode.key === p.key))) {
index = i;
}
else {
for (j=0; j<oldChildrenLength; j++) {
p = oldChildren[j];
if (p!=null) {
if (childVNode.key==null && p.key==null
? (childVNode.type === p.type) : (childVNode.key
=== p.key)) {
index = j;
break;
}
}
}
} if (index!=null) {
oldVNode = oldChildren[index];
oldChildren[index] = null;
} nextDom = childDom!=null && childDom.nextSibling;
newDom = diff (oldVNode==null ? null : oldVNode._dom,
parentDom, childVNode, oldVNode, context, isSvg,
excessDomChildren, mounts, ancestorComponent,
null); if (childVNode!=null && newDom !=null)
{
focus = document.activeElement; if (childVNode._lastDomChild != null) {
newDom = childVNode._lastDomChild;
}
else if (excessDomChildren==oldVNode || newDom!=childDom
|| newDom.parentNode==null) { outer: if (childDom==null || childDom.parentNode !==parentDom)
{
parentDom.appendChild(newDom);
}
else {
sibDom = childDom;
j = 0;
while ((sibDom=sibDom.nextSibling) &&
j++<oldChildrenLength/2) {
if (sibDom===newDom) {
break outer;
}
}
parentDom.insertBefore (newDom, childDom);
}
} if (focus!== document.activeElement) {
focus.focus();
} childDom = newDom! =null ? newDom.nextSibling
: nextDom;
}
}
for (i=oldChildrenLength; i--; ) {
if (oldChildren[i]!=null) {
unmount(oldChildren[i], ancestorComponent);
}
}
} |
diffChildrenÊÇ×îΪ¸´ÔÓµÄÒ»²¿·ÖÄÚÈÝ¡£×ÓVNode×÷Ϊһ¸öÊý×é, Êý×éÖеÄÄÚÈÝ¿ÉÄܸıäÁË˳Ðò»òÕßÊýÄ¿,
ºÜÄÑÈ·¶¨ÐµÄVNodeÒªºÍÄÇÒ»¸ö¾ÉµÄVNode±È½Ï¡£ËùÒÔpreactÖе±Ãæ¶ÔÁбíʱ£¬ÎÒÃǽ«ÒªÇóÓû§Ìṩkey,
°ïÖúÎÒÃDZȽÏVNode¡£´ïµ½¸´ÓÃDomµÄÄ¿µÄ¡£
ÔÚdiffChildrenÖУ¬ÎÒÃÇ»áÊ×ÏÈͨ¹ýtoChildArrayº¯Êý½«×Ó½ÚµãÒÔÊý×éµÄÐÎʽ´æ´¢ÔÚ_childrenÊôÐÔÉÏ¡£
childDomΪµÚÒ»¸ö×Ó½ÚµãÕæÊµµÄdom(ÕâºÜÓÐÓÃ, ÎÒÃÇÔÚºóÃæ½«Í¨¹ýËüÀ´ÅжÏÊÇʹÓÃappendChild²åÈënewDom»¹ÊÇʹÓÃinsertBefore²åÈënewDom£¬»òÕßʲô¶¼²»×ö)
½ÓÏÂÀ´±éÀú_childrenÊôÐÔ¡£Èç¹ûVNodeÓÐkeyÊôÐÔ, ÔòÕÒµ½keyÓëkeyÏàµÈµÄ¾ÉµÄVNode¡£Èç¹ûûÓÐkey,
ÔòÕÒµ½×î½üµÄtypeÏàµÈµÄ¾ÉµÄVNode¡£È»ºó½«oldChildren¶ÔÓ¦µÄλÖÃÉèÖÃnull, ±ÜÃâÖØ¸´µÄ²éÕÒ¡£Ê¹ÓÃdiffËã·¨¶Ô±È,
оÉVNode¡£·µ»ØÐµÄdom¡£
Èç¹ûchildDomΪnull, Ôò½«ÐÂdom, appendµÄµ½¸¸DOMÖС£Èç¹ûÕÒµ½ÁËÓëеÄdomÏàµÈµÄdom(ÒýÓÃÀàÐÍ),
ÎÒÃÇÔò²»×öÈκδ¦Àí(propsÒѾÔÚdiffElementNodeÖиüÐÂÁË)¡£Èç¹ûÔÚchildDomµÄnextSiblingûÓÐÕÒµ½ºÍеÄdomÏàµÈµÄdom,
ÎÒÃǽ«dom²åÈëchildDomµÄÇ°Ãæ¡£½ÓןüÐÂchildom¡£
±éÀúÊ£ÓàûÓÐʹÓõ½oldChildren, Ð¶ÔØÕâЩ½Úµã»òÕß×é¼þ¡£
Òì²½setState
preact³ýÁËʹÓÃdiffËã·¨¼õÉÙdom²Ù×÷ÓÅ»¯ÐÔÄÜÍâ, preact»á½«Ò»¶Îʱ¼äÄڵĶà´ÎsetStateºÏ²¢¼õÉÙ×é¼þäÖȾµÄ´ÎÊý¡£
ÎÒÃÇÊ×ÏÈÔÚsetStateÖÐ, ²¢Ã»ÓÐÖ±½Ó¸üÐÂstate, »òÕßÖ±½ÓÖØÐÂäÖȾº¯Êýº¯Êý¡£¶øÊǽ«×é¼þµÄʵÀý´øÈëµ½ÁËenqueueRenderº¯ÊýÖС£
Component.prototype.setState = function(update,
callback) {
let s = (this._nextState!== this.state &&
this._nextState) || (this._nextState = assign({},
this.state)); if (typeof update!== 'function' || (update
= update(s, this.props))) {
assign(s, update);
} if (update==null) return; if (this._vnode) {
if (callback) this._renderCallbacks.push (callback);
enqueueRender(this);
}
};
|
ÔÚenqueueRenderº¯ÊýÖÐ, ÎÒÃǽ«×é¼þpushµ½¶ÓÁÐqÖС£
ͬʱʹÓÃ_dirty¿ØÖÆ, ±ÜÃâq¶ÓÁÐÖб»pushÁËÏàͬµÄ×é¼þ¡£ÎÒÃÇÓ¦¸ÃÔڶ೤ʱ¼äÄÚÇå¿Õq¶ÓÁÐÄØ?
ÎÒÃǸÃÈçºÎ¶¨ÒåÕâôһ¶Îʱ¼äÄØ£¿±È½ÏºÃµÄ×ö·¨ÊÇʹÓÃPromise.resolve()¡£ÔÚÕâÒ»¶Îʱ¼äµÄsetState²Ù×÷¶¼»á±»pushµ½q¶ÓÁÐÖС£_nextState½«»á±»ºÏ²¢ÔÚÇå¿Õ¶ÓÁеÄʱºò£¬Ò»²¢¸üе½stateÉÏ£¬±ÜÃâÁËÖØ¸´µÄäÖȾ¡£
let q = []; export function enqueueRender(c) {
if (!c._dirty && (c._dirty = true) &&
q.push(c) === 1) {
(options.debounceRendering || defer)(process);
}
} function process() {
let p;
while ((p=q.pop())) {
if (p._dirty) p.forceUpdate(false);
}
} const defer = typeof Promise=='function' ?
Promise.prototype.then.bind(Promise.resolve())
: setTimeout;
|
ÔÚºêÈÎÎñÍê³Éºó£¬ÎÒÃÇÖ´ÐÐ΢ÈÎÎñPromise.resolve(),
Çå¿Õq¶ÓÁУ¬Ê¹ÓÃdiff·½·¨¸üжÓÁÐÖеÄ×é¼þ¡£
Component.prototype.forceUpdate
= function(callback) {
let vnode = this._vnode, dom = this._vnode._dom,
parentDom = this._parentDom;
if (parentDom) {
const force = callback!==false; let mounts = [];
dom = diff (dom, parentDom, vnode, vnode, this._context,
parentDom.ownerSVGElement!==undefined, null,
mounts, this._ancestorComponent, force);
if (dom!=null && dom.parentNode!==parentDom)
{
parentDom.appendChild(dom);
}
commitRoot(mounts, vnode);
}
if (callback) callback();
};
|
½áÓï
µ½ÕâÀïÎÒÃÇÒѾ°ÉpreactµÄÔ´Âë´óÖÂä¯ÀÀÁËÒ»±é¡£ÎÒÃǽÓÏÂÀ´¿ÉÒԲο¼preactµÄÔ´Â룬ʵÏÖ×Ô¼ºµÄreact¡£»°ËµÎÒ»¹¸øpreactµÄÏîÄ¿Ìá½»ÁËpr£¬²»¹ý»¹Ã»ÓÐmerge¡£
|