1.小程序自定义组件的意义
所谓的自定义组件,也是由json,wxml,wxss,js四个部分组成
开发步骤:创建组件→声明组件→使用组件
1.1 自定义组件components→Tabs
第一步:创建自定义组件分为几个步骤:
- 首先创建pages同级文件components,在components下面创建Tabs文件夹,其实此后也就认为该Tabs文件夹为自定义组件了。
- 其次右击Tabs选择新建component,输入Tabs回车,即可自动生成相应的Tabs.js,Tabs.json,Tabs.wxml,Tabs.wxss
- 看效果,此时打开Tabs.js与其他js文件差异:
第二步:声明自定义组件,说白了就是立个flag,哪个页面用就在
哪个json中声明
{
"usingComponents": {
"Tabs":"../../components/Tabs/Tabs"
}
} 例如我们在demo17.json中添加,usingComponet里面是键值对:
第三步:使用组件
我们在demo17.wxml中使用<Tabs>00</Tabs>没有显示00,但显示了其自定义组件的里面的信息。
1.2 自定义组件实战-emmet写法
注意,设为 Flex 布局以后,子元素的float
、clear
和vertical-align
属性将失效。参见:
需要注意的是:tabs.wxml中如果这么写,就写死了,这样就没办法把同样的结构放在不同的页面中,我们需要把标题数据,也就是首页,原创,分类,关于抽离出来,放在tabs.js文件中,我们先尝试把它放在组件内部数据中,稍后再由它(组件内部数据)改成父向子传递数据:
<view class="tabs">
<view class="tabs_title">
<view class="title_item">首页</view>
<view class="title_item">原创</view>
<view class="title_item">分类</view>
<view class="title_item">关于</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:0,
name:"关于",
isActive:false
},
]
},
/**
* 组件的方法列表
* 在page页面js中,存放事件回调函数的时候,存放在data同级下
* 在组件页面js中,存放事件回调函数的时候,必须放在methods中
*/
methods: {
hanldeItemTap(e){
// console.log("点击我试试");
// 1. 绑定点击事件,需要在methods中绑定
console.log(e)
// 2. 获取被点击的索引
const {index}= e.currentTarget.dataset;
// const {index}=e.currentTarget.dataset
// 3. 获取原数组
let {tabs}=this.data;//此处涉及解构思想,复杂类型进行结构时候,复制了一份变量引用而已,它与let tabs=this.data.tabs意思一样
//当然最严谨的做法是重新拷贝一个数组,如下:
// let tabs=JSON.prase(JSON.stringify(this.data.tabs));
// 4. 对数组循环,给每个循环项,选中属性改为false,且给当前的索引项添加激活选中效果即可,因有解构,类似于this.data.tabs.forEach一样
tabs.forEach((v,i)=>i===index?v.isActive=true:v.isActive=false);
this.setData({
tabs
})
}
}
})
Tabs.wxss文件代码:
/* components/Tabs/Tabs.wxss */
.tabs{
border: 1rpx blue solid;
}
.tabs_title{
display: flex;/*伸缩盒子*/
padding: 10rpx;
}
.title_item{
flex: 1;
display: flex;/*伸缩盒子*/
justify-content: center;/*垂直对齐*/
align-items: center;/*水平对齐*/
}
.active{/*激活选中效果*/
color:red;
border-bottom: 10rpx solid currentColor;
}
.tabs_content{
border: 1rpx solid yellow;
}
Tabs.wxml代码如下:
<!--components/Tabs/Tabs.wxml-->
<view class="tabs">
<view class="tabs_title">
<!--
<view class="title_item">首页</view>
<view class="title_item">原创</view>
<view class="title_item">分类</view>
<view class="title_item">关于</view>
-->
<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>
demo17.WXML代码:
<!--pages/demo17/demo17.wxml-->
<Tabs></Tabs>
第二步:抽离自定义组件内部Tabs.js下面Componet下面的data里面tabs数据抽离到demo17里面,这样才能实现一个自定义组件多处使用,这就存在了父向子组件传值的问题,最为简单的处理方式是:属性值传递:
具体实现代码如下:
demo17.html
<!--pages/demo17/demo17.wxml-->
<!--
1.父组件(页面)传递数据通过标签属性
2.子组件进行接收
-->
<!--下面代表意思是,我向子组件Tabs传递了一个属性:属性名aaa 属性值123-->
<Tabs tabs="{{tabs}}"></Tabs>
Tabs.wxml
<!--components/Tabs/Tabs.wxml-->
<view class="tabs">
<view class="tabs_title">
<!--
<view class="title_item">首页</view>
<view class="title_item">原创</view>
<view class="title_item">分类</view>
<view class="title_item">关于</view>
-->
<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: {
/*接受父向子传递数据 */
// 从demo17.WXML数据
// aaa:{
// // type:表示要接收的数据类型
// type:String,
// value:''//表示默认值,传123,就123,没有为空
// }
tabs:{
type:Array,
value:[]
}
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
* 在page页面js中,存放事件回调函数的时候,存放在data同级下
* 在组件页面js中,存放事件回调函数的时候,必须放在methods中
*/
methods: {
hanldeItemTap(e){
// console.log("点击我试试");
// 1. 绑定点击事件,需要在methods中绑定
console.log(e)
// 2. 获取被点击的索引
const {index}= e.currentTarget.dataset;
// const {index}=e.currentTarget.dataset
// 3. 获取原数组
let {tabs}=this.data;//此处涉及解构思想,复杂类型进行结构时候,复制了一份变量引用而已,它与let tabs=this.data.tabs意思一样
//当然最严谨的做法是重新拷贝一个数组,如下:
// let tabs=JSON.prase(JSON.stringify(this.data.tabs));
// 4. 对数组循环,给每个循环项,选中属性改为false,且给当前的索引项添加激活选中效果即可,因有解构,类似于this.data.tabs.forEach一样
tabs.forEach((v,i)=>i===index?v.isActive=true:v.isActive=false);
this.setData({
tabs
})
}
}
})
Demo17.js
// pages/demo17/demo17.js
Page({
/**
* 页面的初始数据
*/
data: {
tabs:[{
id:0,
name:"首页",
isActive:true
},
{
id:1,
name:"原创",
isActive:false
},
{
id:2,
name:"分类",
isActive:false
},
{
id:0,
name:"关于",
isActive:false
},
]
},
第三步:我们进行代码优化上图可知:当我们tab.js的properties接受父数据tabs:{ }(父传子),当我们点击标题内容,关于时,应该会触发点击事件,而此时data数据为空的。因此
methods: {
hanldeItemTap(e){
// console.log("点击我试试");
// 1. 绑定点击事件,需要在methods中绑定
console.log(e)
// 2. 获取被点击的索引
const {index}= e.currentTarget.dataset;下一步将出错,因为data为空,
let tabs也就错误了,一句话:isActive值没有改变
// const {index}=e.currentTarget.dataset
// 3. 获取原数组
let {tabs}=this.data;//此处涉及解构思想,复杂类型进行结构时候,复制了一份变量引用而已,它与let tabs=this.data.tabs意思一样
//当然最严谨的做法是重新拷贝一个数组,如下:
// let tabs=JSON.prase(JSON.stringify(this.data.tabs));
// 4. 对数组循环,给每个循环项,选中属性改为false,且给当前的索引项添加激活选中效果即可,因有解构,类似于this.data.tabs.forEach一样
tabs.forEach((v,i)=>i===index?v.isActive=true:v.isActive=false);
this.setData({
tabs
})
}
}
总结一句话,就是当我们点击标签后,激活事件应该改变,如下图就没有改变,这一步操作就是子向父传数据: