|
| 1 | +# Action |
| 2 | + |
| 3 | +Events that describe what happened in your application and trigger state updates. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +`Action` is a marker protocol that represents events or intentions in your application. Actions are the only way to update the state in a UDF application. They describe what happened (not how to update the state), making your application's behavior predictable and testable. |
| 8 | + |
| 9 | +### Key Principles |
| 10 | + |
| 11 | +- **Descriptive**: Actions describe what happened, not how to handle it |
| 12 | +- **Immutable**: Actions should be immutable data structures |
| 13 | +- **Serializable**: Actions should be easily serializable for debugging |
| 14 | +- **Type-Safe**: Use specific action types for different events |
| 15 | + |
| 16 | +## Example |
| 17 | + |
| 18 | +```swift |
| 19 | +// Simple actions |
| 20 | +struct IncrementCounterAction: Action {} |
| 21 | +struct DecrementCounterAction: Action {} |
| 22 | + |
| 23 | +// Actions with data |
| 24 | +struct LoadUserAction: Action { |
| 25 | + let userId: String |
| 26 | +} |
| 27 | + |
| 28 | +struct UserLoadedAction: Action { |
| 29 | + let user: User |
| 30 | +} |
| 31 | + |
| 32 | +struct UserLoadErrorAction: Action { |
| 33 | + let message: String |
| 34 | +} |
| 35 | + |
| 36 | +// Complex actions |
| 37 | +struct UpdateUserProfileAction: Action { |
| 38 | + let userId: String |
| 39 | + let name: String |
| 40 | + let email: String |
| 41 | + let avatar: URL? |
| 42 | +} |
| 43 | +``` |
| 44 | + |
| 45 | +## Action Categories |
| 46 | + |
| 47 | +### User Actions |
| 48 | +Actions triggered by user interactions: |
| 49 | + |
| 50 | +```swift |
| 51 | +struct ButtonTappedAction: Action { |
| 52 | + let buttonId: String |
| 53 | +} |
| 54 | + |
| 55 | +struct TextFieldChangedAction: Action { |
| 56 | + let fieldId: String |
| 57 | + let text: String |
| 58 | +} |
| 59 | + |
| 60 | +struct SwipeGestureAction: Action { |
| 61 | + let direction: SwipeDirection |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +### System Actions |
| 66 | +Actions triggered by system events: |
| 67 | + |
| 68 | +```swift |
| 69 | +struct AppDidBecomeActiveAction: Action {} |
| 70 | +struct AppWillResignActiveAction: Action {} |
| 71 | +struct NetworkStatusChangedAction: Action { |
| 72 | + let isConnected: Bool |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +### Async Actions |
| 77 | +Actions that represent asynchronous operations: |
| 78 | + |
| 79 | +```swift |
| 80 | +struct APIRequestStartedAction: Action { |
| 81 | + let requestId: String |
| 82 | + let endpoint: String |
| 83 | +} |
| 84 | + |
| 85 | +struct APIRequestCompletedAction: Action { |
| 86 | + let requestId: String |
| 87 | + let data: Data |
| 88 | +} |
| 89 | + |
| 90 | +struct APIRequestFailedAction: Action { |
| 91 | + let requestId: String |
| 92 | + let error: Error |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +## Dispatching Actions |
| 97 | + |
| 98 | +Actions are dispatched through the store: |
| 99 | + |
| 100 | +```swift |
| 101 | +// Dispatch simple actions |
| 102 | +store.dispatch(IncrementCounterAction()) |
| 103 | +store.dispatch(DecrementCounterAction()) |
| 104 | + |
| 105 | +// Dispatch actions with data |
| 106 | +store.dispatch(LoadUserAction(userId: "123")) |
| 107 | + |
| 108 | +// Dispatch from components |
| 109 | +class UserViewController: UIViewController, ViewComponent { |
| 110 | + let disposer = Disposer() |
| 111 | + var props: UserProps = UserProps() |
| 112 | + |
| 113 | + @IBAction func loadUserButtonTapped() { |
| 114 | + // Dispatch action through the store |
| 115 | + store.dispatch(LoadUserAction(userId: "123")) |
| 116 | + } |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +## Action Handling in Reducers |
| 121 | + |
| 122 | +Actions are handled in reducers to update state: |
| 123 | + |
| 124 | +```swift |
| 125 | +let userReducer: Reducer<AppState> = { state, action in |
| 126 | + switch action { |
| 127 | + case let action as LoadUserAction: |
| 128 | + state.isLoading = true |
| 129 | + state.error = nil |
| 130 | + // Trigger async operation here |
| 131 | + |
| 132 | + case let action as UserLoadedAction: |
| 133 | + state.user = action.user |
| 134 | + state.isLoading = false |
| 135 | + |
| 136 | + case let action as UserLoadErrorAction: |
| 137 | + state.error = action.message |
| 138 | + state.isLoading = false |
| 139 | + |
| 140 | + default: |
| 141 | + break |
| 142 | + } |
| 143 | +} |
| 144 | +``` |
| 145 | + |
| 146 | +## Action Observation |
| 147 | + |
| 148 | +You can observe actions for logging, analytics, or side effects: |
| 149 | + |
| 150 | +```swift |
| 151 | +store.onAction { state, action in |
| 152 | + // Log all actions |
| 153 | + print("Action: \(type(of: action))") |
| 154 | + |
| 155 | + // Track analytics |
| 156 | + Analytics.track(action: action) |
| 157 | + |
| 158 | + // Handle side effects |
| 159 | + switch action { |
| 160 | + case is AppDidBecomeActiveAction: |
| 161 | + // Refresh data when app becomes active |
| 162 | + store.dispatch(RefreshDataAction()) |
| 163 | + default: |
| 164 | + break |
| 165 | + } |
| 166 | +}.dispose(on: disposer) |
| 167 | +``` |
| 168 | + |
| 169 | +## Best Practices |
| 170 | + |
| 171 | +### Action Naming |
| 172 | +- Use descriptive names that explain what happened |
| 173 | +- Use past tense for completed actions |
| 174 | +- Use present tense for ongoing actions |
| 175 | + |
| 176 | +```swift |
| 177 | +// Good |
| 178 | +struct UserProfileUpdatedAction: Action {} |
| 179 | +struct DataLoadingStartedAction: Action {} |
| 180 | + |
| 181 | +// Avoid |
| 182 | +struct UpdateUserAction: Action {} |
| 183 | +struct LoadDataAction: Action {} |
| 184 | +``` |
| 185 | + |
| 186 | +### Action Structure |
| 187 | +- Keep actions simple and focused |
| 188 | +- Include only necessary data |
| 189 | +- Make actions immutable |
| 190 | + |
| 191 | +```swift |
| 192 | +// Good - focused and immutable |
| 193 | +struct UserNameChangedAction: Action { |
| 194 | + let newName: String |
| 195 | +} |
| 196 | + |
| 197 | +// Avoid - too complex |
| 198 | +struct UserAction: Action { |
| 199 | + var name: String |
| 200 | + var email: String |
| 201 | + var avatar: URL? |
| 202 | + var isEditing: Bool |
| 203 | +} |
| 204 | +``` |
| 205 | + |
| 206 | +### Action Organization |
| 207 | +- Group related actions together |
| 208 | +- Use namespaces for large applications |
| 209 | +- Consider using enums for related actions |
| 210 | + |
| 211 | +```swift |
| 212 | +// Using enums for related actions |
| 213 | +enum UserAction { |
| 214 | + case load(userId: String) |
| 215 | + case loaded(user: User) |
| 216 | + case loadFailed(error: String) |
| 217 | + case update(profile: UserProfile) |
| 218 | + case updateCompleted(user: User) |
| 219 | + case updateFailed(error: String) |
| 220 | +} |
| 221 | +``` |
| 222 | + |
| 223 | +## Summary |
| 224 | + |
| 225 | +Actions are the foundation of the unidirectional data flow pattern: |
| 226 | + |
| 227 | +- **Describe events**: Actions describe what happened in your app |
| 228 | +- **Trigger updates**: Actions are the only way to update state |
| 229 | +- **Enable testing**: Actions make behavior predictable and testable |
| 230 | +- **Support debugging**: Actions provide a clear audit trail |
| 231 | + |
| 232 | +Design your actions to be descriptive, immutable, and focused on specific events in your application. |
0 commit comments