ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
组件详解 === [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> ```