├── .gitattributes
├── .idea
├── misc.xml
├── modules.xml
├── vcs.xml
├── vue3源码解释.iml
└── workspace.xml
├── Packages
├── API_DOC
│ └── index.md
├── BUGS
│ ├── A.png
│ ├── B.png
│ └── iframe-bug.md
├── Reactivity
│ ├── diff.md
│ ├── diff剧本.md
│ ├── index.md
│ ├── 恐.md
│ ├── 恐.png
│ ├── 镜.md
│ ├── 镜.png
│ └── 问题集合.md
├── Runtime
│ ├── OPTION-API.md
│ ├── desc.md
│ ├── desc2.md
│ ├── index.md
│ ├── patch剧本.md
│ ├── question.md
│ ├── 事件队列与组件更新.md
│ └── 类型标记.md
└── compile-core
│ ├── compile-core.md
│ ├── 为什么在slot中事件没有更新.md
│ └── 问题集合.md
├── README.md
├── SUMMARY.md
├── _book
├── .gitattributes
├── .idea
│ ├── misc.xml
│ ├── modules.xml
│ ├── vcs.xml
│ ├── vue3源码解释.iml
│ └── workspace.xml
├── Packages
│ ├── API_DOC
│ │ ├── index.html
│ │ └── index.md
│ ├── BUGS
│ │ ├── A.png
│ │ ├── B.png
│ │ ├── iframe-bug.html
│ │ └── iframe-bug.md
│ ├── Reactivity
│ │ ├── diff.html
│ │ ├── diff.md
│ │ ├── diff剧本.md
│ │ ├── index.html
│ │ ├── index.md
│ │ ├── 恐.md
│ │ ├── 恐.png
│ │ ├── 镜.md
│ │ ├── 镜.png
│ │ ├── 问题集合.html
│ │ └── 问题集合.md
│ ├── Runtime
│ │ ├── OPTION-API.html
│ │ ├── OPTION-API.md
│ │ ├── desc.html
│ │ ├── desc.md
│ │ ├── desc2.html
│ │ ├── desc2.md
│ │ ├── index.html
│ │ ├── index.md
│ │ ├── patch剧本.md
│ │ ├── question.html
│ │ ├── question.md
│ │ ├── 事件队列与组件更新.html
│ │ ├── 事件队列与组件更新.md
│ │ ├── 类型标记.html
│ │ └── 类型标记.md
│ └── compile-core
│ │ ├── compile-core.html
│ │ ├── compile-core.md
│ │ ├── 为什么在slot中事件没有更新.html
│ │ ├── 为什么在slot中事件没有更新.md
│ │ ├── 问题集合.html
│ │ └── 问题集合.md
├── book
│ ├── .gitattributes
│ ├── .idea
│ │ ├── misc.xml
│ │ ├── modules.xml
│ │ ├── vcs.xml
│ │ ├── vue3源码解释.iml
│ │ └── workspace.xml
│ ├── Packages
│ │ ├── API_DOC
│ │ │ ├── index.html
│ │ │ └── index.md
│ │ ├── Reactivity
│ │ │ ├── diff.html
│ │ │ ├── diff.md
│ │ │ ├── index.html
│ │ │ ├── index.md
│ │ │ ├── 恐.md
│ │ │ ├── 恐.png
│ │ │ ├── 镜.md
│ │ │ ├── 镜.png
│ │ │ ├── 问题集合.html
│ │ │ └── 问题集合.md
│ │ ├── Runtime
│ │ │ ├── OPTION-API.html
│ │ │ ├── OPTION-API.md
│ │ │ ├── desc.html
│ │ │ ├── desc.md
│ │ │ ├── desc2.html
│ │ │ ├── desc2.md
│ │ │ ├── index.html
│ │ │ ├── index.md
│ │ │ ├── question.html
│ │ │ ├── question.md
│ │ │ ├── 事件队列与组件更新.html
│ │ │ ├── 事件队列与组件更新.md
│ │ │ ├── 类型标记.html
│ │ │ └── 类型标记.md
│ │ └── compile-core
│ │ │ ├── compile-core.html
│ │ │ ├── compile-core.md
│ │ │ ├── 为什么在slot中事件没有更新.html
│ │ │ ├── 为什么在slot中事件没有更新.md
│ │ │ ├── 问题集合.html
│ │ │ └── 问题集合.md
│ ├── gitbook
│ │ ├── fonts
│ │ │ └── fontawesome
│ │ │ │ ├── FontAwesome.otf
│ │ │ │ ├── fontawesome-webfont.eot
│ │ │ │ ├── fontawesome-webfont.svg
│ │ │ │ ├── fontawesome-webfont.ttf
│ │ │ │ ├── fontawesome-webfont.woff
│ │ │ │ └── fontawesome-webfont.woff2
│ │ ├── gitbook-plugin-fontsettings
│ │ │ ├── fontsettings.js
│ │ │ └── website.css
│ │ ├── gitbook-plugin-highlight
│ │ │ ├── ebook.css
│ │ │ └── website.css
│ │ ├── gitbook-plugin-lunr
│ │ │ ├── lunr.min.js
│ │ │ └── search-lunr.js
│ │ ├── gitbook-plugin-search
│ │ │ ├── lunr.min.js
│ │ │ ├── search-engine.js
│ │ │ ├── search.css
│ │ │ └── search.js
│ │ ├── gitbook-plugin-sharing
│ │ │ └── buttons.js
│ │ ├── gitbook.js
│ │ ├── images
│ │ │ ├── apple-touch-icon-precomposed-152.png
│ │ │ └── favicon.ico
│ │ ├── style.css
│ │ └── theme.js
│ ├── index.html
│ ├── package.json
│ ├── search_index.json
│ ├── update.md
│ └── 更新
│ │ ├── 2020年12月13日22-42.html
│ │ └── 2020年12月13日22-42.md
├── gitbook
│ ├── fonts
│ │ └── fontawesome
│ │ │ ├── FontAwesome.otf
│ │ │ ├── fontawesome-webfont.eot
│ │ │ ├── fontawesome-webfont.svg
│ │ │ ├── fontawesome-webfont.ttf
│ │ │ ├── fontawesome-webfont.woff
│ │ │ └── fontawesome-webfont.woff2
│ ├── gitbook-plugin-fontsettings
│ │ ├── fontsettings.js
│ │ └── website.css
│ ├── gitbook-plugin-highlight
│ │ ├── ebook.css
│ │ └── website.css
│ ├── gitbook-plugin-livereload
│ │ └── plugin.js
│ ├── gitbook-plugin-lunr
│ │ ├── lunr.min.js
│ │ └── search-lunr.js
│ ├── gitbook-plugin-search
│ │ ├── lunr.min.js
│ │ ├── search-engine.js
│ │ ├── search.css
│ │ └── search.js
│ ├── gitbook-plugin-sharing
│ │ └── buttons.js
│ ├── gitbook.js
│ ├── images
│ │ ├── apple-touch-icon-precomposed-152.png
│ │ └── favicon.ico
│ ├── style.css
│ └── theme.js
├── index.html
├── package.json
├── search_index.json
├── update.md
└── 更新
│ └── 2020年12月13日22-42.md
├── book
├── .gitattributes
├── .idea
│ ├── misc.xml
│ ├── modules.xml
│ ├── vcs.xml
│ ├── vue3源码解释.iml
│ └── workspace.xml
├── Packages
│ ├── API_DOC
│ │ ├── index.html
│ │ └── index.md
│ ├── BUGS
│ │ ├── A.png
│ │ ├── B.png
│ │ ├── iframe-bug.html
│ │ └── iframe-bug.md
│ ├── Reactivity
│ │ ├── diff.html
│ │ ├── diff.md
│ │ ├── diff剧本.md
│ │ ├── index.html
│ │ ├── index.md
│ │ ├── 恐.md
│ │ ├── 恐.png
│ │ ├── 镜.md
│ │ ├── 镜.png
│ │ ├── 问题集合.html
│ │ └── 问题集合.md
│ ├── Runtime
│ │ ├── OPTION-API.html
│ │ ├── OPTION-API.md
│ │ ├── desc.html
│ │ ├── desc.md
│ │ ├── desc2.html
│ │ ├── desc2.md
│ │ ├── index.html
│ │ ├── index.md
│ │ ├── patch剧本.md
│ │ ├── question.html
│ │ ├── question.md
│ │ ├── 事件队列与组件更新.html
│ │ ├── 事件队列与组件更新.md
│ │ ├── 类型标记.html
│ │ └── 类型标记.md
│ └── compile-core
│ │ ├── compile-core.html
│ │ ├── compile-core.md
│ │ ├── 为什么在slot中事件没有更新.html
│ │ ├── 为什么在slot中事件没有更新.md
│ │ ├── 问题集合.html
│ │ └── 问题集合.md
├── gitbook
│ ├── fonts
│ │ └── fontawesome
│ │ │ ├── FontAwesome.otf
│ │ │ ├── fontawesome-webfont.eot
│ │ │ ├── fontawesome-webfont.svg
│ │ │ ├── fontawesome-webfont.ttf
│ │ │ ├── fontawesome-webfont.woff
│ │ │ └── fontawesome-webfont.woff2
│ ├── gitbook-plugin-fontsettings
│ │ ├── fontsettings.js
│ │ └── website.css
│ ├── gitbook-plugin-highlight
│ │ ├── ebook.css
│ │ └── website.css
│ ├── gitbook-plugin-lunr
│ │ ├── lunr.min.js
│ │ └── search-lunr.js
│ ├── gitbook-plugin-search
│ │ ├── lunr.min.js
│ │ ├── search-engine.js
│ │ ├── search.css
│ │ └── search.js
│ ├── gitbook-plugin-sharing
│ │ └── buttons.js
│ ├── gitbook.js
│ ├── images
│ │ ├── apple-touch-icon-precomposed-152.png
│ │ └── favicon.ico
│ ├── style.css
│ └── theme.js
├── index.html
├── package.json
├── search_index.json
├── update.md
└── 更新
│ └── 2020年12月13日22-42.md
├── package.json
├── runtime.png
├── update.md
└── 更新
└── 2020年12月13日22-42.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.js linguist-language=TypeScript
2 | *.css linguist-language=TypeScript
3 | *.html linguist-language=TypeScript
4 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
35 | // https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/pre 36 | isPreTag?: (tag: string) => boolean 37 | 38 | // 特定于平台的内置组件,例如39 | isBuiltInComponent?: (tag: string) => symbol | void 40 | 41 | // 自定义于平台的本地元素 42 | isCustomElement?: (tag: string) => boolean 43 | 44 | // 获取标签的名称 45 | getNamespace?: (tag: string, parent: ElementNode | undefined) => Namespace 46 | 47 | // 获取此元素的文本分析模式 48 | getTextMode?: ( 49 | node: ElementNode, 50 | parent: ElementNode | undefined 51 | ) => TextModes 52 | 53 | // 默认 ['{{', '}}'],可修改为你喜欢的 54 | delimiters?: [string, string] 55 | 56 | // Only needed for DOM compilers,解码实体 57 | decodeEntities?: (rawText: string, asAttr: boolean) => string 58 | 59 | // 错误 60 | onError?: (error: CompilerError) => void 61 | } 62 | 63 | 64 | // TransformOptions: 65 | export interface TransformOptions { 66 | /** 67 | * An array of node trasnforms to be applied to every AST node. 68 | */ 69 | nodeTransforms?: NodeTransform[] 70 | /** 71 | * An object of { name: transform } to be applied to every directive attribute 72 | * node found on element nodes. 73 | */ 74 | directiveTransforms?: Record 75 | /** 76 | * An optional hook to transform a node being hoisted. 77 | * used by compiler-dom to turn hoisted nodes into stringified HTML vnodes. 78 | * @default null 79 | */ 80 | transformHoist?: HoistTransform | null 81 | 82 | // 如果提供了其他的内置元素,可以使用该选项标记为内置的,这样编译器将为这些标签生成组件vnode 83 | isBuiltInComponent?: (tag: string) => symbol | void 84 | /** 85 | * 把表达式 {{ foo }} 转换成 `_ctx.foo` 86 | * 如果该选项为false, the generated code will be wrapped in a 87 | * `with (this) { ... }` block. 88 | * - This is force-enabled in module mode, since modules are by default strict 89 | * and cannot use `with` 90 | * @default mode === 'module' 91 | */ 92 | prefixIdentifiers?: boolean 93 | 94 | 95 | // 静态提升到 `_hoisted_x` 变量 96 | // 默认值 false 97 | hoistStatic?: boolean 98 | 99 | // 开启前 { onClick: _ctx.foo } 100 | // 开启后 { 101 | // onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.foo(...args))) 102 | // } 103 | // _cache[index] index根据当前缓存事件数量,保证预留一个空的位置給该事件 104 | // 默认值 false 105 | cacheHandlers?: boolean 106 | 107 | // `@babel/parser` 中的插件, 108 | // https://babeljs.io/docs/en/next/babel-parser#plugins 109 | expressionPlugins?: ParserPlugin[] 110 | 111 | // Single File Component组件中scoped styles ID 112 | scopeId?: string | null 113 | /** 114 | * Generate SSR-optimized render functions instead. 115 | * The resulting funciton must be attached to the component via the 116 | * `ssrRender` option instead of `render`. 117 | */ 118 | ssr?: boolean 119 | onError?: (error: CompilerError) => void 120 | } 121 | 122 | // CodegenOptions 123 | ``` 124 | 125 | 例子: 126 | ```html 127 | 128 | \{\{ world.burn() \}\} 129 |133 | ``` 134 | 135 | 转换成render: 136 | ```typescript 137 | const _Vue = Vue 138 | 139 | return function render(_ctx, _cache) { 140 | with (_ctx) { 141 | const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList } = _Vue 142 | 143 | return (_openBlock(), _createBlock("div", { 144 | id: "foo", 145 | class: bar.baz 146 | }, [ 147 | _createTextVNode(_toDisplayString(world.burn()) + " ", 1 /* TEXT */), 148 | ok 149 | ? (_openBlock(), _createBlock("div", { key: 0 }, "yes")) 150 | : (_openBlock(), _createBlock(_Fragment, { key: 1 }, [ 151 | _createTextVNode("no") 152 | ], 64 /* STABLE_FRAGMENT */)), 153 | (_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (value, index) => { 154 | return (_openBlock(), _createBlock("div", null, [ 155 | _createVNode("span", null, _toDisplayString(value + index), 1 /* TEXT */) 156 | ])) 157 | }), 256 /* UNKEYED_FRAGMENT */)) 158 | ], 2 /* CLASS */)) 159 | } 160 | } 161 | ``` 162 | 163 | -------------------------------------------------------------------------------- /Packages/compile-core/为什么在slot中事件没有更新.md: -------------------------------------------------------------------------------- 1 | # 为什么使用slot,事件没有更新? 2 | 3 | 代码: 4 | 5 | ```html 6 | 7 | 8 | 9 | 10 | 11 | {{ record }} 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |yes130 | no 131 |{{ value + index }}132 |25 |27 | 28 | ``` 29 | 30 | 31 | 32 | 使用compile转换后的代码: 33 | 34 | ```typescript 35 | import { createVNode as _createVNode, toDisplayString as _toDisplayString, resolveComponent as _resolveComponent, withCtx as _withCtx, createTextVNode as _createTextVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from "vue" 36 | 37 | export function render(_ctx, _cache, $props, $setup, $data, $options) { 38 | const _component_B = _resolveComponent("B") 39 | const _component_A = _resolveComponent("A") 40 | 41 | return (_openBlock(), _createBlock(_Fragment, null, [ 42 | _createVNode("button", { 43 | onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.changeText(...args))) 44 | }, "change test"), 45 | _createVNode(_component_A, { 46 | name: _ctx.test, 47 | ref: "A" 48 | }, { 49 | default: _withCtx(({ record }) => [ 50 | _createTextVNode(_toDisplayString(record) + " ", 1 /* TEXT */), 51 | _createVNode(_component_B, { ref: "B" }, { 52 | over: _withCtx(() => [ 53 | _createVNode("button", { 54 | onClick: $event => (_ctx.handleClick(record)) 55 | }, "click me 啊2 ", 8 /* PROPS */, ["onClick"]) 56 | ]), 57 | _: 1 58 | }, 512 /* NEED_PATCH */) 59 | ]), 60 | _: 1 61 | }, 8 /* PROPS */, ["name"]) 62 | ], 64 /* STABLE_FRAGMENT */)) 63 | } 64 | ``` 65 | 66 | A: 67 | 68 | ```typescript 69 | const { renderSlot: _renderSlot, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue 70 | 71 | function render(_ctx, _cache) { 72 | return (_openBlock(), _createBlock("div", null, [ 73 | _renderSlot(_ctx.$slots, "default", { record: _ctx.name }) // { record: _ctx.name } 传入 default 方法中 74 | // createBlock(Fragment, { key: props.key /* 这里参数没有key */ }, default(props), PatchFlags.STABLE_FRAGMENT) 75 | ])) 76 | } 77 | ``` 78 | 79 | 80 | 81 | 点击handle为什么传入的东西还是旧的值?为什么不会更新? 82 | 83 | 84 | 85 | 我现在是假设你已经熟悉首次渲染。 86 | 87 | 我们在更新的时候,在组件A的update中,生成了vnode,该vnode,在patch第一个元素的时候发现record那段textVnode有变化,故更新该元素。 88 | 89 | 第二个元素是组件B,更新组件B前会使用shouldUpdateComponent方法: 90 | 91 | ```typescript 92 | function shouldUpdateComponent(prevVNode, nextVNode, optimized) { 93 | const { props: prevProps, children: prevChildren } = prevVNode; 94 | const { props: nextProps, children: nextChildren, patchFlag } = nextVNode; 95 | // Parent component's render function was hot-updated. Since this may have 96 | // caused the child component's slots content to have changed, we need to 97 | // force the child to update as well. 98 | if ((process.env.NODE_ENV !== 'production') && (prevChildren || nextChildren) && isHmrUpdating) { 99 | return true; 100 | } 101 | // force child update for runtime directive or transition on component vnode. 102 | if (nextVNode.dirs || nextVNode.transition) { 103 | return true; 104 | } 105 | if (patchFlag > 0) { 106 | if (patchFlag & 1024 /* DYNAMIC_SLOTS */) { 107 | // slot content that references values that might have changed, 108 | // e.g. in a v-for 109 | return true; 110 | } 111 | if (patchFlag & 16 /* FULL_PROPS */) { 112 | if (!prevProps) { 113 | return !!nextProps; 114 | } 115 | // presence of this flag indicates props are always non-null 116 | return hasPropsChanged(prevProps, nextProps); 117 | } 118 | else if (patchFlag & 8 /* PROPS */) { 119 | const dynamicProps = nextVNode.dynamicProps; 120 | for (let i = 0; i < dynamicProps.length; i++) { 121 | const key = dynamicProps[i]; 122 | if (nextProps[key] !== prevProps[key]) { 123 | return true; 124 | } 125 | } 126 | } 127 | } 128 | else if (!optimized) { 129 | // this path is only taken by manually written render functions 130 | // so presence of any children leads to a forced update 131 | if (prevChildren || nextChildren) { 132 | if (!nextChildren || !nextChildren.$stable) { 133 | return true; 134 | } 135 | } 136 | if (prevProps === nextProps) { 137 | return false; 138 | } 139 | if (!prevProps) { 140 | return !!nextProps; 141 | } 142 | if (!nextProps) { 143 | return true; 144 | } 145 | return hasPropsChanged(prevProps, nextProps); 146 | } 147 | return false; 148 | } 149 | ``` 150 | 151 | 这个东西是判断你的props和children有没有改变,没有则返回false,所以B组件不会更新。 152 | 153 | 154 | 155 | 那么,你是不是想问,record是指引属性,为什么还是旧的?你可以去认真看看initSlot和renderSlot,是调用为Object的子组件来实行的,旧的record就是在首次渲染的时候,调用defalut中的初始值,应用的地方也就是首次的环境。 156 | 157 | 而新的record根本就没有被更新到组件B上。 158 | 159 | 160 | 161 | **那为什么我直接调用组件B的update方法,还是不更新呢?** 162 | 163 | 这边建议你再去熟悉一下渲染流程,英文组件B的vnode依旧没有发生变化,需要的是组件A创建新的组件B的vnode才能达到你的效果。 164 | 165 | 166 | 167 | ### 解决方法: 168 | 169 | 为了使组件B更新,可以随便绑定一个参数給B,比如 170 | 171 | ```html 172 | 173 | ``` 174 | 175 | 或者 176 | 177 | ```html 178 | 179 | {{ record }} 180 | 181 | ``` 182 | 183 | 184 | 185 | ### 关于修复 186 | 187 | ##### 原始问题:https://github.com/vuejs/vue-next/issues/2618 188 | 189 | **修复方法**:https://github.com/vuejs/vue-next/pull/2568/files -------------------------------------------------------------------------------- /Packages/compile-core/问题集合.md: -------------------------------------------------------------------------------- 1 | # 问题集合 2 | 3 | ## proxy与withProxy的区别 4 | 5 | proxy: 6 | 7 | ```typescript 8 | instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers) 9 | ``` 10 | 11 | withProxy: 12 | 13 | ```typ 14 | instance.withProxy = new Proxy( 15 | instance.ctx, 16 | RuntimeCompiledPublicInstanceProxyHandlers 17 | ) 18 | ``` 19 | 20 | 在调用render的时候,proxy会作为他的第一个参数,如果开启render._rc = true,则会设置withProxy,调用render优先使用withProxy作为第一个参数。 21 | 22 | ```typescript 23 | // 继承PublicInstanceProxyHandlers 但是重写get和has 24 | withProxy = extend({}, PublicInstanceProxyHandlers, { 25 | get(){ 26 | // fast path for unscopables when using `with` block 27 | if ((key as any) === Symbol.unscopables) { 28 | return 29 | } 30 | return PublicInstanceProxyHandlers.get!(target, key, target) 31 | }, 32 | has() 33 | }) 34 | ``` 35 | 36 | 37 | 38 | ### get: 39 | 40 | **RuntimeCompiledPublicInstanceProxyHandlers**判断key是否等于**Symbol.unscopables**,如果相等则不做处理,直接return,不相等则和**PublicInstanceProxyHandlers.get**行为一致。**目的是为了跳过get的一系列查询,优化代码性能。** 41 | 42 | 43 | 44 | ### has: 45 | 46 | has方法用来拦截hasProperty操作,即判断对象是否具有某个属性时。**handler.has()** 方法是针对 in 操作符的代理方法。 47 | 48 | #### **PublicInstanceProxyHandlers**.has: 49 | 50 | 查找的顺序为(都为instance的字段): 51 | 52 | 1.accessCache缓存中查找,这个缓存为key对应的类型,缓存起来可以快速在类型中查找 53 | 54 | 2.data中查找 55 | 56 | 3.setupState中查找 57 | 58 | 4.type.props中查找 59 | 60 | 5.ctx中查找 61 | 62 | 6.**publicPropertiesMap**,非instance字段,$data $el $props $attrs等 63 | 64 | 7.**appContext.config.globalProperties**,有且仅有一个的对象,所有有关系的组件都共同指向一个。 65 | 66 | publicPropertiesMap: 67 | 68 | ```typescript 69 | const publicPropertiesMap: Record< 70 | string, 71 | (i: ComponentInternalInstance) => any 72 | > = { 73 | $: i => i, 74 | $el: i => i.vnode.el, 75 | $data: i => i.data, 76 | $props: i => (__DEV__ ? shallowReadonly(i.props) : i.props), 77 | $attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs), 78 | $slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots), 79 | $refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs), 80 | $parent: i => i.parent && i.parent.proxy, 81 | $root: i => i.root && i.root.proxy, 82 | $emit: i => i.emit, 83 | $options: i => (__FEATURE_OPTIONS__ ? resolveMergedOptions(i) : i.type), 84 | $forceUpdate: i => () => queueJob(i.update), 85 | $nextTick: () => nextTick, 86 | $watch: __FEATURE_OPTIONS__ ? i => instanceWatch.bind(i) : NOOP 87 | } 88 | ``` 89 | 90 | 91 | 92 | #### RuntimeCompiledPublicInstanceProxyHandlers.has: 93 | 94 | 判断查询的key,不能为'_'开头,且key不能在全局白名单中。 95 | 96 | ```typescript 97 | has(_: ComponentRenderContext, key: string) { 98 | const has = key[0] !== '_' && !isGloballyWhitelisted(key) 99 | if (__DEV__ && !has && PublicInstanceProxyHandlers.has!(_, key)) { 100 | warn( 101 | `Property ${JSON.stringify( 102 | key 103 | )} should not start with _ which is a reserved prefix for Vue internals.` 104 | ) 105 | } 106 | return has 107 | } 108 | ``` 109 | 110 | ```typescript 111 | const GLOBALS_WHITE_LISTED = 112 | 'Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,' + 113 | 'decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,' + 114 | 'Object,Boolean,String,RegExp,Map,Set,JSON,Intl' 115 | 116 | export const isGloballyWhitelisted = /*#__PURE__*/ makeMap(GLOBALS_WHITE_LISTED) 117 | ``` 118 | 119 | 120 | 121 | ### 总结 122 | 123 | **RuntimeCompiledPublicInstanceProxyHandlers**继承**publicInstanceProxyHandlers**,前者get方法过滤key === **Symbol.unscopables**,has方法过滤基本类型和'_'开头的key。 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue3源码流程图化解释 2 | 3 | vue3做了最大的变化就是api的细分,适配typescript。給我一种感觉就是,vue3像乐高,一个个拼接起来成模块,模块之间的互相组合,来构成一个整体,这样更利于团队开发了,可以根据团队情况来定制合适的开发架构。composition-api的出现,如果想真正利用好,弄懂vue3源码是必须的。 4 | vue3中重要的包:Reactivity、runtime-x和 compile-x。 5 | 6 | https://www.processon.com/view/link/5f85c9321e085307a0892f7e 7 | 8 | ##### (最后更新时间:2021年03月25日22点18分) 9 |  10 | 11 | (不懂的,想来学习的,或者是想交流的欢迎进群54266285讨论,欢迎大家呀~。目前gitbook还没有整理,有些知识点可能比较乱,表述得不清楚,可以进群探讨) 12 | 13 |26 | 14 |16 |15 |
17 |19 | 20 | gitbook链接:https://kingbultsea.github.io/vue3-analysis/book/index.html 21 | 22 | ## 如何阅读源码 23 | 单步调试单元测试。 24 | 25 | 使用webstrom,Run -> Edit Configurations -> + -> npm 26 |  27 | 28 | [使用vscode](https://blog.csdn.net/weixin_30597269/article/details/99215170) 29 | 30 | 设置好debugger之后,修改jest.config.js文件指向一个你所需要测试的文件即可。 31 | ```typescript 32 | testMatch: ['18 |
/packages/runtime-core/__tests__/**/rendererAttrsFallthrough.spec.[jt]s?(x)'] 33 | ``` 34 | 比如这里是启动runtime-core中的rendererAttrsFallthrough.spec.ts测试用例。 35 | 36 | #### reactivity包的单元测试阅读顺序 37 | ref.spec.ts -> reactive.spec.ts -> readonly.spec.ts -> 38 | reactiveArray.spec.ts -> shallowReactive.spec.ts -> 39 | effect__.spec.ts -> computed.spec.ts -> collections(Map Set weekMap weekSet的处理) 40 | 41 | #### runtime-core包的单元测试阅读顺序 42 | vnode.spec.ts -> h.spec.ts -> vnodeHooks.spec.ts -> scheduler.spec.ts -> 43 | rendererElement.spec.ts -> rendererFragment.spec.ts -> 44 | rendererComponent.spec.ts -> rendererChildren.spec.ts(了解diff算法) -> 45 | rendererAttrsFallthrough.spec.ts 46 | 47 | #### 如何调试promise(microtask微任务)? 48 | 在微任务内部打上断点debugger,单步调试时,直接点击跳转到下一个断点即可。 49 | 50 | ## Reactivity 51 | Reactivity是响应式数据包,vue3中可以使用ref和relative来进行响应式数据化,这个包涉及三个概念effect、track、trigger, 52 | effect(fn)就好比添加影响,fn执行的过程中,遇到响应式数据取值,则触发响应式数据所获取的字段的track, 53 | 添加追踪当前激活的effect,当有事件触发响应式数据对应的字段的修改值的行为时,将会trigger,触发所追踪的effect,再次执行fn的流程。 54 | 55 | 测试用例(reactivity/\_\_tests\_\_/effect.spec.ts): 56 | ```typescript 57 | it('should observe basic properties', () => { 58 | let dummy 59 | const counter = reactive({ num: 0 }) 60 | effect(() => (dummy = counter.num)) 61 | expect(dummy).toBe(0) 62 | counter.num = 7 63 | expect(dummy).toBe(7) 64 | }) 65 | ``` 66 | ## runtime 67 | 涉及到runtime的包有runtime-core、runtime-dom和runtime-test。把vnode渲染成真实的dom,而runtime-core 68 | 和runtime-test的区别是,runtime-core是用到生产环境中,runtime-test是用在测试环境中的,两者的代码并没有本质性的区别, 69 | 只不过是涉及到真实的dom的时候,runtime-test使用nodeOps来模拟真实dom。 70 | 71 | runtime涉及到两个对象,一个是instance,是函数式组件中所用到的(STATEFUL_COMPONENT),用来记录生命周期钩子, 72 | 组件之间的上下文交互。 73 | 另外一个是vnode,渲染属性的记录。 74 | 75 | 测试用例(runtime-core/\_\_test\_\_/rendererElement.spec.ts): 76 | ```typescript 77 | beforeEach(() => { 78 | root = nodeOps.createElement('div') 79 | }) 80 | 81 | it('should create an element', () => { 82 | render(h('div'), root) 83 | expect(inner(root)).toBe('') 84 | }) 85 | ``` 86 | 87 | ## compile 88 | 我们使用webpack的vue-loader插件,总是会帮我们把template标签转换成instance.render函数,如果没有该插件,在手写OPTION API中的template,将会在render渲染过程中的第二个步骤,检测当前使用的版本有没有使用compile,有则调用compile转换。 89 | 90 | 这里compile所做的还有静态标记,静态dom的缓存,重复dom的内存收集。 91 | 92 | 这里说重点:如果不想了解背后,可以直接使用在线模板编译来查看渲染出vnode。 93 | 94 | https://vue-next-template-explorer.netlify.app/ 95 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | * [今日更新](./Packages/BUGS/iframe-bug.md) 3 | * [介绍](README.md) 4 | 5 | * Runtime 6 | * [runtime](Packages/Runtime/index.md) 7 | * [渲染流程](Packages/Runtime/desc.md) 8 | * [渲染流程(子组件)](Packages/Runtime/desc2.md) 9 | * [Options-API](Packages/Runtime/OPTION-API.md) 10 | * [事件队列与组件更新](Packages/Runtime/事件队列与组件更新.md) 11 | * [类型标记](Packages/Runtime/类型标记.md) 12 | * [问题集合](Packages/Runtime/question.md) 13 | 14 | * Reactivity 15 | * [reactivity](Packages/Reactivity/index.md) 16 | * [diff算法](Packages/Reactivity/diff.md) 17 | * [问题集合](Packages/Reactivity/问题集合.md) 18 | 19 | * Compile 20 | * [compile](Packages/compile-core/compile-core.md) 21 | * [为什么在slot中事件没有更新](Packages/compile-core/为什么在slot中事件没有更新.md) 22 | * [问题集合](Packages/compile-core/问题集合.md) 23 | 24 | * API DOC 25 | * [API](Packages/API_DOC/index.md) 26 | 27 | * BUGS 28 | * [iframe](Packages/BUGS/iframe-bug.md) -------------------------------------------------------------------------------- /_book/.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=TypeScript 2 | *.css linguist-language=TypeScript 3 | *.html linguist-language=TypeScript 4 | -------------------------------------------------------------------------------- /_book/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /_book/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 |4 | 5 | 6 |3 | -------------------------------------------------------------------------------- /_book/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 |4 | 8 |5 | 7 |6 | 3 | -------------------------------------------------------------------------------- /_book/.idea/vue3源码解释.iml: -------------------------------------------------------------------------------- 1 | 2 |4 | 6 |5 | 3 | -------------------------------------------------------------------------------- /_book/Packages/BUGS/A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/Packages/BUGS/A.png -------------------------------------------------------------------------------- /_book/Packages/BUGS/B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/Packages/BUGS/B.png -------------------------------------------------------------------------------- /_book/Packages/BUGS/iframe-bug.md: -------------------------------------------------------------------------------- 1 | ### vue在触发dom点击事件的时候,需要验证event.timeStamp 2 | ##### 历史来源: 3 | 4 | vue-2.4.2 #6566 5 | 6 | ##### 操作: 7 | 8 |  9 | 10 | 点击**element-1**,触发了**countA++**和**countB++**。 11 | 12 | ##### 过程: 13 | 14 | 点击element-1,触发element-1本身的click事件,```expand = 1; countA++```。 15 | 16 | 由```expand```变动,把组件effect事件丢进微任务。 17 | 18 | 触发微任务,由于diff算法的原因,block-1会被复用,```patchProps```更新block-1的click事件为block-2的click事件。 19 | 20 | 由element-1所触发的冒泡事件,引起block-1中的click触发(click事件已经被更新成block-2的了)。 21 | 22 | ##### 解决方法: 23 | 24 | 在创建事件的时候,新增一个时间戳,该时间戳为**页面启动时间->创建该事件的时间**。 25 | 26 | 当触发click事件的时候,判断Event的timeStamp是否大于事件的创建时间。 27 | 28 |  29 | 30 | 这样冒泡的时候,Event.timeStamp 小于 事件的创建时间,那么该事件就不会触发了。 31 | 32 | 33 | 34 | 哈哈哈?以为这样就完了?直到我看到 vue-next-#2513 35 | 36 | https://github.com/vuejs/vue-next/issues/2513 37 | 38 | 在iframe中,Event.timeStamp,是由该iframe被创建开始算起的。 39 | 40 | 所以会导致iframe中的点击事件的timeStamp,小于**源页面启动时间->创建该事件的时间**。 41 | 42 | 从而事件不被触发。 43 | 44 | **除非你愿意等待这些时间。** 45 | 46 | https://stackblitz.com/edit/vue3-iframe-teleport-demo?file=src%2FApp.vue 47 | 48 | 尝试一下,点击渲染iframe再疯狂点击count?你就会发现一开并不能触发,要等**页面启动时间->创建该事件的时间**才能触发事件。 49 | 50 | count事件触发后,点击toggle,再重新渲染iframe,你会发现你要等待的时间更久了! -------------------------------------------------------------------------------- /_book/Packages/Reactivity/diff.md: -------------------------------------------------------------------------------- 1 | # 新版Diff 算法 2 | 3 | 好了好了,我们来聊聊diff吧,我就不上代码了... 其实之前哪些写的代码,是可看可不看的。 4 | 5 | ```typescript 6 | const childOne = [1,2,3,4,5] 7 | const childTwo = [1,2,3,4,6,5] 8 | ``` 9 | 10 | 假设你的vnode拥有[1,2,3,4,5]子元素,你的vnode类型是ELEMENT, 11 | 12 | 拥有子5个vnode:[h('span', 1), h('span', 2), h('span', 3), h('span', 4), h('span', 5)] 13 | 14 | 现在把包含这5个vnode的数组简写成[1,2,3,4,5]。 15 | 16 | 进行初始渲染后,因为要更新child,所以再次进行patch。 17 | 18 | > 如果子元素数组没有key,但是字符串相等,那么他们会被判定为同一个vnode。 19 | 20 | > 啥是patch啊?emm... 竟然你都问了,你就当它是一个更新器吧 21 | 22 | 23 | 24 | ## 步骤一 25 | 26 | 从头对比是不是同一个vnode,是则跳过不做任何处理,不是则标记。 27 | 28 | 重复上步骤,从尾到头(标记点也算是头)。 29 | 30 | 按照上述,我们标记到4和5,目前没有进行任何的patch处理。 31 | 32 | **如果没有尾或者头有相同的vnode,索引依旧在两端,并不是不存在。** 33 | 34 |  35 | 36 | 37 | 38 | ## 步骤二 39 | 40 | 对两个标记之间的元素进行patch,也就是进行更新。 41 | 42 |  43 | 44 | 45 | 46 | ## 步骤三 47 | 48 | 对从头标记之后尾标记之前的元素进行patch。 49 | 50 |  51 | 52 | 53 | 54 | ## 步骤四 55 | 56 | 对旧的子vnode要进行unmount。 57 | 58 |  59 | 60 | 61 | 62 | ## 步骤五(重点哦) 63 | 64 | ... 明天再更新~ -------------------------------------------------------------------------------- /_book/Packages/Reactivity/diff剧本.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/Packages/Reactivity/diff剧本.md -------------------------------------------------------------------------------- /_book/Packages/Reactivity/恐.md: -------------------------------------------------------------------------------- 1 | Q1: 话说我用vue3那么久了,老是听别人说什么响应式数据... 我根本不懂怎么办?现在越来越感觉自己像个API开发者了,心里很焦虑,能不能給我科普一下呀 2 | 3 | W1:不用焦虑,成为一个能懂原理的人固然重要,急是急不来的,我们可以先从三大概念入手:Track(追踪)、Trigger(触发)、Effect(影响)。 4 | 5 | 6 | 7 | W1:“A先生,你好呀,如果有来自E家族的人,问你信息,拜托你 记录一下他问了你哪些信息哦。如果你相关信息变动了,就要主动告诉他哦” 8 | 9 | A: “嗯嗯好的,好的我都知道了” 10 | 11 | 12 | 13 | E-sb:"小A,过来一下!" 14 | 15 | A:“来了来了,有啥事呀,大佬” 16 | 17 | E-sb:“你现在身上有多少钱啊?” 18 | 19 | E-sb: "这么少?这次就放过你了" 20 | 21 | A:“好的,大佬,知道了” 22 | 23 | 24 | 25 | “A先生回到家后,就把E-sb问了他关于钱有多少这一事情,记录(Track)在了小本本上。” 26 | 27 | 28 | 29 | “很多天过去了,小A在马路上看到5块钱,马上捡了起来,他总觉得有些事要处理,但是忘了是什么东西了,于是回家看起了小本本,记起来了我们吩咐A做的事(Trigger)” 30 | 31 | 32 | 33 | A:"大佬好,一块两块三块五块左右" 34 | 35 | E-sb: "很好,很主动我很欣赏你" 36 | 37 | 38 | 39 | Q1:“欸?那是不是A的钱每次变化都会主动去告诉E-sb呀,那这样的话如果E-sb当场拿走A的五块钱,A会不会继续告诉E-sb自己有多少钱?” 40 | 41 | 42 | 43 | 情景重现 44 | 45 | 46 | 47 | A:"大佬好,一块两块三块五块左右" 48 | 49 | E-sb: "很好,很主动我很欣赏你,五块钱我要了" 50 | 51 | A:"大佬好,一块两块三块左右" 52 | 53 | 54 | 55 | W1: “对的,如果E-sb向A拿钱,A的钱在无限的情况下,就会造成一个相当阿巴阿巴的场景” 56 | 57 | Q1:“哦,那其实就是A先生是响应式数据,E-sb就是Effect,E-sb与A先生存在钱的来往,然后A先生记录下E-sb询问了他的钱的情况(Track),需要每次在自己的钱变动都会主动通知(Trigger)E-sb。” 58 | 59 | 60 | 61 | Q1:“但是什么是Effect呀... 我平时使用vue3的composition API 也没怎么用过,但是听你这么说,这个东西,是不是computed呀?” 62 | 63 | W1:“猜对了,computed本身用到了effect,返回的是一个响应式数据,同样会拥有Track与Trigger的行为,computed对比effect,还会判断是否重复多次调用effect,仅在一个队列中执行一次哦。” -------------------------------------------------------------------------------- /_book/Packages/Reactivity/恐.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/Packages/Reactivity/恐.png -------------------------------------------------------------------------------- /_book/Packages/Reactivity/镜.md: -------------------------------------------------------------------------------- 1 | watchEffect(() => { 2 | 3 | console.log(A.money) 4 | 5 | }) 6 | 7 | 8 | 9 | Q1:“emmm,大概了解了,能不能详细讲一下watchEffect这个例子” 10 | 11 | R1:“其实watchEffect里面也是把effect封装了起来,流程还是一样的流程” 12 | 13 | 14 | 15 | watchEffect中,接收两个参数①和②,①我们通常设置成一个方法,②是一些配置。 16 | 17 | 这里不聊②,降低复杂性,使用默认配置。 18 | 19 | watchEffect(①) 20 | 21 | 他与普通effect不同的是,会告诉①里面的响应式数据们:“每次运行我(Trigger effect)的时候,请把我放进任务队列中” 22 | 23 | 他也有一个特性,如果检测到当前有正在渲染的组件,而且组件还没有挂载上去,那他就什么不都做。 24 | 25 | 26 | 27 | Q1: "对了,这里在代码层面上,①里面的响应式数据们,是如何捕获到当前①这个方法呢?为什么每次①里面的数据一变动,这个方法就会重新执行?" 28 | 29 | R1:“这很好理解,①当前执行,也就是effect执行,我们标记activeEffect为当前effect就可以了,因为①里面会触发响应式数据的getter方法,我们可以使响应式数据们检测有没有activeEffect,有就捕获他。响应式都捕获到effect了,那么改变当前值的时候,再重新触发effect就好了” 30 | 31 | 32 | 33 | Q1:“那就是说,运行effect,effect内部标记activeEffect为当前effect,然后运行①,①中的响应式数据getter检测到activeEffect,记录(track)下来,响应式数据触发setter的时候,就trigger,重新触发effect,在触发effect前,把所有响应式数据所track到当前的effect的记录全部删除,因为运行①会再次记录下来的,所以才要删除!!! 我懂了!!!” 34 | 35 | 36 | 37 | effect = (fn) => { 38 | 39 | activeEffect = Effect 40 | 41 | return fn() 42 | 43 | } 44 | 45 | 46 | 47 | Q1:“啊... 原理是那么简单的.. 我还以为有什么神奇的东西在里面。那那那... 我平时使用watchEffect的时候,①中假如我用到了响应式数据,我把这个响应式数据疯狂触发getter的时候,这个effect也只是执行一次呀” 48 | 49 | R1:“这就是为什么“每次运行我的时候,请把我放进微任务中了”,这里有一个事件队列,每次执行事件队列的时候,就会去除重复的事件,也就是说,不管你触发多少次trigger,都会只运行一次了 ” 50 | 51 | 52 | 53 | Q1:"那另外一个特性是啥意思.. 有什么用麽?" 54 | 55 | R1:"目前只发现这个和SSR相关,和我们普通使用无关,欸... SSR那块我也不是很清楚了" -------------------------------------------------------------------------------- /_book/Packages/Reactivity/镜.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/Packages/Reactivity/镜.png -------------------------------------------------------------------------------- /_book/Packages/Reactivity/问题集合.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/Packages/Reactivity/问题集合.md -------------------------------------------------------------------------------- /_book/Packages/Runtime/OPTION-API.md: -------------------------------------------------------------------------------- 1 | # OptionsAPI 2 | 3 | vue2.x相关,与vue3的关系仅有component.template使用compile转换成component.render。 4 | 5 | 6 | 7 | ## methods 8 | 9 | -------------------------------------------------------------------------------- /_book/Packages/Runtime/index.md: -------------------------------------------------------------------------------- 1 | # 测试用例 2 | ```typescript 3 | it('should allow attrs to fallthrough', async () => { 4 | debugger 5 | const click = jest.fn() 6 | const childUpdated = jest.fn() 7 | 8 | const Hello = { 9 | setup() { // 如果有render 就优先渲染render了 10 | const count = ref(0) 11 | 12 | function inc() { // 需要了解onClick事件系统 13 | count.value++ 14 | click() 15 | } 16 | 17 | // 这里的任何东西 都不会被本身effect收集 只有return 后的方法 才会 18 | 19 | return () => // 这里还可以当redner来使用 我平时会的只是 return {} template所需要的事件或属性 所以是检查function 还是 object来判断的吧 20 | h(Child, { // 那是不是有一个props effect? 标记也要注意下 21 | foo: count.value + 1, 22 | id: 'test', 23 | class: 'c' + count.value, 24 | style: { color: count.value ? 'red' : 'green' }, 25 | onClick: inc, 26 | 'data-id': count.value + 1 27 | }) 28 | }, 29 | mounted() { 30 | console.log('?') 31 | } 32 | } 33 | 34 | const Child = { // 原来是这样传参数的 那么就不需要this 什么的了 35 | setup(props: any) { 36 | onUpdated(childUpdated) 37 | return () => 38 | h( 39 | 'div', 40 | { 41 | class: 'c2', 42 | style: { fontWeight: 'bold' } 43 | }, 44 | props.foo // 这个为什么是undefinded呢 因为setFullProps执行的时候,判断到的赋值Props方式为attrs,所以instance.props为空 45 | ) 46 | } 47 | } 48 | 49 | const root = document.createElement('div') 50 | document.body.appendChild(root) 51 | render(h(Hello), root) // 这个render 是'@vue/runtime-dom' 我们之前用的 是'@vue/runtime-test' 里面的 测试用的... 但是区别不一样的就是 会不会初始化而已 52 | 53 | const node = root.children[0] as HTMLElement 54 | 55 | expect(node.getAttribute('id')).toBe('test') 56 | expect(node.getAttribute('foo')).toBe('1') 57 | expect(node.getAttribute('class')).toBe('c2 c0') 58 | expect(node.style.color).toBe('green') 59 | expect(node.style.fontWeight).toBe('bold') 60 | expect(node.dataset.id).toBe('1') 61 | 62 | node.dispatchEvent(new CustomEvent('click')) // 事件触发a 63 | node.dispatchEvent(new CustomEvent('click')) // 事件触发a 64 | expect(click).toHaveBeenCalled() 65 | 66 | await nextTick() 67 | expect(childUpdated).toHaveBeenCalled() 68 | expect(node.getAttribute('id')).toBe('test') 69 | expect(node.getAttribute('foo')).toBe('2') 70 | expect(node.getAttribute('class')).toBe('c2 c1') 71 | expect(node.style.color).toBe('red') 72 | expect(node.style.fontWeight).toBe('bold') 73 | expect(node.dataset.id).toBe('2') 74 | }) 75 | ``` 76 | 77 | ## processComponent流程图: 78 | https://www.processon.com/view/link/5f85c9321e085307a0892f7e 79 | 80 |  81 | 82 | 83 | ## vnode: 84 | https://www.processon.com/view/link/5f963f37f346fb06e1ec35b2 85 |  86 | 87 | 88 | ## instance: 89 | https://www.processon.com/view/link/5f963f5fe401fd06fda22681 90 |  91 | -------------------------------------------------------------------------------- /_book/Packages/Runtime/patch剧本.md: -------------------------------------------------------------------------------- 1 | **patch** 在vue中,充当一个渲染角色存在。 2 | 3 | **patch**就像一家工厂,把**vnode**(一个描述**vue组件**,或者描述**NODE**的对象)当作生产原料,进行一系列的加工,最终生产出成品(也就是真实的**NODE**)。 4 | 5 | 一家工厂,可以是由**流程控制器**和**加工机器**组成的。**流程控制器**根据原料**vnode**的类型,来选取机器处理。机器负责把传入来的东西,加工或输出。 6 | 7 | 8 | 9 | 对于**patch**这个工厂来说,有哪些流程控制器? 10 | 11 | 组件渲染流程控制器 12 | 13 | 普通元素渲染流程控制器 14 | 15 | 16 | 17 | 对于**patch**这个工厂来说,有哪些机器? 18 | 19 | (property 和 attribute非常容易混淆,两个单词的中文翻译也都非常相近(property:属性,attribute:特性),但实际上,二者是不同的东西,属于不同的范畴。) 20 | 21 | - property是DOM中的属性,是JavaScript里的对象; 22 | 23 | - attribute是HTML标签上的特性,它的值只能够是字符串; 24 | 25 | - property能够从attribute中得到同步; 26 | 27 | - attribute不会同步property上的值; 28 | 29 | - attribute和property之间的数据绑定是单向的,attribute->property; 30 | 31 | - 更改property和attribute上的任意值,都会将更新反映到HTML页面中; 32 | 33 | 34 | 35 | **patchAttr**: 使用setAttrbute方法,添加、去除和设置HTMLELEMENT标签上特性。 36 | 37 | **patchClass**: 设置HTMLELEMENT的className属性,为什么有patchAttr存在了,还需要patchClass呢,因为速度比patchAttr要快。 38 | 39 | **patchStyle**: 利用setProperty修改HTMLELEMENT的style属性。 40 | 41 | **patchDomProp**: 处理HTMLELEMENT中的属性。 42 | 43 | **patchEvent**: 更新、删除和添加事件(这个事件就是ducument.addEventListener(Event)中的Event)。 44 | 45 | **processText**:创建、插入和更换**TextNode** 46 | 47 | **processCommentNode**: 创建、插入和更换**CommentNode** e.g. \<\!\-\- 这就是comment\-\-\> 48 | 49 | **mountStaticNode**: 创建Node,把Node丢进目标Node中 50 | 51 | 52 | 53 | q1: 抱歉,我又懵逼了,为什么拥有了patchAttr来设置属性了,还需要patchClass、patchStyle和patchDomProp? 54 | 55 | R1: 确实不需要,这么做主要是为了速度,这里就需要了解property和attribute了 56 | 57 | 58 | 59 | patchAttr 60 | 61 | 62 | 63 | 小知识: 64 | 65 | 可以利用双叹号 !! 来把一个值转换成布尔属性(比如0是false,null是false之类的,负数是true,undefinded是false,undefinded是false)。 66 | 67 | 68 | 69 | https://developer.mozilla.org/zh-CN/docs/Web/API/Event/stopImmediatePropagation 70 | 71 | [`Event`](https://developer.mozilla.org/zh-CN/docs/Web/API/Event) 接口的 `stopImmediatePropagation()` 方法阻止监听同一事件的其他事件监听器被调用。 72 | 73 | -------------------------------------------------------------------------------- /_book/Packages/Runtime/question.md: -------------------------------------------------------------------------------- 1 | # 一些问题 2 | #### 1.当连续触发响应式的trigger会导致组件的effectComponent连续执行更新吗? 3 | 并不会。第一次触发trigger的时候会把事件队列状态改成Pending,把flushJobs丢进microtask来执行,第二次触发trigger会检测到当前状态如果是Pending或者是Flushing状态的时候将不进行下去。 4 | 可在packages/runtime-core/\_\_tests\_\_/rendererAttrsFallthrough.spec.ts 'should allow attrs to fallthrough'测试用例中调试。 5 | 6 |  7 | 8 | #### 2.patch到底有多少种类型?分别是干什么的? 9 | 在patch中查看有processText、processCommentNode、mountStaticNode、patchStaticNode、processFragment、processElement(流程图已有)、processComponent(流程图已有)、Teleport、SUSPENSE。 10 | 11 | 12 | #### 3.instance.props、instance.attrs和vnode.props分别是什么?关系是什么? 13 | 先说一下设置的阶段,和如何设置的: 14 | 1.instance.props和instance.attrs是在第二步骤中setupComponent中的initProps实现的,是根据vnode.props来设置的,instance和组件类型相关。 15 | 2.vnode.props创建vnode的时候传入的参数,比如```h(Child, { foo: 1, class: 'parent' })```。 16 | 17 | 使用阶段,拿这个做例子: 18 | ```typescript 19 | const Hello = { 20 | setup() { 21 | const count = ref(0) 22 | 23 | function inc() { 24 | count.value++ 25 | } 26 | 27 | return () => 28 | h(Child, { 29 | foo: count.value + 1, 30 | id: 'test', 31 | class: 'c' + count.value, 32 | style: { color: count.value ? 'red' : 'green' }, 33 | onClick: inc, 34 | 'data-id': count.value + 1 35 | }) 36 | } 37 | } 38 | 39 | const Child = { 40 | setup(props: any) { 41 | return () => 42 | h( 43 | 'div', 44 | { 45 | class: 'c2', 46 | style: { fontWeight: 'bold' } 47 | }, 48 | props.foo 49 | ) 50 | } 51 | } 52 | 53 | const root = document.createElement('div') 54 | document.body.appendChild(root) 55 | render(h(Hello), root) 56 | 57 | ``` 58 | 59 | 步骤一:第一次渲染,流程图②setupComponent的initProps,使用Hello组件的vnode.props(当前为空,所以输出的attrs都是为空的),标准化生成instance.attrs和instance.props 60 | ```typescript 61 | if (!instance.type.props) { 62 | // functional w/ optional props, props === attrs 63 | instance.props = attrs 64 | } else { 65 | // functional w/ declared props 66 | instance.props = props 67 | } 68 | ``` 69 | 70 | 步骤二:②setupConponent中,renderComponentRoot(instance)会把instance.attrs与调用instance.render(Hello.setup返回的方法)所返回的vnode的props进行合并。 71 | 72 | 步骤三:子组件进行patch,同样经过initProps处理, 73 | 在②中调用setup方法,并传入instance.props, instance.setupConetxt,这个时候子组件就会利用到父组件传入的props了。 74 | 75 | _**那更新的时候,是怎么样的?**_ 76 | 77 | 触发父组件instance,renderComponentRoot(instance)中调用instance.render(这个过程就是vnode.props的更新了) 78 | 再次进行当前track追踪(effect运行的特性,是会删除当前追踪),patch(旧vnode1, 新vnode2),processComponent updateComponent, 79 | 新vnode2引用vnode1.component(instance2,子组件instance2),删除queue任务中的instance2.update,instance2.next = n2,执行instance2.update, 80 | 检测到有next字段,执行updateComponentPreRender,这里instance2是为了标识是子instance。 81 | 82 | updateComponentPreRender: 83 | ```typescript 84 | nextVNode.component = instance2 // 对当前没用,因为已经引用了 85 | const prevProps = instance2.vnode.props 86 | instance2.vnode = nextVNode // 更新vnode 87 | instance2.next = null // 删除next 88 | updateProps(instance2, nextVNode.props, prevProps, optimized) 89 | updateSlots(instance2, nextVNode.children) 90 | ``` 91 | updateProps会利用setFullProps来更新attrs和props,这里的更新过的props和attrs可以提供給Child内部使用,如当前使用了props.foo。 92 | -------------------------------------------------------------------------------- /_book/Packages/Runtime/类型标记.md: -------------------------------------------------------------------------------- 1 | # 类型标记 2 | 3 | ```typescript 4 | export declare const enum ShapeFlags { 5 | ELEMENT = 1, // 1 6 | FUNCTIONAL_COMPONENT = 2, // 10 7 | STATEFUL_COMPONENT = 4, // 100 8 | TEXT_CHILDREN = 8, // 1000 9 | ARRAY_CHILDREN = 16, // 10000 10 | SLOTS_CHILDREN = 32, // 100000 11 | TELEPORT = 64, // 1000000 12 | SUSPENSE = 128, // 10000000 13 | COMPONENT_SHOULD_KEEP_ALIVE = 256, // 100000000 14 | COMPONENT_KEPT_ALIVE = 512, // 1000000000 15 | COMPONENT = 6 // 110 16 | } 17 | ``` 18 | 19 | 20 | 按位与: 21 | 22 | ```typescript 23 | 01 | 01 === 01 24 | 01 | 10 === 11 25 | 10 | 10 === 10 26 | ``` 27 | 28 | 29 | 30 | 判断类型的按位且: 31 | 32 | ```typescript 33 | 11 & 11 === 11 34 | 01 & 10 === 00 35 | 10 & 10 === 10 36 | ``` 37 | 38 | 对应位置1 & 1 === 1, 1 | 0 === 0,就像&&的逻辑,要满足所有为真值。 39 | 40 | 41 | 42 | 比如100代表STATEFUL_COMPONENT类型,10000代表ARRAY_CHILDREN类型,合并使用 43 | 44 | ```typescript 45 | const initial = 100 | 10000 === 10100 46 | ``` 47 | 48 | 49 | 50 | 要判断10100是否包含STATEFUL_COMPONENT类型: 51 | 52 | ```typescript 53 | const result = initial & ShapeFlags.STATEFUL_COMPONENT 54 | // result === 100 55 | if (result > 0) { 56 | console.log('包含STATEFUL_COMPONENT类型') 57 | } 58 | ``` 59 | 60 | 61 | 62 | 利用标记的方式,可以轻松合并类型和检测类型,对代码的可阅读性强。 -------------------------------------------------------------------------------- /_book/Packages/compile-core/compile-core.md: -------------------------------------------------------------------------------- 1 | ## 关于compile 2 | 3 | 在带有编译版本的vue中,finishComponentSetup会对没有render方法,但是有template的Component,做编译处理。 4 | 5 | 这里推荐一个compile在线转换成render的网站: 6 | 7 | https://vue-next-template-explorer.netlify.app/ 8 | 9 | 10 | 11 | finishComponentSetup的代码片段: 12 | 13 | ```typescript 14 | Component.render = compile(Component.template, { 15 | isCustomElement: instance.appContext.config.isCustomElement || NO 16 | }) 17 | ``` 18 | 19 | 20 | 21 | 去查看一下compile的CompilerOptions相关配置。 22 | 23 | ```typescript 24 | type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions 25 | 26 | // ParserOptions: 27 | export interface ParserOptions { 28 | // 平台上本地元素, 例如4 | 8 |5 | 6 | 7 | 是网页上的本地标签 29 | isNativeTag?: (tag: string) => boolean 30 | 31 | // 不需要闭合的原生元素, 例如,
,
32 | isVoidTag?: (tag: string) => boolean 33 | 34 | // 应该保留内部空白的元素, 例如35 | // https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/pre 36 | isPreTag?: (tag: string) => boolean 37 | 38 | // 特定于平台的内置组件,例如39 | isBuiltInComponent?: (tag: string) => symbol | void 40 | 41 | // 自定义于平台的本地元素 42 | isCustomElement?: (tag: string) => boolean 43 | 44 | // 获取标签的名称 45 | getNamespace?: (tag: string, parent: ElementNode | undefined) => Namespace 46 | 47 | // 获取此元素的文本分析模式 48 | getTextMode?: ( 49 | node: ElementNode, 50 | parent: ElementNode | undefined 51 | ) => TextModes 52 | 53 | // 默认 ['{{', '}}'],可修改为你喜欢的 54 | delimiters?: [string, string] 55 | 56 | // Only needed for DOM compilers,解码实体 57 | decodeEntities?: (rawText: string, asAttr: boolean) => string 58 | 59 | // 错误 60 | onError?: (error: CompilerError) => void 61 | } 62 | 63 | 64 | // TransformOptions: 65 | export interface TransformOptions { 66 | /** 67 | * An array of node trasnforms to be applied to every AST node. 68 | */ 69 | nodeTransforms?: NodeTransform[] 70 | /** 71 | * An object of { name: transform } to be applied to every directive attribute 72 | * node found on element nodes. 73 | */ 74 | directiveTransforms?: Record 75 | /** 76 | * An optional hook to transform a node being hoisted. 77 | * used by compiler-dom to turn hoisted nodes into stringified HTML vnodes. 78 | * @default null 79 | */ 80 | transformHoist?: HoistTransform | null 81 | 82 | // 如果提供了其他的内置元素,可以使用该选项标记为内置的,这样编译器将为这些标签生成组件vnode 83 | isBuiltInComponent?: (tag: string) => symbol | void 84 | /** 85 | * 把表达式 {{ foo }} 转换成 `_ctx.foo` 86 | * 如果该选项为false, the generated code will be wrapped in a 87 | * `with (this) { ... }` block. 88 | * - This is force-enabled in module mode, since modules are by default strict 89 | * and cannot use `with` 90 | * @default mode === 'module' 91 | */ 92 | prefixIdentifiers?: boolean 93 | 94 | 95 | // 静态提升到 `_hoisted_x` 变量 96 | // 默认值 false 97 | hoistStatic?: boolean 98 | 99 | // 开启前 { onClick: _ctx.foo } 100 | // 开启后 { 101 | // onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.foo(...args))) 102 | // } 103 | // _cache[index] index根据当前缓存事件数量,保证预留一个空的位置給该事件 104 | // 默认值 false 105 | cacheHandlers?: boolean 106 | 107 | // `@babel/parser` 中的插件, 108 | // https://babeljs.io/docs/en/next/babel-parser#plugins 109 | expressionPlugins?: ParserPlugin[] 110 | 111 | // Single File Component组件中scoped styles ID 112 | scopeId?: string | null 113 | /** 114 | * Generate SSR-optimized render functions instead. 115 | * The resulting funciton must be attached to the component via the 116 | * `ssrRender` option instead of `render`. 117 | */ 118 | ssr?: boolean 119 | onError?: (error: CompilerError) => void 120 | } 121 | 122 | // CodegenOptions 123 | ``` 124 | 125 | 例子: 126 | ```html 127 | 128 | \{\{ world.burn() \}\} 129 |133 | ``` 134 | 135 | 转换成render: 136 | ```typescript 137 | const _Vue = Vue 138 | 139 | return function render(_ctx, _cache) { 140 | with (_ctx) { 141 | const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList } = _Vue 142 | 143 | return (_openBlock(), _createBlock("div", { 144 | id: "foo", 145 | class: bar.baz 146 | }, [ 147 | _createTextVNode(_toDisplayString(world.burn()) + " ", 1 /* TEXT */), 148 | ok 149 | ? (_openBlock(), _createBlock("div", { key: 0 }, "yes")) 150 | : (_openBlock(), _createBlock(_Fragment, { key: 1 }, [ 151 | _createTextVNode("no") 152 | ], 64 /* STABLE_FRAGMENT */)), 153 | (_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (value, index) => { 154 | return (_openBlock(), _createBlock("div", null, [ 155 | _createVNode("span", null, _toDisplayString(value + index), 1 /* TEXT */) 156 | ])) 157 | }), 256 /* UNKEYED_FRAGMENT */)) 158 | ], 2 /* CLASS */)) 159 | } 160 | } 161 | ``` 162 | 163 | -------------------------------------------------------------------------------- /_book/Packages/compile-core/为什么在slot中事件没有更新.md: -------------------------------------------------------------------------------- 1 | # 为什么使用slot,事件没有更新? 2 | 3 | 代码: 4 | 5 | ```html 6 | 7 | 8 | 9 | 10 | 11 | {{ record }} 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |yes130 | no 131 |{{ value + index }}132 |25 |27 | 28 | ``` 29 | 30 | 31 | 32 | 使用compile转换后的代码: 33 | 34 | ```typescript 35 | import { createVNode as _createVNode, toDisplayString as _toDisplayString, resolveComponent as _resolveComponent, withCtx as _withCtx, createTextVNode as _createTextVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from "vue" 36 | 37 | export function render(_ctx, _cache, $props, $setup, $data, $options) { 38 | const _component_B = _resolveComponent("B") 39 | const _component_A = _resolveComponent("A") 40 | 41 | return (_openBlock(), _createBlock(_Fragment, null, [ 42 | _createVNode("button", { 43 | onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.changeText(...args))) 44 | }, "change test"), 45 | _createVNode(_component_A, { 46 | name: _ctx.test, 47 | ref: "A" 48 | }, { 49 | default: _withCtx(({ record }) => [ 50 | _createTextVNode(_toDisplayString(record) + " ", 1 /* TEXT */), 51 | _createVNode(_component_B, { ref: "B" }, { 52 | over: _withCtx(() => [ 53 | _createVNode("button", { 54 | onClick: $event => (_ctx.handleClick(record)) 55 | }, "click me 啊2 ", 8 /* PROPS */, ["onClick"]) 56 | ]), 57 | _: 1 58 | }, 512 /* NEED_PATCH */) 59 | ]), 60 | _: 1 61 | }, 8 /* PROPS */, ["name"]) 62 | ], 64 /* STABLE_FRAGMENT */)) 63 | } 64 | ``` 65 | 66 | A: 67 | 68 | ```typescript 69 | const { renderSlot: _renderSlot, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue 70 | 71 | function render(_ctx, _cache) { 72 | return (_openBlock(), _createBlock("div", null, [ 73 | _renderSlot(_ctx.$slots, "default", { record: _ctx.name }) // { record: _ctx.name } 传入 default 方法中 74 | // createBlock(Fragment, { key: props.key /* 这里参数没有key */ }, default(props), PatchFlags.STABLE_FRAGMENT) 75 | ])) 76 | } 77 | ``` 78 | 79 | 80 | 81 | 点击handle为什么传入的东西还是旧的值?为什么不会更新? 82 | 83 | 84 | 85 | 我现在是假设你已经熟悉首次渲染。 86 | 87 | 我们在更新的时候,在组件A的update中,生成了vnode,该vnode,在patch第一个元素的时候发现record那段textVnode有变化,故更新该元素。 88 | 89 | 第二个元素是组件B,更新组件B前会使用shouldUpdateComponent方法: 90 | 91 | ```typescript 92 | function shouldUpdateComponent(prevVNode, nextVNode, optimized) { 93 | const { props: prevProps, children: prevChildren } = prevVNode; 94 | const { props: nextProps, children: nextChildren, patchFlag } = nextVNode; 95 | // Parent component's render function was hot-updated. Since this may have 96 | // caused the child component's slots content to have changed, we need to 97 | // force the child to update as well. 98 | if ((process.env.NODE_ENV !== 'production') && (prevChildren || nextChildren) && isHmrUpdating) { 99 | return true; 100 | } 101 | // force child update for runtime directive or transition on component vnode. 102 | if (nextVNode.dirs || nextVNode.transition) { 103 | return true; 104 | } 105 | if (patchFlag > 0) { 106 | if (patchFlag & 1024 /* DYNAMIC_SLOTS */) { 107 | // slot content that references values that might have changed, 108 | // e.g. in a v-for 109 | return true; 110 | } 111 | if (patchFlag & 16 /* FULL_PROPS */) { 112 | if (!prevProps) { 113 | return !!nextProps; 114 | } 115 | // presence of this flag indicates props are always non-null 116 | return hasPropsChanged(prevProps, nextProps); 117 | } 118 | else if (patchFlag & 8 /* PROPS */) { 119 | const dynamicProps = nextVNode.dynamicProps; 120 | for (let i = 0; i < dynamicProps.length; i++) { 121 | const key = dynamicProps[i]; 122 | if (nextProps[key] !== prevProps[key]) { 123 | return true; 124 | } 125 | } 126 | } 127 | } 128 | else if (!optimized) { 129 | // this path is only taken by manually written render functions 130 | // so presence of any children leads to a forced update 131 | if (prevChildren || nextChildren) { 132 | if (!nextChildren || !nextChildren.$stable) { 133 | return true; 134 | } 135 | } 136 | if (prevProps === nextProps) { 137 | return false; 138 | } 139 | if (!prevProps) { 140 | return !!nextProps; 141 | } 142 | if (!nextProps) { 143 | return true; 144 | } 145 | return hasPropsChanged(prevProps, nextProps); 146 | } 147 | return false; 148 | } 149 | ``` 150 | 151 | 这个东西是判断你的props和children有没有改变,没有则返回false,所以B组件不会更新。 152 | 153 | 154 | 155 | 那么,你是不是想问,record是指引属性,为什么还是旧的?你可以去认真看看initSlot和renderSlot,是调用为Object的子组件来实行的,旧的record就是在首次渲染的时候,调用defalut中的初始值,应用的地方也就是首次的环境。 156 | 157 | 而新的record根本就没有被更新到组件B上。 158 | 159 | 160 | 161 | **那为什么我直接调用组件B的update方法,还是不更新呢?** 162 | 163 | 这边建议你再去熟悉一下渲染流程,英文组件B的vnode依旧没有发生变化,需要的是组件A创建新的组件B的vnode才能达到你的效果。 164 | 165 | 166 | 167 | ### 解决方法: 168 | 169 | 为了使组件B更新,可以随便绑定一个参数給B,比如 170 | 171 | ```html 172 | 173 | ``` 174 | 175 | 或者 176 | 177 | ```html 178 | 179 | {{ record }} 180 | 181 | ``` 182 | 183 | 184 | 185 | ### 关于修复 186 | 187 | ##### 原始问题:https://github.com/vuejs/vue-next/issues/2618 188 | 189 | **修复方法**:https://github.com/vuejs/vue-next/pull/2568/files -------------------------------------------------------------------------------- /_book/Packages/compile-core/问题集合.md: -------------------------------------------------------------------------------- 1 | # 问题集合 2 | 3 | ## proxy与withProxy的区别 4 | 5 | proxy: 6 | 7 | ```typescript 8 | instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers) 9 | ``` 10 | 11 | withProxy: 12 | 13 | ```typ 14 | instance.withProxy = new Proxy( 15 | instance.ctx, 16 | RuntimeCompiledPublicInstanceProxyHandlers 17 | ) 18 | ``` 19 | 20 | 在调用render的时候,proxy会作为他的第一个参数,如果开启render._rc = true,则会设置withProxy,调用render优先使用withProxy作为第一个参数。 21 | 22 | ```typescript 23 | // 继承PublicInstanceProxyHandlers 但是重写get和has 24 | withProxy = extend({}, PublicInstanceProxyHandlers, { 25 | get(){ 26 | // fast path for unscopables when using `with` block 27 | if ((key as any) === Symbol.unscopables) { 28 | return 29 | } 30 | return PublicInstanceProxyHandlers.get!(target, key, target) 31 | }, 32 | has() 33 | }) 34 | ``` 35 | 36 | 37 | 38 | ### get: 39 | 40 | **RuntimeCompiledPublicInstanceProxyHandlers**判断key是否等于**Symbol.unscopables**,如果相等则不做处理,直接return,不相等则和**PublicInstanceProxyHandlers.get**行为一致。**目的是为了跳过get的一系列查询,优化代码性能。** 41 | 42 | 43 | 44 | ### has: 45 | 46 | has方法用来拦截hasProperty操作,即判断对象是否具有某个属性时。**handler.has()** 方法是针对 in 操作符的代理方法。 47 | 48 | #### **PublicInstanceProxyHandlers**.has: 49 | 50 | 查找的顺序为(都为instance的字段): 51 | 52 | 1.accessCache缓存中查找,这个缓存为key对应的类型,缓存起来可以快速在类型中查找 53 | 54 | 2.data中查找 55 | 56 | 3.setupState中查找 57 | 58 | 4.type.props中查找 59 | 60 | 5.ctx中查找 61 | 62 | 6.**publicPropertiesMap**,非instance字段,$data $el $props $attrs等 63 | 64 | 7.**appContext.config.globalProperties**,有且仅有一个的对象,所有有关系的组件都共同指向一个。 65 | 66 | publicPropertiesMap: 67 | 68 | ```typescript 69 | const publicPropertiesMap: Record< 70 | string, 71 | (i: ComponentInternalInstance) => any 72 | > = { 73 | $: i => i, 74 | $el: i => i.vnode.el, 75 | $data: i => i.data, 76 | $props: i => (__DEV__ ? shallowReadonly(i.props) : i.props), 77 | $attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs), 78 | $slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots), 79 | $refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs), 80 | $parent: i => i.parent && i.parent.proxy, 81 | $root: i => i.root && i.root.proxy, 82 | $emit: i => i.emit, 83 | $options: i => (__FEATURE_OPTIONS__ ? resolveMergedOptions(i) : i.type), 84 | $forceUpdate: i => () => queueJob(i.update), 85 | $nextTick: () => nextTick, 86 | $watch: __FEATURE_OPTIONS__ ? i => instanceWatch.bind(i) : NOOP 87 | } 88 | ``` 89 | 90 | 91 | 92 | #### RuntimeCompiledPublicInstanceProxyHandlers.has: 93 | 94 | 判断查询的key,不能为'_'开头,且key不能在全局白名单中。 95 | 96 | ```typescript 97 | has(_: ComponentRenderContext, key: string) { 98 | const has = key[0] !== '_' && !isGloballyWhitelisted(key) 99 | if (__DEV__ && !has && PublicInstanceProxyHandlers.has!(_, key)) { 100 | warn( 101 | `Property ${JSON.stringify( 102 | key 103 | )} should not start with _ which is a reserved prefix for Vue internals.` 104 | ) 105 | } 106 | return has 107 | } 108 | ``` 109 | 110 | ```typescript 111 | const GLOBALS_WHITE_LISTED = 112 | 'Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,' + 113 | 'decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,' + 114 | 'Object,Boolean,String,RegExp,Map,Set,JSON,Intl' 115 | 116 | export const isGloballyWhitelisted = /*#__PURE__*/ makeMap(GLOBALS_WHITE_LISTED) 117 | ``` 118 | 119 | 120 | 121 | ### 总结 122 | 123 | **RuntimeCompiledPublicInstanceProxyHandlers**继承**publicInstanceProxyHandlers**,前者get方法过滤key === **Symbol.unscopables**,has方法过滤基本类型和'_'开头的key。 -------------------------------------------------------------------------------- /_book/book/.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=TypeScript 2 | *.css linguist-language=TypeScript 3 | *.html linguist-language=TypeScript 4 | -------------------------------------------------------------------------------- /_book/book/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 |26 | 3 | -------------------------------------------------------------------------------- /_book/book/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 |4 | 5 | 6 |3 | -------------------------------------------------------------------------------- /_book/book/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 |4 | 8 |5 | 7 |6 | 3 | -------------------------------------------------------------------------------- /_book/book/.idea/vue3源码解释.iml: -------------------------------------------------------------------------------- 1 | 2 |4 | 6 |5 | 3 | -------------------------------------------------------------------------------- /_book/book/Packages/Reactivity/diff.md: -------------------------------------------------------------------------------- 1 | # 新版Diff 算法 2 | 3 | 好了好了,我们来聊聊diff吧,我就不上代码了... 其实之前哪些写的代码,是可看可不看的。 4 | 5 | ```typescript 6 | const childOne = [1,2,3,4,5] 7 | const childTwo = [1,2,3,4,6,5] 8 | ``` 9 | 10 | 假设你的vnode拥有[1,2,3,4,5]子元素,你的vnode类型是ELEMENT, 11 | 12 | 拥有子5个vnode:[h('span', 1), h('span', 2), h('span', 3), h('span', 4), h('span', 5)] 13 | 14 | 现在把包含这5个vnode的数组简写成[1,2,3,4,5]。 15 | 16 | 进行初始渲染后,因为要更新child,所以再次进行patch。 17 | 18 | > 如果子元素数组没有key,但是字符串相等,那么他们会被判定为同一个vnode。 19 | 20 | > 啥是patch啊?emm... 竟然你都问了,你就当它是一个更新器吧 21 | 22 | 23 | 24 | ## 步骤一 25 | 26 | 从头对比是不是同一个vnode,是则跳过不做任何处理,不是则标记。 27 | 28 | 重复上步骤,从尾到头(标记点也算是头)。 29 | 30 | 按照上述,我们标记到4和5,目前没有进行任何的patch处理。 31 | 32 | **如果没有尾或者头有相同的vnode,索引依旧在两端,并不是不存在。** 33 | 34 |  35 | 36 | 37 | 38 | ## 步骤二 39 | 40 | 对两个标记之间的元素进行patch,也就是进行更新。 41 | 42 |  43 | 44 | 45 | 46 | ## 步骤三 47 | 48 | 对从头标记之后尾标记之前的元素进行patch。 49 | 50 |  51 | 52 | 53 | 54 | ## 步骤四 55 | 56 | 对旧的子vnode要进行unmount。 57 | 58 |  59 | 60 | 61 | 62 | ## 步骤五(重点哦) 63 | 64 | ... 明天再更新~ -------------------------------------------------------------------------------- /_book/book/Packages/Reactivity/恐.md: -------------------------------------------------------------------------------- 1 | Q1: 话说我用vue3那么久了,老是听别人说什么响应式数据... 我根本不懂怎么办?现在越来越感觉自己像个API开发者了,心里很焦虑,能不能給我科普一下呀 2 | 3 | W1:不用焦虑,成为一个能懂原理的人固然重要,急是急不来的,我们可以先从三大概念入手:Track(追踪)、Trigger(触发)、Effect(影响)。 4 | 5 | 6 | 7 | W1:“A先生,你好呀,如果有来自E家族的人,问你信息,拜托你 记录一下他问了你哪些信息哦。如果你相关信息变动了,就要主动告诉他哦” 8 | 9 | A: “嗯嗯好的,好的我都知道了” 10 | 11 | 12 | 13 | E-sb:"小A,过来一下!" 14 | 15 | A:“来了来了,有啥事呀,大佬” 16 | 17 | E-sb:“你现在身上有多少钱啊?” 18 | 19 | E-sb: "这么少?这次就放过你了" 20 | 21 | A:“好的,大佬,知道了” 22 | 23 | 24 | 25 | “A先生回到家后,就把E-sb问了他关于钱有多少这一事情,记录(Track)在了小本本上。” 26 | 27 | 28 | 29 | “很多天过去了,小A在马路上看到5块钱,马上捡了起来,他总觉得有些事要处理,但是忘了是什么东西了,于是回家看起了小本本,记起来了我们吩咐A做的事(Trigger)” 30 | 31 | 32 | 33 | A:"大佬好,一块两块三块五块左右" 34 | 35 | E-sb: "很好,很主动我很欣赏你" 36 | 37 | 38 | 39 | Q1:“欸?那是不是A的钱每次变化都会主动去告诉E-sb呀,那这样的话如果E-sb当场拿走A的五块钱,A会不会继续告诉E-sb自己有多少钱?” 40 | 41 | 42 | 43 | 情景重现 44 | 45 | 46 | 47 | A:"大佬好,一块两块三块五块左右" 48 | 49 | E-sb: "很好,很主动我很欣赏你,五块钱我要了" 50 | 51 | A:"大佬好,一块两块三块左右" 52 | 53 | 54 | 55 | W1: “对的,如果E-sb向A拿钱,A的钱在无限的情况下,就会造成一个相当阿巴阿巴的场景” 56 | 57 | Q1:“哦,那其实就是A先生是响应式数据,E-sb就是Effect,E-sb与A先生存在钱的来往,然后A先生记录下E-sb询问了他的钱的情况(Track),需要每次在自己的钱变动都会主动通知(Trigger)E-sb。” 58 | 59 | 60 | 61 | Q1:“但是什么是Effect呀... 我平时使用vue3的composition API 也没怎么用过,但是听你这么说,这个东西,是不是computed呀?” 62 | 63 | W1:“猜对了,computed本身用到了effect,返回的是一个响应式数据,同样会拥有Track与Trigger的行为,computed对比effect,还会判断是否重复多次调用effect,仅在一个队列中执行一次哦。” -------------------------------------------------------------------------------- /_book/book/Packages/Reactivity/恐.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/book/Packages/Reactivity/恐.png -------------------------------------------------------------------------------- /_book/book/Packages/Reactivity/镜.md: -------------------------------------------------------------------------------- 1 | watchEffect(() => { 2 | 3 | console.log(A.money) 4 | 5 | }) 6 | 7 | 8 | 9 | Q1:“emmm,大概了解了,能不能详细讲一下watchEffect这个例子” 10 | 11 | R1:“其实watchEffect里面也是把effect封装了起来,流程还是一样的流程” 12 | 13 | 14 | 15 | watchEffect中,接收两个参数①和②,①我们通常设置成一个方法,②是一些配置。 16 | 17 | 这里不聊②,降低复杂性,使用默认配置。 18 | 19 | watchEffect(①) 20 | 21 | 他与普通effect不同的是,会告诉①里面的响应式数据们:“每次运行我(Trigger effect)的时候,请把我放进任务队列中” 22 | 23 | 他也有一个特性,如果检测到当前有正在渲染的组件,而且组件还没有挂载上去,那他就什么不都做。 24 | 25 | 26 | 27 | Q1: "对了,这里在代码层面上,①里面的响应式数据们,是如何捕获到当前①这个方法呢?为什么每次①里面的数据一变动,这个方法就会重新执行?" 28 | 29 | R1:“这很好理解,①当前执行,也就是effect执行,我们标记activeEffect为当前effect就可以了,因为①里面会触发响应式数据的getter方法,我们可以使响应式数据们检测有没有activeEffect,有就捕获他。响应式都捕获到effect了,那么改变当前值的时候,再重新触发effect就好了” 30 | 31 | 32 | 33 | Q1:“那就是说,运行effect,effect内部标记activeEffect为当前effect,然后运行①,①中的响应式数据getter检测到activeEffect,记录(track)下来,响应式数据触发setter的时候,就trigger,重新触发effect,在触发effect前,把所有响应式数据所track到当前的effect的记录全部删除,因为运行①会再次记录下来的,所以才要删除!!! 我懂了!!!” 34 | 35 | 36 | 37 | effect = (fn) => { 38 | 39 | activeEffect = Effect 40 | 41 | return fn() 42 | 43 | } 44 | 45 | 46 | 47 | Q1:“啊... 原理是那么简单的.. 我还以为有什么神奇的东西在里面。那那那... 我平时使用watchEffect的时候,①中假如我用到了响应式数据,我把这个响应式数据疯狂触发getter的时候,这个effect也只是执行一次呀” 48 | 49 | R1:“这就是为什么“每次运行我的时候,请把我放进微任务中了”,这里有一个事件队列,每次执行事件队列的时候,就会去除重复的事件,也就是说,不管你触发多少次trigger,都会只运行一次了 ” 50 | 51 | 52 | 53 | Q1:"那另外一个特性是啥意思.. 有什么用麽?" 54 | 55 | R1:"目前只发现这个和SSR相关,和我们普通使用无关,欸... SSR那块我也不是很清楚了" -------------------------------------------------------------------------------- /_book/book/Packages/Reactivity/镜.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/book/Packages/Reactivity/镜.png -------------------------------------------------------------------------------- /_book/book/Packages/Reactivity/问题集合.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/book/Packages/Reactivity/问题集合.md -------------------------------------------------------------------------------- /_book/book/Packages/Runtime/OPTION-API.md: -------------------------------------------------------------------------------- 1 | # OptionsAPI 2 | 3 | vue2.x相关,与vue3的关系仅有component.template使用compile转换成component.render。 4 | 5 | 6 | 7 | ## methods 8 | 9 | -------------------------------------------------------------------------------- /_book/book/Packages/Runtime/index.md: -------------------------------------------------------------------------------- 1 | # 测试用例 2 | ```typescript 3 | it('should allow attrs to fallthrough', async () => { 4 | debugger 5 | const click = jest.fn() 6 | const childUpdated = jest.fn() 7 | 8 | const Hello = { 9 | setup() { // 如果有render 就优先渲染render了 10 | const count = ref(0) 11 | 12 | function inc() { // 需要了解onClick事件系统 13 | count.value++ 14 | click() 15 | } 16 | 17 | // 这里的任何东西 都不会被本身effect收集 只有return 后的方法 才会 18 | 19 | return () => // 这里还可以当redner来使用 我平时会的只是 return {} template所需要的事件或属性 所以是检查function 还是 object来判断的吧 20 | h(Child, { // 那是不是有一个props effect? 标记也要注意下 21 | foo: count.value + 1, 22 | id: 'test', 23 | class: 'c' + count.value, 24 | style: { color: count.value ? 'red' : 'green' }, 25 | onClick: inc, 26 | 'data-id': count.value + 1 27 | }) 28 | }, 29 | mounted() { 30 | console.log('?') 31 | } 32 | } 33 | 34 | const Child = { // 原来是这样传参数的 那么就不需要this 什么的了 35 | setup(props: any) { 36 | onUpdated(childUpdated) 37 | return () => 38 | h( 39 | 'div', 40 | { 41 | class: 'c2', 42 | style: { fontWeight: 'bold' } 43 | }, 44 | props.foo // 这个为什么是undefinded呢 因为setFullProps执行的时候,判断到的赋值Props方式为attrs,所以instance.props为空 45 | ) 46 | } 47 | } 48 | 49 | const root = document.createElement('div') 50 | document.body.appendChild(root) 51 | render(h(Hello), root) // 这个render 是'@vue/runtime-dom' 我们之前用的 是'@vue/runtime-test' 里面的 测试用的... 但是区别不一样的就是 会不会初始化而已 52 | 53 | const node = root.children[0] as HTMLElement 54 | 55 | expect(node.getAttribute('id')).toBe('test') 56 | expect(node.getAttribute('foo')).toBe('1') 57 | expect(node.getAttribute('class')).toBe('c2 c0') 58 | expect(node.style.color).toBe('green') 59 | expect(node.style.fontWeight).toBe('bold') 60 | expect(node.dataset.id).toBe('1') 61 | 62 | node.dispatchEvent(new CustomEvent('click')) // 事件触发a 63 | node.dispatchEvent(new CustomEvent('click')) // 事件触发a 64 | expect(click).toHaveBeenCalled() 65 | 66 | await nextTick() 67 | expect(childUpdated).toHaveBeenCalled() 68 | expect(node.getAttribute('id')).toBe('test') 69 | expect(node.getAttribute('foo')).toBe('2') 70 | expect(node.getAttribute('class')).toBe('c2 c1') 71 | expect(node.style.color).toBe('red') 72 | expect(node.style.fontWeight).toBe('bold') 73 | expect(node.dataset.id).toBe('2') 74 | }) 75 | ``` 76 | 77 | ## processComponent流程图: 78 | https://www.processon.com/view/link/5f85c9321e085307a0892f7e 79 | 80 |  81 | 82 | 83 | ## vnode: 84 | https://www.processon.com/view/link/5f963f37f346fb06e1ec35b2 85 |  86 | 87 | 88 | ## instance: 89 | https://www.processon.com/view/link/5f963f5fe401fd06fda22681 90 |  91 | -------------------------------------------------------------------------------- /_book/book/Packages/Runtime/question.md: -------------------------------------------------------------------------------- 1 | # 一些问题 2 | #### 1.当连续触发响应式的trigger会导致组件的effectComponent连续执行更新吗? 3 | 并不会。第一次触发trigger的时候会把事件队列状态改成Pending,把flushJobs丢进microtask来执行,第二次触发trigger会检测到当前状态如果是Pending或者是Flushing状态的时候将不进行下去。 4 | 可在packages/runtime-core/\_\_tests\_\_/rendererAttrsFallthrough.spec.ts 'should allow attrs to fallthrough'测试用例中调试。 5 | 6 |  7 | 8 | #### 2.patch到底有多少种类型?分别是干什么的? 9 | 在patch中查看有processText、processCommentNode、mountStaticNode、patchStaticNode、processFragment、processElement(流程图已有)、processComponent(流程图已有)、Teleport、SUSPENSE。 10 | 11 | 12 | #### 3.instance.props、instance.attrs和vnode.props分别是什么?关系是什么? 13 | 先说一下设置的阶段,和如何设置的: 14 | 1.instance.props和instance.attrs是在第二步骤中setupComponent中的initProps实现的,是根据vnode.props来设置的,instance和组件类型相关。 15 | 2.vnode.props创建vnode的时候传入的参数,比如```h(Child, { foo: 1, class: 'parent' })```。 16 | 17 | 使用阶段,拿这个做例子: 18 | ```typescript 19 | const Hello = { 20 | setup() { 21 | const count = ref(0) 22 | 23 | function inc() { 24 | count.value++ 25 | } 26 | 27 | return () => 28 | h(Child, { 29 | foo: count.value + 1, 30 | id: 'test', 31 | class: 'c' + count.value, 32 | style: { color: count.value ? 'red' : 'green' }, 33 | onClick: inc, 34 | 'data-id': count.value + 1 35 | }) 36 | } 37 | } 38 | 39 | const Child = { 40 | setup(props: any) { 41 | return () => 42 | h( 43 | 'div', 44 | { 45 | class: 'c2', 46 | style: { fontWeight: 'bold' } 47 | }, 48 | props.foo 49 | ) 50 | } 51 | } 52 | 53 | const root = document.createElement('div') 54 | document.body.appendChild(root) 55 | render(h(Hello), root) 56 | 57 | ``` 58 | 59 | 步骤一:第一次渲染,流程图②setupComponent的initProps,使用Hello组件的vnode.props(当前为空,所以输出的attrs都是为空的),标准化生成instance.attrs和instance.props 60 | ```typescript 61 | if (!instance.type.props) { 62 | // functional w/ optional props, props === attrs 63 | instance.props = attrs 64 | } else { 65 | // functional w/ declared props 66 | instance.props = props 67 | } 68 | ``` 69 | 70 | 步骤二:②setupConponent中,renderComponentRoot(instance)会把instance.attrs与调用instance.render(Hello.setup返回的方法)所返回的vnode的props进行合并。 71 | 72 | 步骤三:子组件进行patch,同样经过initProps处理, 73 | 在②中调用setup方法,并传入instance.props, instance.setupConetxt,这个时候子组件就会利用到父组件传入的props了。 74 | 75 | _**那更新的时候,是怎么样的?**_ 76 | 77 | 触发父组件instance,renderComponentRoot(instance)中调用instance.render(这个过程就是vnode.props的更新了) 78 | 再次进行当前track追踪(effect运行的特性,是会删除当前追踪),patch(旧vnode1, 新vnode2),processComponent updateComponent, 79 | 新vnode2引用vnode1.component(instance2,子组件instance2),删除queue任务中的instance2.update,instance2.next = n2,执行instance2.update, 80 | 检测到有next字段,执行updateComponentPreRender,这里instance2是为了标识是子instance。 81 | 82 | updateComponentPreRender: 83 | ```typescript 84 | nextVNode.component = instance2 // 对当前没用,因为已经引用了 85 | const prevProps = instance2.vnode.props 86 | instance2.vnode = nextVNode // 更新vnode 87 | instance2.next = null // 删除next 88 | updateProps(instance2, nextVNode.props, prevProps, optimized) 89 | updateSlots(instance2, nextVNode.children) 90 | ``` 91 | updateProps会利用setFullProps来更新attrs和props,这里的更新过的props和attrs可以提供給Child内部使用,如当前使用了props.foo。 92 | -------------------------------------------------------------------------------- /_book/book/Packages/Runtime/类型标记.md: -------------------------------------------------------------------------------- 1 | # 类型标记 2 | 3 | ```typescript 4 | export declare const enum ShapeFlags { 5 | ELEMENT = 1, // 1 6 | FUNCTIONAL_COMPONENT = 2, // 10 7 | STATEFUL_COMPONENT = 4, // 100 8 | TEXT_CHILDREN = 8, // 1000 9 | ARRAY_CHILDREN = 16, // 10000 10 | SLOTS_CHILDREN = 32, // 100000 11 | TELEPORT = 64, // 1000000 12 | SUSPENSE = 128, // 10000000 13 | COMPONENT_SHOULD_KEEP_ALIVE = 256, // 100000000 14 | COMPONENT_KEPT_ALIVE = 512, // 1000000000 15 | COMPONENT = 6 // 110 16 | } 17 | ``` 18 | 19 | 20 | 按位与: 21 | 22 | ```typescript 23 | 01 | 01 === 01 24 | 01 | 10 === 11 25 | 10 | 10 === 10 26 | ``` 27 | 28 | 29 | 30 | 判断类型的按位且: 31 | 32 | ```typescript 33 | 11 & 11 === 11 34 | 01 & 10 === 00 35 | 10 & 10 === 10 36 | ``` 37 | 38 | 对应位置1 & 1 === 1, 1 | 0 === 0,就像&&的逻辑,要满足所有为真值。 39 | 40 | 41 | 42 | 比如100代表STATEFUL_COMPONENT类型,10000代表ARRAY_CHILDREN类型,合并使用 43 | 44 | ```typescript 45 | const initial = 100 | 10000 === 10100 46 | ``` 47 | 48 | 49 | 50 | 要判断10100是否包含STATEFUL_COMPONENT类型: 51 | 52 | ```typescript 53 | const result = initial & ShapeFlags.STATEFUL_COMPONENT 54 | // result === 100 55 | if (result > 0) { 56 | console.log('包含STATEFUL_COMPONENT类型') 57 | } 58 | ``` 59 | 60 | 61 | 62 | 利用标记的方式,可以轻松合并类型和检测类型,对代码的可阅读性强。 -------------------------------------------------------------------------------- /_book/book/Packages/compile-core/compile-core.md: -------------------------------------------------------------------------------- 1 | ## 关于compile 2 | 3 | 在带有编译版本的vue中,finishComponentSetup会对没有render方法,但是有template的Component,做编译处理。 4 | 5 | 这里推荐一个compile在线转换成render的网站: 6 | 7 | https://vue-next-template-explorer.netlify.app/ 8 | 9 | 10 | 11 | finishComponentSetup的代码片段: 12 | 13 | ```typescript 14 | Component.render = compile(Component.template, { 15 | isCustomElement: instance.appContext.config.isCustomElement || NO 16 | }) 17 | ``` 18 | 19 | 20 | 21 | 去查看一下compile的CompilerOptions相关配置。 22 | 23 | ```typescript 24 | type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions 25 | 26 | // ParserOptions: 27 | export interface ParserOptions { 28 | // 平台上本地元素, 例如4 | 8 |5 | 6 | 7 | 是网页上的本地标签 29 | isNativeTag?: (tag: string) => boolean 30 | 31 | // 不需要闭合的原生元素, 例如,
,
32 | isVoidTag?: (tag: string) => boolean 33 | 34 | // 应该保留内部空白的元素, 例如35 | // https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/pre 36 | isPreTag?: (tag: string) => boolean 37 | 38 | // 特定于平台的内置组件,例如39 | isBuiltInComponent?: (tag: string) => symbol | void 40 | 41 | // 自定义于平台的本地元素 42 | isCustomElement?: (tag: string) => boolean 43 | 44 | // 获取标签的名称 45 | getNamespace?: (tag: string, parent: ElementNode | undefined) => Namespace 46 | 47 | // 获取此元素的文本分析模式 48 | getTextMode?: ( 49 | node: ElementNode, 50 | parent: ElementNode | undefined 51 | ) => TextModes 52 | 53 | // 默认 ['{{', '}}'],可修改为你喜欢的 54 | delimiters?: [string, string] 55 | 56 | // Only needed for DOM compilers,解码实体 57 | decodeEntities?: (rawText: string, asAttr: boolean) => string 58 | 59 | // 错误 60 | onError?: (error: CompilerError) => void 61 | } 62 | 63 | 64 | // TransformOptions: 65 | export interface TransformOptions { 66 | /** 67 | * An array of node trasnforms to be applied to every AST node. 68 | */ 69 | nodeTransforms?: NodeTransform[] 70 | /** 71 | * An object of { name: transform } to be applied to every directive attribute 72 | * node found on element nodes. 73 | */ 74 | directiveTransforms?: Record 75 | /** 76 | * An optional hook to transform a node being hoisted. 77 | * used by compiler-dom to turn hoisted nodes into stringified HTML vnodes. 78 | * @default null 79 | */ 80 | transformHoist?: HoistTransform | null 81 | 82 | // 如果提供了其他的内置元素,可以使用该选项标记为内置的,这样编译器将为这些标签生成组件vnode 83 | isBuiltInComponent?: (tag: string) => symbol | void 84 | /** 85 | * 把表达式 {{ foo }} 转换成 `_ctx.foo` 86 | * 如果该选项为false, the generated code will be wrapped in a 87 | * `with (this) { ... }` block. 88 | * - This is force-enabled in module mode, since modules are by default strict 89 | * and cannot use `with` 90 | * @default mode === 'module' 91 | */ 92 | prefixIdentifiers?: boolean 93 | 94 | 95 | // 静态提升到 `_hoisted_x` 变量 96 | // 默认值 false 97 | hoistStatic?: boolean 98 | 99 | // 开启前 { onClick: _ctx.foo } 100 | // 开启后 { 101 | // onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.foo(...args))) 102 | // } 103 | // _cache[index] index根据当前缓存事件数量,保证预留一个空的位置給该事件 104 | // 默认值 false 105 | cacheHandlers?: boolean 106 | 107 | // `@babel/parser` 中的插件, 108 | // https://babeljs.io/docs/en/next/babel-parser#plugins 109 | expressionPlugins?: ParserPlugin[] 110 | 111 | // Single File Component组件中scoped styles ID 112 | scopeId?: string | null 113 | /** 114 | * Generate SSR-optimized render functions instead. 115 | * The resulting funciton must be attached to the component via the 116 | * `ssrRender` option instead of `render`. 117 | */ 118 | ssr?: boolean 119 | onError?: (error: CompilerError) => void 120 | } 121 | 122 | // CodegenOptions 123 | ``` 124 | 125 | 例子: 126 | ```html 127 | 128 | \{\{ world.burn() \}\} 129 |133 | ``` 134 | 135 | 转换成render: 136 | ```typescript 137 | const _Vue = Vue 138 | 139 | return function render(_ctx, _cache) { 140 | with (_ctx) { 141 | const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList } = _Vue 142 | 143 | return (_openBlock(), _createBlock("div", { 144 | id: "foo", 145 | class: bar.baz 146 | }, [ 147 | _createTextVNode(_toDisplayString(world.burn()) + " ", 1 /* TEXT */), 148 | ok 149 | ? (_openBlock(), _createBlock("div", { key: 0 }, "yes")) 150 | : (_openBlock(), _createBlock(_Fragment, { key: 1 }, [ 151 | _createTextVNode("no") 152 | ], 64 /* STABLE_FRAGMENT */)), 153 | (_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (value, index) => { 154 | return (_openBlock(), _createBlock("div", null, [ 155 | _createVNode("span", null, _toDisplayString(value + index), 1 /* TEXT */) 156 | ])) 157 | }), 256 /* UNKEYED_FRAGMENT */)) 158 | ], 2 /* CLASS */)) 159 | } 160 | } 161 | ``` 162 | 163 | -------------------------------------------------------------------------------- /_book/book/Packages/compile-core/为什么在slot中事件没有更新.md: -------------------------------------------------------------------------------- 1 | # 为什么使用slot,事件没有更新? 2 | 3 | 代码: 4 | 5 | ```html 6 | 7 | 8 | 9 | 10 | 11 | {{ record }} 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |yes130 | no 131 |{{ value + index }}132 |25 |27 | 28 | ``` 29 | 30 | 31 | 32 | 使用compile转换后的代码: 33 | 34 | ```typescript 35 | import { createVNode as _createVNode, toDisplayString as _toDisplayString, resolveComponent as _resolveComponent, withCtx as _withCtx, createTextVNode as _createTextVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from "vue" 36 | 37 | export function render(_ctx, _cache, $props, $setup, $data, $options) { 38 | const _component_B = _resolveComponent("B") 39 | const _component_A = _resolveComponent("A") 40 | 41 | return (_openBlock(), _createBlock(_Fragment, null, [ 42 | _createVNode("button", { 43 | onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.changeText(...args))) 44 | }, "change test"), 45 | _createVNode(_component_A, { 46 | name: _ctx.test, 47 | ref: "A" 48 | }, { 49 | default: _withCtx(({ record }) => [ 50 | _createTextVNode(_toDisplayString(record) + " ", 1 /* TEXT */), 51 | _createVNode(_component_B, { ref: "B" }, { 52 | over: _withCtx(() => [ 53 | _createVNode("button", { 54 | onClick: $event => (_ctx.handleClick(record)) 55 | }, "click me 啊2 ", 8 /* PROPS */, ["onClick"]) 56 | ]), 57 | _: 1 58 | }, 512 /* NEED_PATCH */) 59 | ]), 60 | _: 1 61 | }, 8 /* PROPS */, ["name"]) 62 | ], 64 /* STABLE_FRAGMENT */)) 63 | } 64 | ``` 65 | 66 | A: 67 | 68 | ```typescript 69 | const { renderSlot: _renderSlot, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue 70 | 71 | function render(_ctx, _cache) { 72 | return (_openBlock(), _createBlock("div", null, [ 73 | _renderSlot(_ctx.$slots, "default", { record: _ctx.name }) // { record: _ctx.name } 传入 default 方法中 74 | // createBlock(Fragment, { key: props.key /* 这里参数没有key */ }, default(props), PatchFlags.STABLE_FRAGMENT) 75 | ])) 76 | } 77 | ``` 78 | 79 | 80 | 81 | 点击handle为什么传入的东西还是旧的值?为什么不会更新? 82 | 83 | 84 | 85 | 我现在是假设你已经熟悉首次渲染。 86 | 87 | 我们在更新的时候,在组件A的update中,生成了vnode,该vnode,在patch第一个元素的时候发现record那段textVnode有变化,故更新该元素。 88 | 89 | 第二个元素是组件B,更新组件B前会使用shouldUpdateComponent方法: 90 | 91 | ```typescript 92 | function shouldUpdateComponent(prevVNode, nextVNode, optimized) { 93 | const { props: prevProps, children: prevChildren } = prevVNode; 94 | const { props: nextProps, children: nextChildren, patchFlag } = nextVNode; 95 | // Parent component's render function was hot-updated. Since this may have 96 | // caused the child component's slots content to have changed, we need to 97 | // force the child to update as well. 98 | if ((process.env.NODE_ENV !== 'production') && (prevChildren || nextChildren) && isHmrUpdating) { 99 | return true; 100 | } 101 | // force child update for runtime directive or transition on component vnode. 102 | if (nextVNode.dirs || nextVNode.transition) { 103 | return true; 104 | } 105 | if (patchFlag > 0) { 106 | if (patchFlag & 1024 /* DYNAMIC_SLOTS */) { 107 | // slot content that references values that might have changed, 108 | // e.g. in a v-for 109 | return true; 110 | } 111 | if (patchFlag & 16 /* FULL_PROPS */) { 112 | if (!prevProps) { 113 | return !!nextProps; 114 | } 115 | // presence of this flag indicates props are always non-null 116 | return hasPropsChanged(prevProps, nextProps); 117 | } 118 | else if (patchFlag & 8 /* PROPS */) { 119 | const dynamicProps = nextVNode.dynamicProps; 120 | for (let i = 0; i < dynamicProps.length; i++) { 121 | const key = dynamicProps[i]; 122 | if (nextProps[key] !== prevProps[key]) { 123 | return true; 124 | } 125 | } 126 | } 127 | } 128 | else if (!optimized) { 129 | // this path is only taken by manually written render functions 130 | // so presence of any children leads to a forced update 131 | if (prevChildren || nextChildren) { 132 | if (!nextChildren || !nextChildren.$stable) { 133 | return true; 134 | } 135 | } 136 | if (prevProps === nextProps) { 137 | return false; 138 | } 139 | if (!prevProps) { 140 | return !!nextProps; 141 | } 142 | if (!nextProps) { 143 | return true; 144 | } 145 | return hasPropsChanged(prevProps, nextProps); 146 | } 147 | return false; 148 | } 149 | ``` 150 | 151 | 这个东西是判断你的props和children有没有改变,没有则返回false,所以B组件不会更新。 152 | 153 | 154 | 155 | 那么,你是不是想问,record是指引属性,为什么还是旧的?你可以去认真看看initSlot和renderSlot,是调用为Object的子组件来实行的,旧的record就是在首次渲染的时候,调用defalut中的初始值,应用的地方也就是首次的环境。 156 | 157 | 而新的record根本就没有被更新到组件B上。 158 | 159 | 160 | 161 | **那为什么我直接调用组件B的update方法,还是不更新呢?** 162 | 163 | 这边建议你再去熟悉一下渲染流程,英文组件B的vnode依旧没有发生变化,需要的是组件A创建新的组件B的vnode才能达到你的效果。 164 | 165 | 166 | 167 | ### 解决方法: 168 | 169 | 为了使组件B更新,可以随便绑定一个参数給B,比如 170 | 171 | ```html 172 | 173 | ``` 174 | 175 | 或者 176 | 177 | ```html 178 | 179 | {{ record }} 180 | 181 | ``` 182 | 183 | 184 | 185 | ### 关于修复 186 | 187 | ##### 原始问题:https://github.com/vuejs/vue-next/issues/2618 188 | 189 | **修复方法**:https://github.com/vuejs/vue-next/pull/2568/files -------------------------------------------------------------------------------- /_book/book/Packages/compile-core/问题集合.md: -------------------------------------------------------------------------------- 1 | # 问题集合 2 | 3 | ## proxy与withProxy的区别 4 | 5 | proxy: 6 | 7 | ```typescript 8 | instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers) 9 | ``` 10 | 11 | withProxy: 12 | 13 | ```typ 14 | instance.withProxy = new Proxy( 15 | instance.ctx, 16 | RuntimeCompiledPublicInstanceProxyHandlers 17 | ) 18 | ``` 19 | 20 | 在调用render的时候,proxy会作为他的第一个参数,如果开启render._rc = true,则会设置withProxy,调用render优先使用withProxy作为第一个参数。 21 | 22 | ```typescript 23 | // 继承PublicInstanceProxyHandlers 但是重写get和has 24 | withProxy = extend({}, PublicInstanceProxyHandlers, { 25 | get(){ 26 | // fast path for unscopables when using `with` block 27 | if ((key as any) === Symbol.unscopables) { 28 | return 29 | } 30 | return PublicInstanceProxyHandlers.get!(target, key, target) 31 | }, 32 | has() 33 | }) 34 | ``` 35 | 36 | 37 | 38 | ### get: 39 | 40 | **RuntimeCompiledPublicInstanceProxyHandlers**判断key是否等于**Symbol.unscopables**,如果相等则不做处理,直接return,不相等则和**PublicInstanceProxyHandlers.get**行为一致。**目的是为了跳过get的一系列查询,优化代码性能。** 41 | 42 | 43 | 44 | ### has: 45 | 46 | has方法用来拦截hasProperty操作,即判断对象是否具有某个属性时。**handler.has()** 方法是针对 in 操作符的代理方法。 47 | 48 | #### **PublicInstanceProxyHandlers**.has: 49 | 50 | 查找的顺序为(都为instance的字段): 51 | 52 | 1.accessCache缓存中查找,这个缓存为key对应的类型,缓存起来可以快速在类型中查找 53 | 54 | 2.data中查找 55 | 56 | 3.setupState中查找 57 | 58 | 4.type.props中查找 59 | 60 | 5.ctx中查找 61 | 62 | 6.**publicPropertiesMap**,非instance字段,$data $el $props $attrs等 63 | 64 | 7.**appContext.config.globalProperties**,有且仅有一个的对象,所有有关系的组件都共同指向一个。 65 | 66 | publicPropertiesMap: 67 | 68 | ```typescript 69 | const publicPropertiesMap: Record< 70 | string, 71 | (i: ComponentInternalInstance) => any 72 | > = { 73 | $: i => i, 74 | $el: i => i.vnode.el, 75 | $data: i => i.data, 76 | $props: i => (__DEV__ ? shallowReadonly(i.props) : i.props), 77 | $attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs), 78 | $slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots), 79 | $refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs), 80 | $parent: i => i.parent && i.parent.proxy, 81 | $root: i => i.root && i.root.proxy, 82 | $emit: i => i.emit, 83 | $options: i => (__FEATURE_OPTIONS__ ? resolveMergedOptions(i) : i.type), 84 | $forceUpdate: i => () => queueJob(i.update), 85 | $nextTick: () => nextTick, 86 | $watch: __FEATURE_OPTIONS__ ? i => instanceWatch.bind(i) : NOOP 87 | } 88 | ``` 89 | 90 | 91 | 92 | #### RuntimeCompiledPublicInstanceProxyHandlers.has: 93 | 94 | 判断查询的key,不能为'_'开头,且key不能在全局白名单中。 95 | 96 | ```typescript 97 | has(_: ComponentRenderContext, key: string) { 98 | const has = key[0] !== '_' && !isGloballyWhitelisted(key) 99 | if (__DEV__ && !has && PublicInstanceProxyHandlers.has!(_, key)) { 100 | warn( 101 | `Property ${JSON.stringify( 102 | key 103 | )} should not start with _ which is a reserved prefix for Vue internals.` 104 | ) 105 | } 106 | return has 107 | } 108 | ``` 109 | 110 | ```typescript 111 | const GLOBALS_WHITE_LISTED = 112 | 'Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,' + 113 | 'decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,' + 114 | 'Object,Boolean,String,RegExp,Map,Set,JSON,Intl' 115 | 116 | export const isGloballyWhitelisted = /*#__PURE__*/ makeMap(GLOBALS_WHITE_LISTED) 117 | ``` 118 | 119 | 120 | 121 | ### 总结 122 | 123 | **RuntimeCompiledPublicInstanceProxyHandlers**继承**publicInstanceProxyHandlers**,前者get方法过滤key === **Symbol.unscopables**,has方法过滤基本类型和'_'开头的key。 -------------------------------------------------------------------------------- /_book/book/gitbook/fonts/fontawesome/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/book/gitbook/fonts/fontawesome/FontAwesome.otf -------------------------------------------------------------------------------- /_book/book/gitbook/fonts/fontawesome/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/book/gitbook/fonts/fontawesome/fontawesome-webfont.eot -------------------------------------------------------------------------------- /_book/book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /_book/book/gitbook/fonts/fontawesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/book/gitbook/fonts/fontawesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /_book/book/gitbook/fonts/fontawesome/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/book/gitbook/fonts/fontawesome/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /_book/book/gitbook/gitbook-plugin-highlight/ebook.css: -------------------------------------------------------------------------------- 1 | pre, 2 | code { 3 | /* http://jmblog.github.io/color-themes-for-highlightjs */ 4 | /* Tomorrow Comment */ 5 | /* Tomorrow Red */ 6 | /* Tomorrow Orange */ 7 | /* Tomorrow Yellow */ 8 | /* Tomorrow Green */ 9 | /* Tomorrow Aqua */ 10 | /* Tomorrow Blue */ 11 | /* Tomorrow Purple */ 12 | } 13 | pre .hljs-comment, 14 | code .hljs-comment, 15 | pre .hljs-title, 16 | code .hljs-title { 17 | color: #8e908c; 18 | } 19 | pre .hljs-variable, 20 | code .hljs-variable, 21 | pre .hljs-attribute, 22 | code .hljs-attribute, 23 | pre .hljs-tag, 24 | code .hljs-tag, 25 | pre .hljs-regexp, 26 | code .hljs-regexp, 27 | pre .hljs-deletion, 28 | code .hljs-deletion, 29 | pre .ruby .hljs-constant, 30 | code .ruby .hljs-constant, 31 | pre .xml .hljs-tag .hljs-title, 32 | code .xml .hljs-tag .hljs-title, 33 | pre .xml .hljs-pi, 34 | code .xml .hljs-pi, 35 | pre .xml .hljs-doctype, 36 | code .xml .hljs-doctype, 37 | pre .html .hljs-doctype, 38 | code .html .hljs-doctype, 39 | pre .css .hljs-id, 40 | code .css .hljs-id, 41 | pre .css .hljs-class, 42 | code .css .hljs-class, 43 | pre .css .hljs-pseudo, 44 | code .css .hljs-pseudo { 45 | color: #c82829; 46 | } 47 | pre .hljs-number, 48 | code .hljs-number, 49 | pre .hljs-preprocessor, 50 | code .hljs-preprocessor, 51 | pre .hljs-pragma, 52 | code .hljs-pragma, 53 | pre .hljs-built_in, 54 | code .hljs-built_in, 55 | pre .hljs-literal, 56 | code .hljs-literal, 57 | pre .hljs-params, 58 | code .hljs-params, 59 | pre .hljs-constant, 60 | code .hljs-constant { 61 | color: #f5871f; 62 | } 63 | pre .ruby .hljs-class .hljs-title, 64 | code .ruby .hljs-class .hljs-title, 65 | pre .css .hljs-rules .hljs-attribute, 66 | code .css .hljs-rules .hljs-attribute { 67 | color: #eab700; 68 | } 69 | pre .hljs-string, 70 | code .hljs-string, 71 | pre .hljs-value, 72 | code .hljs-value, 73 | pre .hljs-inheritance, 74 | code .hljs-inheritance, 75 | pre .hljs-header, 76 | code .hljs-header, 77 | pre .hljs-addition, 78 | code .hljs-addition, 79 | pre .ruby .hljs-symbol, 80 | code .ruby .hljs-symbol, 81 | pre .xml .hljs-cdata, 82 | code .xml .hljs-cdata { 83 | color: #718c00; 84 | } 85 | pre .css .hljs-hexcolor, 86 | code .css .hljs-hexcolor { 87 | color: #3e999f; 88 | } 89 | pre .hljs-function, 90 | code .hljs-function, 91 | pre .python .hljs-decorator, 92 | code .python .hljs-decorator, 93 | pre .python .hljs-title, 94 | code .python .hljs-title, 95 | pre .ruby .hljs-function .hljs-title, 96 | code .ruby .hljs-function .hljs-title, 97 | pre .ruby .hljs-title .hljs-keyword, 98 | code .ruby .hljs-title .hljs-keyword, 99 | pre .perl .hljs-sub, 100 | code .perl .hljs-sub, 101 | pre .javascript .hljs-title, 102 | code .javascript .hljs-title, 103 | pre .coffeescript .hljs-title, 104 | code .coffeescript .hljs-title { 105 | color: #4271ae; 106 | } 107 | pre .hljs-keyword, 108 | code .hljs-keyword, 109 | pre .javascript .hljs-function, 110 | code .javascript .hljs-function { 111 | color: #8959a8; 112 | } 113 | pre .hljs, 114 | code .hljs { 115 | display: block; 116 | background: white; 117 | color: #4d4d4c; 118 | padding: 0.5em; 119 | } 120 | pre .coffeescript .javascript, 121 | code .coffeescript .javascript, 122 | pre .javascript .xml, 123 | code .javascript .xml, 124 | pre .tex .hljs-formula, 125 | code .tex .hljs-formula, 126 | pre .xml .javascript, 127 | code .xml .javascript, 128 | pre .xml .vbscript, 129 | code .xml .vbscript, 130 | pre .xml .css, 131 | code .xml .css, 132 | pre .xml .hljs-cdata, 133 | code .xml .hljs-cdata { 134 | opacity: 0.5; 135 | } 136 | -------------------------------------------------------------------------------- /_book/book/gitbook/gitbook-plugin-lunr/search-lunr.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | // Define global search engine 6 | function LunrSearchEngine() { 7 | this.index = null; 8 | this.store = {}; 9 | this.name = 'LunrSearchEngine'; 10 | } 11 | 12 | // Initialize lunr by fetching the search index 13 | LunrSearchEngine.prototype.init = function() { 14 | var that = this; 15 | var d = $.Deferred(); 16 | 17 | $.getJSON(gitbook.state.basePath+'/search_index.json') 18 | .then(function(data) { 19 | // eslint-disable-next-line no-undef 20 | that.index = lunr.Index.load(data.index); 21 | that.store = data.store; 22 | d.resolve(); 23 | }); 24 | 25 | return d.promise(); 26 | }; 27 | 28 | // Search for a term and return results 29 | LunrSearchEngine.prototype.search = function(q, offset, length) { 30 | var that = this; 31 | var results = []; 32 | 33 | if (this.index) { 34 | results = $.map(this.index.search(q), function(result) { 35 | var doc = that.store[result.ref]; 36 | 37 | return { 38 | title: doc.title, 39 | url: doc.url, 40 | body: doc.summary || doc.body 41 | }; 42 | }); 43 | } 44 | 45 | return $.Deferred().resolve({ 46 | query: q, 47 | results: results.slice(0, length), 48 | count: results.length 49 | }).promise(); 50 | }; 51 | 52 | // Set gitbook research 53 | gitbook.events.bind('start', function(e, config) { 54 | var engine = gitbook.search.getEngine(); 55 | if (!engine) { 56 | gitbook.search.setEngine(LunrSearchEngine, config); 57 | } 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /_book/book/gitbook/gitbook-plugin-search/search-engine.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | // Global search objects 6 | var engine = null; 7 | var initialized = false; 8 | 9 | // Set a new search engine 10 | function setEngine(Engine, config) { 11 | initialized = false; 12 | engine = new Engine(config); 13 | 14 | init(config); 15 | } 16 | 17 | // Initialize search engine with config 18 | function init(config) { 19 | if (!engine) throw new Error('No engine set for research. Set an engine using gitbook.research.setEngine(Engine).'); 20 | 21 | return engine.init(config) 22 | .then(function() { 23 | initialized = true; 24 | gitbook.events.trigger('search.ready'); 25 | }); 26 | } 27 | 28 | // Launch search for query q 29 | function query(q, offset, length) { 30 | if (!initialized) throw new Error('Search has not been initialized'); 31 | return engine.search(q, offset, length); 32 | } 33 | 34 | // Get stats about search 35 | function getEngine() { 36 | return engine? engine.name : null; 37 | } 38 | 39 | function isInitialized() { 40 | return initialized; 41 | } 42 | 43 | // Initialize gitbook.search 44 | gitbook.search = { 45 | setEngine: setEngine, 46 | getEngine: getEngine, 47 | query: query, 48 | isInitialized: isInitialized 49 | }; 50 | }); -------------------------------------------------------------------------------- /_book/book/gitbook/gitbook-plugin-search/search.css: -------------------------------------------------------------------------------- 1 | /* 2 | This CSS only styled the search results section, not the search input 3 | It defines the basic interraction to hide content when displaying results, etc 4 | */ 5 | #book-search-results .search-results { 6 | display: none; 7 | } 8 | #book-search-results .search-results ul.search-results-list { 9 | list-style-type: none; 10 | padding-left: 0; 11 | } 12 | #book-search-results .search-results ul.search-results-list li { 13 | margin-bottom: 1.5rem; 14 | padding-bottom: 0.5rem; 15 | /* Highlight results */ 16 | } 17 | #book-search-results .search-results ul.search-results-list li p em { 18 | background-color: rgba(255, 220, 0, 0.4); 19 | font-style: normal; 20 | } 21 | #book-search-results .search-results .no-results { 22 | display: none; 23 | } 24 | #book-search-results.open .search-results { 25 | display: block; 26 | } 27 | #book-search-results.open .search-noresults { 28 | display: none; 29 | } 30 | #book-search-results.no-results .search-results .has-results { 31 | display: none; 32 | } 33 | #book-search-results.no-results .search-results .no-results { 34 | display: block; 35 | } 36 | -------------------------------------------------------------------------------- /_book/book/gitbook/gitbook-plugin-search/search.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | var MAX_RESULTS = 15; 6 | var MAX_DESCRIPTION_SIZE = 500; 7 | 8 | var usePushState = (typeof history.pushState !== 'undefined'); 9 | 10 | // DOM Elements 11 | var $body = $('body'); 12 | var $bookSearchResults; 13 | var $searchInput; 14 | var $searchList; 15 | var $searchTitle; 16 | var $searchResultsCount; 17 | var $searchQuery; 18 | 19 | // Throttle search 20 | function throttle(fn, wait) { 21 | var timeout; 22 | 23 | return function() { 24 | var ctx = this, args = arguments; 25 | if (!timeout) { 26 | timeout = setTimeout(function() { 27 | timeout = null; 28 | fn.apply(ctx, args); 29 | }, wait); 30 | } 31 | }; 32 | } 33 | 34 | function displayResults(res) { 35 | $bookSearchResults.addClass('open'); 36 | 37 | var noResults = res.count == 0; 38 | $bookSearchResults.toggleClass('no-results', noResults); 39 | 40 | // Clear old results 41 | $searchList.empty(); 42 | 43 | // Display title for research 44 | $searchResultsCount.text(res.count); 45 | $searchQuery.text(res.query); 46 | 47 | // Create an26 | element for each result 48 | res.results.forEach(function(res) { 49 | var $li = $(' ', { 50 | 'class': 'search-results-item' 51 | }); 52 | 53 | var $title = $(' '); 54 | 55 | var $link = $('', { 56 | 'href': gitbook.state.basePath + '/' + res.url, 57 | 'text': res.title 58 | }); 59 | 60 | var content = res.body.trim(); 61 | if (content.length > MAX_DESCRIPTION_SIZE) { 62 | content = content.slice(0, MAX_DESCRIPTION_SIZE).trim()+'...'; 63 | } 64 | var $content = $('
').html(content); 65 | 66 | $link.appendTo($title); 67 | $title.appendTo($li); 68 | $content.appendTo($li); 69 | $li.appendTo($searchList); 70 | }); 71 | } 72 | 73 | function launchSearch(q) { 74 | // Add class for loading 75 | $body.addClass('with-search'); 76 | $body.addClass('search-loading'); 77 | 78 | // Launch search query 79 | throttle(gitbook.search.query(q, 0, MAX_RESULTS) 80 | .then(function(results) { 81 | displayResults(results); 82 | }) 83 | .always(function() { 84 | $body.removeClass('search-loading'); 85 | }), 1000); 86 | } 87 | 88 | function closeSearch() { 89 | $body.removeClass('with-search'); 90 | $bookSearchResults.removeClass('open'); 91 | } 92 | 93 | function launchSearchFromQueryString() { 94 | var q = getParameterByName('q'); 95 | if (q && q.length > 0) { 96 | // Update search input 97 | $searchInput.val(q); 98 | 99 | // Launch search 100 | launchSearch(q); 101 | } 102 | } 103 | 104 | function bindSearch() { 105 | // Bind DOM 106 | $searchInput = $('#book-search-input input'); 107 | $bookSearchResults = $('#book-search-results'); 108 | $searchList = $bookSearchResults.find('.search-results-list'); 109 | $searchTitle = $bookSearchResults.find('.search-results-title'); 110 | $searchResultsCount = $searchTitle.find('.search-results-count'); 111 | $searchQuery = $searchTitle.find('.search-query'); 112 | 113 | // Launch query based on input content 114 | function handleUpdate() { 115 | var q = $searchInput.val(); 116 | 117 | if (q.length == 0) { 118 | closeSearch(); 119 | } 120 | else { 121 | launchSearch(q); 122 | } 123 | } 124 | 125 | // Detect true content change in search input 126 | // Workaround for IE < 9 127 | var propertyChangeUnbound = false; 128 | $searchInput.on('propertychange', function(e) { 129 | if (e.originalEvent.propertyName == 'value') { 130 | handleUpdate(); 131 | } 132 | }); 133 | 134 | // HTML5 (IE9 & others) 135 | $searchInput.on('input', function(e) { 136 | // Unbind propertychange event for IE9+ 137 | if (!propertyChangeUnbound) { 138 | $(this).unbind('propertychange'); 139 | propertyChangeUnbound = true; 140 | } 141 | 142 | handleUpdate(); 143 | }); 144 | 145 | // Push to history on blur 146 | $searchInput.on('blur', function(e) { 147 | // Update history state 148 | if (usePushState) { 149 | var uri = updateQueryString('q', $(this).val()); 150 | history.pushState({ path: uri }, null, uri); 151 | } 152 | }); 153 | } 154 | 155 | gitbook.events.on('page.change', function() { 156 | bindSearch(); 157 | closeSearch(); 158 | 159 | // Launch search based on query parameter 160 | if (gitbook.search.isInitialized()) { 161 | launchSearchFromQueryString(); 162 | } 163 | }); 164 | 165 | gitbook.events.on('search.ready', function() { 166 | bindSearch(); 167 | 168 | // Launch search from query param at start 169 | launchSearchFromQueryString(); 170 | }); 171 | 172 | function getParameterByName(name) { 173 | var url = window.location.href; 174 | name = name.replace(/[\[\]]/g, '\\$&'); 175 | var regex = new RegExp('[?&]' + name + '(=([^]*)|&|#|$)', 'i'), 176 | results = regex.exec(url); 177 | if (!results) return null; 178 | if (!results[2]) return ''; 179 | return decodeURIComponent(results[2].replace(/\+/g, ' ')); 180 | } 181 | 182 | function updateQueryString(key, value) { 183 | value = encodeURIComponent(value); 184 | 185 | var url = window.location.href; 186 | var re = new RegExp('([?&])' + key + '=.*?(&|#|$)(.*)', 'gi'), 187 | hash; 188 | 189 | if (re.test(url)) { 190 | if (typeof value !== 'undefined' && value !== null) 191 | return url.replace(re, '$1' + key + '=' + value + '$2$3'); 192 | else { 193 | hash = url.split('#'); 194 | url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, ''); 195 | if (typeof hash[1] !== 'undefined' && hash[1] !== null) 196 | url += '#' + hash[1]; 197 | return url; 198 | } 199 | } 200 | else { 201 | if (typeof value !== 'undefined' && value !== null) { 202 | var separator = url.indexOf('?') !== -1 ? '&' : '?'; 203 | hash = url.split('#'); 204 | url = hash[0] + separator + key + '=' + value; 205 | if (typeof hash[1] !== 'undefined' && hash[1] !== null) 206 | url += '#' + hash[1]; 207 | return url; 208 | } 209 | else 210 | return url; 211 | } 212 | } 213 | }); 214 | -------------------------------------------------------------------------------- /_book/book/gitbook/gitbook-plugin-sharing/buttons.js: -------------------------------------------------------------------------------- 1 | require(['gitbook', 'jquery'], function(gitbook, $) { 2 | var SITES = { 3 | 'facebook': { 4 | 'label': 'Facebook', 5 | 'icon': 'fa fa-facebook', 6 | 'onClick': function(e) { 7 | e.preventDefault(); 8 | window.open('http://www.facebook.com/sharer/sharer.php?s=100&p[url]='+encodeURIComponent(location.href)); 9 | } 10 | }, 11 | 'twitter': { 12 | 'label': 'Twitter', 13 | 'icon': 'fa fa-twitter', 14 | 'onClick': function(e) { 15 | e.preventDefault(); 16 | window.open('http://twitter.com/home?status='+encodeURIComponent(document.title+' '+location.href)); 17 | } 18 | }, 19 | 'google': { 20 | 'label': 'Google+', 21 | 'icon': 'fa fa-google-plus', 22 | 'onClick': function(e) { 23 | e.preventDefault(); 24 | window.open('https://plus.google.com/share?url='+encodeURIComponent(location.href)); 25 | } 26 | }, 27 | 'weibo': { 28 | 'label': 'Weibo', 29 | 'icon': 'fa fa-weibo', 30 | 'onClick': function(e) { 31 | e.preventDefault(); 32 | window.open('http://service.weibo.com/share/share.php?content=utf-8&url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)); 33 | } 34 | }, 35 | 'instapaper': { 36 | 'label': 'Instapaper', 37 | 'icon': 'fa fa-instapaper', 38 | 'onClick': function(e) { 39 | e.preventDefault(); 40 | window.open('http://www.instapaper.com/text?u='+encodeURIComponent(location.href)); 41 | } 42 | }, 43 | 'vk': { 44 | 'label': 'VK', 45 | 'icon': 'fa fa-vk', 46 | 'onClick': function(e) { 47 | e.preventDefault(); 48 | window.open('http://vkontakte.ru/share.php?url='+encodeURIComponent(location.href)); 49 | } 50 | } 51 | }; 52 | 53 | 54 | 55 | gitbook.events.bind('start', function(e, config) { 56 | var opts = config.sharing; 57 | 58 | // Create dropdown menu 59 | var menu = $.map(opts.all, function(id) { 60 | var site = SITES[id]; 61 | 62 | return { 63 | text: site.label, 64 | onClick: site.onClick 65 | }; 66 | }); 67 | 68 | // Create main button with dropdown 69 | if (menu.length > 0) { 70 | gitbook.toolbar.createButton({ 71 | icon: 'fa fa-share-alt', 72 | label: 'Share', 73 | position: 'right', 74 | dropdown: [menu] 75 | }); 76 | } 77 | 78 | // Direct actions to share 79 | $.each(SITES, function(sideId, site) { 80 | if (!opts[sideId]) return; 81 | 82 | gitbook.toolbar.createButton({ 83 | icon: site.icon, 84 | label: site.text, 85 | position: 'right', 86 | onClick: site.onClick 87 | }); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /_book/book/gitbook/images/apple-touch-icon-precomposed-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/book/gitbook/images/apple-touch-icon-precomposed-152.png -------------------------------------------------------------------------------- /_book/book/gitbook/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/book/gitbook/images/favicon.ico -------------------------------------------------------------------------------- /_book/book/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-book", 3 | "version": "1.0.0", 4 | "description": "vue3做了最大的变化就是api的细分,适配typescript。給我一种感觉就是,vue3像乐高,一个个拼接起来成模块, 模块之间的互相组合,来构成一个整体, 这样更利于团队开发了,可以根据团队情况来定制合适的开发架构。composition-api的出现,如果想真正利用好,弄懂vue3源码是必须的。 vue3中重要的包:Reactivity、runtime-x和 compiler-x。", 5 | "main": "index.js", 6 | "scripts": { 7 | "serve": "gitbook serve", 8 | "build": "gitbook build ./ ./book", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /_book/book/update.md: -------------------------------------------------------------------------------- 1 | # 今日更新 2 | 3 | **2020年12月13日22点39分** 4 | 5 | 补充了一下响应式的例子,因为群里有人问 6 | 7 | > “就拿监听来讲,它是怎么做到我们在里面丢进去的值里,哪个发生改变,哪个就进行输出的” 8 | 9 | > “我就丢进去了个事件,这个事件里有三行代码,每行代码都是与响应式有关,哇擦,它能做到某个值发生改变后,就将某个值相对应的console进行输出” 10 | 11 | > “我也能联想到这个关系到收集响应值及所依赖的,就是不太懂它是怎么做到上面那样功能的,是将传过去的函数转成字符串再进行文本解析出响应值吗” 12 | 13 |  -------------------------------------------------------------------------------- /_book/book/更新/2020年12月13日22-42.md: -------------------------------------------------------------------------------- 1 | # 今日更新 2 | 3 | **2020年12月13日22点39分** 4 | 5 | 补充了一下响应式的例子,因为群里有人问 6 | 7 | > “就拿监听来讲,它是怎么做到我们在里面丢进去的值里,哪个发生改变,哪个就进行输出的” 8 | 9 | > “我就丢进去了个事件,这个事件里有三行代码,每行代码都是与响应式有关,哇擦,它能做到某个值发生改变后,就将某个值相对应的console进行输出” 10 | 11 | > “我也能联想到这个关系到收集响应值及所依赖的,就是不太懂它是怎么做到上面那样功能的,是将传过去的函数转成字符串再进行文本解析出响应值吗” 12 | 13 |  -------------------------------------------------------------------------------- /_book/gitbook/fonts/fontawesome/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/gitbook/fonts/fontawesome/FontAwesome.otf -------------------------------------------------------------------------------- /_book/gitbook/fonts/fontawesome/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/gitbook/fonts/fontawesome/fontawesome-webfont.eot -------------------------------------------------------------------------------- /_book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-fontsettings/fontsettings.js: -------------------------------------------------------------------------------- 1 | require(['gitbook', 'jquery'], function(gitbook, $) { 2 | // Configuration 3 | var MAX_SIZE = 4, 4 | MIN_SIZE = 0, 5 | BUTTON_ID; 6 | 7 | // Current fontsettings state 8 | var fontState; 9 | 10 | // Default themes 11 | var THEMES = [ 12 | { 13 | config: 'white', 14 | text: 'White', 15 | id: 0 16 | }, 17 | { 18 | config: 'sepia', 19 | text: 'Sepia', 20 | id: 1 21 | }, 22 | { 23 | config: 'night', 24 | text: 'Night', 25 | id: 2 26 | } 27 | ]; 28 | 29 | // Default font families 30 | var FAMILIES = [ 31 | { 32 | config: 'serif', 33 | text: 'Serif', 34 | id: 0 35 | }, 36 | { 37 | config: 'sans', 38 | text: 'Sans', 39 | id: 1 40 | } 41 | ]; 42 | 43 | // Return configured themes 44 | function getThemes() { 45 | return THEMES; 46 | } 47 | 48 | // Modify configured themes 49 | function setThemes(themes) { 50 | THEMES = themes; 51 | updateButtons(); 52 | } 53 | 54 | // Return configured font families 55 | function getFamilies() { 56 | return FAMILIES; 57 | } 58 | 59 | // Modify configured font families 60 | function setFamilies(families) { 61 | FAMILIES = families; 62 | updateButtons(); 63 | } 64 | 65 | // Save current font settings 66 | function saveFontSettings() { 67 | gitbook.storage.set('fontState', fontState); 68 | update(); 69 | } 70 | 71 | // Increase font size 72 | function enlargeFontSize(e) { 73 | e.preventDefault(); 74 | if (fontState.size >= MAX_SIZE) return; 75 | 76 | fontState.size++; 77 | saveFontSettings(); 78 | } 79 | 80 | // Decrease font size 81 | function reduceFontSize(e) { 82 | e.preventDefault(); 83 | if (fontState.size <= MIN_SIZE) return; 84 | 85 | fontState.size--; 86 | saveFontSettings(); 87 | } 88 | 89 | // Change font family 90 | function changeFontFamily(configName, e) { 91 | if (e && e instanceof Event) { 92 | e.preventDefault(); 93 | } 94 | 95 | var familyId = getFontFamilyId(configName); 96 | fontState.family = familyId; 97 | saveFontSettings(); 98 | } 99 | 100 | // Change type of color theme 101 | function changeColorTheme(configName, e) { 102 | if (e && e instanceof Event) { 103 | e.preventDefault(); 104 | } 105 | 106 | var $book = gitbook.state.$book; 107 | 108 | // Remove currently applied color theme 109 | if (fontState.theme !== 0) 110 | $book.removeClass('color-theme-'+fontState.theme); 111 | 112 | // Set new color theme 113 | var themeId = getThemeId(configName); 114 | fontState.theme = themeId; 115 | if (fontState.theme !== 0) 116 | $book.addClass('color-theme-'+fontState.theme); 117 | 118 | saveFontSettings(); 119 | } 120 | 121 | // Return the correct id for a font-family config key 122 | // Default to first font-family 123 | function getFontFamilyId(configName) { 124 | // Search for plugin configured font family 125 | var configFamily = $.grep(FAMILIES, function(family) { 126 | return family.config == configName; 127 | })[0]; 128 | // Fallback to default font family 129 | return (!!configFamily)? configFamily.id : 0; 130 | } 131 | 132 | // Return the correct id for a theme config key 133 | // Default to first theme 134 | function getThemeId(configName) { 135 | // Search for plugin configured theme 136 | var configTheme = $.grep(THEMES, function(theme) { 137 | return theme.config == configName; 138 | })[0]; 139 | // Fallback to default theme 140 | return (!!configTheme)? configTheme.id : 0; 141 | } 142 | 143 | function update() { 144 | var $book = gitbook.state.$book; 145 | 146 | $('.font-settings .font-family-list li').removeClass('active'); 147 | $('.font-settings .font-family-list li:nth-child('+(fontState.family+1)+')').addClass('active'); 148 | 149 | $book[0].className = $book[0].className.replace(/\bfont-\S+/g, ''); 150 | $book.addClass('font-size-'+fontState.size); 151 | $book.addClass('font-family-'+fontState.family); 152 | 153 | if(fontState.theme !== 0) { 154 | $book[0].className = $book[0].className.replace(/\bcolor-theme-\S+/g, ''); 155 | $book.addClass('color-theme-'+fontState.theme); 156 | } 157 | } 158 | 159 | function init(config) { 160 | // Search for plugin configured font family 161 | var configFamily = getFontFamilyId(config.family), 162 | configTheme = getThemeId(config.theme); 163 | 164 | // Instantiate font state object 165 | fontState = gitbook.storage.get('fontState', { 166 | size: config.size || 2, 167 | family: configFamily, 168 | theme: configTheme 169 | }); 170 | 171 | update(); 172 | } 173 | 174 | function updateButtons() { 175 | // Remove existing fontsettings buttons 176 | if (!!BUTTON_ID) { 177 | gitbook.toolbar.removeButton(BUTTON_ID); 178 | } 179 | 180 | // Create buttons in toolbar 181 | BUTTON_ID = gitbook.toolbar.createButton({ 182 | icon: 'fa fa-font', 183 | label: 'Font Settings', 184 | className: 'font-settings', 185 | dropdown: [ 186 | [ 187 | { 188 | text: 'A', 189 | className: 'font-reduce', 190 | onClick: reduceFontSize 191 | }, 192 | { 193 | text: 'A', 194 | className: 'font-enlarge', 195 | onClick: enlargeFontSize 196 | } 197 | ], 198 | $.map(FAMILIES, function(family) { 199 | family.onClick = function(e) { 200 | return changeFontFamily(family.config, e); 201 | }; 202 | 203 | return family; 204 | }), 205 | $.map(THEMES, function(theme) { 206 | theme.onClick = function(e) { 207 | return changeColorTheme(theme.config, e); 208 | }; 209 | 210 | return theme; 211 | }) 212 | ] 213 | }); 214 | } 215 | 216 | // Init configuration at start 217 | gitbook.events.bind('start', function(e, config) { 218 | var opts = config.fontsettings; 219 | 220 | // Generate buttons at start 221 | updateButtons(); 222 | 223 | // Init current settings 224 | init(opts); 225 | }); 226 | 227 | // Expose API 228 | gitbook.fontsettings = { 229 | enlargeFontSize: enlargeFontSize, 230 | reduceFontSize: reduceFontSize, 231 | setTheme: changeColorTheme, 232 | setFamily: changeFontFamily, 233 | getThemes: getThemes, 234 | setThemes: setThemes, 235 | getFamilies: getFamilies, 236 | setFamilies: setFamilies 237 | }; 238 | }); 239 | 240 | 241 | -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-highlight/ebook.css: -------------------------------------------------------------------------------- 1 | pre, 2 | code { 3 | /* http://jmblog.github.io/color-themes-for-highlightjs */ 4 | /* Tomorrow Comment */ 5 | /* Tomorrow Red */ 6 | /* Tomorrow Orange */ 7 | /* Tomorrow Yellow */ 8 | /* Tomorrow Green */ 9 | /* Tomorrow Aqua */ 10 | /* Tomorrow Blue */ 11 | /* Tomorrow Purple */ 12 | } 13 | pre .hljs-comment, 14 | code .hljs-comment, 15 | pre .hljs-title, 16 | code .hljs-title { 17 | color: #8e908c; 18 | } 19 | pre .hljs-variable, 20 | code .hljs-variable, 21 | pre .hljs-attribute, 22 | code .hljs-attribute, 23 | pre .hljs-tag, 24 | code .hljs-tag, 25 | pre .hljs-regexp, 26 | code .hljs-regexp, 27 | pre .hljs-deletion, 28 | code .hljs-deletion, 29 | pre .ruby .hljs-constant, 30 | code .ruby .hljs-constant, 31 | pre .xml .hljs-tag .hljs-title, 32 | code .xml .hljs-tag .hljs-title, 33 | pre .xml .hljs-pi, 34 | code .xml .hljs-pi, 35 | pre .xml .hljs-doctype, 36 | code .xml .hljs-doctype, 37 | pre .html .hljs-doctype, 38 | code .html .hljs-doctype, 39 | pre .css .hljs-id, 40 | code .css .hljs-id, 41 | pre .css .hljs-class, 42 | code .css .hljs-class, 43 | pre .css .hljs-pseudo, 44 | code .css .hljs-pseudo { 45 | color: #c82829; 46 | } 47 | pre .hljs-number, 48 | code .hljs-number, 49 | pre .hljs-preprocessor, 50 | code .hljs-preprocessor, 51 | pre .hljs-pragma, 52 | code .hljs-pragma, 53 | pre .hljs-built_in, 54 | code .hljs-built_in, 55 | pre .hljs-literal, 56 | code .hljs-literal, 57 | pre .hljs-params, 58 | code .hljs-params, 59 | pre .hljs-constant, 60 | code .hljs-constant { 61 | color: #f5871f; 62 | } 63 | pre .ruby .hljs-class .hljs-title, 64 | code .ruby .hljs-class .hljs-title, 65 | pre .css .hljs-rules .hljs-attribute, 66 | code .css .hljs-rules .hljs-attribute { 67 | color: #eab700; 68 | } 69 | pre .hljs-string, 70 | code .hljs-string, 71 | pre .hljs-value, 72 | code .hljs-value, 73 | pre .hljs-inheritance, 74 | code .hljs-inheritance, 75 | pre .hljs-header, 76 | code .hljs-header, 77 | pre .hljs-addition, 78 | code .hljs-addition, 79 | pre .ruby .hljs-symbol, 80 | code .ruby .hljs-symbol, 81 | pre .xml .hljs-cdata, 82 | code .xml .hljs-cdata { 83 | color: #718c00; 84 | } 85 | pre .css .hljs-hexcolor, 86 | code .css .hljs-hexcolor { 87 | color: #3e999f; 88 | } 89 | pre .hljs-function, 90 | code .hljs-function, 91 | pre .python .hljs-decorator, 92 | code .python .hljs-decorator, 93 | pre .python .hljs-title, 94 | code .python .hljs-title, 95 | pre .ruby .hljs-function .hljs-title, 96 | code .ruby .hljs-function .hljs-title, 97 | pre .ruby .hljs-title .hljs-keyword, 98 | code .ruby .hljs-title .hljs-keyword, 99 | pre .perl .hljs-sub, 100 | code .perl .hljs-sub, 101 | pre .javascript .hljs-title, 102 | code .javascript .hljs-title, 103 | pre .coffeescript .hljs-title, 104 | code .coffeescript .hljs-title { 105 | color: #4271ae; 106 | } 107 | pre .hljs-keyword, 108 | code .hljs-keyword, 109 | pre .javascript .hljs-function, 110 | code .javascript .hljs-function { 111 | color: #8959a8; 112 | } 113 | pre .hljs, 114 | code .hljs { 115 | display: block; 116 | background: white; 117 | color: #4d4d4c; 118 | padding: 0.5em; 119 | } 120 | pre .coffeescript .javascript, 121 | code .coffeescript .javascript, 122 | pre .javascript .xml, 123 | code .javascript .xml, 124 | pre .tex .hljs-formula, 125 | code .tex .hljs-formula, 126 | pre .xml .javascript, 127 | code .xml .javascript, 128 | pre .xml .vbscript, 129 | code .xml .vbscript, 130 | pre .xml .css, 131 | code .xml .css, 132 | pre .xml .hljs-cdata, 133 | code .xml .hljs-cdata { 134 | opacity: 0.5; 135 | } 136 | -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-livereload/plugin.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var newEl = document.createElement('script'), 3 | firstScriptTag = document.getElementsByTagName('script')[0]; 4 | 5 | if (firstScriptTag) { 6 | newEl.async = 1; 7 | newEl.src = '//' + window.location.hostname + ':35729/livereload.js'; 8 | firstScriptTag.parentNode.insertBefore(newEl, firstScriptTag); 9 | } 10 | 11 | })(); 12 | -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-lunr/search-lunr.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | // Define global search engine 6 | function LunrSearchEngine() { 7 | this.index = null; 8 | this.store = {}; 9 | this.name = 'LunrSearchEngine'; 10 | } 11 | 12 | // Initialize lunr by fetching the search index 13 | LunrSearchEngine.prototype.init = function() { 14 | var that = this; 15 | var d = $.Deferred(); 16 | 17 | $.getJSON(gitbook.state.basePath+'/search_index.json') 18 | .then(function(data) { 19 | // eslint-disable-next-line no-undef 20 | that.index = lunr.Index.load(data.index); 21 | that.store = data.store; 22 | d.resolve(); 23 | }); 24 | 25 | return d.promise(); 26 | }; 27 | 28 | // Search for a term and return results 29 | LunrSearchEngine.prototype.search = function(q, offset, length) { 30 | var that = this; 31 | var results = []; 32 | 33 | if (this.index) { 34 | results = $.map(this.index.search(q), function(result) { 35 | var doc = that.store[result.ref]; 36 | 37 | return { 38 | title: doc.title, 39 | url: doc.url, 40 | body: doc.summary || doc.body 41 | }; 42 | }); 43 | } 44 | 45 | return $.Deferred().resolve({ 46 | query: q, 47 | results: results.slice(0, length), 48 | count: results.length 49 | }).promise(); 50 | }; 51 | 52 | // Set gitbook research 53 | gitbook.events.bind('start', function(e, config) { 54 | var engine = gitbook.search.getEngine(); 55 | if (!engine) { 56 | gitbook.search.setEngine(LunrSearchEngine, config); 57 | } 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-search/search-engine.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | // Global search objects 6 | var engine = null; 7 | var initialized = false; 8 | 9 | // Set a new search engine 10 | function setEngine(Engine, config) { 11 | initialized = false; 12 | engine = new Engine(config); 13 | 14 | init(config); 15 | } 16 | 17 | // Initialize search engine with config 18 | function init(config) { 19 | if (!engine) throw new Error('No engine set for research. Set an engine using gitbook.research.setEngine(Engine).'); 20 | 21 | return engine.init(config) 22 | .then(function() { 23 | initialized = true; 24 | gitbook.events.trigger('search.ready'); 25 | }); 26 | } 27 | 28 | // Launch search for query q 29 | function query(q, offset, length) { 30 | if (!initialized) throw new Error('Search has not been initialized'); 31 | return engine.search(q, offset, length); 32 | } 33 | 34 | // Get stats about search 35 | function getEngine() { 36 | return engine? engine.name : null; 37 | } 38 | 39 | function isInitialized() { 40 | return initialized; 41 | } 42 | 43 | // Initialize gitbook.search 44 | gitbook.search = { 45 | setEngine: setEngine, 46 | getEngine: getEngine, 47 | query: query, 48 | isInitialized: isInitialized 49 | }; 50 | }); -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-search/search.css: -------------------------------------------------------------------------------- 1 | /* 2 | This CSS only styled the search results section, not the search input 3 | It defines the basic interraction to hide content when displaying results, etc 4 | */ 5 | #book-search-results .search-results { 6 | display: none; 7 | } 8 | #book-search-results .search-results ul.search-results-list { 9 | list-style-type: none; 10 | padding-left: 0; 11 | } 12 | #book-search-results .search-results ul.search-results-list li { 13 | margin-bottom: 1.5rem; 14 | padding-bottom: 0.5rem; 15 | /* Highlight results */ 16 | } 17 | #book-search-results .search-results ul.search-results-list li p em { 18 | background-color: rgba(255, 220, 0, 0.4); 19 | font-style: normal; 20 | } 21 | #book-search-results .search-results .no-results { 22 | display: none; 23 | } 24 | #book-search-results.open .search-results { 25 | display: block; 26 | } 27 | #book-search-results.open .search-noresults { 28 | display: none; 29 | } 30 | #book-search-results.no-results .search-results .has-results { 31 | display: none; 32 | } 33 | #book-search-results.no-results .search-results .no-results { 34 | display: block; 35 | } 36 | -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-search/search.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | var MAX_RESULTS = 15; 6 | var MAX_DESCRIPTION_SIZE = 500; 7 | 8 | var usePushState = (typeof history.pushState !== 'undefined'); 9 | 10 | // DOM Elements 11 | var $body = $('body'); 12 | var $bookSearchResults; 13 | var $searchInput; 14 | var $searchList; 15 | var $searchTitle; 16 | var $searchResultsCount; 17 | var $searchQuery; 18 | 19 | // Throttle search 20 | function throttle(fn, wait) { 21 | var timeout; 22 | 23 | return function() { 24 | var ctx = this, args = arguments; 25 | if (!timeout) { 26 | timeout = setTimeout(function() { 27 | timeout = null; 28 | fn.apply(ctx, args); 29 | }, wait); 30 | } 31 | }; 32 | } 33 | 34 | function displayResults(res) { 35 | $bookSearchResults.addClass('open'); 36 | 37 | var noResults = res.count == 0; 38 | $bookSearchResults.toggleClass('no-results', noResults); 39 | 40 | // Clear old results 41 | $searchList.empty(); 42 | 43 | // Display title for research 44 | $searchResultsCount.text(res.count); 45 | $searchQuery.text(res.query); 46 | 47 | // Create an
element for each result 48 | res.results.forEach(function(res) { 49 | var $li = $(' ', { 50 | 'class': 'search-results-item' 51 | }); 52 | 53 | var $title = $(' '); 54 | 55 | var $link = $('', { 56 | 'href': gitbook.state.basePath + '/' + res.url, 57 | 'text': res.title 58 | }); 59 | 60 | var content = res.body.trim(); 61 | if (content.length > MAX_DESCRIPTION_SIZE) { 62 | content = content.slice(0, MAX_DESCRIPTION_SIZE).trim()+'...'; 63 | } 64 | var $content = $('
').html(content); 65 | 66 | $link.appendTo($title); 67 | $title.appendTo($li); 68 | $content.appendTo($li); 69 | $li.appendTo($searchList); 70 | }); 71 | } 72 | 73 | function launchSearch(q) { 74 | // Add class for loading 75 | $body.addClass('with-search'); 76 | $body.addClass('search-loading'); 77 | 78 | // Launch search query 79 | throttle(gitbook.search.query(q, 0, MAX_RESULTS) 80 | .then(function(results) { 81 | displayResults(results); 82 | }) 83 | .always(function() { 84 | $body.removeClass('search-loading'); 85 | }), 1000); 86 | } 87 | 88 | function closeSearch() { 89 | $body.removeClass('with-search'); 90 | $bookSearchResults.removeClass('open'); 91 | } 92 | 93 | function launchSearchFromQueryString() { 94 | var q = getParameterByName('q'); 95 | if (q && q.length > 0) { 96 | // Update search input 97 | $searchInput.val(q); 98 | 99 | // Launch search 100 | launchSearch(q); 101 | } 102 | } 103 | 104 | function bindSearch() { 105 | // Bind DOM 106 | $searchInput = $('#book-search-input input'); 107 | $bookSearchResults = $('#book-search-results'); 108 | $searchList = $bookSearchResults.find('.search-results-list'); 109 | $searchTitle = $bookSearchResults.find('.search-results-title'); 110 | $searchResultsCount = $searchTitle.find('.search-results-count'); 111 | $searchQuery = $searchTitle.find('.search-query'); 112 | 113 | // Launch query based on input content 114 | function handleUpdate() { 115 | var q = $searchInput.val(); 116 | 117 | if (q.length == 0) { 118 | closeSearch(); 119 | } 120 | else { 121 | launchSearch(q); 122 | } 123 | } 124 | 125 | // Detect true content change in search input 126 | // Workaround for IE < 9 127 | var propertyChangeUnbound = false; 128 | $searchInput.on('propertychange', function(e) { 129 | if (e.originalEvent.propertyName == 'value') { 130 | handleUpdate(); 131 | } 132 | }); 133 | 134 | // HTML5 (IE9 & others) 135 | $searchInput.on('input', function(e) { 136 | // Unbind propertychange event for IE9+ 137 | if (!propertyChangeUnbound) { 138 | $(this).unbind('propertychange'); 139 | propertyChangeUnbound = true; 140 | } 141 | 142 | handleUpdate(); 143 | }); 144 | 145 | // Push to history on blur 146 | $searchInput.on('blur', function(e) { 147 | // Update history state 148 | if (usePushState) { 149 | var uri = updateQueryString('q', $(this).val()); 150 | history.pushState({ path: uri }, null, uri); 151 | } 152 | }); 153 | } 154 | 155 | gitbook.events.on('page.change', function() { 156 | bindSearch(); 157 | closeSearch(); 158 | 159 | // Launch search based on query parameter 160 | if (gitbook.search.isInitialized()) { 161 | launchSearchFromQueryString(); 162 | } 163 | }); 164 | 165 | gitbook.events.on('search.ready', function() { 166 | bindSearch(); 167 | 168 | // Launch search from query param at start 169 | launchSearchFromQueryString(); 170 | }); 171 | 172 | function getParameterByName(name) { 173 | var url = window.location.href; 174 | name = name.replace(/[\[\]]/g, '\\$&'); 175 | var regex = new RegExp('[?&]' + name + '(=([^]*)|&|#|$)', 'i'), 176 | results = regex.exec(url); 177 | if (!results) return null; 178 | if (!results[2]) return ''; 179 | return decodeURIComponent(results[2].replace(/\+/g, ' ')); 180 | } 181 | 182 | function updateQueryString(key, value) { 183 | value = encodeURIComponent(value); 184 | 185 | var url = window.location.href; 186 | var re = new RegExp('([?&])' + key + '=.*?(&|#|$)(.*)', 'gi'), 187 | hash; 188 | 189 | if (re.test(url)) { 190 | if (typeof value !== 'undefined' && value !== null) 191 | return url.replace(re, '$1' + key + '=' + value + '$2$3'); 192 | else { 193 | hash = url.split('#'); 194 | url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, ''); 195 | if (typeof hash[1] !== 'undefined' && hash[1] !== null) 196 | url += '#' + hash[1]; 197 | return url; 198 | } 199 | } 200 | else { 201 | if (typeof value !== 'undefined' && value !== null) { 202 | var separator = url.indexOf('?') !== -1 ? '&' : '?'; 203 | hash = url.split('#'); 204 | url = hash[0] + separator + key + '=' + value; 205 | if (typeof hash[1] !== 'undefined' && hash[1] !== null) 206 | url += '#' + hash[1]; 207 | return url; 208 | } 209 | else 210 | return url; 211 | } 212 | } 213 | }); 214 | -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-sharing/buttons.js: -------------------------------------------------------------------------------- 1 | require(['gitbook', 'jquery'], function(gitbook, $) { 2 | var SITES = { 3 | 'facebook': { 4 | 'label': 'Facebook', 5 | 'icon': 'fa fa-facebook', 6 | 'onClick': function(e) { 7 | e.preventDefault(); 8 | window.open('http://www.facebook.com/sharer/sharer.php?s=100&p[url]='+encodeURIComponent(location.href)); 9 | } 10 | }, 11 | 'twitter': { 12 | 'label': 'Twitter', 13 | 'icon': 'fa fa-twitter', 14 | 'onClick': function(e) { 15 | e.preventDefault(); 16 | window.open('http://twitter.com/home?status='+encodeURIComponent(document.title+' '+location.href)); 17 | } 18 | }, 19 | 'google': { 20 | 'label': 'Google+', 21 | 'icon': 'fa fa-google-plus', 22 | 'onClick': function(e) { 23 | e.preventDefault(); 24 | window.open('https://plus.google.com/share?url='+encodeURIComponent(location.href)); 25 | } 26 | }, 27 | 'weibo': { 28 | 'label': 'Weibo', 29 | 'icon': 'fa fa-weibo', 30 | 'onClick': function(e) { 31 | e.preventDefault(); 32 | window.open('http://service.weibo.com/share/share.php?content=utf-8&url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)); 33 | } 34 | }, 35 | 'instapaper': { 36 | 'label': 'Instapaper', 37 | 'icon': 'fa fa-instapaper', 38 | 'onClick': function(e) { 39 | e.preventDefault(); 40 | window.open('http://www.instapaper.com/text?u='+encodeURIComponent(location.href)); 41 | } 42 | }, 43 | 'vk': { 44 | 'label': 'VK', 45 | 'icon': 'fa fa-vk', 46 | 'onClick': function(e) { 47 | e.preventDefault(); 48 | window.open('http://vkontakte.ru/share.php?url='+encodeURIComponent(location.href)); 49 | } 50 | } 51 | }; 52 | 53 | 54 | 55 | gitbook.events.bind('start', function(e, config) { 56 | var opts = config.sharing; 57 | 58 | // Create dropdown menu 59 | var menu = $.map(opts.all, function(id) { 60 | var site = SITES[id]; 61 | 62 | return { 63 | text: site.label, 64 | onClick: site.onClick 65 | }; 66 | }); 67 | 68 | // Create main button with dropdown 69 | if (menu.length > 0) { 70 | gitbook.toolbar.createButton({ 71 | icon: 'fa fa-share-alt', 72 | label: 'Share', 73 | position: 'right', 74 | dropdown: [menu] 75 | }); 76 | } 77 | 78 | // Direct actions to share 79 | $.each(SITES, function(sideId, site) { 80 | if (!opts[sideId]) return; 81 | 82 | gitbook.toolbar.createButton({ 83 | icon: site.icon, 84 | label: site.text, 85 | position: 'right', 86 | onClick: site.onClick 87 | }); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /_book/gitbook/images/apple-touch-icon-precomposed-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/gitbook/images/apple-touch-icon-precomposed-152.png -------------------------------------------------------------------------------- /_book/gitbook/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/_book/gitbook/images/favicon.ico -------------------------------------------------------------------------------- /_book/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-book", 3 | "version": "1.0.0", 4 | "description": "vue3做了最大的变化就是api的细分,适配typescript。給我一种感觉就是,vue3像乐高,一个个拼接起来成模块, 模块之间的互相组合,来构成一个整体, 这样更利于团队开发了,可以根据团队情况来定制合适的开发架构。composition-api的出现,如果想真正利用好,弄懂vue3源码是必须的。 vue3中重要的包:Reactivity、runtime-x和 compiler-x。", 5 | "main": "index.js", 6 | "scripts": { 7 | "serve": "gitbook serve", 8 | "build": "gitbook build ./ ./book", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /_book/update.md: -------------------------------------------------------------------------------- 1 | # 今日更新 2 | 3 | **2020年12月13日22点39分** 4 | 5 | 补充了一下响应式的例子,因为群里有人问 6 | 7 | > “就拿监听来讲,它是怎么做到我们在里面丢进去的值里,哪个发生改变,哪个就进行输出的” 8 | 9 | > “我就丢进去了个事件,这个事件里有三行代码,每行代码都是与响应式有关,哇擦,它能做到某个值发生改变后,就将某个值相对应的console进行输出” 10 | 11 | > “我也能联想到这个关系到收集响应值及所依赖的,就是不太懂它是怎么做到上面那样功能的,是将传过去的函数转成字符串再进行文本解析出响应值吗” 12 | 13 |  -------------------------------------------------------------------------------- /_book/更新/2020年12月13日22-42.md: -------------------------------------------------------------------------------- 1 | # 今日更新 2 | 3 | **2020年12月13日22点39分** 4 | 5 | 补充了一下响应式的例子,因为群里有人问 6 | 7 | > “就拿监听来讲,它是怎么做到我们在里面丢进去的值里,哪个发生改变,哪个就进行输出的” 8 | 9 | > “我就丢进去了个事件,这个事件里有三行代码,每行代码都是与响应式有关,哇擦,它能做到某个值发生改变后,就将某个值相对应的console进行输出” 10 | 11 | > “我也能联想到这个关系到收集响应值及所依赖的,就是不太懂它是怎么做到上面那样功能的,是将传过去的函数转成字符串再进行文本解析出响应值吗” 12 | 13 |  -------------------------------------------------------------------------------- /book/.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=TypeScript 2 | *.css linguist-language=TypeScript 3 | *.html linguist-language=TypeScript 4 | -------------------------------------------------------------------------------- /book/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 |
3 | -------------------------------------------------------------------------------- /book/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 |4 | 5 | 6 |3 | -------------------------------------------------------------------------------- /book/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 |4 | 8 |5 | 7 |6 | 3 | -------------------------------------------------------------------------------- /book/.idea/vue3源码解释.iml: -------------------------------------------------------------------------------- 1 | 2 |4 | 6 |5 | 3 | -------------------------------------------------------------------------------- /book/Packages/BUGS/A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/book/Packages/BUGS/A.png -------------------------------------------------------------------------------- /book/Packages/BUGS/B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/book/Packages/BUGS/B.png -------------------------------------------------------------------------------- /book/Packages/BUGS/iframe-bug.md: -------------------------------------------------------------------------------- 1 | ### vue在触发dom点击事件的时候,需要验证event.timeStamp 2 | ##### 历史来源: 3 | 4 | vue-2.4.2 #6566 5 | 6 | ##### 操作: 7 | 8 |  9 | 10 | 点击**element-1**,触发了**countA++**和**countB++**。 11 | 12 | ##### 过程: 13 | 14 | 点击element-1,触发element-1本身的click事件,```expand = 1; countA++```。 15 | 16 | 由```expand```变动,把组件effect事件丢进微任务。 17 | 18 | 触发微任务,由于diff算法的原因,block-1会被复用,```patchProps```更新block-1的click事件为block-2的click事件。 19 | 20 | 由element-1所触发的冒泡事件,引起block-1中的click触发(click事件已经被更新成block-2的了)。 21 | 22 | ##### 解决方法: 23 | 24 | 在创建事件的时候,新增一个时间戳,该时间戳为**页面启动时间->创建该事件的时间**。 25 | 26 | 当触发click事件的时候,判断Event的timeStamp是否大于事件的创建时间。 27 | 28 |  29 | 30 | 这样冒泡的时候,Event.timeStamp 小于 事件的创建时间,那么该事件就不会触发了。 31 | 32 | 33 | 34 | 哈哈哈?以为这样就完了?直到我看到 vue-next-#2513 35 | 36 | https://github.com/vuejs/vue-next/issues/2513 37 | 38 | 在iframe中,Event.timeStamp,是由该iframe被创建开始算起的。 39 | 40 | 所以会导致iframe中的点击事件的timeStamp,小于**源页面启动时间->创建该事件的时间**。 41 | 42 | 从而事件不被触发。 43 | 44 | **除非你愿意等待这些时间。** 45 | 46 | https://stackblitz.com/edit/vue3-iframe-teleport-demo?file=src%2FApp.vue 47 | 48 | 尝试一下,点击渲染iframe再疯狂点击count?你就会发现一开并不能触发,要等**页面启动时间->创建该事件的时间**才能触发事件。 49 | 50 | count事件触发后,点击toggle,再重新渲染iframe,你会发现你要等待的时间更久了! -------------------------------------------------------------------------------- /book/Packages/Reactivity/diff.md: -------------------------------------------------------------------------------- 1 | # 新版Diff 算法 2 | 3 | 好了好了,我们来聊聊diff吧,我就不上代码了... 其实之前哪些写的代码,是可看可不看的。 4 | 5 | ```typescript 6 | const childOne = [1,2,3,4,5] 7 | const childTwo = [1,2,3,4,6,5] 8 | ``` 9 | 10 | 假设你的vnode拥有[1,2,3,4,5]子元素,你的vnode类型是ELEMENT, 11 | 12 | 拥有子5个vnode:[h('span', 1), h('span', 2), h('span', 3), h('span', 4), h('span', 5)] 13 | 14 | 现在把包含这5个vnode的数组简写成[1,2,3,4,5]。 15 | 16 | 进行初始渲染后,因为要更新child,所以再次进行patch。 17 | 18 | > 如果子元素数组没有key,但是字符串相等,那么他们会被判定为同一个vnode。 19 | 20 | > 啥是patch啊?emm... 竟然你都问了,你就当它是一个更新器吧 21 | 22 | 23 | 24 | ## 步骤一 25 | 26 | 从头对比是不是同一个vnode,是则跳过不做任何处理,不是则标记。 27 | 28 | 重复上步骤,从尾到头(标记点也算是头)。 29 | 30 | 按照上述,我们标记到4和5,目前没有进行任何的patch处理。 31 | 32 | **如果没有尾或者头有相同的vnode,索引依旧在两端,并不是不存在。** 33 | 34 |  35 | 36 | 37 | 38 | ## 步骤二 39 | 40 | 对两个标记之间的元素进行patch,也就是进行更新。 41 | 42 |  43 | 44 | 45 | 46 | ## 步骤三 47 | 48 | 对从头标记之后尾标记之前的元素进行patch。 49 | 50 |  51 | 52 | 53 | 54 | ## 步骤四 55 | 56 | 对旧的子vnode要进行unmount。 57 | 58 |  59 | 60 | 61 | 62 | ## 步骤五(重点哦) 63 | 64 | ... 明天再更新~ -------------------------------------------------------------------------------- /book/Packages/Reactivity/diff剧本.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/book/Packages/Reactivity/diff剧本.md -------------------------------------------------------------------------------- /book/Packages/Reactivity/恐.md: -------------------------------------------------------------------------------- 1 | Q1: 话说我用vue3那么久了,老是听别人说什么响应式数据... 我根本不懂怎么办?现在越来越感觉自己像个API开发者了,心里很焦虑,能不能給我科普一下呀 2 | 3 | W1:不用焦虑,成为一个能懂原理的人固然重要,急是急不来的,我们可以先从三大概念入手:Track(追踪)、Trigger(触发)、Effect(影响)。 4 | 5 | 6 | 7 | W1:“A先生,你好呀,如果有来自E家族的人,问你信息,拜托你 记录一下他问了你哪些信息哦。如果你相关信息变动了,就要主动告诉他哦” 8 | 9 | A: “嗯嗯好的,好的我都知道了” 10 | 11 | 12 | 13 | E-sb:"小A,过来一下!" 14 | 15 | A:“来了来了,有啥事呀,大佬” 16 | 17 | E-sb:“你现在身上有多少钱啊?” 18 | 19 | E-sb: "这么少?这次就放过你了" 20 | 21 | A:“好的,大佬,知道了” 22 | 23 | 24 | 25 | “A先生回到家后,就把E-sb问了他关于钱有多少这一事情,记录(Track)在了小本本上。” 26 | 27 | 28 | 29 | “很多天过去了,小A在马路上看到5块钱,马上捡了起来,他总觉得有些事要处理,但是忘了是什么东西了,于是回家看起了小本本,记起来了我们吩咐A做的事(Trigger)” 30 | 31 | 32 | 33 | A:"大佬好,一块两块三块五块左右" 34 | 35 | E-sb: "很好,很主动我很欣赏你" 36 | 37 | 38 | 39 | Q1:“欸?那是不是A的钱每次变化都会主动去告诉E-sb呀,那这样的话如果E-sb当场拿走A的五块钱,A会不会继续告诉E-sb自己有多少钱?” 40 | 41 | 42 | 43 | 情景重现 44 | 45 | 46 | 47 | A:"大佬好,一块两块三块五块左右" 48 | 49 | E-sb: "很好,很主动我很欣赏你,五块钱我要了" 50 | 51 | A:"大佬好,一块两块三块左右" 52 | 53 | 54 | 55 | W1: “对的,如果E-sb向A拿钱,A的钱在无限的情况下,就会造成一个相当阿巴阿巴的场景” 56 | 57 | Q1:“哦,那其实就是A先生是响应式数据,E-sb就是Effect,E-sb与A先生存在钱的来往,然后A先生记录下E-sb询问了他的钱的情况(Track),需要每次在自己的钱变动都会主动通知(Trigger)E-sb。” 58 | 59 | 60 | 61 | Q1:“但是什么是Effect呀... 我平时使用vue3的composition API 也没怎么用过,但是听你这么说,这个东西,是不是computed呀?” 62 | 63 | W1:“猜对了,computed本身用到了effect,返回的是一个响应式数据,同样会拥有Track与Trigger的行为,computed对比effect,还会判断是否重复多次调用effect,仅在一个队列中执行一次哦。” -------------------------------------------------------------------------------- /book/Packages/Reactivity/恐.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/book/Packages/Reactivity/恐.png -------------------------------------------------------------------------------- /book/Packages/Reactivity/镜.md: -------------------------------------------------------------------------------- 1 | watchEffect(() => { 2 | 3 | console.log(A.money) 4 | 5 | }) 6 | 7 | 8 | 9 | Q1:“emmm,大概了解了,能不能详细讲一下watchEffect这个例子” 10 | 11 | R1:“其实watchEffect里面也是把effect封装了起来,流程还是一样的流程” 12 | 13 | 14 | 15 | watchEffect中,接收两个参数①和②,①我们通常设置成一个方法,②是一些配置。 16 | 17 | 这里不聊②,降低复杂性,使用默认配置。 18 | 19 | watchEffect(①) 20 | 21 | 他与普通effect不同的是,会告诉①里面的响应式数据们:“每次运行我(Trigger effect)的时候,请把我放进任务队列中” 22 | 23 | 他也有一个特性,如果检测到当前有正在渲染的组件,而且组件还没有挂载上去,那他就什么不都做。 24 | 25 | 26 | 27 | Q1: "对了,这里在代码层面上,①里面的响应式数据们,是如何捕获到当前①这个方法呢?为什么每次①里面的数据一变动,这个方法就会重新执行?" 28 | 29 | R1:“这很好理解,①当前执行,也就是effect执行,我们标记activeEffect为当前effect就可以了,因为①里面会触发响应式数据的getter方法,我们可以使响应式数据们检测有没有activeEffect,有就捕获他。响应式都捕获到effect了,那么改变当前值的时候,再重新触发effect就好了” 30 | 31 | 32 | 33 | Q1:“那就是说,运行effect,effect内部标记activeEffect为当前effect,然后运行①,①中的响应式数据getter检测到activeEffect,记录(track)下来,响应式数据触发setter的时候,就trigger,重新触发effect,在触发effect前,把所有响应式数据所track到当前的effect的记录全部删除,因为运行①会再次记录下来的,所以才要删除!!! 我懂了!!!” 34 | 35 | 36 | 37 | effect = (fn) => { 38 | 39 | activeEffect = Effect 40 | 41 | return fn() 42 | 43 | } 44 | 45 | 46 | 47 | Q1:“啊... 原理是那么简单的.. 我还以为有什么神奇的东西在里面。那那那... 我平时使用watchEffect的时候,①中假如我用到了响应式数据,我把这个响应式数据疯狂触发getter的时候,这个effect也只是执行一次呀” 48 | 49 | R1:“这就是为什么“每次运行我的时候,请把我放进微任务中了”,这里有一个事件队列,每次执行事件队列的时候,就会去除重复的事件,也就是说,不管你触发多少次trigger,都会只运行一次了 ” 50 | 51 | 52 | 53 | Q1:"那另外一个特性是啥意思.. 有什么用麽?" 54 | 55 | R1:"目前只发现这个和SSR相关,和我们普通使用无关,欸... SSR那块我也不是很清楚了" -------------------------------------------------------------------------------- /book/Packages/Reactivity/镜.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/book/Packages/Reactivity/镜.png -------------------------------------------------------------------------------- /book/Packages/Reactivity/问题集合.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/book/Packages/Reactivity/问题集合.md -------------------------------------------------------------------------------- /book/Packages/Runtime/OPTION-API.md: -------------------------------------------------------------------------------- 1 | # OptionsAPI 2 | 3 | vue2.x相关,与vue3的关系仅有component.template使用compile转换成component.render。 4 | 5 | 6 | 7 | ## methods 8 | 9 | -------------------------------------------------------------------------------- /book/Packages/Runtime/index.md: -------------------------------------------------------------------------------- 1 | # 测试用例 2 | ```typescript 3 | it('should allow attrs to fallthrough', async () => { 4 | debugger 5 | const click = jest.fn() 6 | const childUpdated = jest.fn() 7 | 8 | const Hello = { 9 | setup() { // 如果有render 就优先渲染render了 10 | const count = ref(0) 11 | 12 | function inc() { // 需要了解onClick事件系统 13 | count.value++ 14 | click() 15 | } 16 | 17 | // 这里的任何东西 都不会被本身effect收集 只有return 后的方法 才会 18 | 19 | return () => // 这里还可以当redner来使用 我平时会的只是 return {} template所需要的事件或属性 所以是检查function 还是 object来判断的吧 20 | h(Child, { // 那是不是有一个props effect? 标记也要注意下 21 | foo: count.value + 1, 22 | id: 'test', 23 | class: 'c' + count.value, 24 | style: { color: count.value ? 'red' : 'green' }, 25 | onClick: inc, 26 | 'data-id': count.value + 1 27 | }) 28 | }, 29 | mounted() { 30 | console.log('?') 31 | } 32 | } 33 | 34 | const Child = { // 原来是这样传参数的 那么就不需要this 什么的了 35 | setup(props: any) { 36 | onUpdated(childUpdated) 37 | return () => 38 | h( 39 | 'div', 40 | { 41 | class: 'c2', 42 | style: { fontWeight: 'bold' } 43 | }, 44 | props.foo // 这个为什么是undefinded呢 因为setFullProps执行的时候,判断到的赋值Props方式为attrs,所以instance.props为空 45 | ) 46 | } 47 | } 48 | 49 | const root = document.createElement('div') 50 | document.body.appendChild(root) 51 | render(h(Hello), root) // 这个render 是'@vue/runtime-dom' 我们之前用的 是'@vue/runtime-test' 里面的 测试用的... 但是区别不一样的就是 会不会初始化而已 52 | 53 | const node = root.children[0] as HTMLElement 54 | 55 | expect(node.getAttribute('id')).toBe('test') 56 | expect(node.getAttribute('foo')).toBe('1') 57 | expect(node.getAttribute('class')).toBe('c2 c0') 58 | expect(node.style.color).toBe('green') 59 | expect(node.style.fontWeight).toBe('bold') 60 | expect(node.dataset.id).toBe('1') 61 | 62 | node.dispatchEvent(new CustomEvent('click')) // 事件触发a 63 | node.dispatchEvent(new CustomEvent('click')) // 事件触发a 64 | expect(click).toHaveBeenCalled() 65 | 66 | await nextTick() 67 | expect(childUpdated).toHaveBeenCalled() 68 | expect(node.getAttribute('id')).toBe('test') 69 | expect(node.getAttribute('foo')).toBe('2') 70 | expect(node.getAttribute('class')).toBe('c2 c1') 71 | expect(node.style.color).toBe('red') 72 | expect(node.style.fontWeight).toBe('bold') 73 | expect(node.dataset.id).toBe('2') 74 | }) 75 | ``` 76 | 77 | ## processComponent流程图: 78 | https://www.processon.com/view/link/5f85c9321e085307a0892f7e 79 | 80 |  81 | 82 | 83 | ## vnode: 84 | https://www.processon.com/view/link/5f963f37f346fb06e1ec35b2 85 |  86 | 87 | 88 | ## instance: 89 | https://www.processon.com/view/link/5f963f5fe401fd06fda22681 90 |  91 | -------------------------------------------------------------------------------- /book/Packages/Runtime/patch剧本.md: -------------------------------------------------------------------------------- 1 | **patch** 在vue中,充当一个渲染角色存在。 2 | 3 | **patch**就像一家工厂,把**vnode**(一个描述**vue组件**,或者描述**NODE**的对象)当作生产原料,进行一系列的加工,最终生产出成品(也就是真实的**NODE**)。 4 | 5 | 一家工厂,可以是由**流程控制器**和**加工机器**组成的。**流程控制器**根据原料**vnode**的类型,来选取机器处理。机器负责把传入来的东西,加工或输出。 6 | 7 | 8 | 9 | 对于**patch**这个工厂来说,有哪些流程控制器? 10 | 11 | 组件渲染流程控制器 12 | 13 | 普通元素渲染流程控制器 14 | 15 | 16 | 17 | 对于**patch**这个工厂来说,有哪些机器? 18 | 19 | (property 和 attribute非常容易混淆,两个单词的中文翻译也都非常相近(property:属性,attribute:特性),但实际上,二者是不同的东西,属于不同的范畴。) 20 | 21 | - property是DOM中的属性,是JavaScript里的对象; 22 | 23 | - attribute是HTML标签上的特性,它的值只能够是字符串; 24 | 25 | - property能够从attribute中得到同步; 26 | 27 | - attribute不会同步property上的值; 28 | 29 | - attribute和property之间的数据绑定是单向的,attribute->property; 30 | 31 | - 更改property和attribute上的任意值,都会将更新反映到HTML页面中; 32 | 33 | 34 | 35 | **patchAttr**: 使用setAttrbute方法,添加、去除和设置HTMLELEMENT标签上特性。 36 | 37 | **patchClass**: 设置HTMLELEMENT的className属性,为什么有patchAttr存在了,还需要patchClass呢,因为速度比patchAttr要快。 38 | 39 | **patchStyle**: 利用setProperty修改HTMLELEMENT的style属性。 40 | 41 | **patchDomProp**: 处理HTMLELEMENT中的属性。 42 | 43 | **patchEvent**: 更新、删除和添加事件(这个事件就是ducument.addEventListener(Event)中的Event)。 44 | 45 | **processText**:创建、插入和更换**TextNode** 46 | 47 | **processCommentNode**: 创建、插入和更换**CommentNode** e.g. \<\!\-\- 这就是comment\-\-\> 48 | 49 | **mountStaticNode**: 创建Node,把Node丢进目标Node中 50 | 51 | 52 | 53 | q1: 抱歉,我又懵逼了,为什么拥有了patchAttr来设置属性了,还需要patchClass、patchStyle和patchDomProp? 54 | 55 | R1: 确实不需要,这么做主要是为了速度,这里就需要了解property和attribute了 56 | 57 | 58 | 59 | patchAttr 60 | 61 | 62 | 63 | 小知识: 64 | 65 | 可以利用双叹号 !! 来把一个值转换成布尔属性(比如0是false,null是false之类的,负数是true,undefinded是false,undefinded是false)。 66 | 67 | 68 | 69 | https://developer.mozilla.org/zh-CN/docs/Web/API/Event/stopImmediatePropagation 70 | 71 | [`Event`](https://developer.mozilla.org/zh-CN/docs/Web/API/Event) 接口的 `stopImmediatePropagation()` 方法阻止监听同一事件的其他事件监听器被调用。 72 | 73 | -------------------------------------------------------------------------------- /book/Packages/Runtime/question.md: -------------------------------------------------------------------------------- 1 | # 一些问题 2 | #### 1.当连续触发响应式的trigger会导致组件的effectComponent连续执行更新吗? 3 | 并不会。第一次触发trigger的时候会把事件队列状态改成Pending,把flushJobs丢进microtask来执行,第二次触发trigger会检测到当前状态如果是Pending或者是Flushing状态的时候将不进行下去。 4 | 可在packages/runtime-core/\_\_tests\_\_/rendererAttrsFallthrough.spec.ts 'should allow attrs to fallthrough'测试用例中调试。 5 | 6 |  7 | 8 | #### 2.patch到底有多少种类型?分别是干什么的? 9 | 在patch中查看有processText、processCommentNode、mountStaticNode、patchStaticNode、processFragment、processElement(流程图已有)、processComponent(流程图已有)、Teleport、SUSPENSE。 10 | 11 | 12 | #### 3.instance.props、instance.attrs和vnode.props分别是什么?关系是什么? 13 | 先说一下设置的阶段,和如何设置的: 14 | 1.instance.props和instance.attrs是在第二步骤中setupComponent中的initProps实现的,是根据vnode.props来设置的,instance和组件类型相关。 15 | 2.vnode.props创建vnode的时候传入的参数,比如```h(Child, { foo: 1, class: 'parent' })```。 16 | 17 | 使用阶段,拿这个做例子: 18 | ```typescript 19 | const Hello = { 20 | setup() { 21 | const count = ref(0) 22 | 23 | function inc() { 24 | count.value++ 25 | } 26 | 27 | return () => 28 | h(Child, { 29 | foo: count.value + 1, 30 | id: 'test', 31 | class: 'c' + count.value, 32 | style: { color: count.value ? 'red' : 'green' }, 33 | onClick: inc, 34 | 'data-id': count.value + 1 35 | }) 36 | } 37 | } 38 | 39 | const Child = { 40 | setup(props: any) { 41 | return () => 42 | h( 43 | 'div', 44 | { 45 | class: 'c2', 46 | style: { fontWeight: 'bold' } 47 | }, 48 | props.foo 49 | ) 50 | } 51 | } 52 | 53 | const root = document.createElement('div') 54 | document.body.appendChild(root) 55 | render(h(Hello), root) 56 | 57 | ``` 58 | 59 | 步骤一:第一次渲染,流程图②setupComponent的initProps,使用Hello组件的vnode.props(当前为空,所以输出的attrs都是为空的),标准化生成instance.attrs和instance.props 60 | ```typescript 61 | if (!instance.type.props) { 62 | // functional w/ optional props, props === attrs 63 | instance.props = attrs 64 | } else { 65 | // functional w/ declared props 66 | instance.props = props 67 | } 68 | ``` 69 | 70 | 步骤二:②setupConponent中,renderComponentRoot(instance)会把instance.attrs与调用instance.render(Hello.setup返回的方法)所返回的vnode的props进行合并。 71 | 72 | 步骤三:子组件进行patch,同样经过initProps处理, 73 | 在②中调用setup方法,并传入instance.props, instance.setupConetxt,这个时候子组件就会利用到父组件传入的props了。 74 | 75 | _**那更新的时候,是怎么样的?**_ 76 | 77 | 触发父组件instance,renderComponentRoot(instance)中调用instance.render(这个过程就是vnode.props的更新了) 78 | 再次进行当前track追踪(effect运行的特性,是会删除当前追踪),patch(旧vnode1, 新vnode2),processComponent updateComponent, 79 | 新vnode2引用vnode1.component(instance2,子组件instance2),删除queue任务中的instance2.update,instance2.next = n2,执行instance2.update, 80 | 检测到有next字段,执行updateComponentPreRender,这里instance2是为了标识是子instance。 81 | 82 | updateComponentPreRender: 83 | ```typescript 84 | nextVNode.component = instance2 // 对当前没用,因为已经引用了 85 | const prevProps = instance2.vnode.props 86 | instance2.vnode = nextVNode // 更新vnode 87 | instance2.next = null // 删除next 88 | updateProps(instance2, nextVNode.props, prevProps, optimized) 89 | updateSlots(instance2, nextVNode.children) 90 | ``` 91 | updateProps会利用setFullProps来更新attrs和props,这里的更新过的props和attrs可以提供給Child内部使用,如当前使用了props.foo。 92 | -------------------------------------------------------------------------------- /book/Packages/Runtime/类型标记.md: -------------------------------------------------------------------------------- 1 | # 类型标记 2 | 3 | ```typescript 4 | export declare const enum ShapeFlags { 5 | ELEMENT = 1, // 1 6 | FUNCTIONAL_COMPONENT = 2, // 10 7 | STATEFUL_COMPONENT = 4, // 100 8 | TEXT_CHILDREN = 8, // 1000 9 | ARRAY_CHILDREN = 16, // 10000 10 | SLOTS_CHILDREN = 32, // 100000 11 | TELEPORT = 64, // 1000000 12 | SUSPENSE = 128, // 10000000 13 | COMPONENT_SHOULD_KEEP_ALIVE = 256, // 100000000 14 | COMPONENT_KEPT_ALIVE = 512, // 1000000000 15 | COMPONENT = 6 // 110 16 | } 17 | ``` 18 | 19 | 20 | 按位与: 21 | 22 | ```typescript 23 | 01 | 01 === 01 24 | 01 | 10 === 11 25 | 10 | 10 === 10 26 | ``` 27 | 28 | 29 | 30 | 判断类型的按位且: 31 | 32 | ```typescript 33 | 11 & 11 === 11 34 | 01 & 10 === 00 35 | 10 & 10 === 10 36 | ``` 37 | 38 | 对应位置1 & 1 === 1, 1 | 0 === 0,就像&&的逻辑,要满足所有为真值。 39 | 40 | 41 | 42 | 比如100代表STATEFUL_COMPONENT类型,10000代表ARRAY_CHILDREN类型,合并使用 43 | 44 | ```typescript 45 | const initial = 100 | 10000 === 10100 46 | ``` 47 | 48 | 49 | 50 | 要判断10100是否包含STATEFUL_COMPONENT类型: 51 | 52 | ```typescript 53 | const result = initial & ShapeFlags.STATEFUL_COMPONENT 54 | // result === 100 55 | if (result > 0) { 56 | console.log('包含STATEFUL_COMPONENT类型') 57 | } 58 | ``` 59 | 60 | 61 | 62 | 利用标记的方式,可以轻松合并类型和检测类型,对代码的可阅读性强。 -------------------------------------------------------------------------------- /book/Packages/compile-core/compile-core.md: -------------------------------------------------------------------------------- 1 | ## 关于compile 2 | 3 | 在带有编译版本的vue中,finishComponentSetup会对没有render方法,但是有template的Component,做编译处理。 4 | 5 | 这里推荐一个compile在线转换成render的网站: 6 | 7 | https://vue-next-template-explorer.netlify.app/ 8 | 9 | 10 | 11 | finishComponentSetup的代码片段: 12 | 13 | ```typescript 14 | Component.render = compile(Component.template, { 15 | isCustomElement: instance.appContext.config.isCustomElement || NO 16 | }) 17 | ``` 18 | 19 | 20 | 21 | 去查看一下compile的CompilerOptions相关配置。 22 | 23 | ```typescript 24 | type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions 25 | 26 | // ParserOptions: 27 | export interface ParserOptions { 28 | // 平台上本地元素, 例如4 | 8 |5 | 6 | 7 | 是网页上的本地标签 29 | isNativeTag?: (tag: string) => boolean 30 | 31 | // 不需要闭合的原生元素, 例如,
,
32 | isVoidTag?: (tag: string) => boolean 33 | 34 | // 应该保留内部空白的元素, 例如35 | // https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/pre 36 | isPreTag?: (tag: string) => boolean 37 | 38 | // 特定于平台的内置组件,例如39 | isBuiltInComponent?: (tag: string) => symbol | void 40 | 41 | // 自定义于平台的本地元素 42 | isCustomElement?: (tag: string) => boolean 43 | 44 | // 获取标签的名称 45 | getNamespace?: (tag: string, parent: ElementNode | undefined) => Namespace 46 | 47 | // 获取此元素的文本分析模式 48 | getTextMode?: ( 49 | node: ElementNode, 50 | parent: ElementNode | undefined 51 | ) => TextModes 52 | 53 | // 默认 ['{{', '}}'],可修改为你喜欢的 54 | delimiters?: [string, string] 55 | 56 | // Only needed for DOM compilers,解码实体 57 | decodeEntities?: (rawText: string, asAttr: boolean) => string 58 | 59 | // 错误 60 | onError?: (error: CompilerError) => void 61 | } 62 | 63 | 64 | // TransformOptions: 65 | export interface TransformOptions { 66 | /** 67 | * An array of node trasnforms to be applied to every AST node. 68 | */ 69 | nodeTransforms?: NodeTransform[] 70 | /** 71 | * An object of { name: transform } to be applied to every directive attribute 72 | * node found on element nodes. 73 | */ 74 | directiveTransforms?: Record 75 | /** 76 | * An optional hook to transform a node being hoisted. 77 | * used by compiler-dom to turn hoisted nodes into stringified HTML vnodes. 78 | * @default null 79 | */ 80 | transformHoist?: HoistTransform | null 81 | 82 | // 如果提供了其他的内置元素,可以使用该选项标记为内置的,这样编译器将为这些标签生成组件vnode 83 | isBuiltInComponent?: (tag: string) => symbol | void 84 | /** 85 | * 把表达式 {{ foo }} 转换成 `_ctx.foo` 86 | * 如果该选项为false, the generated code will be wrapped in a 87 | * `with (this) { ... }` block. 88 | * - This is force-enabled in module mode, since modules are by default strict 89 | * and cannot use `with` 90 | * @default mode === 'module' 91 | */ 92 | prefixIdentifiers?: boolean 93 | 94 | 95 | // 静态提升到 `_hoisted_x` 变量 96 | // 默认值 false 97 | hoistStatic?: boolean 98 | 99 | // 开启前 { onClick: _ctx.foo } 100 | // 开启后 { 101 | // onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.foo(...args))) 102 | // } 103 | // _cache[index] index根据当前缓存事件数量,保证预留一个空的位置給该事件 104 | // 默认值 false 105 | cacheHandlers?: boolean 106 | 107 | // `@babel/parser` 中的插件, 108 | // https://babeljs.io/docs/en/next/babel-parser#plugins 109 | expressionPlugins?: ParserPlugin[] 110 | 111 | // Single File Component组件中scoped styles ID 112 | scopeId?: string | null 113 | /** 114 | * Generate SSR-optimized render functions instead. 115 | * The resulting funciton must be attached to the component via the 116 | * `ssrRender` option instead of `render`. 117 | */ 118 | ssr?: boolean 119 | onError?: (error: CompilerError) => void 120 | } 121 | 122 | // CodegenOptions 123 | ``` 124 | 125 | 例子: 126 | ```html 127 | 128 | \{\{ world.burn() \}\} 129 |133 | ``` 134 | 135 | 转换成render: 136 | ```typescript 137 | const _Vue = Vue 138 | 139 | return function render(_ctx, _cache) { 140 | with (_ctx) { 141 | const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList } = _Vue 142 | 143 | return (_openBlock(), _createBlock("div", { 144 | id: "foo", 145 | class: bar.baz 146 | }, [ 147 | _createTextVNode(_toDisplayString(world.burn()) + " ", 1 /* TEXT */), 148 | ok 149 | ? (_openBlock(), _createBlock("div", { key: 0 }, "yes")) 150 | : (_openBlock(), _createBlock(_Fragment, { key: 1 }, [ 151 | _createTextVNode("no") 152 | ], 64 /* STABLE_FRAGMENT */)), 153 | (_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (value, index) => { 154 | return (_openBlock(), _createBlock("div", null, [ 155 | _createVNode("span", null, _toDisplayString(value + index), 1 /* TEXT */) 156 | ])) 157 | }), 256 /* UNKEYED_FRAGMENT */)) 158 | ], 2 /* CLASS */)) 159 | } 160 | } 161 | ``` 162 | 163 | -------------------------------------------------------------------------------- /book/Packages/compile-core/为什么在slot中事件没有更新.md: -------------------------------------------------------------------------------- 1 | # 为什么使用slot,事件没有更新? 2 | 3 | 代码: 4 | 5 | ```html 6 | 7 | 8 | 9 | 10 | 11 | {{ record }} 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |yes130 | no 131 |{{ value + index }}132 |25 |27 | 28 | ``` 29 | 30 | 31 | 32 | 使用compile转换后的代码: 33 | 34 | ```typescript 35 | import { createVNode as _createVNode, toDisplayString as _toDisplayString, resolveComponent as _resolveComponent, withCtx as _withCtx, createTextVNode as _createTextVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from "vue" 36 | 37 | export function render(_ctx, _cache, $props, $setup, $data, $options) { 38 | const _component_B = _resolveComponent("B") 39 | const _component_A = _resolveComponent("A") 40 | 41 | return (_openBlock(), _createBlock(_Fragment, null, [ 42 | _createVNode("button", { 43 | onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.changeText(...args))) 44 | }, "change test"), 45 | _createVNode(_component_A, { 46 | name: _ctx.test, 47 | ref: "A" 48 | }, { 49 | default: _withCtx(({ record }) => [ 50 | _createTextVNode(_toDisplayString(record) + " ", 1 /* TEXT */), 51 | _createVNode(_component_B, { ref: "B" }, { 52 | over: _withCtx(() => [ 53 | _createVNode("button", { 54 | onClick: $event => (_ctx.handleClick(record)) 55 | }, "click me 啊2 ", 8 /* PROPS */, ["onClick"]) 56 | ]), 57 | _: 1 58 | }, 512 /* NEED_PATCH */) 59 | ]), 60 | _: 1 61 | }, 8 /* PROPS */, ["name"]) 62 | ], 64 /* STABLE_FRAGMENT */)) 63 | } 64 | ``` 65 | 66 | A: 67 | 68 | ```typescript 69 | const { renderSlot: _renderSlot, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue 70 | 71 | function render(_ctx, _cache) { 72 | return (_openBlock(), _createBlock("div", null, [ 73 | _renderSlot(_ctx.$slots, "default", { record: _ctx.name }) // { record: _ctx.name } 传入 default 方法中 74 | // createBlock(Fragment, { key: props.key /* 这里参数没有key */ }, default(props), PatchFlags.STABLE_FRAGMENT) 75 | ])) 76 | } 77 | ``` 78 | 79 | 80 | 81 | 点击handle为什么传入的东西还是旧的值?为什么不会更新? 82 | 83 | 84 | 85 | 我现在是假设你已经熟悉首次渲染。 86 | 87 | 我们在更新的时候,在组件A的update中,生成了vnode,该vnode,在patch第一个元素的时候发现record那段textVnode有变化,故更新该元素。 88 | 89 | 第二个元素是组件B,更新组件B前会使用shouldUpdateComponent方法: 90 | 91 | ```typescript 92 | function shouldUpdateComponent(prevVNode, nextVNode, optimized) { 93 | const { props: prevProps, children: prevChildren } = prevVNode; 94 | const { props: nextProps, children: nextChildren, patchFlag } = nextVNode; 95 | // Parent component's render function was hot-updated. Since this may have 96 | // caused the child component's slots content to have changed, we need to 97 | // force the child to update as well. 98 | if ((process.env.NODE_ENV !== 'production') && (prevChildren || nextChildren) && isHmrUpdating) { 99 | return true; 100 | } 101 | // force child update for runtime directive or transition on component vnode. 102 | if (nextVNode.dirs || nextVNode.transition) { 103 | return true; 104 | } 105 | if (patchFlag > 0) { 106 | if (patchFlag & 1024 /* DYNAMIC_SLOTS */) { 107 | // slot content that references values that might have changed, 108 | // e.g. in a v-for 109 | return true; 110 | } 111 | if (patchFlag & 16 /* FULL_PROPS */) { 112 | if (!prevProps) { 113 | return !!nextProps; 114 | } 115 | // presence of this flag indicates props are always non-null 116 | return hasPropsChanged(prevProps, nextProps); 117 | } 118 | else if (patchFlag & 8 /* PROPS */) { 119 | const dynamicProps = nextVNode.dynamicProps; 120 | for (let i = 0; i < dynamicProps.length; i++) { 121 | const key = dynamicProps[i]; 122 | if (nextProps[key] !== prevProps[key]) { 123 | return true; 124 | } 125 | } 126 | } 127 | } 128 | else if (!optimized) { 129 | // this path is only taken by manually written render functions 130 | // so presence of any children leads to a forced update 131 | if (prevChildren || nextChildren) { 132 | if (!nextChildren || !nextChildren.$stable) { 133 | return true; 134 | } 135 | } 136 | if (prevProps === nextProps) { 137 | return false; 138 | } 139 | if (!prevProps) { 140 | return !!nextProps; 141 | } 142 | if (!nextProps) { 143 | return true; 144 | } 145 | return hasPropsChanged(prevProps, nextProps); 146 | } 147 | return false; 148 | } 149 | ``` 150 | 151 | 这个东西是判断你的props和children有没有改变,没有则返回false,所以B组件不会更新。 152 | 153 | 154 | 155 | 那么,你是不是想问,record是指引属性,为什么还是旧的?你可以去认真看看initSlot和renderSlot,是调用为Object的子组件来实行的,旧的record就是在首次渲染的时候,调用defalut中的初始值,应用的地方也就是首次的环境。 156 | 157 | 而新的record根本就没有被更新到组件B上。 158 | 159 | 160 | 161 | **那为什么我直接调用组件B的update方法,还是不更新呢?** 162 | 163 | 这边建议你再去熟悉一下渲染流程,英文组件B的vnode依旧没有发生变化,需要的是组件A创建新的组件B的vnode才能达到你的效果。 164 | 165 | 166 | 167 | ### 解决方法: 168 | 169 | 为了使组件B更新,可以随便绑定一个参数給B,比如 170 | 171 | ```html 172 | 173 | ``` 174 | 175 | 或者 176 | 177 | ```html 178 | 179 | {{ record }} 180 | 181 | ``` 182 | 183 | 184 | 185 | ### 关于修复 186 | 187 | ##### 原始问题:https://github.com/vuejs/vue-next/issues/2618 188 | 189 | **修复方法**:https://github.com/vuejs/vue-next/pull/2568/files -------------------------------------------------------------------------------- /book/Packages/compile-core/问题集合.md: -------------------------------------------------------------------------------- 1 | # 问题集合 2 | 3 | ## proxy与withProxy的区别 4 | 5 | proxy: 6 | 7 | ```typescript 8 | instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers) 9 | ``` 10 | 11 | withProxy: 12 | 13 | ```typ 14 | instance.withProxy = new Proxy( 15 | instance.ctx, 16 | RuntimeCompiledPublicInstanceProxyHandlers 17 | ) 18 | ``` 19 | 20 | 在调用render的时候,proxy会作为他的第一个参数,如果开启render._rc = true,则会设置withProxy,调用render优先使用withProxy作为第一个参数。 21 | 22 | ```typescript 23 | // 继承PublicInstanceProxyHandlers 但是重写get和has 24 | withProxy = extend({}, PublicInstanceProxyHandlers, { 25 | get(){ 26 | // fast path for unscopables when using `with` block 27 | if ((key as any) === Symbol.unscopables) { 28 | return 29 | } 30 | return PublicInstanceProxyHandlers.get!(target, key, target) 31 | }, 32 | has() 33 | }) 34 | ``` 35 | 36 | 37 | 38 | ### get: 39 | 40 | **RuntimeCompiledPublicInstanceProxyHandlers**判断key是否等于**Symbol.unscopables**,如果相等则不做处理,直接return,不相等则和**PublicInstanceProxyHandlers.get**行为一致。**目的是为了跳过get的一系列查询,优化代码性能。** 41 | 42 | 43 | 44 | ### has: 45 | 46 | has方法用来拦截hasProperty操作,即判断对象是否具有某个属性时。**handler.has()** 方法是针对 in 操作符的代理方法。 47 | 48 | #### **PublicInstanceProxyHandlers**.has: 49 | 50 | 查找的顺序为(都为instance的字段): 51 | 52 | 1.accessCache缓存中查找,这个缓存为key对应的类型,缓存起来可以快速在类型中查找 53 | 54 | 2.data中查找 55 | 56 | 3.setupState中查找 57 | 58 | 4.type.props中查找 59 | 60 | 5.ctx中查找 61 | 62 | 6.**publicPropertiesMap**,非instance字段,$data $el $props $attrs等 63 | 64 | 7.**appContext.config.globalProperties**,有且仅有一个的对象,所有有关系的组件都共同指向一个。 65 | 66 | publicPropertiesMap: 67 | 68 | ```typescript 69 | const publicPropertiesMap: Record< 70 | string, 71 | (i: ComponentInternalInstance) => any 72 | > = { 73 | $: i => i, 74 | $el: i => i.vnode.el, 75 | $data: i => i.data, 76 | $props: i => (__DEV__ ? shallowReadonly(i.props) : i.props), 77 | $attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs), 78 | $slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots), 79 | $refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs), 80 | $parent: i => i.parent && i.parent.proxy, 81 | $root: i => i.root && i.root.proxy, 82 | $emit: i => i.emit, 83 | $options: i => (__FEATURE_OPTIONS__ ? resolveMergedOptions(i) : i.type), 84 | $forceUpdate: i => () => queueJob(i.update), 85 | $nextTick: () => nextTick, 86 | $watch: __FEATURE_OPTIONS__ ? i => instanceWatch.bind(i) : NOOP 87 | } 88 | ``` 89 | 90 | 91 | 92 | #### RuntimeCompiledPublicInstanceProxyHandlers.has: 93 | 94 | 判断查询的key,不能为'_'开头,且key不能在全局白名单中。 95 | 96 | ```typescript 97 | has(_: ComponentRenderContext, key: string) { 98 | const has = key[0] !== '_' && !isGloballyWhitelisted(key) 99 | if (__DEV__ && !has && PublicInstanceProxyHandlers.has!(_, key)) { 100 | warn( 101 | `Property ${JSON.stringify( 102 | key 103 | )} should not start with _ which is a reserved prefix for Vue internals.` 104 | ) 105 | } 106 | return has 107 | } 108 | ``` 109 | 110 | ```typescript 111 | const GLOBALS_WHITE_LISTED = 112 | 'Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,' + 113 | 'decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,' + 114 | 'Object,Boolean,String,RegExp,Map,Set,JSON,Intl' 115 | 116 | export const isGloballyWhitelisted = /*#__PURE__*/ makeMap(GLOBALS_WHITE_LISTED) 117 | ``` 118 | 119 | 120 | 121 | ### 总结 122 | 123 | **RuntimeCompiledPublicInstanceProxyHandlers**继承**publicInstanceProxyHandlers**,前者get方法过滤key === **Symbol.unscopables**,has方法过滤基本类型和'_'开头的key。 -------------------------------------------------------------------------------- /book/gitbook/fonts/fontawesome/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/book/gitbook/fonts/fontawesome/FontAwesome.otf -------------------------------------------------------------------------------- /book/gitbook/fonts/fontawesome/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/book/gitbook/fonts/fontawesome/fontawesome-webfont.eot -------------------------------------------------------------------------------- /book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /book/gitbook/fonts/fontawesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/book/gitbook/fonts/fontawesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /book/gitbook/fonts/fontawesome/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/book/gitbook/fonts/fontawesome/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /book/gitbook/gitbook-plugin-fontsettings/fontsettings.js: -------------------------------------------------------------------------------- 1 | require(['gitbook', 'jquery'], function(gitbook, $) { 2 | // Configuration 3 | var MAX_SIZE = 4, 4 | MIN_SIZE = 0, 5 | BUTTON_ID; 6 | 7 | // Current fontsettings state 8 | var fontState; 9 | 10 | // Default themes 11 | var THEMES = [ 12 | { 13 | config: 'white', 14 | text: 'White', 15 | id: 0 16 | }, 17 | { 18 | config: 'sepia', 19 | text: 'Sepia', 20 | id: 1 21 | }, 22 | { 23 | config: 'night', 24 | text: 'Night', 25 | id: 2 26 | } 27 | ]; 28 | 29 | // Default font families 30 | var FAMILIES = [ 31 | { 32 | config: 'serif', 33 | text: 'Serif', 34 | id: 0 35 | }, 36 | { 37 | config: 'sans', 38 | text: 'Sans', 39 | id: 1 40 | } 41 | ]; 42 | 43 | // Return configured themes 44 | function getThemes() { 45 | return THEMES; 46 | } 47 | 48 | // Modify configured themes 49 | function setThemes(themes) { 50 | THEMES = themes; 51 | updateButtons(); 52 | } 53 | 54 | // Return configured font families 55 | function getFamilies() { 56 | return FAMILIES; 57 | } 58 | 59 | // Modify configured font families 60 | function setFamilies(families) { 61 | FAMILIES = families; 62 | updateButtons(); 63 | } 64 | 65 | // Save current font settings 66 | function saveFontSettings() { 67 | gitbook.storage.set('fontState', fontState); 68 | update(); 69 | } 70 | 71 | // Increase font size 72 | function enlargeFontSize(e) { 73 | e.preventDefault(); 74 | if (fontState.size >= MAX_SIZE) return; 75 | 76 | fontState.size++; 77 | saveFontSettings(); 78 | } 79 | 80 | // Decrease font size 81 | function reduceFontSize(e) { 82 | e.preventDefault(); 83 | if (fontState.size <= MIN_SIZE) return; 84 | 85 | fontState.size--; 86 | saveFontSettings(); 87 | } 88 | 89 | // Change font family 90 | function changeFontFamily(configName, e) { 91 | if (e && e instanceof Event) { 92 | e.preventDefault(); 93 | } 94 | 95 | var familyId = getFontFamilyId(configName); 96 | fontState.family = familyId; 97 | saveFontSettings(); 98 | } 99 | 100 | // Change type of color theme 101 | function changeColorTheme(configName, e) { 102 | if (e && e instanceof Event) { 103 | e.preventDefault(); 104 | } 105 | 106 | var $book = gitbook.state.$book; 107 | 108 | // Remove currently applied color theme 109 | if (fontState.theme !== 0) 110 | $book.removeClass('color-theme-'+fontState.theme); 111 | 112 | // Set new color theme 113 | var themeId = getThemeId(configName); 114 | fontState.theme = themeId; 115 | if (fontState.theme !== 0) 116 | $book.addClass('color-theme-'+fontState.theme); 117 | 118 | saveFontSettings(); 119 | } 120 | 121 | // Return the correct id for a font-family config key 122 | // Default to first font-family 123 | function getFontFamilyId(configName) { 124 | // Search for plugin configured font family 125 | var configFamily = $.grep(FAMILIES, function(family) { 126 | return family.config == configName; 127 | })[0]; 128 | // Fallback to default font family 129 | return (!!configFamily)? configFamily.id : 0; 130 | } 131 | 132 | // Return the correct id for a theme config key 133 | // Default to first theme 134 | function getThemeId(configName) { 135 | // Search for plugin configured theme 136 | var configTheme = $.grep(THEMES, function(theme) { 137 | return theme.config == configName; 138 | })[0]; 139 | // Fallback to default theme 140 | return (!!configTheme)? configTheme.id : 0; 141 | } 142 | 143 | function update() { 144 | var $book = gitbook.state.$book; 145 | 146 | $('.font-settings .font-family-list li').removeClass('active'); 147 | $('.font-settings .font-family-list li:nth-child('+(fontState.family+1)+')').addClass('active'); 148 | 149 | $book[0].className = $book[0].className.replace(/\bfont-\S+/g, ''); 150 | $book.addClass('font-size-'+fontState.size); 151 | $book.addClass('font-family-'+fontState.family); 152 | 153 | if(fontState.theme !== 0) { 154 | $book[0].className = $book[0].className.replace(/\bcolor-theme-\S+/g, ''); 155 | $book.addClass('color-theme-'+fontState.theme); 156 | } 157 | } 158 | 159 | function init(config) { 160 | // Search for plugin configured font family 161 | var configFamily = getFontFamilyId(config.family), 162 | configTheme = getThemeId(config.theme); 163 | 164 | // Instantiate font state object 165 | fontState = gitbook.storage.get('fontState', { 166 | size: config.size || 2, 167 | family: configFamily, 168 | theme: configTheme 169 | }); 170 | 171 | update(); 172 | } 173 | 174 | function updateButtons() { 175 | // Remove existing fontsettings buttons 176 | if (!!BUTTON_ID) { 177 | gitbook.toolbar.removeButton(BUTTON_ID); 178 | } 179 | 180 | // Create buttons in toolbar 181 | BUTTON_ID = gitbook.toolbar.createButton({ 182 | icon: 'fa fa-font', 183 | label: 'Font Settings', 184 | className: 'font-settings', 185 | dropdown: [ 186 | [ 187 | { 188 | text: 'A', 189 | className: 'font-reduce', 190 | onClick: reduceFontSize 191 | }, 192 | { 193 | text: 'A', 194 | className: 'font-enlarge', 195 | onClick: enlargeFontSize 196 | } 197 | ], 198 | $.map(FAMILIES, function(family) { 199 | family.onClick = function(e) { 200 | return changeFontFamily(family.config, e); 201 | }; 202 | 203 | return family; 204 | }), 205 | $.map(THEMES, function(theme) { 206 | theme.onClick = function(e) { 207 | return changeColorTheme(theme.config, e); 208 | }; 209 | 210 | return theme; 211 | }) 212 | ] 213 | }); 214 | } 215 | 216 | // Init configuration at start 217 | gitbook.events.bind('start', function(e, config) { 218 | var opts = config.fontsettings; 219 | 220 | // Generate buttons at start 221 | updateButtons(); 222 | 223 | // Init current settings 224 | init(opts); 225 | }); 226 | 227 | // Expose API 228 | gitbook.fontsettings = { 229 | enlargeFontSize: enlargeFontSize, 230 | reduceFontSize: reduceFontSize, 231 | setTheme: changeColorTheme, 232 | setFamily: changeFontFamily, 233 | getThemes: getThemes, 234 | setThemes: setThemes, 235 | getFamilies: getFamilies, 236 | setFamilies: setFamilies 237 | }; 238 | }); 239 | 240 | 241 | -------------------------------------------------------------------------------- /book/gitbook/gitbook-plugin-highlight/ebook.css: -------------------------------------------------------------------------------- 1 | pre, 2 | code { 3 | /* http://jmblog.github.io/color-themes-for-highlightjs */ 4 | /* Tomorrow Comment */ 5 | /* Tomorrow Red */ 6 | /* Tomorrow Orange */ 7 | /* Tomorrow Yellow */ 8 | /* Tomorrow Green */ 9 | /* Tomorrow Aqua */ 10 | /* Tomorrow Blue */ 11 | /* Tomorrow Purple */ 12 | } 13 | pre .hljs-comment, 14 | code .hljs-comment, 15 | pre .hljs-title, 16 | code .hljs-title { 17 | color: #8e908c; 18 | } 19 | pre .hljs-variable, 20 | code .hljs-variable, 21 | pre .hljs-attribute, 22 | code .hljs-attribute, 23 | pre .hljs-tag, 24 | code .hljs-tag, 25 | pre .hljs-regexp, 26 | code .hljs-regexp, 27 | pre .hljs-deletion, 28 | code .hljs-deletion, 29 | pre .ruby .hljs-constant, 30 | code .ruby .hljs-constant, 31 | pre .xml .hljs-tag .hljs-title, 32 | code .xml .hljs-tag .hljs-title, 33 | pre .xml .hljs-pi, 34 | code .xml .hljs-pi, 35 | pre .xml .hljs-doctype, 36 | code .xml .hljs-doctype, 37 | pre .html .hljs-doctype, 38 | code .html .hljs-doctype, 39 | pre .css .hljs-id, 40 | code .css .hljs-id, 41 | pre .css .hljs-class, 42 | code .css .hljs-class, 43 | pre .css .hljs-pseudo, 44 | code .css .hljs-pseudo { 45 | color: #c82829; 46 | } 47 | pre .hljs-number, 48 | code .hljs-number, 49 | pre .hljs-preprocessor, 50 | code .hljs-preprocessor, 51 | pre .hljs-pragma, 52 | code .hljs-pragma, 53 | pre .hljs-built_in, 54 | code .hljs-built_in, 55 | pre .hljs-literal, 56 | code .hljs-literal, 57 | pre .hljs-params, 58 | code .hljs-params, 59 | pre .hljs-constant, 60 | code .hljs-constant { 61 | color: #f5871f; 62 | } 63 | pre .ruby .hljs-class .hljs-title, 64 | code .ruby .hljs-class .hljs-title, 65 | pre .css .hljs-rules .hljs-attribute, 66 | code .css .hljs-rules .hljs-attribute { 67 | color: #eab700; 68 | } 69 | pre .hljs-string, 70 | code .hljs-string, 71 | pre .hljs-value, 72 | code .hljs-value, 73 | pre .hljs-inheritance, 74 | code .hljs-inheritance, 75 | pre .hljs-header, 76 | code .hljs-header, 77 | pre .hljs-addition, 78 | code .hljs-addition, 79 | pre .ruby .hljs-symbol, 80 | code .ruby .hljs-symbol, 81 | pre .xml .hljs-cdata, 82 | code .xml .hljs-cdata { 83 | color: #718c00; 84 | } 85 | pre .css .hljs-hexcolor, 86 | code .css .hljs-hexcolor { 87 | color: #3e999f; 88 | } 89 | pre .hljs-function, 90 | code .hljs-function, 91 | pre .python .hljs-decorator, 92 | code .python .hljs-decorator, 93 | pre .python .hljs-title, 94 | code .python .hljs-title, 95 | pre .ruby .hljs-function .hljs-title, 96 | code .ruby .hljs-function .hljs-title, 97 | pre .ruby .hljs-title .hljs-keyword, 98 | code .ruby .hljs-title .hljs-keyword, 99 | pre .perl .hljs-sub, 100 | code .perl .hljs-sub, 101 | pre .javascript .hljs-title, 102 | code .javascript .hljs-title, 103 | pre .coffeescript .hljs-title, 104 | code .coffeescript .hljs-title { 105 | color: #4271ae; 106 | } 107 | pre .hljs-keyword, 108 | code .hljs-keyword, 109 | pre .javascript .hljs-function, 110 | code .javascript .hljs-function { 111 | color: #8959a8; 112 | } 113 | pre .hljs, 114 | code .hljs { 115 | display: block; 116 | background: white; 117 | color: #4d4d4c; 118 | padding: 0.5em; 119 | } 120 | pre .coffeescript .javascript, 121 | code .coffeescript .javascript, 122 | pre .javascript .xml, 123 | code .javascript .xml, 124 | pre .tex .hljs-formula, 125 | code .tex .hljs-formula, 126 | pre .xml .javascript, 127 | code .xml .javascript, 128 | pre .xml .vbscript, 129 | code .xml .vbscript, 130 | pre .xml .css, 131 | code .xml .css, 132 | pre .xml .hljs-cdata, 133 | code .xml .hljs-cdata { 134 | opacity: 0.5; 135 | } 136 | -------------------------------------------------------------------------------- /book/gitbook/gitbook-plugin-lunr/search-lunr.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | // Define global search engine 6 | function LunrSearchEngine() { 7 | this.index = null; 8 | this.store = {}; 9 | this.name = 'LunrSearchEngine'; 10 | } 11 | 12 | // Initialize lunr by fetching the search index 13 | LunrSearchEngine.prototype.init = function() { 14 | var that = this; 15 | var d = $.Deferred(); 16 | 17 | $.getJSON(gitbook.state.basePath+'/search_index.json') 18 | .then(function(data) { 19 | // eslint-disable-next-line no-undef 20 | that.index = lunr.Index.load(data.index); 21 | that.store = data.store; 22 | d.resolve(); 23 | }); 24 | 25 | return d.promise(); 26 | }; 27 | 28 | // Search for a term and return results 29 | LunrSearchEngine.prototype.search = function(q, offset, length) { 30 | var that = this; 31 | var results = []; 32 | 33 | if (this.index) { 34 | results = $.map(this.index.search(q), function(result) { 35 | var doc = that.store[result.ref]; 36 | 37 | return { 38 | title: doc.title, 39 | url: doc.url, 40 | body: doc.summary || doc.body 41 | }; 42 | }); 43 | } 44 | 45 | return $.Deferred().resolve({ 46 | query: q, 47 | results: results.slice(0, length), 48 | count: results.length 49 | }).promise(); 50 | }; 51 | 52 | // Set gitbook research 53 | gitbook.events.bind('start', function(e, config) { 54 | var engine = gitbook.search.getEngine(); 55 | if (!engine) { 56 | gitbook.search.setEngine(LunrSearchEngine, config); 57 | } 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /book/gitbook/gitbook-plugin-search/search-engine.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | // Global search objects 6 | var engine = null; 7 | var initialized = false; 8 | 9 | // Set a new search engine 10 | function setEngine(Engine, config) { 11 | initialized = false; 12 | engine = new Engine(config); 13 | 14 | init(config); 15 | } 16 | 17 | // Initialize search engine with config 18 | function init(config) { 19 | if (!engine) throw new Error('No engine set for research. Set an engine using gitbook.research.setEngine(Engine).'); 20 | 21 | return engine.init(config) 22 | .then(function() { 23 | initialized = true; 24 | gitbook.events.trigger('search.ready'); 25 | }); 26 | } 27 | 28 | // Launch search for query q 29 | function query(q, offset, length) { 30 | if (!initialized) throw new Error('Search has not been initialized'); 31 | return engine.search(q, offset, length); 32 | } 33 | 34 | // Get stats about search 35 | function getEngine() { 36 | return engine? engine.name : null; 37 | } 38 | 39 | function isInitialized() { 40 | return initialized; 41 | } 42 | 43 | // Initialize gitbook.search 44 | gitbook.search = { 45 | setEngine: setEngine, 46 | getEngine: getEngine, 47 | query: query, 48 | isInitialized: isInitialized 49 | }; 50 | }); -------------------------------------------------------------------------------- /book/gitbook/gitbook-plugin-search/search.css: -------------------------------------------------------------------------------- 1 | /* 2 | This CSS only styled the search results section, not the search input 3 | It defines the basic interraction to hide content when displaying results, etc 4 | */ 5 | #book-search-results .search-results { 6 | display: none; 7 | } 8 | #book-search-results .search-results ul.search-results-list { 9 | list-style-type: none; 10 | padding-left: 0; 11 | } 12 | #book-search-results .search-results ul.search-results-list li { 13 | margin-bottom: 1.5rem; 14 | padding-bottom: 0.5rem; 15 | /* Highlight results */ 16 | } 17 | #book-search-results .search-results ul.search-results-list li p em { 18 | background-color: rgba(255, 220, 0, 0.4); 19 | font-style: normal; 20 | } 21 | #book-search-results .search-results .no-results { 22 | display: none; 23 | } 24 | #book-search-results.open .search-results { 25 | display: block; 26 | } 27 | #book-search-results.open .search-noresults { 28 | display: none; 29 | } 30 | #book-search-results.no-results .search-results .has-results { 31 | display: none; 32 | } 33 | #book-search-results.no-results .search-results .no-results { 34 | display: block; 35 | } 36 | -------------------------------------------------------------------------------- /book/gitbook/gitbook-plugin-search/search.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | var MAX_RESULTS = 15; 6 | var MAX_DESCRIPTION_SIZE = 500; 7 | 8 | var usePushState = (typeof history.pushState !== 'undefined'); 9 | 10 | // DOM Elements 11 | var $body = $('body'); 12 | var $bookSearchResults; 13 | var $searchInput; 14 | var $searchList; 15 | var $searchTitle; 16 | var $searchResultsCount; 17 | var $searchQuery; 18 | 19 | // Throttle search 20 | function throttle(fn, wait) { 21 | var timeout; 22 | 23 | return function() { 24 | var ctx = this, args = arguments; 25 | if (!timeout) { 26 | timeout = setTimeout(function() { 27 | timeout = null; 28 | fn.apply(ctx, args); 29 | }, wait); 30 | } 31 | }; 32 | } 33 | 34 | function displayResults(res) { 35 | $bookSearchResults.addClass('open'); 36 | 37 | var noResults = res.count == 0; 38 | $bookSearchResults.toggleClass('no-results', noResults); 39 | 40 | // Clear old results 41 | $searchList.empty(); 42 | 43 | // Display title for research 44 | $searchResultsCount.text(res.count); 45 | $searchQuery.text(res.query); 46 | 47 | // Create an26 | element for each result 48 | res.results.forEach(function(res) { 49 | var $li = $(' ', { 50 | 'class': 'search-results-item' 51 | }); 52 | 53 | var $title = $(' '); 54 | 55 | var $link = $('', { 56 | 'href': gitbook.state.basePath + '/' + res.url, 57 | 'text': res.title 58 | }); 59 | 60 | var content = res.body.trim(); 61 | if (content.length > MAX_DESCRIPTION_SIZE) { 62 | content = content.slice(0, MAX_DESCRIPTION_SIZE).trim()+'...'; 63 | } 64 | var $content = $('
').html(content); 65 | 66 | $link.appendTo($title); 67 | $title.appendTo($li); 68 | $content.appendTo($li); 69 | $li.appendTo($searchList); 70 | }); 71 | } 72 | 73 | function launchSearch(q) { 74 | // Add class for loading 75 | $body.addClass('with-search'); 76 | $body.addClass('search-loading'); 77 | 78 | // Launch search query 79 | throttle(gitbook.search.query(q, 0, MAX_RESULTS) 80 | .then(function(results) { 81 | displayResults(results); 82 | }) 83 | .always(function() { 84 | $body.removeClass('search-loading'); 85 | }), 1000); 86 | } 87 | 88 | function closeSearch() { 89 | $body.removeClass('with-search'); 90 | $bookSearchResults.removeClass('open'); 91 | } 92 | 93 | function launchSearchFromQueryString() { 94 | var q = getParameterByName('q'); 95 | if (q && q.length > 0) { 96 | // Update search input 97 | $searchInput.val(q); 98 | 99 | // Launch search 100 | launchSearch(q); 101 | } 102 | } 103 | 104 | function bindSearch() { 105 | // Bind DOM 106 | $searchInput = $('#book-search-input input'); 107 | $bookSearchResults = $('#book-search-results'); 108 | $searchList = $bookSearchResults.find('.search-results-list'); 109 | $searchTitle = $bookSearchResults.find('.search-results-title'); 110 | $searchResultsCount = $searchTitle.find('.search-results-count'); 111 | $searchQuery = $searchTitle.find('.search-query'); 112 | 113 | // Launch query based on input content 114 | function handleUpdate() { 115 | var q = $searchInput.val(); 116 | 117 | if (q.length == 0) { 118 | closeSearch(); 119 | } 120 | else { 121 | launchSearch(q); 122 | } 123 | } 124 | 125 | // Detect true content change in search input 126 | // Workaround for IE < 9 127 | var propertyChangeUnbound = false; 128 | $searchInput.on('propertychange', function(e) { 129 | if (e.originalEvent.propertyName == 'value') { 130 | handleUpdate(); 131 | } 132 | }); 133 | 134 | // HTML5 (IE9 & others) 135 | $searchInput.on('input', function(e) { 136 | // Unbind propertychange event for IE9+ 137 | if (!propertyChangeUnbound) { 138 | $(this).unbind('propertychange'); 139 | propertyChangeUnbound = true; 140 | } 141 | 142 | handleUpdate(); 143 | }); 144 | 145 | // Push to history on blur 146 | $searchInput.on('blur', function(e) { 147 | // Update history state 148 | if (usePushState) { 149 | var uri = updateQueryString('q', $(this).val()); 150 | history.pushState({ path: uri }, null, uri); 151 | } 152 | }); 153 | } 154 | 155 | gitbook.events.on('page.change', function() { 156 | bindSearch(); 157 | closeSearch(); 158 | 159 | // Launch search based on query parameter 160 | if (gitbook.search.isInitialized()) { 161 | launchSearchFromQueryString(); 162 | } 163 | }); 164 | 165 | gitbook.events.on('search.ready', function() { 166 | bindSearch(); 167 | 168 | // Launch search from query param at start 169 | launchSearchFromQueryString(); 170 | }); 171 | 172 | function getParameterByName(name) { 173 | var url = window.location.href; 174 | name = name.replace(/[\[\]]/g, '\\$&'); 175 | var regex = new RegExp('[?&]' + name + '(=([^]*)|&|#|$)', 'i'), 176 | results = regex.exec(url); 177 | if (!results) return null; 178 | if (!results[2]) return ''; 179 | return decodeURIComponent(results[2].replace(/\+/g, ' ')); 180 | } 181 | 182 | function updateQueryString(key, value) { 183 | value = encodeURIComponent(value); 184 | 185 | var url = window.location.href; 186 | var re = new RegExp('([?&])' + key + '=.*?(&|#|$)(.*)', 'gi'), 187 | hash; 188 | 189 | if (re.test(url)) { 190 | if (typeof value !== 'undefined' && value !== null) 191 | return url.replace(re, '$1' + key + '=' + value + '$2$3'); 192 | else { 193 | hash = url.split('#'); 194 | url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, ''); 195 | if (typeof hash[1] !== 'undefined' && hash[1] !== null) 196 | url += '#' + hash[1]; 197 | return url; 198 | } 199 | } 200 | else { 201 | if (typeof value !== 'undefined' && value !== null) { 202 | var separator = url.indexOf('?') !== -1 ? '&' : '?'; 203 | hash = url.split('#'); 204 | url = hash[0] + separator + key + '=' + value; 205 | if (typeof hash[1] !== 'undefined' && hash[1] !== null) 206 | url += '#' + hash[1]; 207 | return url; 208 | } 209 | else 210 | return url; 211 | } 212 | } 213 | }); 214 | -------------------------------------------------------------------------------- /book/gitbook/gitbook-plugin-sharing/buttons.js: -------------------------------------------------------------------------------- 1 | require(['gitbook', 'jquery'], function(gitbook, $) { 2 | var SITES = { 3 | 'facebook': { 4 | 'label': 'Facebook', 5 | 'icon': 'fa fa-facebook', 6 | 'onClick': function(e) { 7 | e.preventDefault(); 8 | window.open('http://www.facebook.com/sharer/sharer.php?s=100&p[url]='+encodeURIComponent(location.href)); 9 | } 10 | }, 11 | 'twitter': { 12 | 'label': 'Twitter', 13 | 'icon': 'fa fa-twitter', 14 | 'onClick': function(e) { 15 | e.preventDefault(); 16 | window.open('http://twitter.com/home?status='+encodeURIComponent(document.title+' '+location.href)); 17 | } 18 | }, 19 | 'google': { 20 | 'label': 'Google+', 21 | 'icon': 'fa fa-google-plus', 22 | 'onClick': function(e) { 23 | e.preventDefault(); 24 | window.open('https://plus.google.com/share?url='+encodeURIComponent(location.href)); 25 | } 26 | }, 27 | 'weibo': { 28 | 'label': 'Weibo', 29 | 'icon': 'fa fa-weibo', 30 | 'onClick': function(e) { 31 | e.preventDefault(); 32 | window.open('http://service.weibo.com/share/share.php?content=utf-8&url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)); 33 | } 34 | }, 35 | 'instapaper': { 36 | 'label': 'Instapaper', 37 | 'icon': 'fa fa-instapaper', 38 | 'onClick': function(e) { 39 | e.preventDefault(); 40 | window.open('http://www.instapaper.com/text?u='+encodeURIComponent(location.href)); 41 | } 42 | }, 43 | 'vk': { 44 | 'label': 'VK', 45 | 'icon': 'fa fa-vk', 46 | 'onClick': function(e) { 47 | e.preventDefault(); 48 | window.open('http://vkontakte.ru/share.php?url='+encodeURIComponent(location.href)); 49 | } 50 | } 51 | }; 52 | 53 | 54 | 55 | gitbook.events.bind('start', function(e, config) { 56 | var opts = config.sharing; 57 | 58 | // Create dropdown menu 59 | var menu = $.map(opts.all, function(id) { 60 | var site = SITES[id]; 61 | 62 | return { 63 | text: site.label, 64 | onClick: site.onClick 65 | }; 66 | }); 67 | 68 | // Create main button with dropdown 69 | if (menu.length > 0) { 70 | gitbook.toolbar.createButton({ 71 | icon: 'fa fa-share-alt', 72 | label: 'Share', 73 | position: 'right', 74 | dropdown: [menu] 75 | }); 76 | } 77 | 78 | // Direct actions to share 79 | $.each(SITES, function(sideId, site) { 80 | if (!opts[sideId]) return; 81 | 82 | gitbook.toolbar.createButton({ 83 | icon: site.icon, 84 | label: site.text, 85 | position: 'right', 86 | onClick: site.onClick 87 | }); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /book/gitbook/images/apple-touch-icon-precomposed-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/book/gitbook/images/apple-touch-icon-precomposed-152.png -------------------------------------------------------------------------------- /book/gitbook/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/book/gitbook/images/favicon.ico -------------------------------------------------------------------------------- /book/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-book", 3 | "version": "1.0.0", 4 | "description": "vue3做了最大的变化就是api的细分,适配typescript。給我一种感觉就是,vue3像乐高,一个个拼接起来成模块, 模块之间的互相组合,来构成一个整体, 这样更利于团队开发了,可以根据团队情况来定制合适的开发架构。composition-api的出现,如果想真正利用好,弄懂vue3源码是必须的。 vue3中重要的包:Reactivity、runtime-x和 compiler-x。", 5 | "main": "index.js", 6 | "scripts": { 7 | "serve": "gitbook serve", 8 | "build": "gitbook build ./ ./book", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /book/update.md: -------------------------------------------------------------------------------- 1 | # 今日更新 2 | 3 | **2020年12月13日22点39分** 4 | 5 | 补充了一下响应式的例子,因为群里有人问 6 | 7 | > “就拿监听来讲,它是怎么做到我们在里面丢进去的值里,哪个发生改变,哪个就进行输出的” 8 | 9 | > “我就丢进去了个事件,这个事件里有三行代码,每行代码都是与响应式有关,哇擦,它能做到某个值发生改变后,就将某个值相对应的console进行输出” 10 | 11 | > “我也能联想到这个关系到收集响应值及所依赖的,就是不太懂它是怎么做到上面那样功能的,是将传过去的函数转成字符串再进行文本解析出响应值吗” 12 | 13 |  -------------------------------------------------------------------------------- /book/更新/2020年12月13日22-42.md: -------------------------------------------------------------------------------- 1 | # 今日更新 2 | 3 | **2020年12月13日22点39分** 4 | 5 | 补充了一下响应式的例子,因为群里有人问 6 | 7 | > “就拿监听来讲,它是怎么做到我们在里面丢进去的值里,哪个发生改变,哪个就进行输出的” 8 | 9 | > “我就丢进去了个事件,这个事件里有三行代码,每行代码都是与响应式有关,哇擦,它能做到某个值发生改变后,就将某个值相对应的console进行输出” 10 | 11 | > “我也能联想到这个关系到收集响应值及所依赖的,就是不太懂它是怎么做到上面那样功能的,是将传过去的函数转成字符串再进行文本解析出响应值吗” 12 | 13 |  -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-book", 3 | "version": "1.0.0", 4 | "description": "vue3做了最大的变化就是api的细分,适配typescript。給我一种感觉就是,vue3像乐高,一个个拼接起来成模块, 模块之间的互相组合,来构成一个整体, 这样更利于团队开发了,可以根据团队情况来定制合适的开发架构。composition-api的出现,如果想真正利用好,弄懂vue3源码是必须的。 vue3中重要的包:Reactivity、runtime-x和 compiler-x。", 5 | "main": "index.js", 6 | "scripts": { 7 | "serve": "gitbook serve", 8 | "build": "gitbook build ./ ./book", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /runtime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingbultsea/vue3-analysis/34c72e01b0a7fc133884d287cd494947f4f7c60a/runtime.png -------------------------------------------------------------------------------- /update.md: -------------------------------------------------------------------------------- 1 | # 今日更新 2 | 3 | **2020年12月13日22点39分** 4 | 5 | 补充了一下响应式的例子,因为群里有人问 6 | 7 | > “就拿监听来讲,它是怎么做到我们在里面丢进去的值里,哪个发生改变,哪个就进行输出的” 8 | 9 | > “我就丢进去了个事件,这个事件里有三行代码,每行代码都是与响应式有关,哇擦,它能做到某个值发生改变后,就将某个值相对应的console进行输出” 10 | 11 | > “我也能联想到这个关系到收集响应值及所依赖的,就是不太懂它是怎么做到上面那样功能的,是将传过去的函数转成字符串再进行文本解析出响应值吗” 12 | 13 |  -------------------------------------------------------------------------------- /更新/2020年12月13日22-42.md: -------------------------------------------------------------------------------- 1 | # 今日更新 2 | 3 | **2020年12月13日22点39分** 4 | 5 | 补充了一下响应式的例子,因为群里有人问 6 | 7 | > “就拿监听来讲,它是怎么做到我们在里面丢进去的值里,哪个发生改变,哪个就进行输出的” 8 | 9 | > “我就丢进去了个事件,这个事件里有三行代码,每行代码都是与响应式有关,哇擦,它能做到某个值发生改变后,就将某个值相对应的console进行输出” 10 | 11 | > “我也能联想到这个关系到收集响应值及所依赖的,就是不太懂它是怎么做到上面那样功能的,是将传过去的函数转成字符串再进行文本解析出响应值吗” 12 | 13 |  --------------------------------------------------------------------------------