微信小程序开发 —— 基础知识
2021/8/23 20:05:33
本文主要是介绍微信小程序开发 —— 基础知识,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
一、目录结构
二、配置
一个小程序应用程序会包括最基本的两种配置文件。一种是全局配置文件 app.json 和 页面自己的配置文件 page.json.
配置文件中不能出现注释
2.1 全局配置 app.json
app.json 是当前小程序的全局配置,包括了小程序的所有页面路径、界面表现、网络超时时间、底部tab等。
{
"pages":[
"pages/index2/index2",
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"dark",
"navigationBarBackgroundColor": "#0094ff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle":"black",
"enablePullDownRefresh":true
},
"tabBar": {
"list": [
{
"pagePath":"pages/index/index",
"text":"首页",
"iconPath": "icons/home.png",
"selectedIconPath": "icons/home_active.png"
},
{
...
}
],
"backgroundColor": "#CCC",
"color": "#000",
"selectedColor": "#0094ff",
"borderStyle": "white"
}
}
2.2 页面配置page.json
page.json
表示页面目录下 page.json
和小程序页面相关的配置;
我们可以独立为每一个页面定义属性,如顶部颜色、是否允许被下拉刷新等;
page.json
只能设置 app.json
中部分 window
配置项的内容,page.json
会覆盖 app.json
的 window
中相同的配置。
三、视图层
WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合 基础组件、事件系统,可以构成页面的结构
3.1 数据绑定
WXML 中的动态数据均来自对应 Page 的 data。
简单绑定
数据绑定使用 Mustache 语法(双大括号)将变量包起来,可以作用于:
内容
<view> {{ message }} </view>
Page({
data: {
message: 'Hello MINA!'
}
})
组件属性(需要在双引号之内)
<view id="item-{{id}}"> </view>
Page({
data: {
id: 0
}
})
控制属性(需要在双引号之内)
<view wx:if="{{condition}}"> </view>
Page({
data: {
condition: true
}
})
关键字(需要在双引号之内)
true
:boolean 类型的 true,代表真值。
false
: boolean 类型的 false,代表假值。
<checkbox checked="{{false}}"> </checkbox>复制代码
特别注意:不要直接写 checked="false"
,其计算结果是一个字符串,转成 boolean 类型后代表真值。
运算
可以在 {{}}
内进行简单的运算,支持的有如下几种方式:
三元运算
<view hidden="{{flag ? true : false}}"> Hidden </view>
算数运算
<view> {{a + b}} + {{c}} + d </view>
Page({
data: {
a: 1,
b: 2,
c: 3
}
})
view中的内容为 3 + 3 + d
。
逻辑判断
<view wx:if="{{length > 5}}"> </view>
字符串运算
<view>{{"hello" + name}}</view>
Page({
data:{
name: 'MINA'
}
})
数据路径运算
<view>{{object.key}} {{array[0]}}</view>
Page({
data: {
object: {
key: 'Hello '
},
array: ['MINA']
}
})
组合
也可以在 Mustache 内直接进行组合,构成新的对象或者数组。
数组
<view wx:for="{{[zero, 1, 2, 3, 4]}}"> {{item}} </view> Page({ data: { zero: 0 } })
最终组合成数组[0, 1, 2, 3, 4]
。
对象
<template is="objectCombine" data="{{for: a, bar: b}}"></template> Page({ data: { a: 1, b: 2 } })
最终组合成的对象是 {for: 1, bar: 2}
也可以用扩展运算符 ...
来将一个对象展开
<template is="objectCombine" data="{{...obj1, ...obj2, e: 5}}"></template> Page({ data: { obj1: { a: 1, b: 2 }, obj2: { c: 3, d: 4 } } })
最终组合成的对象是 {a: 1, b: 2, c: 3, d: 4, e: 5}
。
如果对象的 key 和 value 相同,也可以间接地表达。
<template is="objectCombine" data="{{foo, bar}}"></template> Page({ data: { foo: 'my-foo', bar: 'my-bar' } })
最终组合成的对象是 {foo: 'my-foo', bar:'my-bar'}
。
注意:上述方式可以随意组合,但是如有存在变量名相同的情况,后边的会覆盖前面,如:
<template is="objectCombine" data="{{...obj1, ...obj2, a, c: 6}}"></template> Page({ data: { obj1: { a: 1, b: 2 }, obj2: { b: 3, c: 4 }, a: 5 } })
最终组合成的对象是 {a: 5, b: 3, c: 6}
。
注意: 花括号和引号之间如果有空格,将最终被解析成为字符串
<view wx:for="{{[1,2,3]}} "> {{item}} </view>
等同于
<view wx:for="{{[1,2,3] + ' '}}"> {{item}} </view>
3.2 列表渲染
wx:for
在组件上使用 wx:for
控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。
默认数组的当前项的下标变量名默认为 index
,数组当前项的变量名默认为 item
使用 wx:for-item
可以指定数组当前元素的变量名,
使用 wx:for-index
可以指定数组当前下标的变量名:
<view wx:for="{{array}}" wx:for-index="i" wx:for-item="value"> {{i}} : {{value}} </view>> <view wx:for="{{person}}" wx:for-index="key" wx:for-item="value"> {{key}} : {{value}} </view>
Page({ /** * 页面的初始数据 */ data: { array:["姓名","年龄","性别"], person:{ name:"xiaojian", age:22, gender:"man" } })
wx:for
还可以嵌套
<view wx:for="{{[1,2,3,4,5,6,7,8,9]}}" wx:for-item="i"> <view wx:for="{{[1,2,3,4,5,6,7,8,9]}}" wx:for-item="j"> <view wx:if="{{j <= i}}"> {{j}} * {{i}} = {{j * i}} </view> </view> </view>
注意:当 wx:for
的值没有用两个花括号括起来,将被当成字符串,字符串被解析成字符数组
<view wx:for="[1,2,3,4,5]"> {{index}} : {{item}} </view>
等同于
<view wx:for="{{['[','1','2','3','4','5',']']}}"> {{item}} </view>
输出
0 : [ 1 : 1 2 : 2 3 : 3 4 : 4 5 : 5 6 : ]
注意: 花括号和引号之间如果有空格,将最终被解析成为字符串
wx:key
如果列表中项目的位置会动态改变或者有新的项目添加到列表中,并且希望列表中的项目保持自己的特征和状态(如 input 输入内容,switch 的选中状态) ,需要使用 wx:key
来指定列表中项目的唯一的标识符。
wx:key
的值以两种形式提供
字符串,代表在 for 循环的 array 中 item 的某个 property,该 property 的值需要是列表中唯一的字符串或数字,且不能动态改变。
保留关键字
*this
代表在 for 循环中的 item 本身,这种表示需要 item 本身是一个唯一的字符串或者数字。
当数据改变触发渲染层重新渲染的时候,会校正带有 key 的组件,框架会确保他们被重新排序,而不是重新创建,以确保使组件保持自身的状态,并且提高列表渲染时的效率。
如不提供 wx:key
,会报一个 warning
, 如果明确知道该列表是静态,或者不必关注其顺序,可以选择忽略。
3.3 WXSS
WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式。
尺寸单位
rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
建议: 开发微信小程序时设计师可以用 iPhone6 作为视觉稿的标准。
注意: 在较小的屏幕上不可避免的会有一些毛刺,请在开发时尽量避免这种情况。
样式导入
在一个页面文件夹下的页面文件是不用导入该文件夹下的样式文件的,它们所有的文件都是自动关联的。
要导入外联样式表,需要在该页面文件夹下的 page.wxss 文件里:
使用@import
语句可以导入外联样式表,@import
后跟需要导入的外联样式表的相对路径,用;
表示语句结束。
/* page.wxss */ @import "../../styles/commons.wxss"; ...
且, page.wxss 的样式优先级大于 外部引入的样式表;
即,针对相同的标签写的样式, page.wxss 中的样式会覆盖外部引入的样式
注意: wxss 中的注释只能写成 / ... /
内联样式
框架组件上支持使用 style、class 属性来控制组件的样式。
style:静态的样式统一写到 class 中。style 接收动态的样式,在运行时会进行解析,请尽量避免将静态的样式写进 style 中,以免影响渲染速度。
<view style="color:{{color}};" />
class:用于指定样式规则,其属性值是样式规则中类选择器名(样式类名)的集合,样式类名不需要带上
.
,样式类名之间用空格分隔。
<view class="normal_view" />
四、组件
1、基本组件
1.1 view
属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
---|---|---|---|---|---|
hover-class | string | none | 否 | 指定按下去的样式类。当 hover-class="none" 时,没有点击态效果 | 1.0.0 |
hover-stop-propagation | boolean | false | 否 | 指定是否阻止本节点的祖先节点出现点击态 | 1.5.0 |
hover-start-time | number | 50 | 否 | 按住后多久出现点击态,单位毫秒 | 1.0.0 |
hover-stay-time | number | 400 | 否 | 手指松开后点击态保留时间,单位毫秒 | 1.0.0 |
作者:笑容不是为了我
链接:https://juejin.cn/post/6998715649560313886
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
1.2 text
文本,相当于 html 中的span
text只能嵌套 text
属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
---|---|---|---|---|---|
selectable | boolean | false | 否 | 文本是否可选 (已废弃) | 1.1.0 |
user-select | boolean | false | 否 | 文本是否可选,该属性会使 文本节点显示为 inline-block | 2.12.1 |
space | string | 否 | 显示连续空格 | 1.4.0 | |
decode | boolean | false | 否 | 是否解码 | 1.4.0 |
space 的合法值
值 | 说明 | 最低版本 |
---|---|---|
ensp | 中文字符空格一半大小 | |
emsp | 中文字符空格大小 | |
nbsp | 根据字体设置的空格大小 |
1.3 image
图片标签,image 组件默认宽度 320px、高度 240px
支持 JPG、PNG、SVG、WEBP、GIF 等格式,2.3.0 起支持云文件ID。
注意:该标签 其实是 web 中的 图片和 背景图片 的结合!并且不支持以前 web 中的背景图片的写法!
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
src | string | 否 | 图片资源地址 | |
mode | string | scaleToFill | 否 | 图片裁剪、缩放的模式 |
webp | boolean | false | 否 | 默认不解析 webP 格式,只支持网络资源 |
lazy-load | boolean | false | 否 | 图片懒加载,在即将进入一定范围 (上下三屏)时才开始加载 |
mode 的合法值
值 | 说明 |
---|---|
scaleToFill | 缩放模式,不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素 |
aspectFit | 缩放模式,保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。 |
aspectFill | 缩放模式,保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。 |
widthFix | 缩放模式,宽度不变,高度自动变化,保持原图宽高比不变 |
heightFix | 缩放模式,高度不变,宽度自动变化,保持原图宽高比不变 |
top | 裁剪模式,不缩放图片,只显示图片的顶部区域 |
bottom | 裁剪模式,不缩放图片,只显示图片的底部区域 |
center | 裁剪模式,不缩放图片,只显示图片的中间区域 |
left | 裁剪模式,不缩放图片,只显示图片的左边区域 |
right | 裁剪模式,不缩放图片,只显示图片的右边区域 |
top left | 裁剪模式,不缩放图片,只显示图片的左上边区域 |
top right | 裁剪模式,不缩放图片,只显示图片的右上边区域 |
bottom left | 裁剪模式,不缩放图片,只显示图片的左下边区域 |
bottom right | 裁剪模式,不缩放图片,只显示图片的右下边区域 |
1.4 swiper
微信内置轮播图组件。默认宽度 100%,高度150px.
滑块视图容器。其中只可放置
swiper-item
组件,否则会导致未定义的行为。
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
indicator-dots | boolean | false | 否 | 是否显示面板指示点 |
indicator-color | color | rgba(0, 0, 0, .3) | 否 | 指示点颜色 |
indicator-active-color | color | #000000 | 否 | 当前选中的指示点颜色 |
autoplay | boolean | false | 否 | 是否自动切换 |
current | number | 0 | 否 | 当前所在滑块的 index |
interval | number | 5000 | 否 | 自动切换时间间隔 |
duration | number | 500 | 否 | 滑动动画时长 |
circular | boolean | false | 否 | 是否采用衔接滑动 |
vertical | boolean | false | 否 | 滑动方向是否为纵向 |
easing-function | string | "default" | 否 | 指定 swiper 切换缓动动画类型 |
easing-function 的合法值
值 | 说明 | 最低版本 |
---|---|---|
default | 默认缓动函数 | |
linear | 线性动画 | |
easeInCubic | 缓入动画 | |
easeOutCubic | 缓出动画 | |
easeInOutCubic | 缓入缓出动画 |
1.5 navigator
导航组件,类似超链接标签
属性 | 类型 | 默认值 | 必填 | 说明 | |
---|---|---|---|---|---|
target | string | self | 否 | 在哪个目标上发生跳转,默认当前小程序 | |
url | string | 否 | 当前小程序内的跳转链接 | 链接后不用加文件名.wxml | |
open-type | string | navigate | 否 | 跳转方式 | |
delta | number | 1 | 否 | 当 open-type 为 'navigateBack' 时有效,表示回退的层数 |
target 的合法值
值 | 说明 |
---|---|
self | 当前小程序 |
miniProgram | 其它小程序 |
open-type 的合法值
值 | 说明 |
---|---|
navigate | 保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面。 |
redirect | 关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面 |
switchTab | 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面 |
reLaunch | 关闭所有页面,打开到应用内的某个页面 |
navigateBack | 关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages 获取当前的页面栈,决定需要返回几层 |
exit | 退出小程序,target="miniProgram" 时生效 |
1.6 video
视频。
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
src | string | 是 | 要播放视频的资源地址,支持网络路径、 本地临时路径、云文件ID(2.3.0) | |
duration | number | 否 | 指定视频时长 | |
controls | boolean | true | 否 | 是否显示默认播放控件 (播放/暂停按钮、播放进度、时间) |
autoplay | boolean | false | 否 | 是否自动播放 |
loop | boolean | false | 否 | 是否循环播放 |
muted | boolean | false | 否 | 是否静音播放 |
show-mute-btn | boolean | false | 否 | 是否显示静音按钮 |
initial-time | number | 0 | 否 | 指定视频初始播放位置 |
title | string | 否 | 视频的标题,全屏时在顶部展示 | |
enable-play-gesture | boolean | false | 否 | 是否开启播放手势,即双击切换播放/暂停 |
1.7 button
按钮
样式
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
size | string | default | 否 | 按钮的大小 |
type | string | default | 否 | 按钮的样式类型 |
plain | boolean | false | 否 | 按钮是否镂空,背景色透明 |
disabled | boolean | false | 否 | 是否禁用 |
loading | boolean | false | 否 | 名称前是否带 loading 图标 |
form-type | string | 否 | 用于 form 组件,点击分别会触发 form 组件的 submit/reset 事件 | |
open-type | string | 否 | 微信开放能力 |
size 的合法值
值 | 说明 |
---|---|
default | 默认大小 |
mini | 小尺寸 |
type 的合法值
值 | 说明 |
---|---|
primary | 绿色 |
default | 白色 |
warn | 红色 |
form-type 的合法值
值 | 说明 |
---|---|
submit | 提交表单 |
reset | 重置表单 |
open-type 的合法值
值 | 说明 |
---|---|
contact | 打开客服会话,如果用户在会话中点击消息卡片后返回小程序,可以从 bindcontact 回调中获得具体信息,具体说明 |
share | 触发用户转发,使用前建议先阅读使用指引 |
getPhoneNumber | 获取用户手机号,可以从bindgetphonenumber回调中获取到用户信息,具体说明 |
getUserInfo | 获取用户信息,可以从bindgetuserinfo回调中获取到用户信息 |
launchApp | 打开APP,可以通过app-parameter属性设定向APP传的参数具体说明 |
openSetting | 打开授权设置页 |
feedback | 打开“意见反馈”页面,用户可提交反馈内容并上传日志,开发者可以登录小程序管理后台后进入左侧菜单“客服反馈”页面获取到反馈内容 |
2、自定义组件
小程序允许我们使用自定义组件的方式来构建页面。
自定义组件就像是web中,一个个公共页面,将一个个公共页面提取出来,在其他页面中用到时就直接使用一个标签就解决了。
2.1 创建自定义组件
类似页面,一个自定义组件由
json
、wxml
、wxss
、js
4个文件组成
# MyHeader.json { "component": true, "usingComponents": {} }
# MyHeader.wxml <view class="my_header"> {{initalData}} <view> <!-- 该标签可以加入自定义的内容 --> <slot></slot> </view> </view>
// components/MyHeader/MyHeader.js Component({ /** * 组件的属性列表 * 初始值,当有页面使用这个组件并声明了 initalData 这个属性,初始值将会被替换 * 属性名:{ * type:(类型), * value:(默认值) * } */ properties: { initalData:{ value:"这是初始值", type:String } }, /** * 组件的初始数据 */ data: { }, /** * 组件的方法列表 */ methods: { } })
.my_header{ color:blue; background-color: red; width:200px; height:200px; }
2.2 使用自定义组件
# customModule.json { "usingComponents": { "my":"../../components/MyHeader/MyHeader" } }
# customModule.wxml <view> <my>包含数据</my> </view>>
# customModule.wxml <view initalData="替换值"> <my>自定义???</my> </view>>复制代码
2.3 进阶
自定义组件,一个导航条,对每次点击添加样式。
# Tabs.json { "component": true, "usingComponents": {} }复制代码
<!-- Tabs.wxml --> <view class="tabs"> <view class="tabs_title"> <view wx:for="{{tabs}}" wx:key="id" class="title_item {{item.isActive?'active':'none'}}" bindtap="handleItemTap" data-index="{{index}}"> {{item.name}} </view> </view> <view class="tab_content">内容</view> </view>
/* Tabs.wxss */ .tabs{ } .tabs_title{ display:flex; padding:10rpx; } .title_item{ flex:1; display:flex; justify-content: center; align-items: center; } .active{ color:red; border-top: 5px solid red; }
// 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: { /** * 1.绑定点击事件 需要在methods 中绑定 * 2.获取被点击的索引 * 3.获取原数组 * 4.对数组循环 * 4.1 给每个循环项 选中属性 改为 false * 4.2 给当前索引的项 添加选中 */ handleItemTap(e){ console.log(e); // 2.获取被点击的索引 var indexList = e.currentTarget.dataset.index; // 3.获取原数组 var tabs = this.data.tabs; tabs.forEach((v,i)=>i===indexList?v.isActive=true:v.isActive=false); this.setData({ tabs }) } } })
使用
# customModule.json { "usingComponents": { "Tabs":"../../components/Tabs/Tabs" } }复制代码
<!-- customModule.wxml --> <Tabs></Tabs>
自定义组件--父向子传递数据
自定义组件为 子,使用自定义组件的页面是 父
用 属性列表 替换 自定义组件中的固定值
<!-- customModule.wxml --> <Tabs tabs="{{tabs}}"></Tabs>复制代码
// customModule.js Page({ /** * 页面的初始数据 */ data: { tabs:[ { id:0, name:"热门", isActive:true }, { id:1, name:"重点", isActive:false }, { id:2, name:"声明", isActive:false }, { id:3, name:"反馈", isActive:false } ] } })
// Tabs.js Component({ /** * 组件的属性列表 */ properties: { tabs:{ type:Array, value:[] } }, /** * 组件的初始数据 */ data: { }, /** * 组件的方法列表 */ methods: { handleItemTap(e){ ... } } })
自定义组件--子向父传递数据
根据上方方法,子组件使用的父组件的数据,但是执行点击事件时,修改的仍是子组件数据;所以,要编写成修改父组件中的数据的值
解决方法:
将子组件点击事件交给父组件执行,子组件负责传递数据给父组件
# Tabs.js ... handleItemTap(e){ // 2.获取被点击的索引 var index = e.currentTarget.dataset.index; // 5.点击事件触发的时候 // 触发父组件中的自定义事件 同时传递数据给父组件 this.triggerEvent("itemChange",{index}); } ...
<!-- customModule.wxml --> <!-- 定义事件,bind事件名称,的形式 --> <Tabs tabs="{{tabs}}" binditemChange="handleItemChange"> </Tabs>复制代码
# customModule.js ... // 自定义事件 接受子组件传的数据 并执行相应操作 handleItemChange(e){ // console.log(e); // 2.获取被点击的索引 var index = e.detail.index; // 3.获取原数组 var tabs = this.data.tabs; // 4.对数组循环 tabs.forEach((v,i)=>i===index?v.isActive=true:v.isActive=false); this.setData({ tabs }) } ...
五、小程序生命周期
1、应用生命周期
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
onLaunch | function | 否 | 监听小程序初始化。 | |
onShow | function | 否 | 生命周期回调——监听小程序启动或切前台。 | |
onHide | function | 否 | 生命周期回调——监听小程序切后台。 | |
onError | function | 否 | 错误监听函数。 | |
onPageNotFound | function | 否 | 页面不存在监听函数。 | |
onUnhandledRejection | function | 否 | 未处理的 Promise 拒绝事件监听函数。 | |
onThemeChange | function | 否 | 监听系统主题变化 |
2、页面生命周期
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
data | Object | 页面的初始数据 | ||
onLoad | function | 生命周期回调—监听页面加载 | ||
onShow | function | 生命周期回调—监听页面显示 | ||
onReady | function | 生命周期回调—监听页面初次渲染完成 | ||
onHide | function | 生命周期回调—监听页面隐藏 | ||
onUnload | function | 生命周期回调—监听页面卸载 | ||
onPullDownRefresh | function | 监听用户下拉动作 | ||
onReachBottom | function | 页面上拉触底事件的处理函数 | ||
onShareAppMessage | function | 用户点击右上角转发 | ||
onShareTimeline | function | 用户点击右上角转发到朋友圈 | ||
onAddToFavorites | function | 用户点击右上角收藏 | ||
onPageScroll | function | 页面滚动触发事件的处理函数 | ||
onResize | function | 页面尺寸改变时触发,详见 响应显示区域变化 | ||
onTabItemTap | function | 当前是 tab 页时,点击 tab 时触发 | ||
其他 | any | 开发者可以添加任意的函数或数据到 Object 参数中,在页面的函数中用 this 可以访问 |
3、页面生命周期图解
作者:笑容不是为了我
这篇关于微信小程序开发 —— 基础知识的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2021-07-26小程序自定义标题栏写法(适配各种大小刘海屏)
- 2021-07-25小程序如何实现自定义tabBar
- 2021-07-25微信小程序如何在父组件中修改子组件的样式