의견이 있는 반응: 상태 관리

소개



저는 4년 넘게 React와 함께 일해 왔습니다. 이 시간 동안 나는 응용 프로그램이 어떻게 되어야 한다고 생각하는지에 대한 몇 가지 의견을 형성했습니다. 이것은 그러한 독단적인 작품 시리즈의 3부입니다.

내가 다룰 내용



상태 관리에는 많은 부분이 있습니다. 한자리에 다 담을 수 없습니다. 이 게시물에서는 일반 React를 사용하여 구성 요소의 상태를 관리하는 방법을 보여 드리겠습니다.

다음에 대해 쓸 국가 관리와 관련된 향후 게시물을 보려면 저를 팔로우하세요.
  • 구성 요소 수준 상태 대 전역 상태
  • 좋은 사용 사례와 React 컨텍스트에 대한 내 패턴
  • 부울 대신 상태 열거형

  • 그냥 React를 사용하세요



    나는 팀이 React의 내장 상태 관리 솔루션을 사용하기 전에 Redux, MobX 또는 다른 것과 같은 상태 관리 라이브러리를 채택하는 것을 너무 자주 보았습니다.

    이 라이브러리에는 아무런 문제가 없지만 완전히 작동하는 React 애플리케이션을 빌드하는 데 필요한 것은 아닙니다. 내 경험상 일반 React를 사용하는 것이 훨씬 쉽습니다.
    useState 또는 useReducer 를 사용하는 대신 이러한 라이브러리 중 하나를 사용해야 하는 이유가 있다면 귀하의 사용 사례를 알고 싶기 때문에 의견을 남겨주세요.

    다음에 구성 요소를 빌드할 때 일반 React를 사용해 보세요.

    후크



    위에서 useStateuseReducer 두 개의 후크를 언급했습니다. 다음은 각각을 사용하는 방법입니다.

    useState로 시작



    먼저 useState 후크로 구성 요소를 빌드합니다. 빠르고 작업을 완료합니다.

    const MovieList: React.FC = () => {
      const [movies, setMovies] = React.useState<Movie[]>([])
    
      React.useEffect(() => {
        MovieService
          .fetchInitialMovies()
          .then(initialMovies => setMovies(initialMovies))
      }, [])
    
      return (
        <ul>
          {movies.map(movie => <li key={movie.id}>{movie.title}</li>}
        </ul>
      )
    }
    


    다른 상태 조각이 필요한 경우 다른 useState 후크를 추가하기만 하면 됩니다.

    const MovieList: React.FC = () => {
      const [isLoading, setIsLoading] = React.useState<boolean>(true)
      const [movies, setMovies] = React.useState<Movie[]>([])
    
      React.useEffect(() => {
        MovieService
          .fetchInitialMovies()
          .then(initialMovies => setMovies(initialMovies))
          .then(() => setIsLoading(false))
      }, [])
    
      if (isLoading) {
        return <div>Loading movies...</div>
      }
    
      return (
        <ul>
          {movies.map(movie => <li key={movie.id}>{movie.title}</li>}
        </ul>
      )
    }
    
    


    상태가 많을 때 useReducer



    관련 상태 조각에 대한 내 제한은 2입니다. 서로 관련된 상태 조각이 3개 있으면 useReducer 를 선택합니다.

    위의 예에 따라 영화 가져오기에 실패한 경우 오류 메시지를 표시하고 싶다고 가정해 보겠습니다.

    다른 useState 호출을 추가할 수도 있지만 좀 지저분해 보이는 것 같아요 😢.

    export const MovieList: React.FC = () => {
      const [isLoading, setIsLoading] = React.useState<boolean>(true);
      const [movies, setMovies] = React.useState<Movie[]>([]);
      const [error, setError] = React.useState<string>("");
    
      const handleFetchMovies = () => {
        setIsLoading(true); // 😢
        setError(""); // 😢
        return MovieService.fetchInitialMovies()
          .then(initialMovies => {
            setMovies(initialMovies);
            setIsLoading(false); // 😢
          })
          .catch(err => {
            setError(err.message); // 😢
            setIsLoading(false); // 😢
          });
      };
    
      React.useEffect(() => {
        handleFetchMovies();
      }, []);
    
      if (isLoading) {
        return <div>Loading movies...</div>;
      }
    
      if (error !== "") {
        return (
          <div>
            <p className="text-red">{error}</p>
            <button onClick={handleFetchMovies}>Try again</button>
          </div>
        );
      }
    
      return (
        <ul>
          {movies.map(movie => (
            <li key={movie.id}>{movie.title}</li>
          ))}
        </ul>
      );
    };
    
    

    useReducer 를 사용하도록 이것을 리팩토링하여 논리를 단순화합니다.

    interface MovieListState {
      isLoading: boolean;
      movies: Movie[];
      error: string;
    }
    
    type MoveListAction =
      | { type: "fetching" }
      | { type: "success"; payload: Movie[] }
      | { type: "error"; error: Error };
    
    const initialMovieListState: MovieListState = {
      isLoading: true,
      movies: [],
      error: ""
    };
    
    const movieReducer = (state: MovieListState, action: MoveListAction) => {
      switch (action.type) {
        case "fetching": {
          return { ...state, isLoading: true, error: "" };
        }
        case "success": {
          return { ...state, isLoading: false, movies: action.payload };
        }
        case "error": {
          return { ...state, isLoading: false, error: action.error.message };
        }
        default: {
          return state;
        }
      }
    };
    
    export const MovieList: React.FC = () => {
      const [{ isLoading, error, movies }, dispatch] = React.useReducer(
        movieReducer,
        initialMovieListState
      );
    
      const handleFetchMovies = () => {
        dispatch({ type: "fetching" });
        return MovieService.fetchInitialMovies()
          .then(initialMovies => {
            dispatch({ type: "success", payload: initialMovies });
          })
          .catch(error => {
            dispatch({ type: "error", error });
          });
      };
    
      React.useEffect(() => {
        handleFetchMovies();
      }, []);
    
      if (isLoading) {
        return <div>Loading movies...</div>;
      }
    
      if (error !== "") {
        return (
          <div>
            <p className="text-red">{error}</p>
            <button onClick={handleFetchMovies}>Try again</button>
          </div>
        );
      }
    
      return (
        <ul>
          {movies.map(movie => (
            <li key={movie.id}>{movie.title}</li>
          ))}
        </ul>
      );
    };
    


    Q&A



    모든 게시물은 트위터에서 받은 질문에 답할 것입니다. 이번주 질문입니다.

    내 주요 문제는 내가 언제 Redux를 사용해야 하는지 이해하지 못한다는 것입니다. 나는 Redux를 싫어하지 않습니다. 그것이 어떻게 작동하는지 (간단한 방법으로) 알고 있지만 Redux, Effector 또는 MobX와 같은 상태 관리자와 비교하여 후크가 충분하지 않은 이유를 이해하지 못합니다. 이에 대한 의견을 듣고 싶습니다. v1rtl (@v1rtl )


    나는 더 이상 redux를 사용하지 않습니다. React의 컨텍스트 API가 출시된 이후로 사용하지 않았습니다. IMO, 후크 + 컨텍스트가 애플리케이션을 빌드하기에 충분하다고 생각합니다.

    마무리



    이것은 내가 쓸 시리즈의 3번째 기사입니다. 재밌게 보셨다면 아래 댓글 부탁드립니다. 내가 뭘 더 덮고 싶니? 항상 그렇듯이 저는 피드백과 권장 사항에 열려 있습니다.

    읽어 주셔서 감사합니다.

    추신 아직 확인하지 않았다면 이 시리즈의 이전 게시물을 확인하십시오.


  • 좋은 웹페이지 즐겨찾기