fluttercommunity / redux.dart

Redux for Dart
https://pub.dev/packages/redux
MIT License
515 stars 61 forks source link

Proposal: Add functions and classes for type-safe reducers and middleware. #11

Closed brianegan closed 6 years ago

brianegan commented 6 years ago

Hey hey :) After using this library a bit more and then comparing it to built_redux, one feature that I really liked from built_redux were the ReducerBuilder and MiddlewareBuilder classes. They allow you to bind actions of a given Type to a specific Reducer or Middleware. I realized: We can totally achieve the same result!

While this can result in slightly more code at times, it can be easier to quickly scan to see how each action is being handled and ensure the functions are type-safe.

Let me know whatcha think!

This is a "Before" and "After" of what these additional utilities provide.

Reducer Before

List<Todo> handRolledTodosReducer(List<Todo> todos, action) {
  if (action is AddTodoAction) {
    return new List.from(todos)
      ..add(action.todo);
  } else if (action is DeleteTodoAction) {
    return todos.where((todo) => todo.id != action.id).toList();
  } else if (action is UpdateTodoAction) {
    return todos
        .map((todo) => todo.id == action.id ? action.updatedTodo : todo)
        .toList();
  } else if (action is ClearCompletedAction) {
    return todos.where((todo) => !todo.complete).toList();
  } else if (action is ToggleAllAction) {
    final allComplete = allCompleteSelector(todos);

    return todos.map((todo) => todo.copyWith(complete: !allComplete)).toList();
  } else if (action is TodosLoadedAction) {
    return action.todos;
  } else if (action is TodosNotLoadedAction) {
    return [];
  } else {
    return todos;
  }
}

Reducer After

final todosReducer = combineTypedReducers<List<Todo>>([
  new ReducerBinder<List<Todo>, AddTodoAction>(_addTodo),
  new ReducerBinder<List<Todo>, DeleteTodoAction>(_deleteTodo),
  new ReducerBinder<List<Todo>, UpdateTodoAction>(_updateTodo),
  new ReducerBinder<List<Todo>, ClearCompletedAction>(_clearCompleted),
  new ReducerBinder<List<Todo>, ToggleAllAction>(_toggleAll),
  new ReducerBinder<List<Todo>, TodosLoadedAction>(_setLoadedTodos),
  new ReducerBinder<List<Todo>, TodosNotLoadedAction>(_setNoTodos),
]);

List<Todo> _addTodo(List<Todo> todos, AddTodoAction action) {
  return new List.from(todos)
      ..add(action.todo);
}

List<Todo> _deleteTodo(List<Todo> todos, DeleteTodoAction action) {
  return todos.where((todo) => todo.id != action.id).toList();
}

List<Todo> _updateTodo(List<Todo> todos, UpdateTodoAction action) {
  return todos
      .map((todo) => todo.id == action.id ? action.updatedTodo : todo)
      .toList();
}

List<Todo> _clearCompleted(List<Todo> todos, ClearCompletedAction action) {
  return todos.where((todo) => !todo.complete).toList();
}

List<Todo> _toggleAll(List<Todo> todos, ToggleAllAction action) {
  final allComplete = allCompleteSelector(todos);

  return todos.map((todo) => todo.copyWith(complete: !allComplete)).toList();
}

List<Todo> _setLoadedTodos(List<Todo> todos, TodosLoadedAction action) {
  return action.todos;
}

List<Todo> _setNoTodos(List<Todo> todos, TodosNotLoadedAction action) {
  return [];
}

Middleware Before

Middleware<AppState> createStoreTodosMiddlewareNormal(TodosService service) {
  return (Store<AppState> store, action, NextDispatcher next) {
    if (action is LoadTodosAction) {
      service
          .loadTodos()
          .then((todos) => store.dispatch(new TodosLoadedAction(todos)))
          .catchError((_) => store.dispatch(new TodosNotLoadedAction()));

      next(action);
    } else if (action is AddTodoAction ||
        action is ClearCompletedAction ||
        action is ToggleAllAction ||
        action is UpdateTodoAction ||
        action is TodosLoadedAction) {
      next(action);

      service.saveTodos(store.state.todos);
    }
  };
}

Middleware After

List<Middleware<AppState>> createStoreTodosMiddleware(TodosService service) {
  final saveTodos = _createSaveTodos(service);
  final loadTodos = _createLoadTodos(service);

  return combineTypedMiddleware([
    new MiddlewareBinder<AppState, LoadTodosAction>(loadTodos),
    new MiddlewareBinder<AppState, AddTodoAction>(saveTodos),
    new MiddlewareBinder<AppState, ClearCompletedAction>(saveTodos),
    new MiddlewareBinder<AppState, ToggleAllAction>(saveTodos),
    new MiddlewareBinder<AppState, UpdateTodoAction>(saveTodos),
    new MiddlewareBinder<AppState, TodosLoadedAction>(saveTodos),
  ]);
}

Middleware<AppState> _createSaveTodos(TodosService service) {
  return (Store<AppState> store, action, NextDispatcher next) {
    next(action);

    service.saveTodos(store.state.todos);
  };
}

Middleware<AppState> _createLoadTodos(TodosService service) {
  return (Store<AppState> store, action, NextDispatcher next) {
    service
        .loadTodos()
        .then((todos) => store.dispatch(new TodosLoadedAction(todos)))
        .catchError((_) => store.dispatch(new TodosNotLoadedAction()));

    next(action);
  };
}
johnpryan commented 6 years ago

looks good to me!