TCAバージョン1.4のマイグレーションまとめ
何があった?
- TCAの1.4のアップデートが来ました(2023/11/22時点で1.4.2が最新リリース)
-
Macrosの対応が主な内容でマイグレーションガイドが公開されています- 本稿はこの記事の内容と同じです
Reducerマクロ
-
Reducerのプロトコルを外して
@Reducerをつけるだけ+@Reducer -struct Counter: Reducer { +struct Counter { // ... } -
Reducerマクロ単体ではほぼReducerプロトコルと同じだけど、マクロでしか利用できない追加機能があります(以下に続く)
CaseKeyPathの導入
-
KeyPathでCasePathを記述できるようになった
Reduce { state, action in // ... } -.ifLet(\.child, action: /Action.child) { +.ifLet(\.child, action: \.child) { ChildFeature() } -
1.4~ではCasePathが
soft-deprecatedに- soft-deprecated: 将来的に廃止…くらいのやつ
-
Reducerマクロを適用したReducerでのみ利用できる
-
CasePathableマクロの導入によって、実行時のリフレクションの必要がなくなった- リフレクション: 自分自身の構造やプロパティを調査すること
- https://github.com/pointfreeco/swift-case-paths/blob/1.0.0/Sources/CasePaths/EnumReflection.swift
TestStoreの改善
-
CaseKeyPathによってTestStoreの
receiveメソッドで具体的なenumを記述する必要がなくなった-store.receive(.child(.presented(.response(.success("Hello!"))))) +store.receive(\.child.presented.response.success) -
PresentationActionであればさらに省略した記述ができる
store.receive(\.child.response.success) -
receiveで具体的なenumを受け取る(=比較する)必要がなくなったため、
Action: Equatableの必要がなくなった-
if you use this style of action receiving exclusivelyという条件が書かれていたが、いまいちよくわからなかった- たぶんActionをEquatableで比較しなくなったため、receiveメソッドを同時に処理すると意図しない方に入るかもしれないよ、ってことかな?
-
-
ただし
IdentifiedAction(後述)やStackActionを含むActionについては、subscript(id:)を用いることで具体的なenumを受け取ることができるstore.receive(\.rows[id: 0].response.success)
TaskResultの廃止
-
Action: Equatableの必要がなくなり(上記TestStoreの改善を参照)、TaskResultの存在意義がおおよそなくなったためsoft-deprecatedとなった- TaskResultの主な役割は、Errorを含む型でありながらEquatableに準拠できることだった。これによってActionをEquatableに準拠させやすかった
- 今後はSwiftの
Resultの使用を推奨
IdentifiedActionの導入
-
今までListで表現されるような、複数存在しているUIからのActionは以下のように表現されていた
enum Action { // ... case row(id: State.ID, action: Action) } // ... Reduce { state, action in // ... } .forEach(\.rows, action: /Action.row(id:action:)) { RowFeature() } -
IdentifiedActionによって以下のように書き換えられる。CaseKeyPathを利用しているのでReducerマクロを利用する必要があるenum Action { // ... case rows(IdentifiedActionOf<Nested>) } // ... Reduce { state, action in // ... } .forEach(\.rows, action: \.rows) { RowFeature() } -
以前のように
idとacitonにアクセスしたい場合以下のような差分となる-case let .row(id: id, action: .buttonTapped): +case let .rows(.element(id: id, action: .buttonTapped)):
Reducerマクロのバグ(2023/11/22時点)
補完がバグる
-
ビルダーの型チェックとマクロの展開の順序の問題かもとのこと
-
Reducer内部の補完であれば書き方で解決できる
var body: some Reducer<State, Action> { - Reduce { state, action in + Reduce<State, Action> { state, action in
循環参照エラー
-
同じファイル内で適用されるマクロのコードに対して、拡張を追加することができない
@Reducer struct Feature { struct State { /* ... */ } // ... } extension Feature.State { // 🛑 循環参照エラー // ... } -
Swiftとマクロのバグとのこと
-
別のファイルに移すか、直接記述するしかないとのこと
まとめ
- Macrosを適用して一部記法が変わった
- ちょっとバグがある
- swift 5.9~のみ利用可能
- 使わない限りは関係なく、今まで通りの書き方もできる。がsoft-deprecatedなのでいつまでも放置はできない
- ここで紹介していない変更点もあるので注意
Discussion