💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] >[success] # 从数字渐变组件谈第三方JS库使用 我们接下来要封装一个 **CountTo组件** ,这里使用到了一个第三方库 [**CountUp.js**](http://inorganik.github.io/countUp.js/) ,它的作用主要用来做 **数字动画** ,设置一个 **最终值**,我们的 **数字** 会有一个 **渐变的效果的动画** ,我们将会 **把这个JS库封装成一个Vue组件** , 通过这次 **封装** 来学习一下 **Vue组件封装的一些要点** 。 >[success] ## 组件封装 1. **父组件** 首先在 **路由列表** 的 **路由对象** 中添加新创建的 **count-to** 页面配置路由,**这个页面名字跟上面的组件名重名,但是无关系,这个文件只是用来展示的页面,不是组件,注意一下就好** **src/router/router.js** ~~~ export default [ { path: '/count-to', name: '/count_to', component: () => import('@/views/count-to') } ] ~~~ 然后在 **src/views/count-to.vue** 页面 **引入count-to组件** **src/views/count-to.vue** ~~~ <template> <div> <!-- endValue在子组件中是驼峰命名,但是父组件中使用时建议用end-value形式 --> <count-to ref="countTo" :end-value="endValue" @on-animation-end="handleEnd"> <span slot="left">总金额:</span> <span slot="right">元</span> </count-to> <button @click="getNumber">获取数值</button> <button @click="up">更新值</button> </div> </template> <script> import CountTo from '@/components/count-to' export default { name: 'count_to', components: { CountTo }, data(){ return{ endValue: 100 } }, methods: { // 获取子组件值方法 getNumber(){ this.$refs.countTo.getCount() }, // 更新值方法 up(){ this.endValue += Math.random() * 100 // Math.random()会生成一个0-1的随机数,乘以100就是0-100的随机数 }, // 子传父的监听事件 handleEnd(endValue){ console.log(endValue) } } } </script> ~~~ 引入时 **如果组件是驼峰式的命名** ,在 **template标签** 中使用时 **建议使用小写单词字母中划线区分** ,例如: **CountTo 在使用时写成这样 \<count-to />** 。上面也可以使用 **双标签 :\<count-to>\</count-to>** 一般没有使用到组件中定义的 **插槽** ,还是建议使用 **单标签** 。 2. **子组件** 首先我们在 **src/components** 文件夹中创建 **count-to** 文件夹,跟 **count-to组件有关的文件都放在这个文件夹中** 。 接下来创建一个 **count-to.vue** 文件,该文件 **作为组件文件** **src/components/count-to/count-to.vue** ~~~ <template> <div> <slot name="left"></slot> <span ref="number" :class="countClass" :id="eleId"></span> <slot name="right"></slot> </div> </template> <script> import CountUp from 'countup' export default { name: 'CountTo', computed: { // 用_uid来达到id唯一 eleId(){ return `count_up_${ this._uid }` }, // 支持传入class样式 countClass(){ return [ 'count-to-number', this.className ] } }, data(){ return{ counter: {} // CountUp实例化对象 } }, props: { /** * @description 起始值 */ startValue: { type: Number, default: 0 }, /** * @description 最终值 */ endValue: { type: Number, required: true }, /** * @description 小数点后保留几位小数 */ decimals: { type: Number, default: 0 }, /** * @description 动画延迟开始时间 */ delay: { type: Number, default: 0 }, /** * @description 渐变时长 */ duration: { type: Number, default: 1 }, /** * @description 是否使用变速效果 */ useEasing: { type: Boolean, default: false }, /** * @description 是否使用变速效果 */ useGrouping: { type: Boolean, default: true }, /** * @description 分组符号 */ separator: { type: String, default: ',' }, /** * @description 整数和小数分隔符号 */ decimal: { type: String, default: '.' }, /** * @description 动态添加类名 */ className: { type: String, default: '' } }, methods: { getCount(){ // id或者refs来获取dom元素的值 return this.$refs.number.innerText }, emitEndEvent(){ setTimeout(() => { this.$nextTick(() => { this.$emit('on-animation-end', Number(this.getCount())) }) }, this.duration * 1000 + 5) } }, watch:{ // endValue发生变化重新触发组件更新方法 endValue(newVal, oldVal){ this.counter.update(newVal) this.emitEndEvent() } }, mounted(){ this.$nextTick(() => { // 实例化CountUp对象 this.counter = new CountUp(this.eleId, this.startValue, this.endValue, this.decimals, this.duration, { useEasing: this.useEasing, useGrouping: this.useGrouping, separator: this.separator, decimal: this.decimal }) // 执行动画开始 setTimeout(() => { this.counter.start() this.emitEndEvent() }, this.delay) }) } } </script> <style lang="scss" scoped> @import './count-to.scss' </style> ~~~ 再创建一个 **index.js** ,在 **index.js** 中引入 **count-to.vue组件** 并 **导出(export default)** ,这么做就可以在引入组件的地方直接这么引入:**import CountTo from '@/components/count-to'** ,这样就是相当于引用了 **src/components/count-to** 文件夹中的 **index.js** 文件。 **src/components/count-to/index.js** ~~~ import CountTo from './count-to.vue' export default CountTo ~~~ 然后可以 **单独创建一个只供该组件使用的.scss或者.less** 样式文件 **count-to.scss** ~~~ .count-to-number{ color: palevioletred; } ~~~ **.scss或者.less** 有 **2种引入方式**: 1. 直接在 **script标签** 最上面写: **import'./count-to.scss'** 2. 在 **style标签** 最上面写: **@import './count-to.scss'** 3. 或者直接 **在style标签中直接写样式** >[success] ## 组件中使用id值 上面 **组件中使用到了id值** ,我们都知道 **id是唯一的** ,**如果这个组件在多个页面、多次使用** ,此时 **组件中创建的实例** 就会有问题 ,所以说我们 **每个组件都应该有自己的id,不跟其他组件发生冲突**,我们可以使用 **Vue** 提供的 **this._uid** 用来拼接 ,每个组件中 **_uid** 都不相同,这样就可以保证是 **全局唯一的** 。 >[success] ## 组件中获取DOM 在 **Vue** 中获取 **DOM** 有 **2种方法** ,一种是使用 **id** 来获取 **DOM** ,另外一种是使用 **ref** 的形式来获取 **DOM** 。 **场景** :有时候我们的 **组件** ,有一些 **内部方法** , 可以 **供外部使用** ,这时候就需要 **父组件通过ref调用子组件方法** , 在上面 **封装组件** 的代码中,**父组件** 中通过 **点击button** 执行 **getNumber** 方法中的 **this.$refs.countTo.getCount()** 来通过 **ref** 的方式 **调用子组件方法** , **子组件** 中使用 **ref** 来**取值并且返回值** ,具体实现看上面 **封装组件的代码** 。 >[success] ## 封装组件技巧 1. **创建组件** 时可以在 **components文件夹** 中 **创建一个文件夹储存组件相关文件** 2. **创建组件时** 可以创建一个对应 **index.js** 来达到 **引入时直接简写的方式引入组件** 3. **引入第三方库,npm安装成功** 后,可以在 **package.json** 中的 **dependencies对象** 中看到安装的 **第三方库名称** , **如果看到自己安装的第三方库名称即代表安装成功** 4. **组件中使用id值解决办法,看上面文章** 5. 把 **组件内动态的值都抽离出去,用props传入进来** ,规定 **是否必传,以及默认值** 6. **Vue组件插槽** 6.1. **匿名插槽用法** **子组件** ~~~ <template> <div> <slot></slot> <span :class="countClass" :id="eleId"></span> </div> </template> ~~~ **父组件** ~~~ <template> <div> <count-to :end-value="10"> <span>总金额:</span> </count-to> </div> </template> ~~~ 这样写 **总金额:** 三个字就会在组件的 **slot标签** 位置出现 6.2. **具名插槽** . **子组件** ~~~ <template> <div> <slot name="left"></slot> <span :class="countClass" :id="eleId"></span> <slot name="right"></slot> </div> </template> ~~~ **父组件** ~~~ <template> <div> <!-- endValue在子组件中是驼峰命名,但是父组件中使用时建议用end-value形式 --> <count-to :end-value="10"> <span slot="left">总金额:</span> <span slot="right">元</span> </count-to> </div> </template> ~~~ **具名插槽**父子组件都定义好指定的 **name** ,这样 **插入的内容就会放入到指定 name 的位置** 7. **组件中获取DOM解决办法,看上面文章** 8. **props** 中的 **default** 的值如果是 **Boolean、Number、String** 这 **3种类型** 就直接 **值是什么可以写成什么** , **如果默认值是Object或者Array或者Function** 就需要写成 **函数并且返回return值**