HarmonyOS:删除多层ForEach循环渲染的复杂数据而导致的一系列问题
目录
1.页面效果及需求
2.遇到问题时的初始代码及问题
代码
问题
3.状态变化不能深层监听?
解答
4.使用了@ObjectLink装饰器后为什么数据仍然无法被监听?
Demo
结论
代码修改
5.子组件中定义一个箭头函数,在父组件中通过this.传入方法存在this的指向问题?
原因
解决
6.数组在删除item后,index发生混乱?
7.在更新了forEach的源数据后,为什么页面没有被重新渲染?
原因
值类型变量与引用类型变量
数据的深拷贝与浅拷贝
什么事深拷贝与浅拷贝?
如何实现浅拷贝与深拷贝
解决方案
前言:
小菜鸟又记起了CSDN,还是老生常谈,先介绍一下为什么写这篇文章。最近在学习鸿蒙ArkTs语法,略知皮毛后仿造淘宝想写一个应用商城,前方一切很顺利,在写到购物车中的商品删除的时候,遇到了一点问题,oh,不对,应该是通过一个小问题,引出了一系列的小问题,因为这些小问题让我的进度降低,en.....开玩笑,因为它引出了很多小的知识点,在一次又一次的修正中,通过一个小问题,不断地去发散思维,寻求解决办法的这个过程才是我真正想写这篇博客的原因。
(ps这玩意儿是我解决完问题后写的,文章中出现的代码,我都是通过回忆写的,可能会有一点误差,但重要的是解决这个问题的思路嘛)
好了,正文开始,先列一下,问题的大概顺序大致如下:
- @State装饰器修饰变量的状态变化只能监听第一层?
- 使用了@ObjectLink装饰器后为什么数据仍然无法被监听?
- 子组件中定义一个箭头函数,在父组件中通过this.传入方法存在this的指向问题?
- 数组在删除item后,index发生混乱?
- 在更新了forEach的源数据后,为什么页面没有被重新渲染?
- 数据的深拷贝与浅拷贝
1.页面效果及需求
1.点击‘-’图标,对商品数量进行消减,点击‘+’图标,增加商品数量
2.如果商品数量为0,则从购物车中删除这个商品
3.如果这个商家下的商品数量为0,则删除商家这一整项
2.遇到问题时的初始代码及问题
代码
Model:
@Observed
export class CarGoodsQuantity{
//商品信息
goods:GoodsInformation
//商品被添加到购物车的数量
addtoCarQuantity : number
constructor(goods:GoodsInformation,addtoCarQuantity : number) {
this.goods=goods
this.addtoCarQuantity=addtoCarQuantity
}
}
@Observed
export class CarDataModel{
//商家名称
businessName:string
//这个商家被添加到购物车的商品列表
goods: CarGoodsQuantity[]
constructor(businessName:string,goods:CarGoodsQuantity[]) {
this.businessName=businessName
this.goods=goods
}
}
页面:ShoppingCartPage
@Component
@Preview
export struct ShoppingCartPage{
@State currentGoodsList:CarDataModel[]=InformationModel.getGoodsCarModel() //购物车中每个商家及旗下商品列表
@State isManaged:boolean = false //是否是管理页面
@State checkoutMoney:number=0 //结算总额
build(){
column(){
...
List({space:6}){
//购物车列表
ForEach(this.currentGoodsList,(itemGroup:CarDataModel,parentIndex:number)=>{
ListItemGroup({header:this.groupItemTitleBuilder(itemGroup.businessName,parentIndex)}){
ForEach(itemGroup.goods,(item:CarGoodsQuantity,index:number)=>{
ListItem(){
this.GoodsItemBuilder(item,parentIndex,index)
}
})
}
.backgroundColor('#fff6f4f4')
})
}
.width('100%')
.layoutWeight(1)
}
}
//购物车中,每个商品item
@Builder
GoodsItemBuilder(item:CarGoodsQuantity,parentIndex:number,index:number){
Row(){
Checkbox({name:'goods'+index,group:'checkboxGroup'+parentIndex})
.selectedColor(Color.Orange)
.onChange((isChecked:boolean)=>{
if(isChecked){
this.checkoutMoney += item.goods.price*item.addtoCarQuantity
console.log('kkk',item.goods.goodsDiscription+'商品被选中')
}else {
this.checkoutMoney -= item.goods.price*item.addtoCarQuantity
console.log('kkk','未选中')
}
})
Image(item.goods.image[0])
.width(100)
.height(100)
.borderRadius(5)
.margin({left:10})
Column(){
Row(){
Text(item.goods.goodsDiscription)
.fontSize(14)
.maxLines(1)
.textOverflow({overflow:TextOverflow.Ellipsis})
.width('80%')
Text('X '+item.addtoCarQuantity)
.fontSize(14)
.fontColor(Color.Gray)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
Row(){
Text('¥'+item.goods.price)
.fontSize(18)
.fontColor(Color.Orange)
if(this.isManaged){
Text('-')
.width(24)
.height(24)
.borderRadius(12)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.onClick(()=>{
if(item.addtoCarQuantity>0){
item.addtoCarQuantity--
console.log('kkk','这个商品数量-1:'item.addtoCarQuantity)
} else{
}
})
Text('+')
.width(24)
.height(24)
.margin({left:20})
.borderRadius(12)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.onClick(()=>{
item.addtoCarQuantity++
console.log('kkk','这个商品数量+1 :'+item.addtoCarQuantity)
})
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.margin({left:10})
.height('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.SpaceEvenly)
}.width('100%')
.height(120)
.padding({top:10,bottom:10})
.alignItems(VerticalAlign.Center)
}
}
问题
通过log可以看出,商品的数量是减少了,但是页面上显示的商品数量没有发生变化
3.状态变化不能深层监听?
解答
通过官方文档的这一段可以看出,由@State装饰的变量,它只能够监视第一层,如我们上方的currentGoodsList:CarDateModel[]数组,它只能监听其中CarDataModel对象的变化,但是无法监听到CarDataModel对象中属性的变化
@State装饰的变量只能监视第一层,通过查阅官方文档可以知道@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步
然后我修改了我的代码,通过@ObjectLink去监听我们的数组,代码如下:
ShoppingCartPage:
@Component
@Preview
export struct ShoppingCartPage{
@State currentGoodsList:CarDataModel[]=InformationModel.getGoodsCarModel() //购物车中每个商家及旗下商品列表
build(){
column(){
....
List({space:6}){
//购物车列表
ForEach(this.currentGoodsList,(itemGroup:CarDataModel,parentIndex:number)=>{
ListItemGroup({header:this.groupItemTitleBuilder(itemGroup.businessName,parentIndex)}){
ForEach(itemGroup.goods,(item:CarGoodsQuantity,index:number)=>{
ListItem(){
// this.GoodsItemBuilder(item,parentIndex,index)
GoodsCarItemComponent({
carGoodsModel:itemGroup,
parentIndex:parentIndex,
index:index,
checkoutMoney:this.checkoutMoney,
isManaged:this.isManaged,
})
}
})
}
.backgroundColor('#fff6f4f4')
})
}
.width('100%')
.layoutWeight(1)
}
}
}
GoodsCarItemComponent
@Component
export struct GoodsCarItemComponent{
@ObjectLink carGoodsModel:CarDataModel
private parentIndex:number=0
private index:number=0
@Link checkoutMoney:number
//是否为管理界面
@Prop isManaged:boolean
build(){
...
Column(){
Row(){
Text(this.carGoodsModel.goods[this.index].goods.goodsDiscription)
.fontSize(14)
.maxLines(1)
.textOverflow({overflow:TextOverflow.Ellipsis})
.width('80%')
Text('X '+this.carGoodsModel.goods[this.index].addtoCarQuantity)
.fontSize(14)
.fontColor(Color.Gray)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
Row(){
Text('¥'+this.carGoodsModel.goods[this.index].goods.price)
.fontSize(18)
.fontColor(Color.Orange)
if(this.isManaged){
Text('-')
.width(24)
.height(24)
.borderRadius(12)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.onClick(()=>{
if(this.carGoodsModel.goods[this.index].addtoCarQuantity>=1){
this.carGoodsModel.goods[this.index].addtoCarQuantity--
console.log('kkk','这个商品数量-1: '+this.carGoodsModel.goods[this.index].addtoCarQuantity)
}
})
Text('+')
.width(24)
.height(24)
.margin({left:20})
.borderRadius(12)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.onClick(()=>{
this.carGoodsModel.goods[this.index].addtoCarQuantity++
console.log('kkk','这个商品数量+1 :')
})
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.margin({left:10})
.height('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.SpaceEvenly)
}.width('100%')
.height(120)
.padding({top:10,bottom:10})
.alignItems(VerticalAlign.Center)
}
}
操作结果:通过log可以看出这个购物车商品的数量确实增加以及减少了,但是,状态管理器似乎没有监听到变化,页面中显示的商品数量没有变化
Tips:
@ObjectLink装饰的变量不能被赋值,如果要使用赋值操作,请使用@Prop。
- @Prop装饰的变量和数据源的关系是是单向同步,@Prop装饰的变量在本地拷贝了数据源,所以它允许本地更改,如果父组件中的数据源有更新,@Prop装饰的变量本地的修改将被覆盖;
- @ObjectLink装饰的变量和数据源的关系是双向同步,@ObjectLink装饰的变量相当于指向数据源的指针。如果一旦发生@ObjectLink装饰的变量的赋值,则同步链将被打断。
那么就引入了下一个问题
4.使用了@ObjectLink装饰器后为什么数据仍然无法被监听?
再次回看我的的数据结构:
class CarDataModel{ businessName:string goods: CarGoodsQuantity[]}
class CarGoodsQuantity{ goods:GoodsInformation addtoCarQuantity : number}
并且通过在子组件中,修改carDataModel的值,结合页面变化来看发现
carDataModel.businessName以及carDataModel.goods的变化可以被监听到,再往下就监听不到了
为了防止是因为我项目中有些地方的错误导致得到错误的结论,所以新写一个demo来观察
Demo
数据
@Observed
export class ModelA{
name:string
information : ModelB[]
constructor(name:string,information:ModelB[]) {
this.name=name
this.information=information
}
}
@Observed
export class ModelB{
subject:string
score: number
constructor(subject:string,score: number) {
this.subject=subject
this.score=score
}
}
页面
import { ModelA, ModelB } from '../Model/ModelA';
@Entry
@Component
struct Index {
@State modelList:ModelA[]=[new ModelA('小王',[new ModelB('英语',90),new ModelB('数学',98)])]
build() {
Column() {
ForEach(this.modelList,(item:ModelA,index:number)=>{
ViewSon({modelA:item})
.margin({bottom:30})
})
}
.height('100%')
.width('100%')
}
}
@Component
struct ViewSon{
@ObjectLink modelA:ModelA
build() {
Column({space:30}){
Text('修改名称 : ' + this.modelA.name)
.onClick(()=>{
this.modelA.name='狗狗'
console.log('kkk','修改名称后:' +this.modelA.name)
}).backgroundColor('#f3f3')
Text('修改'+this.modelA.information[0].subject+'成绩 :' + this.modelA.information[0].score )
.onClick(()=>{
this.modelA.information[0].score=100
console.log('kkk','修改成绩后: ' + this.modelA.information[0].score)
}).backgroundColor('#f3f3')
Text('删除英语成绩 ' + JSON.stringify(this.modelA.information))
.onClick(()=>{
this.modelA.information.splice(0,1)
console.log('kkk','删除英语学科成绩后:' + JSON.stringify(this.modelA.information))
}).backgroundColor('#f3f3')
Text('给学科数据重新赋值 :' +JSON.stringify(this.modelA.information))
.onClick(()=>{
this.modelA.information=[new ModelB('政治',80)]
console.log('kkk','给学科数据重新赋值后 :'+JSON.stringify(this.modelA.information))
}).backgroundColor('#f3f3')
}
}
}
页面展示:
依次点击前3个Text组件后,页面显示:
可以看出,只有第一个Text组件中的内容发生了变化
再点击第四个Text组件后页面显示:
结论
由上可以得出结论,@ObjectLink-@Observed装饰器,@ObjectLink装饰了数组item, 在监听数组时,它只能监听到数组item中第一层属性值的变化,更深层次的也是监听不到的
那如果我们需要监听
@State modelList:ModelA[]=[new ModelA('小王',[new ModelB('英语',90),new ModelB('数学',98)])]
中ModelB对象的数据修改怎么办呢?
可以通过两层forEach循环,在子组件中用@ObjectLink组件装饰 ModelB对象即可
代码修改
通过以上结论,我们修改一下我们的代码
当时为什么在子组件中传入CarDataModel对象,而不是CarGoodsQuantity对象,是因为当商品的数量减为0,需要删除这个商品对象,所以当时传入了CarDataModel对象
但是现在给子组件传入CarDataModel对象,在CarGoodsQuantity对象的属性值发生改变的时候,无法被监听到,所以还是给子组件传入CarGoodsQuantity对象,然后再定义一个箭头函数,将删除商品对象的处理交给父组件进行处理
export struct ShoppingCartPage{
@State currentGoodsList:CarDataModel[]=InformationModel.getGoodsCarModel() //购物车中每个商家及旗下商品列表
deleteGoodsItem(parentIndex:number,index:number){
//删除购物车中的这个商品
this.currentGoodsList[parentIndex].goods.splice(index,1)
console.log('kkk','删除此商品后:'+JSON.stringify(this.currentGoodsList[parentIndex].goods))
//如果这个商家下的商品没有了,则删除这一个商家
if(this.currentGoodsList[parentIndex].goods.length==0){
this.currentGoodsList.splice(parentIndex,1)
console.log('kkk','删除商家后:'+JSON.stringify(this.currentGoodsList[parentIndex]))
}
}
build(){
....
List({space:6}){
//购物车列表
ForEach(this.currentGoodsList,(itemGroup:CarDataModel,parentIndex:number)=>{
ListItemGroup({header:this.groupItemTitleBuilder(itemGroup.businessName,parentIndex)}){
ForEach(itemGroup.goods,(item:CarGoodsQuantity,index:number)=>{
ListItem(){
// this.GoodsItemBuilder(item,parentIndex,index)
GoodsCarItemComponent({
item:item,
parentIndex:parentIndex,
index:index,
checkoutMoney:this.checkoutMoney,
isManaged:this.isManaged,
deleteGoodsItem: this.deleteGoodsItem
}
})
}
})
}
.backgroundColor('#fff6f4f4')
})
}
.width('100%')
.layoutWeight(1)
}
}
GoodsCarItemComponent
@Component
export struct GoodsCarItemComponent{
@ObjectLink item:CarGoodsQuantity
private parentIndex:number=0
private index:number=0
@Link checkoutMoney:number
//是否为管理界面
@Prop isManaged:boolean
deleteGoodsItem=(parentIndex:number,index:number)=>{}
build(){
....
Column(){
Row(){
Text(this.item.goods.goodsDiscription)
.fontSize(14)
.maxLines(1)
.textOverflow({overflow:TextOverflow.Ellipsis})
.width('80%')
Text('X '+this.item.addtoCarQuantity)
.fontSize(14)
.fontColor(Color.Gray)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
Row(){
Text('¥'+this.item.goods.price)
.fontSize(18)
.fontColor(Color.Orange)
if(this.isManaged){
Text('-')
.width(24)
.height(24)
.borderRadius(12)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.onClick(()=>{
if(this.item.addtoCarQuantity>=1){
this.item.addtoCarQuantity--
console.log('kkk','这个商品数量-1: '+this.item.addtoCarQuantity)
if(this.item.addtoCarQuantity==0){
//在购物车中删除这个商品
this.deleteGoodsItem(this.parentIndex,this.index)
}
}
})
Text('+')
.width(24)
.height(24)
.margin({left:20})
.borderRadius(12)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.onClick(()=>{
this.item.addtoCarQuantity++
console.log('kkk','这个商品数量+1 :')
})
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.margin({left:10})
.height('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.SpaceEvenly)
}
上面的代码可以看出,我们在给子组件传值时,通过deleteGoodsItem:this.deleteGoodsItem的方式给子组件传递一个箭头函数,这样的写法,会得到如下报错:
引出了下一个问题
5.子组件中定义一个箭头函数,在父组件中通过this.传入方法存在this的指向问题?
原因
this一般指向调用者
deleteGoodsItem()这个方法是被子组件所调用,所以deleteGoodsItem : this.deleteGoodsItem中这个this指向的是子组件,那在deleteGoodsItem()里通过this.调用父组件的属性,会报错undefined
解决
所以在给子组件的箭头函数传值时的写法应修改为以下代码:
GoodsCarItemComponent({
item:item,
parentIndex:parentIndex,
index:index,
checkoutMoney:this.checkoutMoney,
isManaged:this.isManaged,
deleteGoodsItem:(parentIndex:number,index:number)=>{
this.deleteGoodsItem(parentIndex,index)
}
})
}
})
这时的this指向父组件。
接着我们再次重新运行,在点击删除商品的时候,发现了新的问题,有的商品删除了,有的商品无法删除
6.数组在删除item后,index发生混乱?
再次看一下我们deleteGoodsItem()方法
deleteGoodsItem(parentIndex:number,index:number){
//删除购物车中的这个商品
this.currentGoodsList[parentIndex].goods.splice(index,1)
console.log('kkk','删除此商品后:'+JSON.stringify(this.currentGoodsList[parentIndex].goods))
//如果这个商家下的商品没有了,则删除这一个商家
if(this.currentGoodsList[parentIndex].goods.length==0){
this.currentGoodsList.splice(parentIndex,1)
console.log('kkk','删除商家后:'+JSON.stringify(this.currentGoodsList[parentIndex]))
}
}
数组删除item后,index发生混乱的原因还是比较简单清晰的
无非就是有一个数组ListA=[item0,item1,item2,item4], 此时我们想删除它的第二项和第三项
理想状态下:
删第二项item1:listA.splice(1,1)
删第三项item2:listA.splice(2,1)
但事实是,删除第二项item1后,item2的下标变为1,此时如果再用listA.splice(2,1)进行删除的话,只会删除item3
这样该如何解决呢?
如果是循环删除的话,不妨试一下倒序法,就是从后往前删,这样不会影响item的下标,自然也就不会有index混乱的问题
但是循环删除的实际应用还是比较少的,像现在这样,在数组中删除任意一项,又怎样保证数组的index不发生混乱呢?
在删除item后让List组件重新渲染,使得每个item的下标更新为最新,这样再进行删除的时候,也不会发生index错误的情况
如何让List组件重新渲染呢?
我们知道,当List的源数据监听到数据变化时List会重新渲染
然后我将deleteGoodsItem()方法进行了如下修改:
deleteGoodsItem(parentIndex:number,index:number){
//删除购物车中的这个商品
let news=this.currentGoodsList[parentIndex]
news.goods.splice(index,1)
this.currentGoodsList[parentIndex]=news
console.log('kkk','删除此商品后:'+JSON.stringify(this.currentGoodsList[parentIndex].goods))
//如果这个商家下的商品没有了,则删除这一个商家
if(this.currentGoodsList[parentIndex].goods.length==0){
this.currentGoodsList.splice(parentIndex,1)
console.log('kkk','删除商家后:'+JSON.stringify(this.currentGoodsList[parentIndex]))
}
}
我们通过@State装饰器可以知道,它装饰的currentGoodsList中的item变化是可以被监听道德,所以先将需要修改的currentGoodsList[parentIndex]赋值给news,然后对news进行数据修改后,重新赋值给this.currentGoodsList[parentIndex],这样就达到了数组中item的变化,这样应该可以被监听到了吧?
我们运行一下,看下结果:
我们删除‘手工粗棒针毛线帽子’这一项的时候,发现在页面中当它的数量变为0的时候,这一项并没有被删除,但是在我们打出的log可以看到,这一项是被删除了的
所以我们产生了新的疑问:
7.在更新了forEach的源数据后,为什么页面没有被重新渲染?
原因
@State只是监听数组的地址值,所以只有地址值改变时才会重新渲染页面
也就是如下方法,数组的地址值没有发生变化,所以没有监听到,也就不会重新渲染页面
let news=this.currentGoodsList[parentIndex]
news.goods.splice(index,1)
this.currentGoodsList[parentIndex]=news
也就是news和this.currentGoodsList[parentIndex]指的是同一个对象
我们可以通过一个demo来看一下
@State ListA:Array<number> =[1,2,3,4]
build() {
Column() {
Text(this.ListA.toString())
.fontSize(20)
.onClick(()=>{
let ListB = this.ListA
ListB.splice(1,1,100)
console.log('kkk','ListA: '+this.ListA.toString())
console.log('kkk','ListB: '+ListB.toString())
})
}
}
可以看出通过=号被赋值的ListB与ListA其实是同一个对象,指向同一个内存地址,在修改ListB的时候,ListA也会被同样修改
值类型变量与引用类型变量
值类型变量(如int、float、struct等)在赋值时,会复制变量的完整内容到新的变量中。这意味着两个变量在内存中存储的是不同的数据副本,对其中一个变量的修改不会影响到另一个。
引用类型变量(如class、interface等)在赋值时,复制的是变量的引用(即内存地址),而非对象本身。因此,两个引用类型变量可能指向同一个对象实例。对通过任一变量对对象所做的修改都会反映到所有指向该对象的变量上。
所以以下通过=号赋值的news没有任何意义,因为news和this.currentGoodsList[parentIndex]的内存地址是一样的
let news=this.currentGoodsList[parentIndex]
news.goods.splice(index,1)
this.currentGoodsList[parentIndex]=news
那如何赋值给一个新的数组,让其的内存地址是不同的呢?
接下来引入最后一个问题
数据的深拷贝与浅拷贝
官方文档给出了详细的解释:
什么事深拷贝与浅拷贝?
浅拷贝:
- 在堆中为新对象重新创建内存空间。
- 基本数据类型的值被直接拷贝,互不影响。
- 引用类型的内存地址被拷贝,因此拷贝前后的对象共享相同的引用类型数据。
- 修改共享引用类型的数据会影响所有指向该数据的对象。
深拷贝:
- 完整地从内存中拷贝一个对象,包括其所有属性和嵌套的对象。
- 在堆内存中开辟新的区域来存储新对象,确保新对象和原始对象完全独立。
- 修改新对象不会影响原始对象,反之亦然。
如何实现浅拷贝与深拷贝
文档中心https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs-V5/faqs-arkts-94-V5
解决方案
方法1:
//先复制一个临时的列表
let tempList: Array<A> = [...this.dataList]
// 修改tempList数组中的值
tempList[index].key = value;
//将tempList赋值给数组, 页面即可重新渲染
this.List = [...tempList]
方法2:
this.List[index].key = value; //修改数据
this.List = this.List.map(item=>item); //map方法会返回一个新数组,故页面也会重新渲染
最后的deleteGoodsItem()修改为:
deleteGoodsItem(parentIndex:number,index:number){
//删除购物车中的这个商品
let news=[...this.currentGoodsList]
news[parentIndex].goods.splice(index,1)
this.currentGoodsList=news
console.log('kkk','删除此商品后:'+JSON.stringify(this.currentGoodsList[parentIndex].goods))
//如果这个商家下的商品没有了,则删除这一个商家
if(this.currentGoodsList[parentIndex].goods.length==0){
this.currentGoodsList.splice(parentIndex,1)
console.log('kkk','删除商家后:'+JSON.stringify(this.currentGoodsList[parentIndex]))
}
}
运行效果如下:
商品的数量为0时,商品被删除,商家下的商品为0时,商家这一项被删除。
结束语:
写到这里就结束啦,希望对看到这篇文章的你有所帮助~