[微信小程序] 入门笔记2-自定义一个显示组件
0. 准备工程
- 新建一个工程,删除清空app的内容和其余文件夹.然后自己新建
pages
和components
创建1个空组件和1个空页面.
- 设定
view
组件的默认样式,使其自动居中靠上,符合习惯.在app.wxss
内定义,作用做个工程.
/**app.wxss**/
/* 所有 view 容器, 默认, 从左到右从上到下, 完完全全居中对齐 */
view {
/* 弹性盒子布局 */
display: flex;
/* 决定主轴的方向 即项目的排列方向: 主轴为水平方向,起点在左端 */
flex-direction: row;
/* 定义项目是否换行以及如何换行: 换行,第一行在上方 */
flex-wrap: wrap;
/* 定义项目在主轴上的对齐方式: 居中 */
justify-content: center;
/* 定义项目在竖直方向上对齐方式: 交叉轴的中点对齐 */
align-items: center;
/* 定义多根轴线的对齐方式: 与交叉轴的中点对齐 */
align-content: center;
}
1.调用组件
- 在
page1.json
文件中导入组件.
- 在
page1.wxml
中调用组件.
- 将内容放在
view
内,自动居中对齐,上一步修改view
样式起的作用.如果之后需要修改特定的view
样式,再加个类名限定即可.
2.设定组件内容和属性
- 组件脚本
js
的所有内容都需要写在component({...})
内, 然后属性需要写在其中的properties:{}
内. 值得注意的是,属性的定义必须标明类型!!!采样以下固定的形式,必须包含type
和value
2个内容.算是定义了一个对象,然后数据存在该对象的value
属性内容.
properties: {
temp: {
type: String,
value: '666',
},
},
- 主要是定义一些样式, 还有需要显示的数据数组.
/**
* 组件的属性列表
*/
properties: {
/* 字体大小 rpx */
css_font_size: {
type: String,
value: "30",
},
/* 单个数据框高度 vh */
css_item_height: {
type: String,
value: "5",
},
/* 名称框的占比宽度 rpx */
css_name_width: {
type: String,
value: "150",
},
/* 冒号的占比宽度 rpx */
css_colon_width: {
type: String,
value: "40",
},
/* 数据的占比宽度 rpx */
css_val_width: {
type: String,
value: "80",
},
/* 数据数组, 3个元素分别: 名称, 内容, 是否输入 */
data_array: {
type: Array,
value: [{
str_name: '只输出',
str_data: '0',
isOutputOnly: true,
}, {
str_name: '可输入',
str_data: '0',
isOutputOnly: false,
}, ],
},
},
- 然后在
.wxml
文件内放置内容,wxml
内的{{}}
可以直接访问到js
脚本内的属性properties
和数据data
内容!!!data
内数据定义就无需非要指定类型.但是只有属性properties
能接收外部传入值,所以一般data
是处理内部数据使用. - 循环列表渲染,
wx:for
就不过多解释了.
<view class="showData-container" style="font-size:{{css_font_size}}rpx;">
<view class="showData-item" wx:for="{{data_array}}" wx:key="index">
<view style="height:{{css_item_height}}vh;width:{{css_name_width}}rpx;" >{{item.str_name}}</view>
<view style="height:{{css_item_height}}vh;width:{{css_colon_width}}rpx;">{{":"}}</view>
<view style="height:{{css_item_height}}vh;width:{{css_val_width}}rpx;">{{item.str_data}}</view>
</view>
</view>
- 然后打开
可视化
功能,可以单独调试组件的内容.然后结合调试器
.轻松调试.可以看到组件实际渲染效果,rpx
单位会被自动转换成px
单位.更改屏幕大小可以发现等比例相对变化.
3.传入数组
- 在页面
page.js
创建数组;
/* 数据显示数组 */
data_array: [{
str_name: '读取',
str_data: '5.89',
isOutputOnly: true,
}, {
str_name: '写入',
str_data: '0.985',
isOutputOnly: false,
}, ],
- 在
page1.wxml
内传入数组,同时可以修改一下样式,看看效果.不写的话就算保持组件内设定的默认值.
4.动态修改数组
- 在页面内修改了数组
data_array
,是可以同步影响到组件内的显示的. - 添加一个按钮,用于修改数据,可以在
可视化
界面内快速添加.长按拖拽, 或是先选择view
,然后双击添加. - 添加之后,拖拽组件可以修改位子,
ide
会自动 添加绝对位置的样式属性.不推荐这样设置.
- 为按钮添加属性
bind:tap
指定点击时的回调函数.
<button bind:tap="button0_tap">按钮</button>
4.5.bind
- 这是重中之重的要点!!! 第一次接触小程序,看到这个用法属实头蒙.
- 许多功能回调,或是特殊语法,都使用了
bind
前缀命名.在旧版语法中是没有冒号:
做分隔的.新版中支持使用冒号:
分隔,使得可读性提高.看例程时可能看到有些又冒号,有些没用,不要困惑,其实指的是同一个意思. - 然后
bind:tap
不是按钮的专属属性,是所有组件都可以添加的一个属性,意义是在点击时调用指定函数.所以将按钮改为view
或text
,是一样的效果.
<text bind:tap="button0_tap">pages/page1/page1.wxml\n</text>
- 然后在脚本
page1.js
内定义回调函数.
/* 按钮回调,修改显示数组 */
button0_tap(e) { // button0_tap: (e) => { // 错误的,这样函数内的 this 可能就没有了
console.log(e); // 定义看看传入的对象是什么, 可以在调试窗口查看
console.log(this); // 定义在调试仓库查看, 如果使用箭头函数, 这里打印为未定义
let temp_array = this.data.data_array; // 数据不能直接修改,先取出来,实际上对象是引用而非拷贝,所以只是换个短一点的名字
temp_array[0].str_data = Number((Number(temp_array[0].str_data) + 0.5).toFixed(3)) // 修改数据
temp_array[1].str_data = Number((Number(temp_array[1].str_data) - 0.5).toFixed(3))
temp_array.push(temp_array[0]); // 添加
temp_array.push(temp_array[1]); //
this.setData({ // 直接修改 this.data 不会起效果,需要使用 setData 修改
data_array: temp_array,
})
},
- 注意,数据中使用了字符串的形式,因为使用数字
num
形式在表示浮点数时会出现无尽小数的情况.为了避免这一问题,我直接采用字符串的形式. - 注意,不可以使用箭头函数定义,因为想要访问
this
内容,可以使用console
打印出来看看,可以详细查看对象内容.而不是控制台那种几个简单的字符.还能定位打印的位置,点击定位会跳转到sources
的page1.js?[sm]
文件窗口,这是一个调试界面,你可以在界面进行卡断点
,单步调试
,查看变量
等操作.非常方便.也可以使用快捷键Ctrl+P
查找指定的.js?[sm]
文件进行断点调试.
- 通过点击组件调用回调函数,修改了数组内容,自定义组件内的数据也同步变化了.也就是达到了从页面传入动态数据到组件了.组件的样式属性也可以类似的操作.
5.传出数据
- 为组件添加
input
组件,允许写入内容.替换原本的纯文本显示.注意到,回调函数使用bind:blur
指定,改属性代表失去焦点时调用.disabled
代表是否禁用.如果只输出显示数据就禁用.value
指定当前值,该数值也会传入回调函数中.另外还需要更多数据一同被传入回调函数,需要自己定义,使用data-名字="{{数据}}"
,的形式.
<view style="height:{{css_item_height}}vh;width:{{css_val_width}}rpx;">
<input disabled="{{item.isOutputOnly}}" value="{{item.str_data}}" data-index="{{index}}" data-name="{{item.str_name}}" bind:blur="input0_blur" />
</view>
- 然后在脚本
component1.js
中添加回调函数;记得组件的方法,需要写在methods:{...}
内. - 注意,
value
存放在e.detail
中,这是自带的返回数据,而自定义的数据index
和name
是存放在e.currentTarget.dataset
中,切记区分.最后通过this.triggerEvent
,将数据传给指定的回调函数. - 注意,不要使用箭头函数,不然this会丢失,无法指定回调函数.可以使用
input0_blur: function (e) {
的形式.
/**
* 组件的方法列表
*/
methods: {
/* 输入回调函数 */
input0_blur(e) { // input0_blur: function (e) { // input0_blur: (e) => {
console.log(e); // 定义看看传入的对象是什么, 可以在调试窗口查看
console.log(this); // 定义在调试仓库查看, 如果使用箭头函数, 这里打印为未定义
const value = e.detail.value; // input 只传入了这一个值
const index = e.currentTarget.dataset.index; // 这2个是扩展的值
const name = e.currentTarget.dataset.str_name; // 这2个是扩展的值
this.triggerEvent('input1_blur', { // 调用外部回调函数, 指定函数名,与传出去的数据包
value,
index,
name,
});
},
}
- 这时点击模拟窗口,就可以看到效果.打印的
e
能看到数据存放的位置.e
是Event
的缩写.可以以此代表该函数是回调函数,提高代码阅读性.
- 修改
page1.wxml
,添加指定的回调函数.注意,使用了bind
关键字,另外,我故意使用不同的函数名,代表他们之间的引用关系.
<component1
css_font_size="30"
css_name_width="100"
css_val_width="80"
css_item_height="5"
bind:input1_blur="input2_blur"
data_array="{{data_array}}"> </component1>
- 在
page1.js
中添加回调函数;注意到,这次3个变量数据value
,index
,name
.都在e.detail
内,因为人为指定了3个. - 注意, 我使用了索引的方式查找数据,其实应该用键名的方式查找,但是并不能像字典那样直接定位,所以还是选择方便快捷的索引定位.姑且加了保护措施,确定键名符合才进行.
/* 接收数据回调 */
input2_blur(e) {
console.log(e); // 定义看看传入的对象是什么, 可以在调试窗口查看
console.log(this); // 定义在调试仓库查看, 如果使用箭头函数, 这里打印为未定义
let temp_array = this.data.data_array; // 数据不能直接修改,先取出来,实际上对象是引用而非拷贝,所以只是换个短一点的名字
const value = e.detail.value; // input 只传入了这一个值
const index = e.detail.index; // 这2个是扩展的值
const name = e.detail.name; // 这2个是扩展的值
if (name == temp_array[index].str_name) // 保护措施, 使用索引寻找定位, 判断是否定位正确
temp_array[index].str_data = value; // 符合就将数据修改了
temp_array[0].str_data = temp_array[1].str_data; // 这里做点修改,体现在界面上方便才看
this.setData({ // 直接修改 this.data 不会起效果,需要使用 setData 修改
data_array: temp_array,
})
},
- 现在就可以模拟调试看看效果,我就不再截图了.
现在可能还有个问题,每写一个这种组件,如果要接收其返回的数据,都要定义一个回调函数.好像用qt时也差不多,就这样吧.
6.组件样式
- 最后一步是修改样式, 白底黑字太难看.没有框框也没有边界感,而且无法区分输入和输出的样式.
- 对于输入框,添加了样式判断,在
{{}}
中使用经典的三目运算符,选择不同字符串,也就是不同样式名.也可以使用wx:if
判断使用不同组件达到同样效果;
<!--components/component1/component1.wxml-->
<!-- 删去,有错误,修正后的放下面 -->
- 样式具体建议去看看美化网站提供的例子 https://uiverse.io
- 值得说明的是,虽然我在开始的
app.wxss
指定了view
组件的默认样式,但是我在自定义组件内又重新设定一遍,为了方便移植.
/* components/component1/component1.wxss */
/* 删去,有错误,修正后的放下面 */
- 至此算是基本具备一个基础组件的使用.
7.样式警告
- 我发现上面的写法会给出一个样式警告,如下.因此还是修改
app.wxss
的全局组件名样式,修改为类名样式(或直接删去).然后全部页面和组件都使用类名样式.
Some selectors are not allowed in component wxss, including tag name selectors, ID selectors, and attribute selectors.
组件wxs中不允许使用某些选择器,包括标记名选择器、ID选择器和属性选择器。
- 组件布局修改如下. 每个view都指定了类名样式. 同时使用组合类名样式
<!--components/component1/component1.wxml-->
<view class="showData-container" style="font-size:{{css_font_size}}rpx;">
<view class="showData-container showData-item" wx:for="{{data_array}}" wx:key="index">
<view class="showData-container" style="height:{{css_item_height}}vh;width:{{css_name_width}}rpx;" >{{item.str_name}}</view>
<view class="showData-container" style="height:{{css_item_height}}vh;width:{{css_colon_width}}rpx;">{{":"}}</view>
<view class="showData-container" style="height:{{css_item_height}}vh;width:{{css_val_width}}rpx;">
<input class="showData-data {{item.isOutputOnly ? 'showData-output' : 'showData-input'}}"
disabled="{{item.isOutputOnly}}" value="{{item.str_data}}" data-index="{{index}}" data-name="{{item.str_name}}" bind:blur="input0_blur" />
</view>
</view>
</view>
/* components/component1/component1.wxss */
/* 定义该容器内所有 view 的样式 */
.showData-container{
/* 弹性盒子布局 */
display: flex;
/* 决定主轴的方向 即项目的排列方向: 主轴为水平方向,起点在左端 */
flex-direction: row;
/* 定义项目是否换行以及如何换行: 换行,第一行在上方 */
flex-wrap: wrap;
/* 定义项目在主轴上的对齐方式: 居中 */
justify-content: center;
/* 定义项目在竖直方向上对齐方式: 交叉轴的中点对齐 */
align-items: center;
/* 定义多根轴线的对齐方式: 与交叉轴的中点对齐 */
align-content: center;
}
/* 单个框框加阴影边框 */
.showData-item {
/* 边框样式 */
border: 1rpx solid #ccc;
/* 阴影效果 */
box-shadow: 0px 0px 5rpx rgba(0, 0, 0, 0.5);
/* 内外边距 */
padding: 5rpx;
margin: 5rpx;
/* 设定圆角 */
border-radius: 10rpx;
/* 如何计算一个元素的总宽度和总高度: 防止溢出 */
box-sizing: border-box;
}
/* 输出框, 文字居中, 下划线 */
.showData-data {
/* 规定填满父级 view */
height: 100%;
width: 100%;
/* 背景颜色透明, */
background-color: transparent;
/* 文字居中 */
text-align: center;
/* 如何计算一个元素的总宽度和总高度: 防止溢出 */
box-sizing: border-box;
}
/* 输入框 下划线 */
.showData-input {
/* 简写属性, 下边框的所有属性 */
border-bottom: 2rpx solid rgb(61, 61, 61);
}
/* 选中输入框, 外边框 */
.showData-input:focus,
.showData-input:hover {
/* 简写属性, 外边框的所有属性 */
outline: 2rpx solid rgb(61, 61, 61);
}
- 组件调用时修改一下, 添加宽度填满父级,避免布局混乱.
<component1 style="width: 100%;"
css_font_size="32"
css_name_width="100"
css_val_width="120"
css_item_height="5"
bind:input1_blur="input2_blur"
data_array="{{data_array}}"> </component1>