You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用RxSwift与RxDataSources仅更新TableView中变更的单元格

嘿,这个场景太常见了——用RxDataSources的时候,总不想因为单个单元格数据变化就刷新整个列表对吧?其实RxDataSources本身就内置了差异计算的能力,只要你正确配置模型和数据源,就能轻松实现局部刷新。我给你一步步拆解:

1. 先让你的数据模型“支持差异识别”

RxDataSources要能找出哪些数据变了,需要你的数据模型实现两个协议:IdentifiableType(用来唯一标识每个item)和Equatable(用来对比item是否有变化)。

举个实际的例子,假设你有一个待办事项模型:

import RxDataSources

struct TodoItem: IdentifiableType, Equatable {
    let id: String // 唯一标识,比如UUID生成
    var title: String
    var isCompleted: Bool
    
    // IdentifiableType 要求:指定唯一标识的类型和值
    typealias Identity = String
    var identity: Identity { id }
    
    // Equatable 实现:对比需要关注的属性,只有这些属性变化时才认为item不同
    static func == (lhs: TodoItem, rhs: TodoItem) -> Bool {
        return lhs.id == rhs.id 
            && lhs.title == rhs.title 
            && lhs.isCompleted == rhs.isCompleted
    }
}

⚠️ 注意:Equatable的实现一定要准确——如果你的item还有其他需要刷新的属性,记得也加进对比逻辑里;反之,如果某个属性变化不需要刷新单元格,就不要放在这里。

2. 配置RxDataSources的数据源

接下来创建RxTableViewSectionedAnimatedDataSource(如果是CollectionView就用对应的版本),这里的核心是让数据源负责单元格的配置,并且开启它的差异计算能力:

let dataSource = RxTableViewSectionedAnimatedDataSource<AnimatableSectionModel<String, TodoItem>>(
    // 配置单元格的闭包:当数据源发现item变化时,会重新调用这个闭包刷新对应单元格
    configureCell: { dataSource, tableView, indexPath, item in
        let cell = tableView.dequeueReusableCell(withIdentifier: "TodoCell", for: indexPath) as! TodoCell
        // 直接赋值或者用Rx绑定都可以,这里用简单赋值举例
        cell.titleLabel.text = item.title
        cell.completeButton.isSelected = item.isCompleted
        return cell
    },
    // 可选:设置刷新动画,让局部刷新更流畅
    animationConfiguration: AnimationConfiguration(
        insertAnimation: .fade,
        reloadAnimation: .fade,
        deleteAnimation: .fade
    )
)

3. 响应式绑定数据到TableView

现在你需要把你的数据序列(比如BehaviorSubject或者Observable)转换为AnimatableSectionModel的序列,然后绑定到TableView:

import RxSwift

let disposeBag = DisposeBag()
// 用BehaviorSubject存储当前的所有数据,方便后续更新
let itemsSubject = BehaviorSubject<[TodoItem]>(value: [])

// 转换为section模型序列,然后绑定到TableView
itemsSubject
    .map { todos in
        // 这里我们只有一个section,模型用字符串标识即可
        [AnimatableSectionModel(model: "待办列表", items: todos)]
    }
    .bind(to: tableView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)

4. 更新单个item时的正确姿势

当你需要修改某个item的数据时,不要直接修改原数组,而是创建一个新数组替换变化的元素,然后发射新的数组到itemsSubject:

func toggleTodoCompletion(at index: Int) {
    guard var currentTodos = try? itemsSubject.value() else { return }
    // 修改指定位置的item属性
    currentTodos[index].isCompleted.toggle()
    // 发射新数组,触发数据源的差异计算
    itemsSubject.onNext(currentTodos)
}

这时候RxDataSources会自动对比新旧数组的差异,只刷新那个发生变化的单元格,而不是整个TableView!

5. 进阶:单元格交互的响应式处理

如果你的单元格有交互控件(比如完成按钮),可以把控件的事件绑定回ViewModel,实现全响应式的流程:

// 先给自定义单元格添加一个事件Observable
class TodoCell: UITableViewCell {
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var completeButton: UIButton!
    
    let disposeBag = DisposeBag()
    // 对外暴露按钮点击事件
    var onToggleCompletion: Observable<Void> {
        completeButton.rx.tap.asObservable()
    }
}

// 在configureCell里绑定事件
configureCell: { [weak self] dataSource, tableView, indexPath, item in
    let cell = tableView.dequeueReusableCell(withIdentifier: "TodoCell", for: indexPath) as! TodoCell
    cell.titleLabel.text = item.title
    cell.completeButton.isSelected = item.isCompleted
    
    // 绑定按钮点击事件到更新逻辑
    cell.onToggleCompletion
        .subscribe(onNext: {
            self?.toggleTodoCompletion(at: indexPath.row)
        })
        .disposed(by: cell.disposeBag)
    
    return cell
}

这样用户点击按钮时,会自动触发数据更新,然后数据源自动刷新对应的单元格,全程都是响应式的,不用手动管理刷新逻辑。

常见误区避坑

  • ❌ 不要手动调用tableView.reloadData():这会覆盖RxDataSources的局部刷新逻辑,直接刷新整个列表。
  • ❌ 不要用普通的tableView.rx.items(cellIdentifier:)绑定:这种方式会每次刷新整个列表,必须用RxDataSources的数据源绑定。
  • ❌ 不要错误实现Equatable:如果Equatable返回true但实际数据已经变化,单元格不会刷新;如果返回false但数据没变化,会导致不必要的刷新。

内容的提问来源于stack exchange,提问作者Oleg Gordiichuk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 04:36:32