第七章 组件
7.1 组件与复用
7.1.1 为什么使用组件
7.1.2 组件用法
组件注册,全局注册后,任何Vue实例都可以使用。my-component就是注册的组件自定义标签名称,推荐使用小写和减号分割形式命名。
Vue.component("my-component",{ /*选项*/ })
要在父实例中使用该组件时,需要在实例创建之前注册。选项中的template的结构必须被DOM元素包含,如这里的div
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.1.2 组件用法</title> </head> <body> <div id="app"> <my-component></my-component> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component("my-component", { template:"<div>这里是组件的内容</div>" }) var app = new Vue({ el: "#app" }) </script> </body> </html>
使用components注册局部组件,局部组件可以在该实例作用域下有效。组件也可以使用components来注册组件,使组件嵌套。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.1.2 组件用法</title> </head> <body> <div id="app"> <my-component></my-component> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> var Child = { template: "<div>这里是局部注册组件</div>" } var app = new Vue({ el: "#app", componets: { //注册局部组件 "my-component": Child } }) </script> </body> </html>
Vue组件在某些情况下受HTML的限制,如table内规定只允许tr和td、th等,所以在table内直接使用组件是无效的。
这种特殊情况可以用is属性来挂载
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.1.2.1 is属性挂载组件</title> </head> <body> <div id="app"> <table> <tbody is="my-component"></tbody> </table> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component("my-component", { template: "<div>这里是组件内容,可用is属性挂载到table内</div>" }); var app = new Vue({ el: "#app" }) </script> </body> </html>
在组件中,除了有template选项外,还有data,computed,methods等方法,不过data必须是函数,将数据return出去。
JavaScript对象是引用关系,所以如果return出的对象引用了外部的一个对象,那么这个对象就是共享的,任何一方修改数据都会同步。所以我们给组件返回一个新的data对象来独立。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.1.2.2 组件的其他属性</title> </head> <body> <div id="app"> <my-component></my-component> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component("my-component", { template: "<div>消息内容:{{message}}</div>", data: function () { return { message: '这里是return回的内容' } } }); var app = new Vue({ el: "#app" }) </script> </body> </html>
7.2 使用props传递数据
7.2.1 基本用法
组件不仅仅是要把模板的内容进行复用,更重要的是组件间要进行通信。父组件需要向子组件正向传递数据,子组件根据收到的数据进行渲染,正向传递就是通过props来实现;
props值可以是数组,也可以是对象。当使用DOM模板时,驼峰命名的props名称要转换为短横线分割命名。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.2.1 props正向传递参数</title> </head> <body> <div id="app"> <my-component message="来自父组件的数据"></my-component> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component('my-component', { props: ['message'], template: "<div>{{message}}</div>" }) var app = new Vue({ el: "#app" }) </script> </body> </html>
父组件的动态数据传递给子组件,当不使用v-bind时,传递的数字、布尔值、数组、对象等仅是字符串,非源对象类型。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.1.2.3 父组件动态数据传递给子组件</title> </head> <body> <div id="app"> <label for="">父组件中输入:</label> <input type="text" v-model="parentMessage"> <my-component v-bind:message="parentMessage"></my-component> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component('my-component', { template: "<div>子组件:{{message}}</div>", props: ["message"] }); var app = new Vue({ el: "#app", data: { parentMessage: "", } }) </script> </body> </html>
7.2.2 单向数据流
props传递的数据是单向的,即子组件中的修改不会传递给父组件。但是在JavaScript中对象和数组是应用类型,都指向同一个内存空间,所以props是对象和数组时,在子组件中改变会影响父组件。
情况1:父组件传递初始值进来,子组件自己保存,并在自己作用域下使用和修改,这种情况可以在data内再声明一个数据,引用父组件的prop。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.2.2.1 单向数据流之子组件保存</title> </head> <body> <div id="app"> <my-component v-bind:init-count="1"></my-component> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component("my-component", { template: "<div>{{count}}</div>", props: ["initCount"], data: function () { return { count: this.initCount } } }); var app = new Vue({ el: "#app" }) </script> </body> </html>
情况2:prop作为需要被转变的原始值传入,这时使用计算属性就行
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.2.2.2 单向数据流之子组件计算</title> </head> <body> <div id="app"> <my-component v-bind:width="100"></my-component> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component("my-component", { template: "<div v-bind:style='style'>组件内容</div>", props: ["width"], computed: { style: function () { return { width: this.width + 'px' } } } }) var app = new Vue({ el: "#app" }) </script> </body> </html>
7.2.3 数据验证
当prop需要验证时,就需要传入对象。一般组件给别人用时,推荐进行数据验证,比如要求数字类型传入字符串就弹出警告。
<script type="application/javascript"> Vue.component('my-component', { props: { //必须时数字类型 propA: Number, //必须时字符串或数字类型 propB: [String, Number], //布尔值,如果没有定义,默认为true propC: { type: Boolean, default: true }, //数字,而且是比传 propD: { type: Number, required: true }, //如果是数组或对象,默认值必须是一个函数来返回 propE: { type: Array, default: function () { return []; } }, //自定义一个验证函数 propF: { validator: function (value) { return value > 10; } } } }) </script>
验证的type类型可以是:
- String
- Number
- Boolean
- Object
- Array
- Function
当prop验证失败时,在开发版本下会在控制台抛出一条警告。
7.3 组件通信
组件通信分父子组件通信、兄弟组件通信、跨级组件通信
7.3.1 自定义事件
当子组件需要向父组件传递数据时,就需要用到自定义事件。子组件用$emit()来触发事件,父组件用$on()来监听子组件的事件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.3.1 自定义事件</title> </head> <body> <div id="app"> <p>总数:{{total}}</p> <my-component @increase="handleGetTotal" v-on:reduce="handleGetTotal"></my-component> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component('my-component', { template: '<div><button @click="handleIncrease">+1</button><button v-on:click="handleReduce">-1</button></div>', data: function () { return { counter: 0 } }, methods: { handleIncrease: function () { this.counter++; this.$emit('increase', this.counter); }, handleReduce: function () { this.counter--; this.$emit('reduce', this.counter) } } }); var app = new Vue({ el: "#app", data: { total: 0 }, methods: { handleGetTotal: function (total) { this.total = total; } } }) </script> </body> </html>
7.3.2 使用v-model
这里使用的特殊事件名称input,如果不用v-model,也可以使用自定义事件@input=“”来实现
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.3.2.1 使用v-model</title> </head> <body> <div id="app"> <p>总数:{{total}}</p> <my-component v-model="total"></my-component> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component("my-component", { template: '<div><button v-on:click="handleClick">+1</button></div>', data: function () { return { counter: 0 } }, methods: { handleClick: function () { this.counter++; this.$emit("input", this.counter) } } }); var app = new Vue({ el: "#app", data: { total: 0 } }) </script> </body> </html>
用v-model创建自定义的表单输入组件,进行数据双向绑定。
条件:
- 接收一个value属性
- 在有新的value时,出发input事件
这里props中的value就是v-model传入的参数
<!DOCTYPE html> <html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <title>7.3.2.1 使用v-model事件表单输入组件进行数据双向绑定</title> </head> <body> <div id="app"> <p>总数:{{ total }}</p> <my-component v-model="total"></my-component> <button v-on:click="handleReduce">-1</button> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component("my-component", { template: '<input type="text" v-bind:value="value" @input="updateValue">', props: ["value"], methods: { updateValue: function (event) { this.$emit('input', event.target.value); } } }); var app = new Vue({ el: "#app", data: { total: 0 }, methods: { handleReduce: function () { this.total--; } } }) </script> </body> </html>
7.3.3 非父子组件通信
创建一个中间人bus(即一个Vue实例),在事件发布者内部发布事件,在接收者内部接收事件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.3.3 非父子组件通信</title> </head> <body> <div id="app"> {{ message }} <component-a></component-a> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> var bus = new Vue(); Vue.component('component-a', { template: '<button v-on:click="handleEvent">传递事件</button>', methods: { handleEvent: function () { bus.$emit('on-message', "来自component-a的内容") } } }); var app = new Vue({ el: "#app", data: { message: "" }, mounted: function () { var _this = this; bus.$on('on-message', function (msg) { _this.message = msg; }) } }) </script> </body> </html>
父链
在子组件中,使用this.$parent可以直接访问该组件的父实例或组件,父组件也可以通过this.$children访问他所有的子组件,而且可以无限访问,知道根实例或最内层组件。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.3.3.1 父链</title> </head> <body> <div id="app"> {{ message }} <component-a></component-a> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component('component-a', { template: '<button v-on:click="handleEvent">通过父链直接修改数据</button>', methods: { handleEvent: function () { //访问到父链后,可以做任何操作,比如直接修改数据 this.$parent.message = '子组件component-a修改了数据' } } }); var app = new Vue({ el: "#app", data: { message: '根组件原始数据' } }) </script> </body> </html>
子组件索引
当子组件较多时,通过this.$children来遍历找出一个组件比较困难,Vue提供了子组件索引的方法,用特殊的属性ref来为子组件指定索引名称
<!DOCTYPE html> <html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <title>7.3.3.2 子组件索引</title> </head> <body> <div id="app"> <button v-on:click="handleRef">通过ref获取子组件</button> <p ref="p">P内容</p> <component-a ref="child"></component-a> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component('component-a', { template: '<div>子组件</div>', data: function () { return { message: '子组件内容' } } }); var app = new Vue({ el: "#app", methods: { handleRef: function () { var msg = this.$refs.child.message; console.log(msg) console.log(this.$refs.p)//类似v1版本中的v-el,在2.0中,Vue会自动判断时普通标签还是组件 } } }) </script> </body> </html>
7.4 使用slot分发内容
7.4.1 什么时slot
当需要让组件组合使用,混合父组件的内容与子组件的模板时,就会用到slot,该过程叫做内容分发。
特点:
<app>组件不知道它的挂载点会有什么内容。挂载点的内容是由<app>的父组件决定的。
<app>组件很可能有自己的模板。
props传递数据、events触发事件、slot分发内容构成了Vue的组件的3个API来源。
7.4.2 作用域
编译作用域。父组件模板内容是在父组件作用域内编译,子组件模板的内容是在子组件作用域内编译。
7.4.3 slot用法
单个slot:在子组件内使用<slot>元素就可以在这个组件中开启一个slot,在父组件模板里,插入在子组件标签内的所有内容将替代子组件的<slot>标签已经它的内容。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.4.3.1 单slot用法</title> </head> <body> <div id="app"> <child-component> <p>分发的内容</p> <p>更多分发的内容</p> </child-component> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component("child-component", { template: '\ <div>\ <slot>\ <p>如果父组件没有插入内容,我将作为默认出现</p>\ </slot>\ </div>' }); var app = new Vue({ el: "#app" }) </script> </body> </html>
具名slot:给<slot>元素指定一个name后可以分发多个内容,具名slot可以与单个slot共存
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.4.3.2 具名slot用法</title> </head> <body> <div id="app"> <child-component> <h2 slot="header">标题</h2> <p>分发的内容</p> <p>更多分发的内容</p> <div slot="footer">底部信息</div> </child-component> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component('child-component', { template: '\ <div class="container">\ <div class="header"><slot name="header"></slot></div>\ <div class="main"><slot></slot></div>\ <div class="footer"><slot name="footer"></slot></div>\ </div>' }); var app = new Vue({ el: "#app" }) </script> </body> </html>
7.4.4 作用域插槽
作用域插槽是一种特殊的slot,使用一个可以复用的模板替换已渲染的元素。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.4.4.1 作用域插槽</title> </head> <body> <div id="app"> <child-component> <template scope="prols"><!--临时变量来访问子组件插槽的数据msg--> <p>来自父组件的内容</p> <p>{{ prols.msg }}</p> </template> </child-component> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component('child-component', { template: '\ <div class="container">\ <slot msg="来自子组件的内容"></slot>\ </div>' }); var app = new Vue({ el: "#app" }) </script> </body> </html>
使用作用域插槽渲染列表组件,驼峰命名的props名称要转换为短横线分割命名
子组件my-list接收一个来自父级的prop数组books,并且将他在name为book的slot上使用v-for循环指令,暴露一个变量nookName(注意在接收时是减号分割)。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.4.4.2 作用域插槽列表组件</title> </head> <body> <div id="app"> <my-list v-bind:books="books"> <template slot="book" scope="props"> <li>{{ props.bookName }}</li> </template> </my-list> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component('my-list', { template: '\ <ul>\ <slot name="book" v-for="book in books" v-bind:book-name="book.name"></slot>\ </ul>', props: { books: { type: Array, default: function () { return []; } } } }); var app = new Vue({ el: "#app", data: { books: [ {name: "《Vue.js实战》"}, {name: "《JavaScript实战》"}, {name: "《Html5实战》"} ] } }) </script> </body> </html>
7.4.5 访问slot
通过$slots访问slot,default是默认的slot,即没有name属性的slot
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.4.5.1 访问slot</title> </head> <body> <div id="app"> <child-component> <h2 slot="header">标题</h2> <p>正文内容</p> <p>更多正文内容</p> <div slot="footer">底部信息</div> </child-component> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component('child-component', { template: '\ <div class="container">\ <div class="header">\ <slot name="header"></slot>\ </div>\ <div class="main">\ <slot></slot>\ </div>\ <div class="footer">\ <slot name="footer"></slot>\ </div>\ </div>', mounted: function () { var header = this.$slots.header; var main = this.$slots.default; var footer = this.$slots.footer; console.log(footer); console.log(footer[0].elm.innerHTML); } }); var app = new Vue({ el: "#app" }) </script> </body> </html>
7.5 组件高级用法
7.5.1 递归组件
组件在它的模板内可以递归调用自己,只要给组件设置name的选项即可。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.5.1 递归组件</title> </head> <body> <div id="app"> <child-component v-bind:count="1"></child-component> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component("child-component", { name: "child-component", props: { count: { type: Number, default: 1 } }, template: '\ <div class="child">\ 子组件第{{count}}调用\ <child-component v-bind:count="count+1" v-if="count < 3"></child-component>\ </div>\ ' }); var app = new Vue({ el: "#app" }) </script> </body> </html>
7.5.2 内联模板
在使用组件时,给标签使用inline-template特性,组件就会把它的内容当作模板,而不是把它当作内容分发。
在父组件和子组件中都声明的数据,两个都可以渲染,当同名时,优先使用子组件数据。作用域比较难理解,如非特殊不使用内敛模板。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.5.2 内联模板</title> </head> <body> <div id="app"> <child-component inline-template> <div> <h2>在父组件中定义子组件模板</h2> <p>{{ message }}</p> <p>{{ msg }}</p> </div> </child-component> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component("child-component", { data: function () { return { msg: "在子组件声明的数据" } } }); var app = new Vue({ el: "#app", data: { message: "在父组件中声明的数据" } }) </script> </body> </html>
7.5.3 动态组件
Vue提供一个特殊的元素<component>用来动态挂载不同的组件,使用is特性来选择要挂载的组件。
<!DOCTYPE html> <html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <title>7.5.3 动态组件</title> </head> <body> <div id="app"> <component v-bind:is="currentView"></component> <button v-on:click="handleChangeView('A')">切换到A</button> <button v-on:click="handleChangeView('B')">切换到B</button> <button v-on:click="handleChangeView('C')">切换到C</button> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> var app=new Vue({ el:"#app", components:{ conA:{ template:'<div>组件A</div>' }, conB:{ template:'<div>组件B</div>' }, conC:{ template:'<div>组件C</div>' }, }, data:{ currentView:"conA" }, methods:{ handleChangeView:function (component) { this.currentView='con'+component; } } }) </script> </body> </html>
7.5.4 异步组件
Vue动态解析组件,只有组件需要渲染时才触发工厂函数,并把结果进行缓存。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.5.4 异步组件</title> </head> <body> <div id="app"> <child-component></child-component> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component("child-component", function (resolve, reject) { window.setTimeout(function () { resolve({ template: "<div>我是异步渲染的</div>" }); }, 2000) }); var app = new Vue({ el: "#app" }) </script> </body> </html>
7.6 其他
7.6.1 $nextTick
Vue在观察到数据变化时,并不是直接更新DOM,而是开启一个队列,并缓冲在同一事件循环中发生的所有数据改变,在缓冲时会对数据去重,从而避免不必要的计算和DOM操作。
在下一个事件循环tick中,刷新队列并执行实际工作。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.6.1 $nextTick</title> </head> <body> <div id="app"> <div id="div" v-if="showDiv">这是一段文本</div> <button @click="getText">获取div内容</button> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> var app=new Vue({ el:"#app", data:{ showDiv:false }, methods:{ getText:function () { this.showDiv=true; var text=document.getElementById("div").innerHTML; console.log(text); } } }) </script> </body> </html>
这里报错没有获取到div元素,因为在执行this.showDiv=true;时,div任然没有被创建出来,直到下一个Vue事件循环才开始创建。$nextTick就是用来知道什么时候DOM更新完成的。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.6.1 $nextTick</title> </head> <body> <div id="app"> <div id="div" v-if="showDiv">这是一段文本</div> <button @click="getText">获取div内容</button> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> var app=new Vue({ el:"#app", data:{ showDiv:false }, methods:{ getText:function () { this.showDiv=true; this.$nextTick(function () { var text=document.getElementById("div").innerHTML; console.log(text); }) } } }) </script> </body> </html>
7.6.2 X-Templates
另一种定义模板的方式。避免在JavaScript里面拼接字符串。在script标签里使用text/x-template类型,并且指定一个id,将这个id赋给template。
在这个script标签里写html代码,不用考虑换行问题。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>X-Templates</title> </head> <body> <div id="app"> <my-component></my-component> <script type="text/x-template" id="my-component"> <div>这是组件内容</div> </script> </div> <script src="../vue.min.js"></script> <script type="application/javascript"> Vue.component('my-component',{ template:"#my-component"//这个id是一个script标签的id }); var app=new Vue({ el:"#app" }) </script> </body> </html>
7.6.3 手动挂载实例
Vue提供Vue.extent和$mount两个方法来手动挂载一个实例。Vue.extend是基础Vue构造器,创建一个“子类”,参数是一个包含组件选项的对象。
没有el元素就处于未挂载状态,可以用$mount()来手动挂载到一个DOM对象上。可以链式调用实例其他方法。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>7.6.3 手动挂载实例</title> </head> <body> <div id="mount-div"></div> <script src="../vue.min.js"></script> <script type="application/javascript"> var MyComponent = Vue.extend({ template: '<div>Hello:{{ name }}</div>', data: function () { return { name: "VueJs" } } }); new MyComponent().$mount("#mount-div"); </script> </body> </html>
7.7 实战:两个常用组件开发
7.7.1 开发一个数字输入框组件
input-number.js 数字输入框组件
function isValueNumber (value) { return (/(^-?[0-9]+\.{1}\d+$)|(^-?[1-9][0-9]*$)|(^-?(){1}$)/).test(value + ''); } Vue.component('input-number',{ template:'\ <div class="input-number">\ <input type="text" v-bind:value="currentValue" v-on:change="handleChange">\ <button @click="handleDown" :disabled="currentValue <= min">-</button>\ <button @click="handleUp" :disabled="currentValue >= max">+</button>\ </div>\ ', //props向子组件传递数据 props:{ max:{ type:Number, defaule:Infinity }, min:{ type: Number, default:-Infinity }, value:{ type:Number, default:0 } }, data:function () { return { currentValue:this.value } }, watch:{ currentValue:function (val) { this.$emit("input",val); this.$emit("on-change",val); }, value:function (val) { this.updateValue(val); } }, methods:{ updateValue:function (val) { if (val>this.max) val=this.max; if(val<this.min) val=this.min; this.currentValue=val; }, handleDown:function () { if (this.currentValue<=this.min) return; this.currentValue-=1; }, handleUp:function () { if (this.currentValue>=this.max) return; this.currentValue+=1; }, handleChange:function (event) { var val=event.target.value.trim(); var max=this.max; var min=this.min; if (isValueNumber(val)){ val=Number(val); this.currentValue=val; if(val>max){ this.currentValue=max; }else if(val<min){ this.currentValue=min; } }else { event.target.value=this.currentValue; } } }, mounted:function () { this.updateValue(this.value); } });
index.js 根实例
var app=new Vue({ el:"#app", data:{ value:5 } });
index.html 入口页
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index</title> </head> <body> <div id="app"> <input-number v-model="value" v-bind:max="10" v-bind:min="5"></input-number> </div> <script src="vue.min.js"></script> <script src="input-number.js"></script> <script src="index.js"></script> <script type="application/javascript"> </script> </body> </html>
7.7.1 开发一个数字输入框组件