懒加载的坑

欢迎访问我的个人blog
https://ekulelu.github.io/

这次我们来讲讲懒加载。懒加载的意思就是将成员变量的创建放到getter方法里面。使用到这个成员变量的时候再进行创建。这样可以节省内存空间。这里有个主意点:以后使用懒加载的成员变量的时候,请用getter方法,而不是 +成员变量名字 。因为 +成员变量名字 这种写法并不会调用getter方法,所以可能会拿到空的成员变量。 Getter和Setter方法的写法这里就不讲了。这里讲的是各种不经意的写法混合到一起产生的怪异bug。

对于一个UIView或UIViewController里面的控件类型的成员变量。一般是建议使用懒加载来加载的。这里我们举一个在UIViewController里面懒加载imageView为例。但控件是要添加到主View里面的,所以一般创建后还需要调用

 [self.view addSubview:self.imgView];

这句话应该放到哪里呢?一般有3个选择:viewDidLoad,initWithFrame,懒加载方法。现在我们用到了懒加载,放到懒加载里面似乎是个不错的选择。那就先放到懒加载里面。这里还有一个问题,子View的持有属性问题,一般来讲,将子View添加到主View之后,主View会对它的引用计数+1,那么属性那里是可以设置为weak的,当然,设置为strong也没问题。
先讨论设置为weak的情况。

@property (weak, nonatomic) UIImageView *imgView;

设置为weak后,那么在懒加载中创建变量的时候,就不能将这个变量直接赋值给_imgView,因为weak熟悉不会持有,变量创建后就会销毁。 所以得这样写

- (UIImageView *)imgView{
    NSLog(@"get imgView");
    if (_imgView == nil) {
        UIImageView *imgView = [[UIImageView alloc] init];
        _imgView = imgView;
        [self.view addSubview:_imgView];
    }
    return _imgView;
} 

好像这个看得不太优雅,换成下面的写法看看

- (UIImageView *)imgView{
    NSLog(@"get imgView");
    if (_imgView == nil) {
        UIImageView *imgView = [[UIImageView alloc] init];
        [self.view addSubview:_imgView=imgView];
    }
    return _imgView;
} 

这两种写法有没区别呢?看着好似没。

既然我们是一个imgView,这个imgView里面的image可能是从别的类传过来的。那么提供一个接口给外部传递进来是必须的。所以在头文件里面声明了

// .h 文件
- (void)setImage:(UIImage*)image;

然后在.m文件里面写实现,把传进来的image直接赋值给self.imgView。

// .m文件
- (void)setImage:(UIImage*)image{
    NSLog(@"set img");
    self.imgView.image = image;
}

对了,我们还没给self.imgView的frame赋值呢。所以在viewDidLoad里面赋值。

//viewDidLoad
self.imgView.frame = CGRectMake(0, 100, 100, 100);

现在一切看起来都没问题的样子,那么我们就把这个ViewController给push出来看看

ViewController *vc = [[ViewController alloc ] init];
[vc setImage:[UIImage imageNamed:@"home"]];
[self.navigationController pushViewController:vc animated:YES];

恭喜,你被成功绕进去了,这个push出来的ViewController一片空白。这究竟是怎么回事呢?我们来跟一下代码执行情况。
首先是

[vc setImage:[UIImage imageNamed:@"home"]];

这句会调用到setImage里面的 self.imgView ,然后这会激活到懒加载方法。之后到了下面这句话,这句是关键。

[self.view addSubview:_imgView=imgView];

到了这里此刻self.view是空的!所以首先会对view进行创建,然后创建的过程中会执行到viewDidLoad方法。注意,我们在viewDidLoad里面对self.imgView的frame进行了赋值,这又会激活了imgView的懒加载方法,此刻_imgView仍然是空的,所以又再次进行了创建对象的方法,注意,此时创建又了一个新的imgView对象。然后继续到了下面这句,注意,这是第二次进入。

[self.view addSubview:_imgView=imgView];

此时self.view已经有值了。所以接着执行_imgView=imgView,此刻 _imgView终于有值了,但是这个对象是第二次创建的imgView,并将它添加到了self.view里面。随后self.imgView的懒加载完成。回退到viewDidload里面的设置frame方法里面。这个时候对self.imgView的frame进行赋值。
然后viewDidLoad执行完毕,回退到第一次的self.imgView的懒加载方法里面的下面这句

[self.view addSubview:_imgView=imgView];

此时的imgView是第一次创建的imgView。然后执行_imgView=imgView。看到了吗,这里又进行了一次对 _imgView的赋值,此时赋值的是第一次创建的imgView,然后再添加到self.view里面。随后退出imgView的懒加载方法,然后再回到setImage方法里面对这个self.imgView进行image赋值。
留意到没?这个第一次创建的imgView并没有进行frame的赋值,所以显示不出来。
而且这里创建了两个imgView,并且两个都添加到了self.view里面。如果你打印一下self.view.subviews.count你就知道了。

下面说说解决方法
1、你可以把懒加载方法写回下面这种不“优雅”得到形式,这样的话,在viewDidLoad里面调用到懒加载后,此时的_imgView已经有值了,就不会在进行创建。但是其实这里还是会对view进行加载,如果你viewDidLoad里面有其他的一些方法关联到了另外的一些子控件的初始化,可能会导致一些不可控的后果。因此不推荐这种方法。

- (UIImageView *)imgView{
    NSLog(@"get imgView");
    if (_imgView == nil) {
        UIImageView *imgView = [[UIImageView alloc] init];
        _imgView = imgView;
        [self.view addSubview:_imgView];
    }
    return _imgView;
} 

2、不要在view加载前去访问子控件。但是这个约束得我们自己去遵守,如果你写的代码是给别人用的话,这个规则不能确保别人会遵守。而且如果不访问的话,那么就像刚刚设置需要image的话,还得再创建一个属性来保存这个image,很麻烦。
3、不要在懒加载里面去访问self.view。这个是我个人比较推荐的。addsubview方法可以放到viewDidLoad里面或这个方法执行之后。

<think>好的,我现在需要帮用户解决如何在Element UI的el-cascader组件中实现懒加载并根据节点回显的问题。首先,我得回忆一下Element UI的文档,特别是关于Cascader级联选择器和懒加载的部分。 用户提到的是懒加载和回显,这两个都是比较常见的需求,但结合起来可能会有一些懒加载通常用于动态加载子节点,而回显则是在编辑时显示已选中的路径。这需要确保在回显时,即使子节点未被加载,也能正确显示完整的路径。 首先,el-cascader的懒加载是通过lazy属性和lazyload方法实现的。每个节点在展开时会触发lazyload方法,动态加载其子节点。但问题在于,当我们需要回显一个已经选中的值时,比如在编辑表单时,这些子节点可能还没有被加载,导致显示不完整,只显示最后一级的节点,而不是完整的路径。 那怎么解决这个问题呢?可能需要手动加载父节点的数据,构建完整的路径。例如,如果选中的值是[父节点ID, 子节点ID, 孙子节点ID],而懒加载只加载到父节点,子节点和孙子节点尚未加载,这时候直接设置v-model的值可能无法正确显示路径。 这时候可能需要一个方法,当设置默认值时,递归地加载每一层的子节点,直到所有层级的节点都被加载,这样级联选择器才能正确显示完整的路径。具体步骤可能如下: 1. 获取当前选中的值,比如一个数组,包含各级节点的ID。 2. 从根节点开始,逐层检查这些节点是否已经加载。 3. 如果某一层的节点未被加载,调用懒加载的方法获取该节点的子节点,并找到对应的下一级节点。 4. 重复这个过程,直到所有层级的节点都被加载完毕。 5. 最后,将完整的节点路径设置给el-cascader的v-model,这样就能正确回显了。 不过,在实现过程中可能会遇到异步加载的问题,需要确保每一层的加载都完成后再进行下一层的加载。可能需要使用async/await或者Promise来处理异步操作。 另外,Element UI的Cascader组件在懒加载模式下,可能需要通过配置的props中的label和value来指定显示的字段,所以在加载节点数据时需要确保返回的数据结构符合这些配置。 举个例子,假设选中值的路径是[1, 3, 5],其中1是根节点,3是子节点,5是孙子节点。当回显时,如果只有根节点1被加载,而3和5未被加载,这时候需要先加载1的子节点,找到3,然后加载3的子节点,找到5,这样整个路径才能正确显示。 可能需要编写一个函数,接收选中的值数组,然后递归加载每个层级的节点,直到所有节点都加载完成。这个过程可能需要使用组件的lazyload方法,并处理节点的加载状态。 此外,还需要注意,在加载节点时,可能需要维护一个已加载的节点缓存,避免重复加载。同时,需要处理加载失败的情况,提供错误处理机制。 总结一下,实现步骤大致如下: 1. 设置el-cascader的lazy属性和lazyload方法,确保懒加载正常工作。 2. 在回显时,获取选中的节点值数组。 3. 使用递归或循环的方式,逐层加载每个节点的子节点,直到所有层级的节点都被加载。 4. 将加载完成的节点路径设置给v-model,使组件正确显示。 可能还需要参考Element UI的官方文档或社区中的解决方案,看看是否有现成的示例或方法。例如,是否有办法在设置值后强制组件重新渲染,或者在加载完成后触发更新。 最后,测试时需要注意不同情况,比如选中的节点在不同层级,或者某些节点加载失败时的处理,确保用户体验良好,不会出现空白或错误显示。</think>在Element UI的`el-cascader`组件中,实现懒加载(动态加载子节点)并回显已选节点需要特殊处理。以下是分步解决方案: --- ### 一、基础配置懒加载 ```html <el-cascader v-model="selectedValue" :props="cascaderProps" @change="handleChange" /> ``` ```javascript data() { return { selectedValue: [], // 存储选中的值如 [父ID, 子ID, 孙ID] cascaderProps: { lazy: true, lazyLoad: this.lazyLoad } }; }, methods: { async lazyLoad(node, resolve) { const { level } = node; // 当前层级(从0开始) const parentId = node.value || null; // 父节点ID // 模拟接口获取子节点 const childNodes = await fetchChildren(parentId); // 标记叶子节点(无子节点需设置leaf: true) childNodes.forEach(n => n.leaf = n.isLeaf); resolve(childNodes); } } ``` --- ### 二、核心回显逻辑 当选中的值为`[1,3,5]`时,需手动加载所有父节点: ```javascript async loadFullPath(pathIds) { let nodes = []; let currentLevel = 0; // 逐层加载 for (const id of pathIds) { const parentNode = nodes[currentLevel - 1] || null; // 加载当前层级节点 const children = await this.fetchChildren(parentNode?.value); // 找到当前ID对应的节点 const currentNode = children.find(n => n.value === id); if (!currentNode) throw new Error("节点不存在"); nodes.push(currentNode); currentLevel++; } return nodes; } ``` --- ### 三、回显完整路径 ```javascript async initSelectedValue() { const pathIds = [1, 3, 5]; // 从后台获取的已选值 try { // 1. 加载完整路径 const fullPathNodes = await this.loadFullPath(pathIds); // 2. 构造级联选择器所需的value数组 this.selectedValue = fullPathNodes.map(n => n.value); // 3. 强制更新组件(解决缓存问题) this.$nextTick(() => { this.$refs.cascader.panel.lazyLoad(); }); } catch (e) { console.error("回显失败:", e); } } ``` --- ### 四、关键注意事项 1. **节点标识** 确保每个节点包含`value`、`label`、`leaf`(是否为叶子节点)字段 2. **错误处理** - 接口失败时显示错误提示 - 路径中断时显示`部分数据加载失败` 3. **性能优化** - 缓存已加载的节点(可用Map存储) - 防抖处理频繁加载 --- ### 五、效果演示 ```javascript // 模拟接口 const mockData = { 1: [{ value: 3, label: "子节点", leaf: false }], 3: [{ value: 5, label: "孙节点", leaf: true }] }; async function fetchChildren(parentId) { return mockData[parentId] || []; } ``` 最终效果:即使初始未加载任何节点,也能完整显示`父 > 子 > 孙`的路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值