언어 유형: Redux는 유한 상태기입니다

이 글은 Redux와 유형에 대한 지식을 가설하였지만, 마음대로 질문해 주십시오.
이것은 시리즈 문장의 세 번째 편이다.이 직위의 코드는 here




  • 뭐 공부 해요?


    사용자가 양식을 제출하면 AJAX 요청이 실행될 때 로드 상태를 표시하고, AJAX 요청이 완료되면 AJAX 요청이 성공하거나 실패할 때 결과를 표시하는 양식을 구축하고자 합니다.
    이 작업을 비교하기 위해'고전'감속기와'유한 상태기'감속기를 만듭니다.전체 코드는 this repository 에 있습니다.

    클래식 감속기


    이것이 바로'고전'감속기의 외관이다.
    export default (reduxState: State = defaultState, action: Actions): State => {
      switch (action.type) {
        case "SUBMIT_FRUIT":
          return {
            ...reduxState,
            state: "fruit_loading",
            form: action.form
          };
        case "SUBMIT_FRUIT_ERROR":
          return {
            ...reduxState,
            state: "fruit_error",
            error: action.error
          };
        case "SUBMIT_FRUIT_OK":
          return {
            ...reduxState,
            state: "fruit_ok",
            resonse: action.resonse
          };
        default:
          exhaustiveCheck(action.type);
          return reduxState;
      }
    };
    
    SUBMIT_FRUIT는 양식 제출에 응답하여 보내는 동작입니다.SUBMIT_FRUIT_ERRORSUBMIT_FRUIT_OK은 AJAX 요청과 같은 부작용에 대응하기 위해 스케줄링됩니다.부작용에 대해 우리는 서로 다른 해결 방안을 사용할 수 있다. 예를 들어reduxthunk,reduxsaga,reduxobservable,reduxloop 등이다.우리는 이 문제에 관심을 갖지 마라. 반대로 우리는 스케줄링을 통해 부작용을 명확하게 촉발할 것이다.
    다음은 AJAX 요청의 모양입니다.
    export const fruitSubmitSideEffect = (dispatch: Dispatch, form: FruitForm) => {
      // uses fetch inside returns a Promise
      fruitRequest(form).then(
        resonse => {
          dispatch({
            type: "SUBMIT_FRUIT_OK",
            resonse
          });
        },
        error => {
          dispatch({
            type: "SUBMIT_FRUIT_ERROR",
            error
          });
        }
      );
    };
    
    // and later
    
    export default connect(
      () => ({}),
      (dispatch: Dispatch) => ({
        submit: (form: FruitForm) => {
          dispatch({ type: "SUBMIT_FRUIT", form });
          fruitSubmitSideEffect(dispatch, form);
        }
      })
    )(Component);
    
    새 상태의 이전 활성 상태를 생성할 수 있지만 명확하게 확인되지 않았습니다.
    return {
      ...reduxState,
      ...newPartsOfState
    };
    
    유형 State 은 다음과 같습니다.
    export type State = {
      state: "initial" | "fruit_loading" | "fruit_error" | "fruit_ok";
      form?: FruitForm;
      error?: mixed;
      resonse?: FruitResponse;
    };
    
    결과 중 하나는 추가 유형 검사를 작성해야 한다는 것입니다.
    export default ({ state }: { state: State }) => {
      switch (state.state) {
        case "fruit_ok":
          return (
            state.resonse && // additional type check, that it is not undefined
            state.resonse.map(item => {}))
      }
    

    유한 상태기


    유한 상태기(FSM)는 유한 상태를 가정합니다.유형 시스템으로 강제합시다.이것은 흐름 유형이지만 TypeScript는 유사해 보입니다(TS의 경우{||}에는 필요하지 않음).
    export type State =
      | {|
          state: "initial"
        |}
      | {|
          state: "fruit_loading",
          form: FruitForm
        |}
      | {|
          state: "fruit_error",
          form: FruitForm,
          error: mixed
        |}
      | {|
          state: "fruit_ok",
          form: FruitForm,
          resonse: FruitResponse
        |};
    
    지금 우리는 검사 없이 이전 상태를 사용할 수 없다.하면, 만약, 만약...
    return {
      ...reduxState,
      state: "fruit_loading",
      form: action.form
    };
    
    Flow는 다음과 같이 불평합니다.
    Could not decide which case to select. Since case 2 [1] may work but if it doesn't case 3 [2] looks promising too. To fix add a type annotation to .form [3] or to .state [3].
    
         src/redux-fsm/state.js
     [1] 12│   | {|
         13│       state: "fruit_loading",
         14│       form: FruitForm
         15│     |}
     [2] 16│   | {|
         17│       state: "fruit_error",
         18│       form: FruitForm,
         19│       error: mixed
         20│     |}
    
    그래서 지금 우리는 이렇게 해야 한다.
    switch (action.type) {
      case "SUBMIT_FRUIT":
        switch (reduxState.state) {
          case "initial":
            return {
              state: "fruit_loading",
              form: action.form
            };
          default:
            throw new Error("Inavlid transition");
        }
    }
    
    우리는 발생할 동작, 이전의 상태가 무엇인지 검사한 후에 무엇을 할지 결정한다.이런 방법은 우리로 하여금 시스템 중의 모든 전환을 명확하게 고려하게 한다.
    initial
      SUBMIT_FRUIT       -> fruit_loading (1)
      SUBMIT_FRUIT_ERROR -> ?             (2)
      SUBMIT_FRUIT_OK    -> ?             (2)
    fruit_loading
      SUBMIT_FRUIT       -> fruit_loading (3)
      SUBMIT_FRUIT_ERROR -> fruit_error   (4)
      SUBMIT_FRUIT_OK    -> fruit_ok      (5)
    fruit_error
      SUBMIT_FRUIT       -> fruit_loading (6)
      SUBMIT_FRUIT_ERROR -> ?             (7)
      SUBMIT_FRUIT_OK    -> ?             (7)
    fruit_ok
      SUBMIT_FRUIT       -> fruit_loading (6)
      SUBMIT_FRUIT_ERROR -> ?             (7)
      SUBMIT_FRUIT_OK    -> ?             (7)
    

    Side note: Why would you want to do this? To formally specify UIs, to prove that there are no errors in UI logic. For example:

    Side note 2: I implemented "reversed" FSM in the reducer, it checks action first and the state second


    (1,5) "즐거움"경로 - 사용자가 폼을 제출하고 응답을 받습니다.
    (1,4) 오류 경로 - 사용자가 양식을 제출하고 오류를 받았습니다.
    (6) 반복 검색 - 오류 또는 응답이 발생했고 사용자가 반복 검색합니다.
    (2) 일어나지 않는다. - 우리는 그것이 일어나지 않고 이런 상황에서 이상을 일으킨다고 가정할 수 있다.
    (7) 경쟁 조건 - 우리는 이미 하나의 응답(또는 오류)과 새로운 응답을 가지고 있으며, 우리가 한 번에 여러 부작용을 허용할 때만 발생할 수 있다.
    (3) 중복 검색 - 사용자가 다른 것을 요구하거나 귀찮게 클릭할 수 있는 검색 대기가 있습니다.이것은 재미있는 사례다.우리 어떡하지?우리는 다음과 같이 할 수 있습니다.
  • 무시 (비활성화 버튼을 통한 시각 교류도 가능)
  • 이전 요청을 취소하고 새 요청을 시작
  • 새로운 것을 시작하여 이전의 것을 잊어버리다.이것은 기본적으로 우리가 고전적인 방법에서 한 것이지만, 이것도 일종의 상황을 초래할 것이다(7). 이것은 일종의 경쟁 조건이다.그 밖에 이 방법은 (1,5)과 (1,4) 장면에 경쟁 조건을 도입했다.
  • 이 글에 대해 나는 그것을 무시하고 가장 간단한 해결 방안으로 다음 글에서 cancel을 실현할지도 모른다.
    이것이 바로 유한상태기를 사용하는 이유이다. 이런 방법은 논리 중의 빈틈을 발견하는 데 도움이 된다.시스템의 상태가 많을수록 숨겨진 잠재적인 구멍이 많아진다.
    이러한 유형의 버그를 찾는 것이 너무 번거롭다고 생각되면 일반적인 IT 지원 문제를 생각해 보십시오. "버그를 닫고 다시 열어 보셨습니까?"네, 어떤 곳에서는 상태와 관련된 오류가 숨겨져 있습니다. 해결 방법은 시스템을 다시 시작하여 상태를 초기 상태로 초기화하는 것입니다.

    다른 한편, 나는 JS(또는 Flow 또는 TS) 문법이 이런 임무에 있어서 좀 서투르다는 것에 동의한다.스위치의 패턴과 일치하여 표현력이 없습니다.Redux는 기존보다 더 많은 템플릿을 필요로 합니다.너의 생각을 나에게 말해라.만약 그것이 더 적은 템플릿을 필요로 한다면, 당신은 그것을 사용할 수 있습니까?

    Photo by Dan Lohmar on Unsplash


    . 따라오세요.

    좋은 웹페이지 즐겨찾기