Unlock This Episode
Our Free plan includes 1 subscriber-only episode of your choice, plus weekly updates from our newsletter.
We have now made huge improvements to the ergonomics of defining reducers, which make it simpler and more natural to build features with the Composable Architecture. We now implement reducers as types that conform to the
ReducerProtocol, and we can do so in one of two ways: by implementing a
reduce method that mutates state whenever an action comes into the system, or by implementing a
body computed property that expresses how to compose a bunch of reducers together.
Most importantly, everything we have added to the library is still 100% backwards compatible. Every existing Composable Architecture application will still compile and run exactly as it did before these changes.
Further, even though we are using some advanced Swift 5.7 features to make reducer builders and bodies as ergonomic as possible, we can approximate these tools for those who need to stay on Swift 5.6 for a bit longer. This means if you can’t immediately upgrade your project to Xcode 14, you can still write reducers in this style, with just a few small changes. We aren’t going to cover those details right now, but just know that it will be available in the final library release.
But there are more benefits to be had from this new style of defining reducers. We have already completely removed the concept of “environment” from reducers, and instead just hold onto dependencies directly in the conforming type itself, but now we can start to explore more ways to simplify dependency management. What if we could adopt a style similar to SwiftUI’s environment values, where instead of explicitly passing values throughout a view hierarchy, you can have them globally and implicitly available, and then any view can grab ahold of the value whenever they want.
This comes with a ton of benefits. First of all parent views do not need to hold onto dependencies it doesn’t need just so that child views have access to them. We also eliminate the need to create public initializers when modularizing our application just so that we can pass dependencies from one module to another. And we make it easy to override just a single dependency in a child feature, which can be great for running a feature in an alternative environment.