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

Javascript 管理angular2应用程序中的状态-副作用?,javascript,angular,rxjs,Javascript,Angular,Rxjs,这是一个更一般的问题,但基于维克多·萨夫金邮报 让我们考虑使用RXJS:< /P>描述的方法。 interface Todo { id: number; text: string; completed: boolean; } interface AppState { todos: Todo[]; visibilityFilter: string; } function todos(initState: Todo[], actions: Observable<Action>): Ob

这是一个更一般的问题,但基于维克多·萨夫金邮报

让我们考虑使用RXJS:< /P>描述的方法。

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);
}
现在我有两个副作用:

  • 添加todo后,我需要解析文本
  • 在将日期添加到Todo之后,我需要将其添加到日历中
  • 在这种方法中,我如何处理这些副作用?Redux说减速机应该保持清洁,他们也有传奇的概念

    选项1-在添加todo时触发新事件以获得副作用

    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);
    }
    

    但这感觉有点不对劲-不应该所有事情都由操作来管理,我是否应该始终循环所有todo项?

    您的选项1不能按说明工作:
    操作
    是一个可观察的可观察的是只读的,
    onNext
    不是该API的一部分。您需要一个
    观察员来支持选项1。这突出了选项1的真正缺陷:您的状态函数(与Redux reducer相同)需要纯粹且无副作用。这意味着他们不能也不应该采取更多行动

    现在在您引用的博客文章中,代码确实传递了一个主题,它既是观察者又是可观察的。所以你可能有一个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
    ,以在您的状态下获取未解析的todo(如果需要,您的UI可以选择在解析完成之前不显示它)。然后,它将发送另一个智能操作(thunk),该操作将实际调用服务器以获取更多数据,然后将结果发送一个
    EnrichToDoWithParsedData
    操作,以便更新Todo

    至于日历的更新……您可能可以使用上面的模式(在
    addToDo
    parseToDo
    中插入对
    possiblyUpdateCalendar()
    的调用),这样,如果todo包含了您需要的所有内容,它就可以更新日历,并在完成后调度一个操作,将todo标记为已添加

    现在,我展示的这个示例是特定于Redux的,我不认为您正在使用的基于RxJs的示例有任何类似于thunk的东西。在您的方案中添加此支持的一种方法是在主题中添加一个
    flatMap
    操作符,如下所示:

    interface Todo { 
      id: number; 
      text: string; 
      completed: boolean; 
      location?: Coordinates; 
      date?: Date; 
      inCalendar?: boolean; 
      parsed?: boolean;
    }
    
    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);
    }
    
    函数Todo(initState:Todo[],操作:可观察):可观察{
    返回操作。扫描((状态,操作)=>{
    if(AddTodoAction的操作实例){
    const newTodo={id:action.todoId,text:action.text,completed:false};
    返回[…状态,newTodo];
    }else if(EnrichTodoWithParsedData的操作实例){
    //(将状态数组中的todo替换为新的更新todo)
    }else if(AddedToCalendar的操作实例){
    //(将状态数组中的todo替换为新的更新todo)
    }
    }否则{
    返回状态;
    }
    },国家);
    }
    
    谢谢你的详细回答。一切看起来都很好。我不太喜欢的一件事是,我必须在addToDo和parseToDo中插入对
    possiblyUpdateCalendar
    的调用(好的,对于这个简单的例子,它是有效的,但假设我有很多操作可以在Todo上添加日期,因此在每个操作中,我都必须添加
    可能的更新日历
    ).另一个问题-如果Todo是相互依赖的,该怎么办?如果我在Todo上添加日期,我必须在后端重新验证相邻Todo上的日期?关于相互依赖性的问题:在我的示例中未显示的是,redux thunks获得第二个参数(
    getState
    ),该参数允许这些所谓的“智能操作”以实际访问整个状态树。因此,您可以检查状态并应用所需的任何逻辑,以准确确定要分派的进一步操作或要执行的服务器调用。这样就可以轻松地遍历TODO并收集所有需要更新的TODO并将其发送到服务器进行处理,然后分派操作w当服务器返回到更新状态时。至于对
    可能的更新日历的多次调用
    :目前我没有一个好的解决方案。我的项目中没有出现足够的问题来找到或设计一个干净的解决方案。
    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);
    }