[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值**
- vue 26课
- Vue-cli3.0项目搭建
- Vue-ui 创建cli3.0项目
- Vue-ui 界面详解
- 项目目录详解
- public文件夹
- favicon.ico
- index.html
- src文件夹
- api文件夹
- assets文件夹
- components文件夹
- config文件夹
- directive文件夹
- lib文件夹
- mock文件夹
- mock简明文档
- router文件夹
- store文件夹
- views文件夹
- App.vue
- main.js
- .browserslistrc
- .editorconfig
- .eslintrc.js
- .gitignore
- babel.config.js
- package-lock.json
- package.json
- postcss.config.js
- README.en.md
- README.md
- vue.config.js
- Vue Router
- 路由详解(一)----基础篇
- 路由详解(二)----进阶篇
- Vuex
- Bus
- Vuex-基础-state&getter
- Vuex-基础-mutation&action/module
- Vuex-进阶
- Ajax请求
- 解决跨域问题
- 封装axios
- Mock.js模拟Ajax响应
- 组件封装
- 从数字渐变组件谈第三方JS库使用
- 从SplitPane组件谈Vue中如何【操作】DOM
- 渲染函数和JSX快速掌握
- 递归组件的使用
- 登陆/登出以及JWT认证
- 响应式布局
- 可收缩多级菜单的实现
- vue杂项
- vue递归组件
- vue-cli3.0多环境打包配置
- Vue+Canvas实现图片剪切
- vue3系统入门与项目实战
- Vue语法初探
- 初学编写 HelloWorld 和 Counter
- 编写字符串反转和内容隐藏功能
- 编写TodoList功能了解循环与双向绑定
- 组件概念初探,对 TodoList 进行组件代码拆分
- Vue基础语法
- Vue 中应用和组件的基础概念
- 理解 Vue 中的生命周期函数
- 常用模版语法讲解
- 数据,方法,计算属性和侦听器
- 样式绑定语法
- 条件渲染
- 列表循环渲染
- 事件绑定
- 表单中双向绑定指令的使用
- 探索组件的理念
- 组件的定义及复用性,局部组件和全局组件
- 组件间传值及传值校验
- 单向数据流的理解
- Non-Props 属性是什么
- 父子组件间如何通过事件进行通信
- 组件间双向绑定高级内容
- 使用匿名插槽和具名插槽解决组件内容传递问题
- 作用域插槽
- 动态组件和异步组件
- 基础语法知识点查缺补漏
- Vue 中的动画
- 使用 Vue 实现基础的 CSS 过渡与动画效果
- 使用 transition 标签实现单元素组件的过渡和动画效果
- 组件和元素切换动画的实现
- 列表动画
- 状态动画
- Vue 中的高级语法
- Mixin 混入的基础语法
- 开发实现 Vue 中的自定义指令
- Teleport 传送门功能
- 更加底层的 render 函数
- 插件的定义和使用
- 数据校验插件开发实例
- Composition API
- Setup 函数的使用
- ref,reactive 响应式引用的用法和原理
- toRef 以及 context 参数
- 使用 Composition API 开发TodoList
- computed方法生成计算属性
- watch 和 watchEffect 的使用和差异性
- 生命周期函数的新写法
- Provide,Inject,模版 Ref 的用法
- Vue 项目开发配套工具讲解
- VueCLI 的使用和单文件组件
- 使用单文件组件编写 TodoList
- Vue-Router 路由的理解和使用
- VueX 的语法详解
- CompositionAPI 中如何使用 VueX
- 使用 axios 发送ajax 请求
- Vue3.0(正式版) + TS
- 你好 Typescript: 进入类型的世界
- 什么是 Typescript
- 为什么要学习 Typescript
- 安装 Typescript
- 原始数据类型和 Any 类型
- 数组和元组
- Interface- 接口初探
- 函数
- 类型推论 联合类型和 类型断言
- class - 类 初次见面
- 类和接口 - 完美搭档
- 枚举(Enum)
- 泛型(Generics) 第一部分
- 泛型(Generics) 第二部分 - 约束泛型
- 泛型第三部分 - 泛型在类和接口中的使用
- 类型别名,字面量 和 交叉类型
- 声明文件
- 内置类型
- 总结