We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
分析流程
// 项目代码 // app.svelte <script> import { onMount } from 'svelte' import Demo from './Demo.svelte' export let name; let count = 0 const handle = () => { count += 1 } // 可以写多个 onMount(() => console.log('mount')) onMount(() => console.log('mount')) </script> <style> h1 { color: purple; } </style> <h1>Helloza {name}! {count}</h1> <button on:click={handle}>click</button> <Demo msg={count} /> // demo.svelte <script> // export 表示,接收外表的 props export let msg; </script> <h1>Hello {msg}!</h1> // 编译阶段 // code is input,svelt-loader 进行对 svelte 文件编译 // 每个 svelte 文件,执行一次这个方法 // source 是文件的源码字符串 preprocess(source, ...).then(processed => { // ... compile(processed.toString(), compileoptions) }) // 主要作用是根据 option 对 source 做预处理 async function preprocess(source, preprocessor, options) { // 如果有 preprocessor, 根据 preprocessor 对 source 进行处理 // 返回一个对象 return { code: source, dependencies: ..., toString() { return source } } } function compile(source, options = {}) { // 构建一个 stat 实例,主要是保存编译速度之类 // 对 source 进行 parse 为 ast const ast = parse$2(source, options) // 进行组件实例化,组件类里面处理了大量逻辑 component = new Component(ast, source, ...) // 对组件进行浏览器端代码生成,区分 ssr/dom // 通过 dom 函数处理,得到的是一个 function create_fragment(ctx){...} 函数的声明字符串 // 也就是说 js 是一段字符串,最后会将这段字符串与项目源码一起输出到 thunk 里 js = ssr ? ssr(component, options) : dom(component, options) // 返回一个对象 res = component.generate(js) return res } function parse$2(template, options = {}) { // parser 实例化 const parser = new Parser$2(template, options) // parser 对象结构为: { stack:[ {start, end, type, children} ], css: [ {start,end,attributes,children, content} ], js: [ {start, end, context, content} ], html: {start, end, type, children} meta_tags, template, filename, customeElement, } // 返回一个对象 return { html: parser.html, css: parser.css[0], instance: js context is default [0], module: js context is module [0] } } class Parser$2 { // 对模版进行 ast 编译,分离 css/js/html 等。 } // 很复杂 class Component {} // 生成运行时代码片段 function dom(component, options) { // 构造一个 renderer 实例 const renderer = new Renderer(component, options) // 构造一个 builder 实例,之后完这个 builder 里添加各种代码片段 const builder = new CodeBuilder() // 各种处理,往 builder 里插入代码字符串片段 ... // 返回这个 builder return builder.toString() } //////////// dom 生成的函数片段 start ///////// // 根据实际情况,生产的片段可能不一样 function create_fragment(ctx) { var h1, t0, t1, t2, t3, current; var demo = new Demo({ props: { msg: "aa" } }); return { c() { h1 = element("h1"); t0 = text("Helloza "); t1 = text([✂129 - 133✂]); t2 = text("!"); t3 = space(); demo.$$.fragment.c(); attr(h1, "class", "svelte-i7qo5m"); dispose = listen(button, "click", [✂348-354✂]) }, m(target, anchor) { insert(target, h1, anchor); append(h1, t0); append(h1, t1); append(h1, t2); insert(target, t3, anchor); mount_component(demo, target, anchor); current = true; }, p(changed, ctx) { if (!current || changed.name) { set_data(t1, [✂129 - 133✂]); } }, i(local) { if (current) return; transition_in(demo.$$.fragment, local); current = true; }, o(local) { transition_out(demo.$$.fragment, local); current = false; }, d(detaching) { if (detaching) { detach(h1); detach(t3); } destroy_component(demo, detaching); } }; } function instance($$self, $$props, $$invalidate) { [✂10 - 60✂] // 是一个占位符??? $$self.$set = $$props => { if ('name' in $$props) $$invalidate('name', name = $$props.name); }; return { name }; } class App extends SvelteComponent { constructor(options) { super(); // 执行 init 方法 init(this, options, instance, create_fragment, safe_not_equal, ["name"]); } } //////////// dom 生成的函数片段 end ///////// res = component.generate(js) // 的到的对象结构是: { js: { code // 这里包含着我们自己也的代码,以及 dom 函数生成的运行时代码,就是上面的代码片段。最后这些代码经过 babal 编译后,输出到文件 zhunk 中 map // soucemap 实例 }, css:{ code // css 代码 map // soucemap 实例 }, ast, // parse 生成的 ast 对象 warnings: [], // 代码里所有的声明 // eg. in demo.svelte file // export let name // <div>{name}</div> vars: [ { name: 'name', injected: false, module: false, mutated: false, reassigned: false, referenced: true, writable: false } ], stat // 编译的统计 } // 运行时流程 // 入口是 const app = new App({ target: document.body, props: {name: 'hhha'} }) // 编译后的 App 组件 new App extends SvelteComponent { constructor(options) { super(); // 执行 init 方法 init(this, options, instance, create_fragment, safe_not_equal, ["name"]); } } // svelte 没有虚拟 dom,首次渲染时,从父一直往子插入到 html 文档中。更新阶段,直接更新 dom function init(component, options, instance, create_fragment, not_equal$$1, prop_names) { // 保存当前组件的实例 // 初始化 component.$$ 属性,往这个属性上挂载一些属性: const $$ = component.$$ = { fragment, ctx, props, update, bound, on_mount: [], on_destroy: [], before_render: [], after_render: [], context, callbacks, dirty } // 执行 instance 函数,得到当前组件需要的 props, 挂到 ctx 上。这里需要注意 cb 参数,这个函数会将所有的反应数据进行包装。更新数据操作会执行这个 cb 函数。 $$.ctx = instance(component,prosp,cb) || props // 执行 所有的 $$.before_render 数组里存的函数,首次 $$.before_render 数组 为空 // 执行 create_fragment 函数,拿到一个对象,可以看上面的例子 $$.fragment = create_fragment($$.ctx) // 执行 $$.fragment.c(),该函数会创建当前组件的所有 dom 元素,挂载 html 属性到 dom 上,绑定组件的事件。同时执行子组件的 $$.fragment.c() 函数。子组件的实例有了吗?子组件的实例化在父组件执行 create_fragment 函数的时候,就进行实例创建了。这里其实是从父到子,一直执行各自的 create_fragment 函数。生成各自的 dom 和属性挂载。但还没有插入到 html 文档上。 // 执行顺序是:父创建dom -》子创建dom ... -〉挂载子属性 -》子事件绑定到 dom -》挂载父属性 -〉 父事件绑定到 dom // 执行 mount_component 函数, 该函数会从父一直递归到子组件,把前面生成好的 dom + 属性 插入到父中。 // 流程是:父插入到 root -》子插入到父 ... 一直到完成为止。这时候所有的 dom 都插入到html 文档了。首次渲染相当于结束 mount_component(component, options.target, options.anchor) // 更新操作,首次渲染基本可以忽略这个函数 flush() } // 看一下 init 函数的 instance 参数 // 编译后,有这个东西 [✂10-249✂],是原来代码的展位符?? function instance($$self, $$props, $$invalidate) { let {name} = $$props let count = 0 // 可以看出,事件触发 state 更新时,会先执行 $$invalidate 函数, 该函数会执行 make_dirty 方法,从而触发更新 const handle = () => { $$invalidate('count', count += 1) } setTimeout(() => { $$invalidate('count', count += 1) }, 3000) // 组件实例的 $set 方法, 某些方式的更新数据会用到这个方法。例如:调用子的 $set 方法,来更新子的 props $$self.$set = $$props => { if ('name' in $$props) $$invalidate('name', name = $$props.name) } return {name, count, handle} } // 首次渲染阶段,不断递归这个函数 function mount_component(component, target, anchor) { // 执行 $$.fragment.m 函数。该函数会将之前创建好的 dom 插入到 html 文档上,然后递归执行 mount_component 函数。从父到子一直插入到 html 文档上 // 递归完后,执行 after_render 函数,该函数执行时,会执行该组件的 onMount 生命函数,svelte 的生命函数一个组件可以写多个。同时收集该组件的 onMount 函数返回值,等组件卸载时执行。可以用来卸载时清除副作用。 // onMount 函数执行顺序: 子组件 -》 父组件 } // 首次渲染生命函数: 父 beforeUpdate -》子 beforeUpdate -》父 afterUpdate -》 子 afterUpdate -》 父 onMount -》子 onMount // 更新阶段生命函数 父 beforeUpdate -》子 beforeUpdate -》子 afterUpdate -》子 onDestroy -》 父 afterUpdate -》父 onDestroy // 更新阶段 // 每个反应式的属性都会被 $$invalidate 函数包裹:事件、ajax、异步 等 // $$invalidate 执行时,触发 make_dirty function make_dirty(component, key) { // 缓存需要更新的组件实例 push in dirty_components // 执行 schedule_update } function schedule_update() { // 异步执行 flush,在本次事件循环后紧跟的微任务队列中执行 } function flush() { // 如果有 dirty_components.length,循环拿到当前组件,循环执行 update 函数 // 如果有 binding_callbacks ,执行 binding_callbacks // 如果有 render_callbacks,执行 render_callbacks // 如果有 flush_callbacks,执行 flush_callbacks } function update($$) { // 执行生命函数 before_render // 执行 $$.fragment.p 函数,更新所有的的 state $$.fragment.p($$.dirty, $$.ctx) // 收集 after_render } // 每次更新所有的反应式数据 // 同时更新子组件的 props p(changed, ctx) { if (!current || changed.name) { // 更新数据 set_data(t1, [✂280-284✂]); } if (!current || changed.count) { set_data(t3, [✂288-293✂]); } if (!current || changed.hi) { set_data(t5, [✂296-298✂]); } var demo_changes = {}; // 更新子的 props if (changed.count) demo_changes.msg = [✂357-362✂]; demo.$set(demo_changes); // 当有显示隐藏某些片段时,会执行该片段的 p/c/m 函数 if ([✂706-719✂]) { if (if_block) { // 执行片段的 p,进行数据更新或片段更新,这是一个递归操作。不断对片段里的片段或数据进行更新 if_block.p(changed, ctx); transition_in(if_block, 1); } else { // create_if_block 这个函数很重要。当有 if/else 的模版逻辑时。都会编译生成这个函数代码。所以编译后的代码量会很大。这个函数的逻辑与 create_fragment 函数的逻辑相似,该很熟生成子组件的实例。然后返回一个包含操作函数的对象。{c,m,o,i,o,d} if_block = create_if_block(ctx); // 执行片段的 c,进行 dom 元素生成和属性挂载 if_block.c(); transition_in(if_block, 1); // 执行片段的 m,进行 dom 插入 if_block.m(if_block_anchor.parentNode, if_block_anchor); } } else if (if_block) { group_outros(); transition_out(if_block, 1, () => { if_block = null; }); check_outros(); } } // $set 其实也是调用 $$invalidate $$self.$set = $$props => { if ('name' in $$props) $$invalidate('name', name = $$props.name); };
总结
The text was updated successfully, but these errors were encountered:
No branches or pull requests
分析流程
总结
The text was updated successfully, but these errors were encountered: