├── .gitignore ├── README.md ├── babel.config.js ├── doc ├── 1.简介.md ├── 10.表单输入绑定.md ├── 11.生命周期.md ├── 12.侦听器.md ├── 13.模版引用.md ├── 14.组件基础.md ├── 2.创建应用.md ├── 3.模版语法.md ├── 4.响应式基础.md ├── 5.计算属性.md ├── 6.类与样式绑定.md ├── 7.条件渲染.md ├── 8.列表渲染.md ├── 9.事件处理.md └── high │ ├── 1.注册.md │ ├── 2.Props.md │ ├── 3.事件.md │ ├── 4.透传Attributes.md │ ├── 5.插槽slot.md │ └── 6.依赖注入.md ├── example ├── apiInject │ ├── App.js │ └── index.html ├── compiler-base │ ├── App.js │ ├── index.html │ └── main.js ├── componentEmit │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── componentSlot │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── componentUpdate │ ├── App.js │ ├── Child.js │ ├── index.html │ └── main.js ├── currentInstance │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── customRenderer │ ├── App.js │ ├── index.html │ └── main.js ├── helloword │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── nextTicker │ ├── App.js │ ├── index.html │ └── main.js ├── patchChildren │ ├── App.js │ ├── ArrayToArray.js │ ├── ArrayToText.js │ ├── TextToArray.js │ ├── TextToText.js │ ├── index.html │ └── main.js ├── state.js └── update │ ├── App.js │ ├── index.html │ └── main.js ├── lib ├── guide-mini-vue.cjs.js └── guide-mini-vue.esm.js ├── package.json ├── rollup.config.js ├── src ├── compiler-core │ ├── src │ │ ├── ast.ts │ │ ├── codegen.ts │ │ ├── compiler.ts │ │ ├── index.ts │ │ ├── parse.ts │ │ ├── runtimeHelpers.ts │ │ ├── transform.ts │ │ ├── transforms │ │ │ ├── transformElement.ts │ │ │ ├── transformExpression.ts │ │ │ └── transformText.ts │ │ └── utils.ts │ └── tests │ │ ├── __snapshots__ │ │ └── codegen.spec.ts.snap │ │ ├── codegen.spec.ts │ │ ├── parse.spec.ts │ │ └── transform.spec.ts ├── index.ts ├── reactivity │ ├── baseHandlers.ts │ ├── computed.ts │ ├── effect.ts │ ├── index.ts │ ├── reactive.ts │ ├── ref.ts │ └── test │ │ ├── computed.spec.ts │ │ ├── effect.spec.ts │ │ ├── reactive.spec.ts │ │ ├── readonly.spec.ts │ │ ├── ref.spec.ts │ │ └── shallowReadonly.spec.ts ├── runtime-core │ ├── apiInject.ts │ ├── component.ts │ ├── componentEmit.ts │ ├── componentProps.ts │ ├── componentPublicinstance.ts │ ├── componentSlots.ts │ ├── componentUpdateUtils.ts │ ├── createApp.ts │ ├── h.ts │ ├── helpers │ │ └── renderSlots.ts │ ├── index.ts │ ├── renderer.ts │ ├── scheduler.ts │ └── vnode.ts ├── runtime-dom │ └── index.ts └── shared │ ├── ShapeFlag.ts │ ├── index.ts │ ├── test.ts │ └── toDisplayString.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | admindb/ 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | /test/unit/coverage/ 9 | /test/e2e/reports/ 10 | selenium-debug.log 11 | 12 | # Editor directories and files 13 | .idea 14 | .vscode 15 | *.suo 16 | *.ntvs* 17 | *.njsproj 18 | *.sln 19 | package-lock.json 20 | .env.production.local 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-mini-project 2 | 3 | vue-mini-project 4 | vue-mini 实现 5 | 6 | 1. [x] dep done 7 | 2. [x] reactive done 8 | 3. [x] render done 9 | 4. [x] setup 10 | 5. [x] effect & reactive 11 | 6. [x] runner 12 | 7. [x] scheduler 13 | 8. [x] stop 14 | 9. [x] readonly 15 | 10. [x] isReactive isReadonly 16 | 11. [x] shallowreadonly 17 | 12. [x] isref unref 18 | 13. [x] proxyRefs 19 | 14. [x] computed 20 | 15. [x] rollup 21 | 16. [x] element 22 | 17. [x] 组件代理对象 23 | 18. [x] shapeflags 24 | 19. [x] 注册事件 25 | 20. [x] props 26 | 21. [x] emit 27 | 22. [x] slots 28 | 23. [x] props 29 | 24. [x] children 30 | 25. [x] diff 31 | 26. [x] 组件更新 32 | 27. [x] nextTick 33 | 28. [x] test 34 | 29. [x] 编译流程 35 | 30. [x] 插值解析 36 | 31. [x] 解析 element 标签 37 | 32. [x] 解析 text 38 | 33. [x] 解析 text+element 39 | 34. [x] 有限状态机 40 | 35. [x] string--> render + slot-->render + 联合类型 41 | 36. [x] template --> render 42 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: { node: "current" } }], 4 | "@babel/preset-typescript", 5 | ], 6 | }; 7 | // nodejs 下默认的模块规范是common 8 | -------------------------------------------------------------------------------- /doc/1.简介.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 3 | ## vue 4 | 5 | vue 的两个核心功能: 6 | 7 | 1. 声明式渲染:Vue 基于标准 HTML 拓展了一套模版语法,使得我们可以声明式的描述最终输出的 HTML 和 JavaScript 状态之间的关系。 8 | 2. 响应式:Vue 会自动跟踪 JavaScript 状态变化并在改变发生时响应式的更新 DOM。 9 | 10 | ## 渐进式框架 11 | 12 | Vue 是一个框架,也是一个生态。其功能覆盖了大部分前端开发常见的需求。但 Web 世界是十分多样化的,不同的开发者在 Web 上构建的东西可能在形式和规模上会有很大的不同。考虑到这一点,Vue 的设计非常注重灵活性和“可以被逐步集成”这个特点。根据你的需求场景,你可以用不同的方式使用 Vue: 13 | 14 | - 无需构建步骤,渐进式增强静态的 HTML 15 | - 在任何页面中作为 Web Components 嵌入 16 | - 单页应用(SPA) 17 | - 全栈/服务端渲染(SSR) 18 | - Jam stack / 静态站点生成(SSG) 19 | - 开发桌面端,移动端,WebGL,甚至是命令行终端中的界面。 20 | 21 | ## 单文件组件 22 | 23 | 在大多数启用了构建工具的 Vue 项目中,我们可以使用一种类似 HTML 格式的文件来书写 Vue 组件,它被称为单文件组件 (也被称为 \*.vue 文件,英文 Single-File Components,缩写为 SFC)。顾名思义,Vue 的单文件组件会将一个组件的逻辑 (JavaScript),模板 (HTML) 和样式 (CSS) 封装在同一个文件里。下面我们将用单文件组件的格式重写上面的计数器示例: 24 | 25 | ```vue 26 | 35 | 36 | 39 | 40 | 45 | ``` 46 | 47 | ## API 风格 48 | 49 | Vue 的组件可以按两种不同风格书写:选项式 API 和组合式 API. 50 | 51 | ### 选项式 API(Options API) 52 | 53 | 使用选项式 API,我们可以用包含多个选项的对象来描述组件的逻辑,例如 data、methods 和 mounted。选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例。 54 | 55 | ```vue 56 | 81 | 82 | 85 | ``` 86 | 87 | ### 组合式 API(Composition API) 88 | 89 | 通过组合式 API,我们可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与 ` 108 | 111 | ``` 112 | 113 | 两种 API 风格都能够覆盖大部分的应用场景。它们只是同一个底层系统所提供的两套不同的接口。实际上,选项式 API 是在组合式 API 的基础上实现的!关于 Vue 的基础概念和知识在它们之间都是通用的。 114 | 115 | 选项式 API 以“组件实例”的概念为中心 (即上述例子中的 this),对于有面向对象语言背景的用户来说,这通常与基于类的心智模型更为一致。同时,它将响应性相关的细节抽象出来,并强制按照选项来组织代码,从而对初学者而言更为友好。 116 | 117 | 组合式 API 的核心思想是直接在函数作用域内定义响应式状态变量,并将从多个函数中得到的状态组合起来处理复杂问题。这种形式更加自由,也需要你对 Vue 的响应式系统有更深的理解才能高效使用。相应的,它的灵活性也使得组织和重用逻辑的模式变得更加强大。 118 | -------------------------------------------------------------------------------- /doc/10.表单输入绑定.md: -------------------------------------------------------------------------------- 1 | # 表单输入绑定 2 | 3 | 在前端处理表单时,我们常常需要将表单输入框的内容同步给 JavaScript 中响应的变量。手动链接值绑定和更改事件监听器可能会很麻烦。 4 | 5 | ```html 6 | 7 | ``` 8 | 9 | v-model 指令帮我们简化了这一步骤。 10 | 11 | ```html 12 | 13 | ``` 14 | 15 | 另外,v-model 还可以用于各种不同类型的输入, 41 | ``` 42 | 43 | 注意在 48 | 49 | 50 | 51 | ``` 52 | 53 | ### 复选框 54 | 55 | 单一的复选框,绑定布尔类型值: 56 | 57 | ```html 58 | 59 | 60 | ``` 61 | 62 | 我们也可以将多个复选框绑定到同一个数组或集合的值: 63 | 64 | ```js 65 | export default { 66 | data() { 67 | return { 68 | checkedNames: [], 69 | }; 70 | }, 71 | }; 72 | ``` 73 | 74 | ```html 75 |
Checked names: {{ checkedNames }}
76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | ``` 86 | 87 | 在这个例子中,checkedNames 数组将始终包含所有当前被选中的框的值。 88 | 89 | ### 单选按钮 90 | ```html 91 |
Picked: {{ picked }}
92 | 93 | 94 | 95 | 96 | 97 | 98 | ``` 99 | ### 选择器 100 | 单个选择器的示例如下: 101 | ```html 102 |
Selected: {{ selected }}
103 | 104 | 110 | ``` 111 | tip: 112 | 如果 v-model 表达式的初始值不匹配任何一个选择项, 117 | 118 | 119 | 120 | 121 | ``` 122 | 选择器的选项可以使用 v-for 动态渲染: 123 | ```js 124 | export default { 125 | data() { 126 | return { 127 | selected: 'A', 128 | options: [ 129 | { text: 'One', value: 'A' }, 130 | { text: 'Two', value: 'B' }, 131 | { text: 'Three', value: 'C' } 132 | ] 133 | } 134 | } 135 | } 136 | ``` 137 | ```html 138 | 143 | 144 |
Selected: {{ selected }}
145 | ``` 146 | ## 值绑定 147 | 对于单选按钮,复选框和选择器选项,v-model 绑定的值通常是静态的字符串 (或者对复选框是布尔值): 148 | 149 | ```html 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 160 | ``` 161 | 但有时我们可能希望将该值绑定到当前组件实例上的动态数据。这可以通过使用 v-bind 来实现。此外,使用 v-bind 还使我们可以将选项值绑定为非字符串的数据类型。 162 | ### 复选框 163 | ```html 164 | 169 | ``` 170 | true-value 和 false-value 是 Vue 特有的 attributes,仅支持和 v-model 配套使用。这里 toggle 属性的值会在选中时被设为 'yes',取消选择时设为 'no'。你同样可以通过 v-bind 将其绑定为其他动态值: 171 | 172 | ```html 173 | 178 | ``` 179 | tip: 180 | `true-value` 和 `false-value` attributes 不会影响 value attribute,因为浏览器在表单提交时,并不会包含未选择的复选框。为了保证这两个值 (例如:“yes”和“no”) 的其中之一被表单提交,请使用单选按钮作为替代。 181 | ### 单选按钮 182 | ```html 183 | 184 | 185 | ``` 186 | pick 会在第一个按钮选中时被设为 first,在第二个按钮选中时被设为 second。 187 | ### 选择器选项 188 | ```html 189 | 193 | ``` 194 | v-model 同样也支持非字符串类型的值绑定!在上面这个例子中,当某个选项被选中,selected 会被设为该对象字面量值 { number: 123 }。 195 | ## 修饰符 196 | ### .lazy 197 | 默认情况下,v-model 会在每次 input 事件后更新数据 (IME 拼字阶段的状态例外)。你可以添加 lazy 修饰符来改为在每次 change 事件后更新数据: 198 | ```html 199 | 200 | 201 | ``` 202 | ### .number 203 | 如果你想让用户输入自动转换为数字,你可以在 v-model 后添加 .number 修饰符来管理输入: 204 | ```html 205 | 206 | ``` 207 | 如果该值无法被 parseFloat() 处理,那么将返回原始值。 208 | 209 | number 修饰符会在输入框有 `type="number"` 时自动启用。 210 | ### .trim 211 | 如果你想要默认自动去除用户输入内容中两端的空格,你可以在 v-model 后添加 .trim 修饰符: 212 | ```html 213 | 214 | ``` 215 | ## 组件上的 v-model 216 | HTML的内置表单输入类型并不总能班组所有需求。但是我们可以使用Vue构建具有自定义行为的复用输入组件,并且这些输入组件也支持 v-model. -------------------------------------------------------------------------------- /doc/11.生命周期.md: -------------------------------------------------------------------------------- 1 | # 生命周期 2 | 每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码。 3 | ## 注册周期钩子 4 | 举例来说,mounted 钩子可以用来在组件完成初始渲染并创建 DOM 节点后运行代码: 5 | 6 | ```js 7 | export default { 8 | mounted() { 9 | console.log(`the component is now mounted.`) 10 | } 11 | } 12 | ``` 13 | 还有其他一些钩子,会在实例生命周期的不同阶段被调用,最常用的是 mounted、updated 和 unmounted。 14 | 15 | 所有生命周期钩子函数的 this 上下文都会自动指向当前调用它的组件实例。注意:避免用箭头函数来定义生命周期钩子,因为如果这样的话你将无法在函数中通过 this 获取组件实例。 16 | 17 | ## 生命周期图示 18 | ![alt 属性文本](https://cn.vuejs.org/assets/lifecycle.16e4c08e.png) -------------------------------------------------------------------------------- /doc/12.侦听器.md: -------------------------------------------------------------------------------- 1 | # 侦听器 2 | ## 基本示例 3 | 计算属性允许我们声明性的计算衍生值。然而在有些情况下,我们需要在状态变化的时候执行一些副作用。例如更改DOM,或是根据异步操作的结果去修改另一处的状态。 4 | 在选项式API中,我们可以使用watch选项在每次响应式属性发生变化时触发一个函数。 5 | ```js 6 | export default { 7 | data() { 8 | return { 9 | question: '', 10 | answer: 'Questions usually contain a question mark. ;-)' 11 | } 12 | }, 13 | watch: { 14 | // 每当 question 改变时,这个函数就会执行 15 | question(newQuestion, oldQuestion) { 16 | if (newQuestion.includes('?')) { 17 | this.getAnswer() 18 | } 19 | } 20 | }, 21 | methods: { 22 | async getAnswer() { 23 | this.answer = 'Thinking...' 24 | try { 25 | const res = await fetch('https://yesno.wtf/api') 26 | this.answer = (await res.json()).answer 27 | } catch (error) { 28 | this.answer = 'Error! Could not reach the API. ' + error 29 | } 30 | } 31 | } 32 | } 33 | ``` 34 | ```html 35 |

36 | Ask a yes/no question: 37 | 38 |

39 |

{{ answer }}

40 | ``` 41 | watch 选项也支持把键设置成用 . 分隔的路径: 42 | ```js 43 | export default { 44 | watch: { 45 | // 注意:只能是简单的路径,不支持表达式。 46 | 'some.nested.key'(newValue) { 47 | // ... 48 | } 49 | } 50 | } 51 | ``` 52 | ## 深层侦听器 53 | watch 默认是浅层的:被侦听的属性,仅在被赋新值时,才会触发回调函数——而嵌套属性的变化不会触发。如果想侦听所有嵌套的变更,你需要深层侦听器: 54 | 55 | ```js 56 | export default { 57 | watch: { 58 | someObject: { 59 | handler(newValue, oldValue) { 60 | // 注意:在嵌套的变更中, 61 | // 只要没有替换对象本身, 62 | // 那么这里的 `newValue` 和 `oldValue` 相同 63 | }, 64 | deep: true 65 | } 66 | } 67 | } 68 | ``` 69 | 谨慎使用: 70 | 深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能。 71 | ## 即时回调的侦听器 72 | watch 默认是懒执行的:仅当数据源变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。举例来说,我们想请求一些初始数据,然后在相关状态更改时重新请求数据。 73 | 74 | 我们可以用一个对象来声明侦听器,这个对象有 handler 方法和 immediate: true 选项,这样便能强制回调函数立即执行: 75 | 76 | ```js 77 | export default { 78 | // ... 79 | watch: { 80 | question: { 81 | handler(newQuestion) { 82 | // 在组件实例创建时会立即调用 83 | }, 84 | // 强制立即执行回调 85 | immediate: true 86 | } 87 | } 88 | // ... 89 | } 90 | ``` 91 | 回调函数的初次执行就发生在 created 钩子之前。Vue 此时已经处理了 data、computed 和 methods 选项,所以这些属性在第一次调用时就是可用的。 92 | ## 回调的触发时机 93 | 当你更改了响应式状态,他可能会同时触发Vue组件更新和侦听器回调。 94 | 默认情况下,用户创建的侦听器回调,都会在Vue组件之前被调用。这意味着你在侦听器回调中访问的DOM将是被Vue更新之前的状态。 95 | 96 | 如果想在侦听器回调中能访问被Vue更新之后的DOM,你需要指明:`flush: 'post'`选项: 97 | ```js 98 | export default { 99 | // ... 100 | watch: { 101 | key: { 102 | handler() {}, 103 | flush: 'post' 104 | } 105 | } 106 | } 107 | ``` 108 | ## this.$watch() 109 | 可以使用组件示例的`$watch()` 来命令式的创建一个侦听器: 110 | ```js 111 | export default { 112 | created(){ 113 | this.$watch('question', (newQuestion)=>{ 114 | // ... 115 | }) 116 | } 117 | } 118 | ``` 119 | 如果要在特定条件下设置一个侦听器,或者只侦听响应用户交互的内容,这方法很有用。他还允许你提前停止改侦听器。 120 | 121 | ## 停止侦听器 122 | 用 watch 选项或者 $watch() 实例方法声明的侦听器,会在宿主组件卸载时自动停止。因此,在大多数场景下,你无需关心怎么停止它。 123 | 124 | 在少数情况下,你的确需要在组件卸载之前就停止一个侦听器,这时可以调用 $watch() API 返回的函数: 125 | ```js 126 | const unwatch = this.$watch('foo', callback) 127 | // ...当该侦听器不再需要时 128 | unwatch() 129 | ``` -------------------------------------------------------------------------------- /doc/13.模版引用.md: -------------------------------------------------------------------------------- 1 | # 模版引用 2 | 虽然Vue的声明性渲染模型为你抽象来大部分对DOM的直接操作,但是在某些情况下,我们仍然需要直接访问底层的DOM元素。要实现这一点,可以使用特殊的 ref attribute: 3 | `` 4 | `ref` 是一个特殊的attribute,和 v-for 章节中提到的 key 类似。他允许我们在体格特定的DOM元素或子组件实例被挂载后,获得对他的直接引用。这可能很有用。比如说在组件挂载时将焦点设置到一个input元素上,或在一个元素上初始化一个第三方库。 5 | 6 | ## 访问模版引用 7 | 挂载可数之后引用都会暴露在 this.$refs 之上: 8 | ```js 9 | 16 | 17 | 20 | ``` 21 | 注意,你只可以在组件挂载后才能访问模版引用。如果你想在模版中的表达式上访问 `$refs.input`,在初次渲染时会时null。这是因为在初次渲染前,这个元素还不存在。 22 | 23 | ## v-for 中的模版引用 24 | // 需要 v3.2.25 及以上版本 25 | 当在 v-for 中使用模版引用时,响应的引用中包含的值时一个数组: 26 | ```js 27 | export default { 28 | data() { 29 | return { 30 | list: [ 31 | /* ... */ 32 | ] 33 | } 34 | }, 35 | mounted() { 36 | console.log(this.$refs.items) 37 | } 38 | } 39 | 40 | 41 | 48 | ``` 49 | 应该注意的是,ref数组并不保证与源数组相同的顺序。 50 | 51 | ## 函数模版引用 52 | 除了使用字符串值作为名字,ref attribute 还可以绑定为一个函数,会在每次组件更新时都被调用。该函数会收到元素引用作为其第一个参数: 53 | `` 54 | 注意我们这里需要使用动态的 `:ref`绑定才能够传入一个函数。当绑定的元素被卸载时,函数也会被调用一次,此时 el 参数会是 null。你当然也可以绑定一个组件方法而不是内联函数。 55 | 56 | ## 组件上的ref 57 | 模版引用也可以被用在一个子组件上。这种情况下引用中获得的值是组件实例: 58 | ```vue 59 | 71 | 72 | 75 | ``` 76 | 如果一个子组件使用的是选项式 API ,被引用的组件实例和该子组件的 this 完全一致,这意味着父组件对子组件的每一个属性和方法都有完全的访问权。这使得在父组件和子组件之间创建紧密耦合的实现细节变得很容易,当然也因此,应该只在绝对需要时才使用组件引用。大多数情况下,你应该首先使用标准的 props 和 emit 接口来实现父子组件交互。 77 | 78 | expose 选项可以用于限制对子组件实例的访问: 79 | ```js 80 | export default { 81 | expose: ['publicData', 'publicMethod'], 82 | data() { 83 | return { 84 | publicData: 'foo', 85 | privateData: 'bar' 86 | } 87 | }, 88 | methods: { 89 | publicMethod() { 90 | /* ... */ 91 | }, 92 | privateMethod() { 93 | /* ... */ 94 | } 95 | } 96 | } 97 | ``` 98 | 在上面这个例子中,父组件通过模板引用访问到子组件实例后,仅能访问 publicData 和 publicMethod。 99 | -------------------------------------------------------------------------------- /doc/14.组件基础.md: -------------------------------------------------------------------------------- 1 | # 组件基础 2 | 3 | 组件允许我们将 UI 划分为独立的,可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层签到的树状结构: 4 | ![img](https://cn.vuejs.org/assets/components.7fbb3771.png) 5 | 6 | 这和我们嵌套的 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们在每个组件内封装自定义内容与逻辑。Vue 同样也能很好的配合原声 WebComponent.本章节讲解 Vue 组件与原声 WebComponents 之间的关系。 7 | 8 | ## 定义一个组件 9 | 10 | 当使用构建步骤时,我们一般会将 Vue 组件定义在一个单独的 .vue 文件中,这被叫做单文件组件 (简称 SFC): 11 | 12 | ```vue 13 | 22 | 23 | 26 | ``` 27 | 28 | 当不实用构建步骤时,一个 Vue 组件以一个包含 Vue 特性选项的 JavaScript 对象来定义: 29 | 30 | ```js 31 | export default { 32 | data() { 33 | return { 34 | count: 0, 35 | }; 36 | }, 37 | template: ` 38 | `, 41 | }; 42 | ``` 43 | 44 | 这里的模版是一个内联的 JavaScript 字符串,Vue 将会在运行时编译他。也可以使用 ID 选择器来指向一个元素,通常是原生的