VergeNeue 6.0.0-alpha.1

VergeNeue 6.0.0-alpha.1

Maintained by muukii.



VergeNeue 6.0.0-alpha.1

Verge - Neue (SwiftUI / UIKit)

⚠️ Currently in progress

Latest released Verge => master branch

Verge - Neue is an unidirectional-data-flow framework.

Link to Demo Application Video

Architecture

The sources are in VergeNeue directory.

Demo Application is in VergeNeueDemo directory.
Demo implementations are super experimentally code. I think there are no best practices. So these may be updated.

  • Store / ScopedStore
  • Reducer
    • Mutation
    • Action

🏬 Store

let rootStore = Store(
  state: RootState(),
  reducer: RootReducer()
)

📦 State

struct RootState {
     
  var count: Int = 0
  
  var photos: [Photo.ID : Photo] = [:]
  var comments: [Commenet.ID : Comment] = [:]
  
}
struct Photo

struct Photo: Identifiable {
  
  let id: String
  let url: URL
}

struct Comment

struct Comment: Identifiable {
  
  let id: String
  let photoID: Photo.ID
  let body: String
 
}

Use State

struct HomeView: View {
  
  @ObservedObject var store: Store<RootReducer>
      
  var body: some View {
    NavigationView {
      List(store.state.photos) { (photo) in
        NavigationLink(destination: PhotoDetailView(photoID: photo.id)) {
          Cell(photo: photo, comments: self.sessionStore.state.comments(for: photo.id))
        }
      }
      .navigationBarTitle("Home")
    }
    .onAppear {
      self.sessionStore.dispatch { $0.fetchPhotos() }
    }
  }
}
PhotoDetailView

struct PhotoDetailView: View {    
  let photoID: Photo.ID
  
  @EnvironmentObject var sessionStore: SessionStateReducer.StoreType
  @State private var draftCommentBody: String = ""
  
  private var photo: Photo {
    sessionStore.state.photosStorage[photoID]!
  }
      
  var body: some View {
    VStack {
      Text("\(photo.id)")
      TextField("Enter comment here", text: $draftCommentBody)
        .padding(16)
      Button(action: {
        
        guard self.draftCommentBody.isEmpty == false else { return }
        
        self.sessionStore.dispatch {
          $0.submitComment(body: self.draftCommentBody, photoID: self.photoID)
        }
        self.draftCommentBody = ""
        
      }) {
        Text("Submit")
      }
    }
  }
}

💥 Reducer

We can choose class or struct depends on use cases.

Store uses Reducer as an instance.
This means Reducer can have some dependencies. (e.g Database, API client)

Firstly, In order to implement Reducer, Use ReducerType on the object.
And then, ReducerType needs a type of state to update the state with type-safety.

class RootReducer: ReducerType {
  
  typealias TargetState = RootState

Define Mutation

The only way to actually change state in a Verge store is by committing a mutation.
Define a function that returns Mutation object. That expresses that function is Mutation

Mutation object is simple struct that has a closure what passes current state to change it.

Mutation does not run asynchronous operation.

extension RootReducer: ReducerType {
  func syncIncrement(adding number: Int) -> Mutation {
    return .init {
      $0.count += number
    }
  }
}

Commit Mutation

store.commit { $0.syncIncrement() }

Define Action

Action is similar to Mutation.
Action can contain arbitrary asynchronous operations.

To commit Mutations inside Action, Use context.commit.

extension RootReducer: ReducerType {
  func asyncIncrement() -> Action<Void> {
    return .init { context in
      DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        context.commit { $0.syncIncrement() }
      }
    }
  }
}

Dispatch Action

store.dispatch { $0.asyncIncrement() }
More sample Reducer implementations

 func submitComment(body: String, photoID: Photo.ID) -> Action<Void> {
    return .init { context in
      
      let comment = Comment(photoID: photoID, body: body)
      context.commit { _ in
        .init {
          $0.commentsStorage[comment.id] = comment
        }
      }
      
    }
  }

Advanced Informations

ScopedStore

ScopedStore is a node object detached from Store
It initializes with Store as parent store and WritableKeyPath to take fragment of parent store's state.

Its side-effects dispatch and commit affects parent-store.
And receives parent-store's side-effects