Swift Academy · Lección

Efectos y dependencias

Gestione efectos secundarios e inyecte dependencias.

Lección 3 de 413 pasos

Efectos y dependencias es una lección gratuita de Swift Academy en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Swift Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Swift Academy incluye 4 lecciones en total.

Por qué existen los efectos

Los reducers deben mantenerse puros: dado un estado y una acción, solo modifican el estado. Sin embargo, las aplicaciones reales necesitan efectos secundarios, como solicitudes de red y temporizadores. TCA los modela como Effects que devuelve el reducer, manteniendo pura la mutación mientras el trabajo asíncrono se ejecuta por separado.

Esta separación es lo que hace que las funcionalidades se puedan probar.

import ComposableArchitecture

// Reducer stays pure; side effects are values it returns.
// Returning .none means no further work.

Devolver .none

Cuando una acción solo cambia el estado y no necesita trabajo posterior, el reducer devuelve .none. Este es el efecto más habitual e indica que no hay nada asíncrono que hacer.

Ya ha visto este caso; es la opción básica a la que vuelve cada rama.

import ComposableArchitecture

Reduce { state, action in
    switch action {
    case .incrementTapped:
        state.count += 1
        return .none
    }
}

Effect.run para el trabajo asíncrono

Para realizar trabajo asíncrono, devuelva .run. Su cierre recibe un controlador send que se utiliza para devolver los resultados al sistema como acciones nuevas.

Aquí, una acción de obtención carga datos y envía una acción de respuesta cuando termina.

import ComposableArchitecture

Reduce { state, action in
    switch action {
    case .reloadTapped:
        return .run { send in
            let value = try await loadNumber()
            await send(.responseReceived(value))
        }
    case let .responseReceived(value):
        state.count = value
        return .none
    }
}

Devolver resultados como acciones

Un efecto no puede modificar el estado directamente; en su lugar, envía acciones mediante send. Después, el reducer gestiona esas acciones y actualiza el estado. Este ciclo mantiene todas las mutaciones dentro del reducer.

Así, una respuesta de red se convierte en una acción de respuesta que el reducer almacena.

import ComposableArchitecture

.run { send in
    let result = try await fetch()
    await send(.dataLoaded(result))
}
// .dataLoaded is then handled by the reducer to set state.

Gestionar errores en los efectos

El trabajo asíncrono puede lanzar errores. Puede capturarlos dentro del efecto y enviar una acción de error, lo que permite que el reducer pase limpiamente a un estado de error.

El cierre opcional catch de .run recibe el error lanzado.

import ComposableArchitecture

.run { send in
    let value = try await loadNumber()
    await send(.responseReceived(value))
} catch: { error, send in
    await send(.loadFailed)
}

Por qué son importantes las dependencias

Si un reducer llama directamente a un cliente de red o lee la fecha, resulta difícil probarlo y controlarlo. TCA resuelve esto mediante dependencies: servicios inyectables a los que el reducer accede mediante @Dependency.

En las pruebas, puede sustituirlos por simulaciones predecibles.

import ComposableArchitecture

// Instead of calling APIClient.shared directly,
// inject it as a dependency so tests can replace it.

El property wrapper @Dependency

Dentro de un reducer, declare una dependencia con @Dependency y asígnele el nombre de su key path en los valores de dependencias. La biblioteca proporciona elementos integrados, como un reloj y un generador de uuid.

Aquí, el reducer incorpora el reloj continuo.

import ComposableArchitecture

@Reducer
struct TimerFeature {
    @ObservableState
    struct State: Equatable { var seconds = 0 }
    enum Action { case start, tick }

    @Dependency(\.continuousClock) var clock

    var body: some ReducerOf<Self> {
        Reduce { state, action in .none }
    }
}

Usar una dependencia en un efecto

Una vez declarada, use la dependencia dentro de .run. Como el reloj se inyecta, las pruebas pueden proporcionar un reloj de prueba que avance el tiempo cuando se solicite.

Aquí, el temporizador emite un evento cada segundo mediante el reloj inyectado.

import ComposableArchitecture

var body: some ReducerOf<Self> {
    Reduce { state, action in
        switch action {
        case .start:
            return .run { send in
                for await _ in self.clock.timer(interval: .seconds(1)) {
                    await send(.tick)
                }
            }
        case .tick:
            state.seconds += 1
            return .none
        }
    }
}

Definir su propia dependencia

Registre dependencias personalizadas haciendo que un valor cumpla DependencyKey y proporcionando un liveValue. Después, añada una propiedad calculada a DependencyValues para que obtenga un key path.

Aquí, un cliente de API se convierte en una dependencia de primera clase.

import ComposableArchitecture

struct NumberClient {
    var fetch: () async throws -> Int
}

extension NumberClient: DependencyKey {
    static let liveValue = NumberClient {
        try await loadNumber()
    }
}

extension DependencyValues {
    var numberClient: NumberClient {
        get { self[NumberClient.self] }
        set { self[NumberClient.self] = newValue }
    }
}

Inyectar la dependencia personalizada

Una vez configurado el key path, el reducer declara el cliente con @Dependency(\.numberClient) y lo llama dentro de los efectos. En producción se usa el valor real; en las pruebas se sustituye.

Aquí, una acción de recarga obtiene los datos mediante el cliente inyectado.

import ComposableArchitecture

@Reducer
struct NumberFeature {
    @ObservableState
    struct State: Equatable { var value = 0 }
    enum Action { case reload, response(Int) }

    @Dependency(\.numberClient) var numberClient

    var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .reload:
                return .run { send in
                    let n = try await numberClient.fetch()
                    await send(.response(n))
                }
            case let .response(n):
                state.value = n
                return .none
            }
        }
    }
}

Cancelar efectos

Los efectos de larga duración, como los temporizadores, deben poder cancelarse. Asigne a un efecto un identificador cancellable y cancélelo más adelante desde otra acción con .cancel.

Aquí, el temporizador se puede detener cuando sea necesario.

import ComposableArchitecture

enum CancelID { case timer }

// Start a cancellable effect:
.run { send in /* ... */ }
    .cancellable(id: CancelID.timer)

// Stop it from another action:
.cancel(id: CancelID.timer)

Comprobación rápida: efectos y resultados

Recuerde cómo un efecto comunica su resultado al reductor.

Resumen: efectos y dependencias

Ha aprendido cómo TCA gestiona los efectos secundarios y la inyección:

  • Los reductores se mantienen puros y devuelven valores Effect; .none indica que no hay trabajo que realizar.
  • .run ejecuta trabajo asíncrono y devuelve los resultados mediante await send(.action).
  • Los errores se gestionan con el cierre catch del efecto.
  • @Dependency inyecta servicios como el reloj o sus propios clientes, declarados mediante DependencyKey y DependencyValues.
  • Los efectos de larga duración se pueden marcar como cancellable mediante un identificador.

A continuación compondrá funcionalidades y las probará exhaustivamente.

import ComposableArchitecture

// Recap: pure reducer + Effect.run for async work
// + @Dependency for testable, injectable services.
Gratis para empezar

Aprende Swift con un tutor de IA — gratis

Escribe y ejecuta código real en tu navegador, obtén ayuda instantánea de un tutor de IA disponible 24/7 y continúa donde lo dejaste en la web o en la aplicación.

Cursos
122
Lecciones
409

Preguntas frecuentes

¿La lección «Efectos y dependencias» es gratis?

Sí — el texto completo de «Efectos y dependencias» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Swift Academy, actualiza a CoddyKit PRO. El curso de Swift Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Efectos y dependencias»?

Gestione efectos secundarios e inyecte dependencias. Practicas Swift Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Swift Academy?

No se requiere experiencia previa. Swift Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.

¿Cuánto tiempo toma la lección «Efectos y dependencias»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Swift Academy?

Sí. Cada lección de Swift Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Estado, acción y reductor
  2. El Store e integración con SwiftUI
  3. Efectos y dependencias
  4. Composición y pruebas de funcionalidades
← Volver a Swift Academy