apple/RxSwift, ReactorKit

[ReactorKit] ReactorKit 공부하기 #3 RxTodo 따라잡기 (1)

lgvv 2022. 9. 7. 21:08

ReactorKit 공부하기 #3 RxTodo 따라잡기 (1)

 

리액터 킷을 공부하는데 RxTodo를 보면서 따라해보자

  • 우선 ReactorKit의 Create기능을 스스로 구현해보기


결과물 영상

 

RxTodo의 Create만 구현하기!

 

 

 

 

리액터 간단 정리

직접 따라하고 쳐보면서 사용법이 이해가 되기 시작함. 

  • ViewController에서 map을 통해 원하는 Action으로 변경해서 reactor에 전달
  • reactor에서는 mutate함수를 통해서 이전 상태를 받아서 다음 상태를 반환
  • reduce는 해당 로직 처리
  • 그리고 변경된 값은 viewController에서 map을 통해 reactor.state를 가져다가 사용

 

ViewController 코드 구현

base에 존재하는 Reactor 제대로 연결해야 함. 

//
//  TaskListViewController.swift
//  AppleCollectionView
//
//  Created by Hamlit Jason on 2022/09/07.
//

import UIKit
import ReactorKit

final class TaskListViewController: UIViewController, View {
    var disposeBag = DisposeBag()
    let reactor: TaskListViewReactor
    
    // MARK: - Views
    let addButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: nil, action: nil)
    let tableView = UITableView()
    
    // MARK: - Initialize
    init(reactor: TaskListViewReactor) {
        self.reactor = reactor
        super.init(nibName: nil, bundle: nil)
        self.navigationItem.leftBarButtonItem = self.editButtonItem
        self.navigationItem.rightBarButtonItem = self.addButtonItem
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK: - Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupTableView()
        bind(reactor: reactor)
        // MARK: - 초기 테이블 뷰 로딩을 위해서
        reactor.action.onNext(TaskListViewReactor.Action.refresh)
    }
    
    func bind(reactor: TaskListViewReactor) {
        // MARK: - Action
        
        self.addButtonItem.rx.tap
            .map { TaskListViewReactor.Action.create } // 1. create액션으로 바꿔서
            .bind(to: reactor.action) // 2. 액션에 bind
            .disposed(by: disposeBag)
                
        // MARK: - State
        
        reactor.state
            .map { _ in reactor.currentState.tasks }
            .bind(to: tableView.rx.items(
                cellIdentifier: TaskCell.identifier,
                cellType: TaskCell.self)
            ) { index, item, cell in
                cell.configureCell(with: item)
            }
            .disposed(by: disposeBag)
    }
}

extension TaskListViewController {
    func setupTableView() {
        view.addSubview(tableView)
        
        tableView.register(
            TaskCell.self,
            forCellReuseIdentifier: TaskCell.identifier
        )
        
        tableView.snp.makeConstraints {
            $0.edges.equalTo(0)
        }
    }
}

 

 

Reactor 코드 구현

간단하다!! 액션과 스테이트를 분리하는게 참 맘에 든다

//
//  TaskListViewReactor.swift
//  AppleCollectionView
//
//  Created by Hamlit Jason on 2022/09/07.
//

import Foundation
import ReactorKit

class TaskListViewReactor: Reactor {
    var initialState = State()
    
    // MARK: - User의 행동을 정의
    enum Action {
        case refresh // 테이블 뷰 리로드
        case create // 테이블 아이템 생성
    }
    
    // MARK: - Action을 받았을 때 변화
    enum Mutation {
        case refreshItem // 테이블뷰 리로드
        case insertItem // 테이블 뷰에 아이템 추가
    }
    
    // MARK: - 변화를 받은 후의 상태를 기록
    struct State {
        // 현재 상태!
        var tasks = [Task]()
    }
    
    // MARK: - 이전 상태와 처리 단위를 받아서 다음 상태를 반환
    func mutate(action: Action) -> Observable<Mutation> {
        switch action {
        case .refresh:
            return Observable.just(.refreshItem)
        case .create:
            return Observable.just(.insertItem)
        }
    }
    
    // MARK: - 변화에 맞게끔 값을 설정해!
    func reduce(state: State, mutation: Mutation) -> State {
        var newState = state
        switch mutation {
        case .refreshItem:
            newState.tasks = Task.readTask()
        case .insertItem:
            Task.createTask()
            newState.tasks = Task.readTask()
        }
        
        return newState
    }
}

 

cell 코드 구현

셀도 리액터로 분리하길래 우선 간단하게 구현해보기

//
//  TaskCell.swift
//  AppleCollectionView
//
//  Created by Hamlit Jason on 2022/09/07.
//

import UIKit

class TaskCell: UITableViewCell {
    
    // MARK: - Views
    var contentLabel = UILabel()
    
    // MARK: - Initialize
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        setupContentLabel(contentLabel)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func configureCell(with item: Task) {
        self.contentLabel.text = item.content
    }
}

extension TaskCell {
    private func setupContentLabel(_ sender: UILabel) {
        contentView.addSubview(sender)
        
        sender.font = UIFont.preferredFont(forTextStyle: .body, compatibleWith: .current)
        sender.textColor = .systemTeal
        
        sender.snp.makeConstraints {
            $0.centerY.equalToSuperview()
            $0.leading.equalToSuperview().offset(15)
        }
    }
}