Svelte运行时原理
现在我们又有了一个新的问题。我们已经可以感知到值的变化,那是怎么将值得变化更新到页面中的了。
你可能马上想到的是create_fragment
返回的updata
方法啊。这里仅仅是提供了更新页面DOM的方法,那是什么样的时机调用这个更新方法的呢?
init
方法
其实,svelte的编译结果是运行时运行的代码。在进入运行时,首先执行init
方法,该方法大致流程如下:
- 💎初始化状态
- 💎初始化周期函数
- 💎执行
instance
方法,在回调函数中标记脏组件
- 💎执行所有
beforeUpdate
生命周期的函数 - 💎执行创建片段
create_fragment
函数 - 💎挂载当前组件并执行
create_fragement
返回的m(mounted)
方法 - 💎执行
flush
方法
你可以跳过这段代码,不影响阅读
export function init( component, options, instance, create_fragment, not_equal, props, append_styles, dirty = [-1] ) { const parent_component = current_component; set_current_component(component); const $$: T$$ = component.$$ = { fragment: null, ctx: [], // state props, update: noop, not_equal, bound: blank_object(), // lifecycle on_mount: [], on_destroy: [], on_disconnect: [], before_update: [], after_update: [], context: new Map(options.context || (parent_component ? parent_component.$$.context : [])), // everything else callbacks: blank_object(), dirty, skip_bound: false, root: options.target || parent_component.$$.root }; append_styles && append_styles($$.root); let ready = false; $$.ctx = instance ? instance(component, options.props || {}, (i, ret, ...rest) => { const value = rest.length ? rest[0] : ret; if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) { if (!$$.skip_bound && $$.bound[i]) $$.bound[i](value); if (ready) make_dirty(component, i); } return ret; }) : []; $$.update(); ready = true; run_all($$.before_update); // `false` as a special case of no DOM component $$.fragment = create_fragment ? create_fragment($$.ctx) : false; if (options.target) { if (options.hydrate) { start_hydrating(); const nodes = children(options.target); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion $$.fragment && $$.fragment!.l(nodes); nodes.forEach(detach); } else { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion $$.fragment && $$.fragment!.c(); } if (options.intro) transition_in(component.$$.fragment); mount_component(component, options.target, options.anchor, options.customElement); end_hydrating(); flush(); } set_current_component(parent_component); }
看起来,flush
方法很可能才是我们需要的答案。
flush
方法
flush
的方法主要做了一件事:
遍历需要更新的组件(dirty_components
),然后更新它,并且调用afterUpdate
方法。
export function flush() { // Do not reenter flush while dirty components are updated, as this can // result in an infinite loop. Instead, let the inner flush handle it. // Reentrancy is ok afterwards for bindings etc. if (flushidx !== 0) { return; } const saved_component = current_component; do { // first, call beforeUpdate functions // and update components try { while (flushidx < dirty_components.length) { const component = dirty_components[flushidx]; flushidx++; set_current_component(component); update(component.$$); } } catch (e) { // reset dirty state to not end up in a deadlocked state and then rethrow dirty_components.length = 0; flushidx = 0; throw e; } set_current_component(null); dirty_components.length = 0; flushidx = 0; // then, once components are updated, call // afterUpdate functions. This may cause // subsequent updates... for (let i = 0; i < render_callbacks.length; i += 1) { const callback = render_callbacks[i]; if (!seen_callbacks.has(callback)) { // ...so guard against infinite loops seen_callbacks.add(callback); callback(); } } render_callbacks.length = 0; } while (dirty_components.length); while (flush_callbacks.length) { flush_callbacks.pop()(); } update_scheduled = false; seen_callbacks.clear(); set_current_component(saved_component); }
我们再来看看具体的更新操作update
函数做了啥
- 首先执行所有的
before_update
方法 - 然后执行
create_fragment
返回的p(update)
方法
function update($$) { if ($$.fragment !== null) { $$.update(); run_all($$.before_update); const dirty = $$.dirty; $$.dirty = [-1]; $$.fragment && $$.fragment.p($$.ctx, dirty); $$.after_update.forEach(add_render_callback); } }
好了,我们总结下:在运行时
- 💎首先,初始化状态、初始化周期函数
- 💎接着,执行
instance
方法,在回调函数中标记脏组件
- 💎接着,执行所有
beforeUpdate
生命周期的函数 - 💎然后,执行创建片段
create_fragment
函数 - 💎接着,挂载当前组件并执行
create_fragement
返回的m(mounted)
方法 - 💎然后,执行
flush
方法 - 💎首先,执行所有的
before_update
方法 - 💎然后,执行
create_fragment
返回的p(update)
方法 - 💎最后,执行
afterUpdate
方法
总结
好了,今天的分享就这些了,总的来说,Svelte
的响应式原理虽然很朴素,但是却拥有了更好的性能,同时也降低了开发者的记忆负担。我觉得这是svelte
最成功的地方。
如果你发现文章有错误的地方,请及时告诉我,十分感谢。
今天的分享就到这了,如果你觉得还不错,也可以关注我的微信公众号:萌萌哒草头将军