组件详解
===
[TOC]
# 计数器组件的例子
下面的示例演示 uni-app自定义组件button-counter,每次点击按钮计数器增加1。
components/button-counter.vue文件:
```
<template>
<view>
<button type="primary" @click="count++">Clicked {{ count }} times.</button>
</view>
</template>
<script>
export default {
data() {
return {
count: 0,
}
}
}
</script>
<style>
button {
height: 100px;
}
</style>
```
当我们定义这个`<button-counter>`组件时,你可能会发现它的data并不是像这样直接提供一个对象:
```
data: {
count: 0
}
```
取而代之的是,一个组件的data选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
```
data: function () {
return {
count: 0
}
}
```
放置3个button-counter组件,执行的时候发现每一个组件的状态是独立的。
```
<template>
<view class="uni-flex uni-column">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</view>
</template>
```
![](https://box.kancloud.cn/ee7fbd56545cd6445e0c6664c94acd4c_168x144.png)
组件是可复用的 Vue 实例,且带有一个名字:在这个例子中是 `<button-counter>`。我们可以把这个组件作为自定义元素来使用。uni-app目前只支持单文件组件,通过import语句导入组件文件
```
import buttonCounter from '../../components/button-counter.vue'
```
并且在components部分声明组件,注意命名规则为驼峰写法。
```
components: {
buttonCounter,
},
```
完整的代码:
```
<template>
<view class="uni-flex uni-column">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</view>
</template>
<script>
import buttonCounter from '../../components/button-counter.vue'
export default {
components: {
buttonCounter,
},
}
</script>
```
# 使用props传递数据
组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。可以使用props把数据传给子组件。prop是组件数据的一个字段,期望从父组件传下来。子组件需要显式地用props选项声明props,以下示例构造一个数字输入的组件,完整代码见后面的案例分析。
```
props: {
value: {
type: Number,
default: 0
},
},
```
然后向它传入一个普通数值:
```
<number-box v-model="number"></number-box>
```
# Vue2中props通信方式
在Vue2中组件的props的数据流动改为了只能单向流动,即只能由组件外(调用组件方)通过组件的DOM属性attribute传递props给组件内,组件内只能被动接收组件外传递过来的数据,并且在组件内,不能修改由外层传来的props数据。
![](https://box.kancloud.cn/429216a0fefe0a0217a59c42fe2a1b07_540x274.png)
官方文档给出的解释:
> prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。
也就是说:废弃了props的双向绑定,虽然对于整个项目整体而言是有利且正确的,但是在某些时候我们确实需要从组件内部修改props的需求。
案例:
定义一个开关组件:
components/switch-button.vue
```
//开关组件代码
<template>
<view @click='change'>{{result?'开':'关'}}</view>
</template>
<script>
export default {
props: ["result"],
methods: {
change() {
this.result = !this.result;
}
},
}
</script>
```
引用 :
组件内不能修改props的值,同时修改的值也不会同步到组件外层,即调用组件方不知道组件内部当前的状态是什么。
那么如何双向绑定状态呢?
Vue父子组件传值通过props和$emit,父组件传来一个result,这个只在子组件中用props注册,虽然在子组件中不能直接修改,但是自组建可以通过$emit通知父组件修改,在子组件中定义一个sonReault:this.result,我们只需要通过观察者模式监听result 和 sonReault:
```
//开关组件代码
<template>
<view @click='change'>{{result?'开':'关'}}</view>
</template>
<script>
export default {
props: {
result: {
type: Boolean,
default: false
},
},
methods: {
change() {
this.result = !this.result;
}
},
data() {
return {
myResult: this.result,
}
},
watch: {
result(val) {
this.myResult = val
},
myResult (val) {
this.$emit('toFather', val)
}
}
}
</script>
```
![](https://box.kancloud.cn/72545f657295bd250eb0af936f9dc95d_619x478.png)
通过这种方式便可以实现父子组件props的双向绑定。
# 实战:数字输入框组件
因为组件是可复用的 Vue 实例,所以它们可以使用 data、computed、watch、methods 以及生命周期钩子等。
## 数据的双向绑定
父组件使用props传递参数给子组件,但是这是单向传递,不能实现双向数据绑定,通过watch可以实现了数据的双向绑定。
首先给属性value声明一个data副本currentValue,默认引用value的值,然后在组件内部维护这个data。
```
props: {
value: {
type: Number,
default: 0
},
},
data() {
return {
currentValue: this.value,
}
},
```
## 组件定义
完整的示例代码如下:
components/number-box.vue
```
<template>
<view class="uni-numbox">
<view class="uni-numbox-minus" @click="handleReduce">-</view>
<input class="uni-numbox-value" type="number" :value="currentValue" @change="handleChange" disabled>
<view class="uni-numbox-plus" @click="handleIncrease">+</view>
</view>
</template>
<script>
export default {
props: {
value: {
type: Number,
default: 0
},
},
data() {
return {
currentValue: this.value,
}
},
watch: {
currentValue(val) {
this.$emit('input', val);
this.$emit('on-change', val);
},
value(val) {
this.updateValue(val);
}
},
methods: {
updateValue(val) {
this.currentValue = val;
},
handleChange(event) {
//允许进一步判断输入的是否是数字
// event.target.value = this.currentValue;
},
handleIncrease() {
this.$emit('increase');
this.currentValue++;
},
handleReduce() {
this.$emit('reduce');
//最少得留一件
if (this.currentValue <= 1) return;
this.currentValue--;
}
},
mounted() {
this.updateValue(this.value);
}
}
</script>
<style>
.uni-numbox {
display: flex;
flex-direction: row;
height: 50px;
}
.uni-numbox-minus,
.uni-numbox-plus {
margin: 0;
background-color: #f9f9f9;
width: 50px;
line-height: 50px;
justify-content: center;
color: #555555;
font-size: 24px;
border: 0px;
}
.uni-numbox-value {
border: 0px;
width: 100px;
font-size: 24px;
text-align: center;
}
</style>
```
## 代码分析
首先,在需要引用组件的地方使用@import语句导入组件定义文件
```
import numberBox from '../../components/number-box.vue'
```
并且在component部分申明应用的组件
```
components: {
numberBox
}
```
然后就可以使用组件了。
```
<number-box v-model="number" @increase="handleCount(1)" @reduce="handleCount(-1)"/>
```
> HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名。因此定义组件的时候使用number-box,引用组件的时候使用numberBox。
测试代码:
```
<template>
<view class="container">
<view class="uni-flex uni-flex-item uni-row">
<number-box v-model="number" @increase="handleCount(1)" @reduce="handleCount(-1)"></number-box>
</view>
</view>
</template>
<script>
import numberBox from '../../components/number-box.vue'
export default {
data: {
number: 1
},
methods: {
handleCount(count) {
console.log('current number:' + this.number);
}
},
components: {
numberBox
}
}
</script>
<style>
.container {
padding: 22px 30px 22px 30px;
}
</style>
```
# 全局组件
uni-app 支持配置全局组件,需在 main.js 里进行全局注册,注册后就可在所有页面里使用该组件。
示例
main.js 里进行全局注册
```
import Vue from 'vue'
import pageHead from './components/page-head.vue'
Vue.component('page-head',pageHead)
```
index.vue 里可直接使用组件
```
<template>
<view>
<page-head></page-head>
</view>
</template>
```
- 内容介绍
- EcmaScript基础
- 快速入门
- 常量与变量
- 字符串
- 函数的基本概念
- 条件判断
- 数组
- 循环
- while循环
- for循环
- 函数基础
- 对象
- 对象的方法
- 函数
- 变量作用域
- 箭头函数
- 闭包
- 高阶函数
- map/reduce
- filter
- sort
- Promise
- 基本对象
- Arguments 对象
- 剩余参数
- Map和Set
- Json基础
- RegExp
- Date
- async
- callback
- promise基础
- promise-api
- promise链
- async-await
- 项目实践
- 标签系统
- 远程API请求
- 面向对象编程
- 创建对象
- 原型继承
- 项目实践
- Classes
- 构造函数
- extends
- static
- 项目实践
- 模块
- import
- export
- 项目实践
- 第三方扩展库
- immutable
- Vue快速入门
- 理解MVVM
- Vue中的MVVM模型
- Webpack+Vue快速入门
- 模板语法
- 计算属性和侦听器
- Class 与 Style 绑定
- 条件渲染
- 列表渲染
- 事件处理
- 表单输入绑定
- 组件基础
- 组件注册
- Prop
- 自定义事件
- 插槽
- 混入
- 过滤器
- 项目实践
- 标签编辑
- 移动客户端开发
- uni-app基础
- 快速入门程序
- 单页程序
- 底部Tab导航
- Vue语法基础
- 模版语法
- 计算属性与侦听器
- Class与Style绑定
- 样式与布局
- Box模型
- Flex布局
- 内置指令
- 基本指令
- v-model与表单
- 条件渲染指令
- 列表渲染指令v-for
- 事件与自定义属性
- 生命周期
- 项目实践
- 学生实验
- 贝店商品列表
- 加载更多数据
- 详情页面
- 自定义组件
- 内置组件
- 表单组件
- 技术专题
- 状态管理vuex
- Flyio
- Mockjs
- SCSS
- 条件编译
- 常用功能实现
- 上拉加载更多数据
- 数据加载综合案例
- Teaset UI组件库
- Teaset设计
- Teaset使用基础
- ts-tag
- ts-badge
- ts-button
- ta-banner
- ts-list
- ts-icon
- ts-load-more
- ts-segmented-control
- 代码模版
- 项目实践
- 标签组件
- 失物招领客户端原型
- 发布页面
- 检索页面
- 详情页面
- 服务端开发技术
- 服务端开发环境配置
- Koajs快速入门
- 快速入门
- 常用Koa中间件介绍
- 文件上传
- RestfulApi
- 一个复杂的RESTful例子
- 使用Mockjs生成模拟数据
- Thinkjs快速入门
- MVC模式
- Thinkjs介绍
- 快速入门
- RESTful服务
- RBAC案例
- 关联模型
- 应用开发框架
- 服务端开发
- PC端管理界面开发
- 移动端开发
- 项目实践
- 失物招领项目
- 移动客户端UI设计
- 服务端设计
- 数据库设计
- Event(事件)
- 客户端设计
- 事件列表页面
- 发布页面
- 事件详情页面
- API设计
- image
- event
- 微信公众号开发
- ui设计规范