繁体   English   中英

在angular2应用程序中管理状态-副作用?

[英]Managing state in angular2 application - side effects?

这更像是一个普遍的问题,但是基于Victor Savkin的帖子, 在angular2中管理状态

让我们考虑那里描述的使用RxJ的方法:

interface Todo { id: number; text: string; completed: boolean; }
interface AppState { todos: Todo[]; visibilityFilter: string; }

function todos(initState: Todo[], actions: Observable<Action>): Observable<Todo[]> {
  return actions.scan((state, action) => {
    if (action instanceof AddTodoAction) {
      const newTodo = {id: action.todoId, text: action.text, completed: false};
      return [...state, newTodo];
    } else {
      return state;
    }
  }, initState);
}

一切都很好,但让我们添加更多要求:

  • 添加新的待办事项后,应将其文本发送到后端并进行分析,以提取可能的到期日期和位置。
  • 如果待办事项有到期日,则应将其添加到我的Google日历中

因此,如果我添加Todo“在星期四在Sally's Saloon整理头发”,则第一个电话我将从后端Sally's Saloon获得,日期设置为本周(或下周)星期四,第二个电话将这个待办事项添加到我的电话中Google日历,并将项目标记为日历中的。

因此,我的新Todo项目结构可能如下所示:

interface Todo { 
  id: number; 
  text: string; 
  completed: boolean; 
  location?: Coordinates; 
  date?: Date; 
  inCalendar?: boolean; 
  parsed?: boolean;
}

现在我有两个副作用:

  1. 添加待办事项后,我需要解析文本
  2. 将日期添加到Todo之后,我需要将其添加到日历中。

如何以这种方式应对这些副作用? Redux表示减速器应保持清洁,并且它们也具有Sagas的概念。

选项1-在添加待办事项时触发新事件以产生副作用

function todos(initState: Todo[], actions: Observable<Action>): Observable<Todo[]> {
  return actions.scan((state, action) => {
    if (action instanceof AddTodoAction) {
      const newTodo = {id: action.todoId, text: action.text, completed: false};
      actions.onNext(new ParseTodoAction(action.todoId));
      return [...state, newTodo];
    } else if (action instanceOf ParseTodoAction){
      const todo = state.find(t => t.todoId === action.todoId)
      parserService
      .parse(todo.todoId, todo.text)
      .subscribe(r => actions.onNext(new TodoParsedAction(todo.todoId, r.coordinates, r.date)))
    } else {
      return state;
    }
  }, initState);
}

但这将失败,因为新的待办事项在该州尚不可用。 我当然可以只使用TodoParsedAction而不是ParseTodoAction直接调用后端调用内联,但这也将假定后端调用将需要更长的处理时间,并且在完成状态时将已经具有新的Todo项,这很难等待发生。

选项2-订阅操作并检查每个待办事项是否缺少属性

actions
.flatMap(todos => Observable.from(todos))
.subscribe(todo => {
  if (!todo.coordinates && !todo.parsed) {
     parserService
      .parse(todo.todoId, todo.text)
      .subscribe(r => actions.onNext(new TodoParsedAction(todo.todoId, r.coordinates, r.date)))
  }
  if (todo.date && todo.inCalendar === undefined) {
    calendarService
      .add(todo.text, todo.date)
      .subscribe(_ => actions.onNext(new TodoInCalendarAction(todo.todoId, true)))
  }
})

但这感觉不对劲-难道不是一切都由动作来管理,我是否应该始终遍历所有待办事项?

您的选项1不能按如下所述工作: actions是一个Observable<Action> observables是只读的, onNext不属于该API。 您需要一个Observer<Action>来支持选项1。这凸显了选项1的真正缺陷:状态函数(与Redux reducer相同)需要是纯净的且无副作用。 这意味着他们不能也不应派遣更多行动。

现在,在您引用的博客文章中,确实代码确实传入了主题,即Observer和Observable。 所以您可能确实有一个onNext。 但是我可以告诉您,在处理由该主题发布的数据时,将数据递归发布到该主题将使您无尽麻烦,并且很少值得为使工作正常进行而头痛。

在Redux中,调用后端处理以丰富您的状态的典型解决方案是,在您已经决定分派AddTodo时,一开始就分派多个操作。 这通常可以通过使用redux-thunk和调度功能作为“智能操作”来完成:

代替:

export function addToDo(args) {
    return new AddToDoAction(args);
}

你会做:

export function addToDo(args) {
    return (dispatch) => {
        dispatch(new AddToDoAction(args)); // if you want to dispatch the Todo before parsing
        dispatch(parseToDo(args)); // handle parsing
    };
}

export function parseToDo(args) {
    return (dispatch) => {
        if (thisToDoNeedsParsing(args)) {
            callServerAndParse(args).then(result => {
                  // dispatch an action to update the Todo
                  dispatch(new EnrichToDoWithParsedData(result));
            });
        }
    };
}

// UI code would do:
dispatch(addToDo(args));

UI调度了一个智能操作(thunk),该操作将调度AddToDoAction以使未解析的待办事项处于您的状态(如果需要,您的UI可以选择在解析完成之前不显示它)。 然后,它调度另一个智能操作(thunk),该操作实际上将调用服务器以获取更多数据,然后调度一个EnrichToDoWithParsedData操作及其结果,以便可以更新您的Todo。

至于日历更新......你也许可以用上面(插入到通话模式possiblyUpdateCalendar()两个addToDoparseToDo因此,如果待办事项拥有所有你需要的东西,它可以更新日历和调度结束时将待办事项标记为已添加的操作。

现在,我显示的这个示例是Redux特定的,并且我认为您正在使用的基于RxJs的示例没有类似thunk的东西。 在您的方案中增加对此支持的一种方法是向主题添加一个flatMap运算符,如下所示:

let actionStream = actionsSubject.flatMap(action => {
    if (typeof action !== "function") {
        // not a thunk.  just return it as a simple observable
        return Rx.Observable.of(action);
    }
    // call the function and give it a dispatch method to collect any actions it dispatches
    var actions = [];
    var dispatch = a => actions.push(a);
    action(dispatch);

    // now return the actions that the action thunk dispatched
    return Rx.Observable.of(actions);
});

// pass actionStream to your stateFns instead of passing the raw subject
var state$ = stateFn(initState, actionStream);

// Now your UI code *can* pass in "smart" actions:
actionSubject.onNext(addTodo(args));
// or "dumb" actions:
actionSubject.onNext(new SomeSimpleAction(args));

请注意,以上所有代码均在分派操作的代码中。 我没有显示任何状态功能。 您的状态函数将是纯净的,类似于:

function todos(initState: Todo[], actions: Observable<Action>): Observable<Todo[]> {
  return actions.scan((state, action) => {
    if (action instanceof AddTodoAction) {
      const newTodo = {id: action.todoId, text: action.text, completed: false};
      return [...state, newTodo];
    } else if (action instanceof EnrichTodoWithParsedData) {
      // (replace the todo inside the state array with a new updated one)
    } else if (action instanceof AddedToCalendar) {
      // (replace the todo inside the state array with a new updated one)
    }
    } else {
      return state;
    }
  }, initState);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM