1、创建自定义组件
首先,创建一个位于 /components/Tabs 下的组件Tabs
2、自定义组件的内容、样式和行为
tabs.wxml
<view class="tabs">
<view class="tabs_title">
<view
wx:for="{{tabs}}"
wx:key="id"
class="title_item {{item.isActive?'active':''}}"
>
{{item.name}}
</view>
</view>
<view class="tabs_content">
内容
</view>
</view>
tabs.js
// components/Tabs/Tabs.js
Component({
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
tabs:[{
id: 0,
name:"首页",
isActive:true
},
{
id: 1,
name:"原创",
isActive:false
},
{
id: 2,
name:"分类",
isActive:false
},
{
id: 3,
name:"关于",
isActive:false
}]
},
/**
* 组件的方法列表
*/
methods: {
}
})
tabs.wxss
.tabs{}
.tabs_title{
display: flex;
padding: 10rpx 0;
}
.title_item{
display: flex;
/* flex增长系数 */
flex: 1;
/* 水平对齐 */
justify-content: center;
/* 垂直对齐 */
align-items: center;
}
.active{
color: red;
border-bottom: 10rpx solid currentColor;
}
.tabs_content{}
3、声明引入自定义组件
新建一个页面,在 *.json文件 中引入这个自定义组件
demo6.json
{
"usingComponents": {
"tabs":"/components/Tabs/Tabs"
}
}
demo6.wxml
<tabs></tabs>
4、加入标题激活选中效果
tabs.wxml
增加了 bindtap 和 data-index两个属性
<view class="tabs">
<view class="tabs_title">
<view
wx:for="{{tabs}}"
wx:key="id"
class="title_item {{item.isActive?'active':''}}"
bindtap="hanldeItemTap"
data-index="{{index}}"
>
{{item.name}}
</view>
</view>
<view class="tabs_content">
内容
</view>
</view>
tabs.js
// components/Tabs/Tabs.js
Component({
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
tabs:[{
id: 0,
name:"首页",
isActive:true
},
{
id: 1,
name:"原创",
isActive:false
},
{
id: 2,
name:"分类",
isActive:false
},
{
id: 3,
name:"关于",
isActive:false
}]
},
/**
* 组件的方法列表
*/
methods: {
hanldeItemTap(e){
/* 1 绑定点击事件 需要在methods中绑定
2 获取被点击的索引
3 获取原数组
4 对数组循环
1 给每一个循环性 选中属性 改为 false
2 给 当前的索引的 项 添加激活选中效果就可以了!!!
5 点击事件触发的时候
触发父组件中的自定义事件 同时传递数据给 父组件
this.triggerEvent("父组件自定义事件的名称",要传递的参数) */
// 2 获取被点击的索引
const {index} = e.currentTarget.dataset
// 3 获取原数组
let {tabs} = this.data
// 4 对数组循环
// [].forEach 遍历数组 遍历数组的时候修改了v, 也会导致原数组被修改
tabs.forEach((v,i)=>i===index?v.isActive=true:v.isActive=false)
this.setData({
tabs
})
}
}
})
疑惑?
1. const {index} 和 let {tabs} 为什么要加{ }?
这种做法叫做解构,对复杂类型进行解构的时候复制了一份,只是变量的引用而已,例如, let {tabs} = this.data 相当于 let tabs = this.data.tabs , 但是,还有一种最严谨的做法,就是重新拷贝一份数组,再对这个数组的备份进行处理,如 let tabs=JSON.parse(JSON.stringify(this.data.tabs));
2. const和let有什么区别?
let声明的变量可以改变,值和类型都可以改变,没有限制。
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
参考的文章
5、使用父向子传递数据
我们现在来修改一下自定义组件(子组件)中内部的数据(tabs),将这个数据抽离出来变成页面(父组件)的数据,然后将这个数据传递过来,即由父组件将这个数据传递给子组件。
那么,为什么要这么做呢?
假想一下,如果我们将这些标题数据写死在这个自定义组件中,万一有其他页面也要使用这个自定义组件,那么其他页面怎么实现它的动态化呢? 所以啊,我们得把这些数据抽离出来,由页面(父组件)将数据传给自定义组件(子组件),子组件再进行接收并调用
步骤:
(1)先在父组件(页面)的js文件中,在data项中写入上面的tabs数据
// pages/demo6/demo6.js
Page({
/**
* 页面的初始数据
*/
data: {
tabs:[{
id: 0,
name:"首页",
isActive:true
},
{
id: 1,
name:"原创",
isActive:false
},
{
id: 2,
name:"分类",
isActive:false
},
{
id: 3,
name:"关于",
isActive:false
}]
}
})
(2)父组件(页面)向子组件传递数据时,需要通过标签属性的方式来传递
(3)在子组件的js文件中进行接收
// components/Tabs/Tabs.js
Component({
/**
* 里面存放的是 要从父组件中接收的数据
*/
properties: {
// 要接收的数据的名称
tabs:{
// type 要接收的数据的类型
type:Array,
// value 默认值
value:[]
}
},
//和上面的一样
. . .
}
(4)在子组件的wxml文件中将这个数据当成是data中的数据直接用即可
最后的效果还是一样
6、使用子向父传递数据
为什么要使用子向父传递数据呢?上面不是已经完美了吗?
然而并不是,你有没有发现以下问题:
- data中没有数据,为什么使用 let {tabs} = this.data还能成功?
解答看下图
- 看开发工具的控制台,为什么原创的isActice还是false?
解答
也就是点击原创时会将tabs数据再拷贝一份到data项中,这也就使得data中有两个tabs数据(首页一个,点击原创又一个),其实这里我也说不懂,有没有大佬发表见解 。。。
所以接下来,我们需使用子向父传递数据的索引,解决这一问题,步骤:
(1)先在父组件中自定义触发事件
<tabs tabs="{{tabs}}" binditemChange="handleItemChange">
</tabs>
(2)在子组件的js文件中绑定父组件自定义的触发事件,传递tabs的索引
methods: {
hanldeItemTap(e){
/* 1 绑定点击事件 需要在methods中绑定
2 获取被点击的索引
3 获取原数组
4 对数组循环
1 给每一个循环性 选中属性 改为 false
2 给 当前的索引的 项 添加激活选中效果就可以了!!!
5 点击事件触发的时候
触发父组件中的自定义事件 同时传递数据给 父组件
this.triggerEvent("父组件自定义事件的名称",要传递的参数) */
// 2 获取被点击的索引
const {index} = e.currentTarget.dataset
// 5 触发父组件中的自定义事件 同时传递数据给 父组件
this.triggerEvent("itemChange",{index})
// 3 获取原数组
// let {tabs} = this.data
// 4 对数组循环
// [].forEach 遍历数组 遍历数组的时候修改了v, 也会导致原数组被修改
// tabs.forEach((v,i)=>i===index?v.isActive=true:v.isActive=false)
// this.setData({
// tabs
// })
}
}
(3)在父组件的js文件写触发事件的行为,用来接收子组件传递的数据
// 自定义事件 用来接收子组件传递的数据的
handleItemChange(e) {
// 接收传递过来的参数
const { index } = e.detail;
let {tabs} = this.data
tabs.forEach((v,i)=>i===index?v.isActive=true:v.isActive=false)
this.setData({
tabs
})
}
总结:当使用父向子传递数据时,子组件不能改变传递过来的数据,如上面说的isActive激活状态,只有使用子向父传递数据的方法,通过索引值(index)触发父组件的自定义事件来改变数据
7、使用插槽slot改变页面内容
你如果跟着上面做会发现,不管你点导航栏哪个选项,都只显示内容两字,所以,为了改变页面内容,我们可以使用slot标签
步骤:
(1)在子组件中定义一个slot标签
<view class="tabs">
<view class="tabs_title">
<view
wx:for="{{tabs}}"
wx:key="id"
class="title_item {{item.isActive?'active':''}}"
bindtap="hanldeItemTap"
data-index="{{index}}"
>
{{item.name}}
</view>
</view>
<view class="tabs_content">
<slot></slot>
</view>
</view>
(2)在父组件中使用block包装元素和wx:if控制属性来写页面内容
<tabs tabs="{{tabs}}" binditemChange="handleItemChange">
</tabs>
<block wx:if="{{tabs[0].isActive}}">
hello
</block>
<block wx:elif="{{tabs[1].isActive}}">你好</block>
<block wx:elif="{{tabs[2].isActive}}">savadica</block>
<block wx:else>bye</block>