-
[React, Redux] 내가 리덕스를 쓰지 않는 이유programing/Web 2020. 7. 18. 15:34
안녕하세요, Einere입니다.
(광고차단 기능을 꺼주시면 감사하겠습니다.)
해당 포스트는 Why I Stopped Using Redux를 번역한 글입니다.
리덕스는 리액트 환경에서 혁신적인 기술입니다. 리덕스는 불변 데이터와 전역 저장소를 통해 컴포넌트 트리에서 prop-drilling(특정 데이터 prop을 통해 깊이 보내는 것)을 방지할 수 있습니다. 앱 내에서 불변 값을 공유하는 상황에서, 리덕스는 최고의 툴입니다.
하지만 왜 전역 저장소를 둬야 할까요? FE는 정말로 그렇게 복잡한가요? 우리는 왜 리덕스로 너무 많은 일을 하려고 하는 것인가요?
SPA의 문제점
리액트와 같은 SPASingle Page Application의 장점은 웹앱 개발의 많은 변화를 가져다주었습니다. FE 코드에서 BE코드를 분리하는 것은 관심사를 분리하고 전문화하게 해주었습니다. 또한 상태와 같은 복잡성도 생겨나게 되었습니다.
비동기적으로 데이터를 가져오는 것은 데이터가 FE와 BE, 두 영역에 존재한다는 것을 의미합니다. 우리는 네트워크 레이턴시를 줄이기 위해 데이터를 캐싱하면서 모든 컴포넌트가 사용할 수 있는 전역 데이터를 저장하는 최선의 방법을 고려해야 합니다. 이제 FE 개발의 큰 부분은 상태 관련 버그, 데이터 비정규화, 오래된 데이터에 방해받지 않고 전역 저장소를 관리하는 방법으로 인해 부담을 겪고 있습니다.
리덕스는 캐시가 아니다
리덕스나 유사한 라이브러리를 사용할 때 제일 큰 문제는, 우리가 그것을 BE 상태의 캐시로 취급한다는 것입니다. 우리는 BE로부터 데이터를 가져오고, 리듀서나 액션으로 그 데이터를 저장하고, 해당 상태가 최신임을 보장하기 위해 주기적으로 리패치(refatch)합니다. 우리는 리덕스가 너무 많은 일을 하도록 만들며, 만능 솔루션으로 취급합니다.
명심해야 할 것은 FE 상태와 BE 상태는 실제로 동기화되지 않는다는 것입니다. 이것은 클라이언트-서버 모델의 단점이며, FE에 캐시를 둬야 하는 이유입니다. 그러나 상태를 캐싱하고 동기화하는 것은 엄청나게 복잡하므로, 리덕스가 권장하는 것 처럼 BE 상태를 처음부터 재작성하는것은 하지 말아야 합니다.
FE에서 DB를 재작성하기 시작하면 FE와 BE의 경계가 빠르게 모호해집니다. FE 개발자는 간단한 UI를 만들기 위해 테이블과 관계에 대한 깊은 지식이 필요하지는 않으며, 데이터를 정규화하는 최고의 방법 또한 알 필요가 없습니다. 그 책임은 테이블을 설계하는 사람, 즉 BE 개발자에게 있어야 합니다. 그리고 BE 개발자는 추상화된 API를 FE 개발자에게 제공해야 합니다.
BE의 데이터를 관리할 수 있도록 리덕스 중심으로 설계된 수 많은 라이브러리(redux-observable, redux-saga, redux-thunk...)가 있습니다. 각 라이브러리는 이미 무거운 리덕스에 복잡한 레이어를 추가합니다. 저는 이 라이브러리들이 중요한 것을 놓치고 있다고 생각합니다. 가끔은 나아가기 전에 뒤로 한 걸음 물러서야 합니다.
만약 우리가 BE 상태를 FE에서 관리하려는 것을 멈추고, 대신 그것을 주기적으로 업데이트해야 하는 캐시로 취급하면 어떻게 될까요? FE를 단순히 디스플레이 레이어로 취급하므로써 작업하기 편해지며 순수한 FE 개발자의 진입장벽이 낮아집니다. 우리는 SPA를 구축할 때 발생하는 대부분의 단점 없이, 관심사 분리를 통해 얻을 수 있는 장점만을 취할 수 있습니다.
BE 상태에 접근하는 더 간단한 접근방법
BE 상태를 저장하기 위해 리덕스를 사용하는 것 보다 더 좋다고 생각하는 라이브러리가 몇가지 있습니다.
React Query
저는 개인 및 회사 프로젝트에 React Query를 수개월간 사용해봤습니다. 이것은 간단한 API이며, 쿼리와 뮤테이션을 관리하기 위한 몇가지 훅을 가지고 있습니다. React Query를 사용하므로써, 생산성이 향상되었을 뿐 만 아니라, 리덕스를 위한 보일러 플레이트 코드 작성을 10배나 줄였습니다. 모든 BE 상태를 고려할 필요 없이, UI/UX 개발에만 집중하기 쉽다는 것을 알게 되었습니다.
이 라이브러리를 리덕스와 비교하기 위해 두 메소드 코드 샘플을 보여드리겠습니다. 저는 간단한 TODO 리스트를 서버로부터 가져오는 두 메소드를 구현해봤습니다.
먼저, 리덕스 코드입니다.
import React, { useEffect } from "react"; import { useSelector, useDispatch } from "react-redux"; import axios from 'axios'; const SET_TODOS = "SET_TODOS"; export const rootReducer = (state = { todos: [] }, action) => { switch (action.type) { case SET_TODOS: return { ...state, todos: action.payload }; default: return state; } }; export const App = () => { const todos = useSelector((state) => state.todos); const dispatch = useDispatch(); useEffect(() => { const fetchPosts = async () => { const { data } = await axios.get("/api/todos"); dispatch({ type: SET_TODOS, payload: data} ); }; fetchPosts(); }, []); return ( <ul>{todos.length > 0 && todos.map((todo) => <li>{todo.text}</li>)}</ul> ); };
이 코드는 리패칭, 캐싱, 유효성 검사를 하지 않는다는 것을 주목해주세요. 단순히 데이터를 가져와서 전역 저장소에 저장할 뿐입니다.
이번엔 React Query 코드입니다.
import React from "react"; import { useQuery } from "react-query"; import axios from "axios"; // 캐싱 설정을 어느곳에서든 정의할 수 있습니다 const fetchTodos = () => { const { data } = axios.get("/api/todos"); // 필요하다면 데이터 유효성 검사를 여기서 수행하여, 실제로 값을 사용하는 곳에서는 검증된 값을 사용할 수 있습니다 return data; }; const App = () => { // 데이터가 필요한 곳에서 호출하면 됩니다 const { data } = useQuery("todos", fetchTodos); return data ? ( <ul>{data.length > 0 && data.map((todo) => <li>{todo.text}</li>)}</ul> ) : null; };
기본적으로 이 예제는 리패칭, 캐싱, 꽤 예민한 기본값으로 유효성 검사를 포함하고 있습니다. 당신은 캐싱 설정을 전역 레벨에 설정하고 신경쓰지 않아도 됩니다. (대부분 그것은 예상한대로 동작합니다.) 위 코드의 자세한 동작 방식이 궁금하다면 React Query Docs를 읽어보세요. 엄청나게 많은 설정 옵션이 있으며, 이것은 수박 겉 햝기에 지나지 않습니다. (맞는 번역인가?)
이 데이터가 필요한 어느곳에서나 유니크 키(예제에서는
todos
)와 비동기 호출(예제에서는fetchTodos()
), useQuery hook을 이용해 데이터를 가져올 수 있습니다. 이 훅은 비동기적일뿐만 아니라, 구현은 마음대로 해도 됩니다. (즉,axios
대신fetch API
를 사용해도 된다는 말이죠.)BE 상태를 조작하기 위해, React Query는 useMutation hook도 제공하고 있습니다.
SWR
SWRStale-While-Revalidate은 기본적인 개념은 React Query와 동일합니다. React Query와 SWR은 비슷한 시기에 개발되었으며, 서로 긍정적인 방향으로 영향을 끼쳐 왔습니다. 두 라이브러리에 대한 비교는 React Query Docs - Comparison에 나와 있습니다.
React Query와 같이, SWR도 굉장히 가독성이 좋은 문서가 있습니다. 대부분의 경우, 두 라이브러리를 사용한다면 잘못될 경우는 없습니다. 두 라이브러리 중 무엇이 표준이 되던 간에, 리덕스보다 리팩토링이 쉽습니다.
Apollo Client
위 두 라이브러리는 REST API를 기반으로 하고 있습니다. 만약 GraphQL 기반 라이브러리를 원한다면 Apollo Client를 추천드립니다. React Query와 유사한 문법을 가지고 있어, 배우기 편할 것입니다.
FE 상태는?
이러한 라이브러리를 사용한다면, 대부분의 프로젝트에서 리덕스는 과하다는 것을 알 수 있습니다. 데이터 가져오기 및 캐싱을 구현할 때, FE에서 처리할 전역 상태는 거의 없습니다. 남은 것들을 핸들링 하기 위해선, 가짜 리덕스를 만들기 위한 Context API로 충분합니다.
혹은 React에서 제공하는 기본적인 상태 훅을 사용하세요.
// clean, beautiful, and simple const [state, setState] = useState();
모호한 상태에 머무르는 대신, FE와 BE의 분리를 수용합시다. 위 라이브러리들은 SPA에서 우리가 어떻게 상태를 관리해야 하는지에 대한 변화를 일으키고 있으며, 올바른 방향으로의 큰 걸음입니다. 저는 이 라이브러리들이 리액트 커뮤니티를 어디로 이끌어갈지 보는 것이 기쁩니다.
참고
https://dev.to/g_abud/why-i-quit-redux-1knl
'programing > Web' 카테고리의 다른 글
[Web] JSON Web Token (0) 2020.12.03 [WEB] CORS에 대한 정리 (0) 2020.07.22 [CSS] media query에 대해 알아보자 (0) 2020.06.16 [Storybook] 스토리북에 대해 알아보자 (2) 2020.06.15 [Deno] Deno에 대해 알아보자 (3) 2020.05.31 댓글