간단한 반응이 상하문에서 통제력을 잃을 때

40603 단어 reactreduxjavascript

TL;박사:

  • 때로는 K.I.S.S.의 해결 방안이 프랑켄슈타인으로 바뀔 수도 있다.
  • 리액션 상하문에서 사용하고 싶은 것을 발견하면 useEffect 심사숙고하세요.
  • 더 중요한 것은 전 세계 상태에 의존하는 useEffects를 조심해야 한다는 것이다.
  • 켄트 C 도드는 건립React Context API에 대해 뚜렷한 생각을 가지고 있다.
  • 지금부터 제'적용'상하문에서 기본값 useReducer 으로 하겠습니다.
  • 간단하게 시작합시다.


    우리 팀은 새로운 React 프로그램을 시작했습니다. React 상하문 API 단순 useState 을 사용하면 어떻게 될지 보고 싶습니다.우리는 또한 모든 상하문을 유사한 데이터의 '상자' 로 간주하기를 희망한다.
    응용 프로그램이 두 개의 컨텍스트를 필요로 할 정도로 발전했다고 가정해 봅시다.
  • 1은 "Auth"
  • 를 나타냅니다.
  • 1은 타임라인을 나타냅니다. [더 좋은 이름이 없기 때문입니다.]
  •   const AuthContext = React.createContext();
    
      const AuthContextProvider = ({ children }) => {
        const [user, setUser] = useState();
        const [isLoggedIn, setIsLoggedIn] = useState();
    
        const state = { user, isLoggedIn };
    
        return (
          <AuthContext.Provider value={{ state, setUser, setIsLoggedIn }}>
            {children}
          </AuthContext.Provider>
        );
      };
    
    AuthContext는 신분 검증과 관련된 상태를 포함한다.사용자가 로그인할 때 setIs LoggedIn (true) 과 setUser ({email,username}) 함수를 호출합니다.이것은 AuthContext의 상태를 바꾸고 응용 프로그램에서 천천히 전파할 수 있다.
    const TimelineContext = React.createContext();
    
    const TimelineContextProvider = ({ children }) => {
      const [posts, setPosts] = useState([]);
      // For the purposes of this blog, selectedPost will be used to display
      // the "show page"
      const [selectedPost, setSelectedPost] = useState(null);
      // And let's imagine we want to do the same thing for a comment.
      const [selectedComment, setSelectedComment] = useState(null);
    
      const state = { posts, selectedPost, selectedComment };
    
      return (
        <TimelineContext.Provider
          value={{ state, setPosts, setSelectedPost, setSelectedComment }}
        >
          {children}
        </TimelineContext.Provider>
      );
    };
    
    TimelineContextposts, aselectedPost와 aselectedComment의 목록을 포함하여 우리의 시간선 유지 보수 상태를 제공할 것입니다.
    이것들은 모두 매우 간단하다, 그렇지?
    그 중 하나는 모든 위아래 문장의 반환값이다.현재 우리는 우리가 새로운 상태를 추가하면서 반환치의 증가가 매우 빠르다는 것을 알 수 있다.
    우리 계속 TimelineContext에서 이 문제를 해결합시다.
      const TimelineContextProvider = ({ children }) => {
        const [posts, setPosts] = useState([]);
        const [selectedPost, setSelectedPost] = useState(null)
        const [selectedComment, setSelectedComment] = useState(null)
    
        const state = { posts, selectedPost, selectedComment };
        const actions = { setPosts, setSelectedPost, setSelectedComment }
    
        return (
          <TimelineContext.Provider value={{ state, actions}}>
            {children}
          </TimelineContext.Provider>
        );
      };
    
    그래.이게 좀 도움이 돼요.귀환 대상은 stateactions로 제한됩니다.

    또 다른 고민거리는 이런 상황이 갈수록 심각해지면 된다는 것이다.우리가 추가한 데이터가 많을수록useStates 관리하기가 더욱 어렵다.이것은 여러 가지 배경이 있는 생각이다.우리는 관심사를 명확하게 분리할 수 있다.

    새로운 요구!


    현재 프로그램에서 선택한 댓글과 댓글을 설정하고 싶습니다.댓글이 댓글에 달려 있다면 새 댓글을 선택할 때 취소해야 합니다selectedComment.
    이것은 상당히 간단하다.우리는 직접 useEffect와 큰 소리를 낼 수 있다.
      const TimelineContextProvider = ({ children }) => {
        const [posts, setPosts] = useState([]);
        const [selectedPost, setSelectedPost] = useState(null)
        const [selectedComment, setSelectedComment] = useState(null)
    
        const state = { posts, selectedPost, selectedComment };
        const actions = { setPosts, setSelectedPost, setSelectedComment }
    
        useEffect(() => {
          setSelectedComment(null)
        }, [selectedPost])
    
        return (
          <TimelineContext.Provider value={{ state, actions}}>
            {children}
          </TimelineContext.Provider>
        );
      };
    

    더 많은 수정!!!


    현재, 테스트 목적으로 초기 {SelectedPost와 SelectedComment}를 추가하려고 합니다.어리석고 간단하다.아니면 이렇게?
    현재 설정에 따라useEffect는 처음 렌더링할 때initialSelectedCommentnull로 설정합니다.오, 부작용 없어!!!
    그래서 우리의 배경은 다음과 같다.
    const TimelineContextProvider = ({
      initialSelectedPost,
      initialSelectedComment,
      children
    }) => {
      const [posts, setPosts] = useState([]);
      const [selectedPost, setSelectedPost] = useState(initialSelectedPost);
      const [selectedComment, setSelectedComment] = useState(
        initialSelectedComment
      );
    
      const state = { posts, selectedPost, selectedComment };
      const actions = { setPosts, setSelectedPost, setSelectedComment };
    
      useEffect(() => {
        if (initialSelectedPost != initialSelectedComment) {
          setSelectedComment(null);
        }
      }, [selectedPost]);
    
      return (
        <TimelineContext.Provider value={{ state, actions }}>
          {children}
        </TimelineContext.Provider>
      );
    };
    
    이것은 큰 문제가 아닐 수도 있지만, 그것은 우리로 하여금 단지 상태를 바꾸는 것만으로 발생할 수 있는 어떤 결과도 고려하지 않을 수 없게 할 것이다.

    전 세계 진상의 단일한 출처


    팀의 불평 중 하나는 "구성 요소에서 어떤 {X} 상하문을 사용해야 합니까?"이다.AuthContextTimelineContext는 모두 전역 상태의 일부이기 때문에 하나의 해결 방안은 그것들을 조합하여state 대상 내의 영역을 분리하는 것이다.우리 이 문제를 해결하는 것부터 시작합시다.
    const AppContextProvider = ({
      initialSelectedPost,
      initialSelectedComment,
      children
    }) => {
      const [user, setUser] = useState();
      const [isLoggedIn, setIsLoggedIn] = useState();
      const [posts, setPosts] = useState([]);
      const [selectedPost, setSelectedPost] = useState(initialSelectedPost);
      const [selectedComment, setSelectedComment] = useState(
        initialSelectedComment
      );
    
      const state = {
        auth: { user, isLoggedIn },
        timeline: { posts, selectedPost, selectedComment }
      };
    
      const actions = {
        setUser,
        setIsLoggedIn,
        setPosts,
        setSelectedPost,
        setSelectedComment
      };
    
      useEffect(() => {
        if (initialSelectedPost != initialSelectedComment) {
          setSelectedComment(null);
        }
      }, [selectedPost]);
    
      return (
        <AppContext.Provider value={{ state, actions }}>
          {children}
        </AppContext.Provider>
      );
    };
    
    내가 보기엔 큰 승리는 아니었지만, 지금은 팀이 더 즐거워졌다.

    광희의 부작용


    React hooks와 1년 동안 합작한 후에 나는 어떤 환경에서useEffect가 나쁜 생각일 수도 있다는 결론을 얻었다.(겸사겸사 한마디 하자면, 나는 네가 이 일을 한 예를 보고 싶다.
    내가 얻은 더욱 구체적인 규칙은 우리의 응용 프로그램에서 전역 상태에 의존해서는 안 된다는 것이다. useEffect나는 이것이 날카로운 칼로 너의 눈을 쉽게 찔러 뚫을 수 있다고 생각한다.그것은 하루하루 앞장서서 일하지 않는 사람들을 위해 프로젝트에서 일하는 데 장애를 증가시켰다.코드 라이브러리에서 일하는 사람들에게도 명심해야 할 일이다."{X} 을 변경하면 이 리셋이 실행될 것입니다. 수정해야 합니까?"
    나의 해결 방안은 전역 상태에서 시종일관 (보통 95%) 사용하고 useReducer 전역 상태의 일부분에 의존하지 않는 것이다.
    가자!

    초기 상태


    우선, 우리는 응용 프로그램의 초기 상태부터 시작할 것이다.
    const initialState = {
      auth: { user: null, isLoggedIn: false },
      timeline: { posts: [], selectedPost: null, selectedComment: null }
    };
    
    그것은 매우 쉽다!우리의 초기 상태를 정의하면 우리의 모든 전 세계 상태를 한눈에 볼 수 있다.언제든지 전체 상태에 뭔가를 추가하고 싶을 때 useEffect 대상에 합리적인 기본값을 추가할 수 있습니다.예를 들어 initialState는 처음에는 가짜였고 isLoggedIn는 처음에는 공수조였다.

    사랑하는 화생


    Reducer 모드에서 내가 가장 좋아하는 부분은 Reducer의 모든 동작을 응용 프로그램과의 단일한 상호작용으로 볼 수 있다는 것이다.이러한 상호작용은 네트워크 요청이나 사용자 이벤트일 수 있습니다.동작을 설정할 때 "{X}가 발생할 때 상태가 어떻게 변합니까?"라고 묻습니다.그리고 정확한 유효 하중과 팔동작만 사용하면 이 조작을 수행할 수 있다.완성!현재 같은 상호작용이 두 곳에서 발생한다면 다른 구성 요소를 열고 논리를 기억할 필요가 없다.너는 행동만 발표하면 된다.
    상하문posts 부분에 대해 우리는 두 가지 상호작용이 있는데 그것이 바로 로그인과 로그아웃이다.
    코드 좀 봅시다.
    const ActionTypes = {
      SET_USER: "set-user",
      LOGOUT_USER: "logout-user",
    }
    const reducer = (state, action) => {
      switch (action.type) {
        case ActionTypes.SET_USER: {
          return {
            ...state,
            auth: { ...state.auth, user: action.payload, isLoggedIn: true }
          };
        }
        case ActionTypes.LOGOUT_USER: {
          return {
            ...state,
            auth: { ...state.auth, user: null, isLoggedIn: false }
          };
        }
        ...
      }
    };
    
    와, 이거 K.I.s.s.:D야.
    현재 우리는 호출authsetUser를 기억할 필요가 없다. 우리는 주어진 상호작용에 상응하는 조작을 할당하기만 하면 된다.
    다음은 setIsLoggedIn 상태에 대한 조작을 추가합니다.
    const ActionTypes = {
      ...,
      ADD_POSTS: "add-posts",
      SELECT_POST: "select-post",
      SELECT_COMMENT: "select-comment"
    };
    
    const reducer = (state, action) => {
      switch (action.type) {
        ...,
        case ActionTypes.ADD_POSTS: {
          return {
            ...state,
            timeline: {
              ...state.timeline,
              posts: [...state.timeline.posts, ...action.payload]
            }
          };
        }
        case ActionTypes.SELECT_POST: {
          return {
            ...state,
            timeline: {
              ...state.timeline,
              selectedPost: action.payload,
              selectedComment: null
            }
          };
        }
        case ActionTypes.SELECT_COMMENT: {
          return {
            ...state,
            timeline: {
              ...state.timeline,
              selectedComment: action.payload
            }
          };
        }
        ...,
      }
    };
    
    너는 아직 의식하지 못했겠지만timeline 조작은useEffect의 부작용 문제를 해결했다!기억하신다면, 원시 상하문 중 하나 SELECT_POST 가 있습니다. useEffect 가 변할 때 selectedComment 는 무효입니다.현재 우리는 selectedPostinitialSelectedPost를 설치할 수 있으며 initialSelectedComment의 발사는 걱정하지 않아도 된다.테스트 목적으로만 사용되는useEffect 상태 요구 사항 제거

    새로운 환경


    마지막 난제는 React 상하문을 통해 응용 프로그램에 새로운 Reducer를 제공하는 것입니다.
    const AppProvider = ({ initialState, reducer, children }) => {
      const [state, dispatch] = useReducer(reducer, initialState);
    
      return (
        <AppContext.Provider value={{ state, dispatch }}>
          {children}
        </AppContext.Provider>
      );
    };
    
    그래, 그럼 훨씬 깨끗해졌어.우리 팀은 Rails의 전체에서 일한다. 이것이 바로 내가 ifinitialStatereducer의 도구로 만들기로 결정한 이유이다.이 방법은 우리가 만들기로 결정한 모든 React 프로그램에 같은 공급자를 사용할 수 있도록 합니다.

    결론


    현재 이것은 내가 가장 좋아하는 React 응용 프로그램에서 전체 상태를 관리하는 방법이다. (이따가 블로그에서 추가 마법을 소개할 것이다.)
  • 의존항이 추가되지 않았습니다.
  • 전 세계 상태에 대한 기억이 필요 없는 부작용.
  • 모든 상호작용은 하나의 봉인 동작에 비친다.
  • 이 모든 것을 한데 놓아라.
    const initialState = {
      auth: { user: null, isLoggedIn: false },
      timeline: { posts: [], selectedPost: null, selectedComment: null }
    };
    
    const ActionTypes = {
      SET_USER: "set-user",
      LOGOUT_USER: "logout-user",
      ADD_POSTS: "add-posts",
      SELECT_POST: "select-post",
      SELECT_COMMENT: "select-comment"
    };
    
    const reducer = (state, action) => {
      switch (action.type) {
        case ActionTypes.SET_USER: {
          return {
            ...state,
            auth: { ...state.auth, user: action.payload, isLoggedIn: true }
          };
        }
        case ActionTypes.LOGOUT_USER: {
          return {
            ...state,
            auth: { ...state.auth, user: null, isLoggedIn: false }
          };
        }
        case ActionTypes.ADD_POSTS: {
          return {
            ...state,
            timeline: {
              ...state.timeline,
              posts: [...state.timeline.posts, ...action.payload]
            }
          };
        }
        case ActionTypes.SELECT_POST: {
          return {
            ...state,
            timeline: {
              ...state.timeline,
              selectedPost: action.payload,
              selectedComment: null
            }
          };
        }
        case ActionTypes.SELECT_COMMENT: {
          return {
            ...state,
            timeline: {
              ...state.timeline,
              selectedComment: action.payload
            }
          };
        }
        default:
          return state;
      }
    };
    
    const AppProvider = ({ initialState, reducer, children }) => {
      const [state, dispatch] = useReducer(reducer, initialState);
    
      return (
        <AppContext.Provider value={{ state, dispatch }}>
          {children}
        </AppContext.Provider>
      );
    };
    
    너는 트위터에서 나의 랜덤 기술을 찾아서 잡담을 할 수 있다

    도구책


    Shout out to Kent Dodds. He has some killer React patterns on his blog. Check it out.
    The docs on AppProvider from React

    좋은 웹페이지 즐겨찾기