리덕스란, 자바스크립트 애플리케이션에서 상태를 효율적으로 관리할 수 있게 도와주는 도구입니다. 복잡한 상태 관리가 이루어지는 SPA(Single Page Application)에서 특히 유용하게 사용됩니다. 물론, 리덕스는 리액트 뿐만 아니라 jQuery, Angular 등을 사용하는 애플리케이션에서도 사용할 수 있지만, 이번 글은 리액트와 함께 사용하는 리덕스에 대해서 공부해보고자 합니다.
리액트를 사용하면서, 상태 관리를 하는 것은 매우 중요한 요소 중 하나입니다. 만약, 부모 컴포넌트(stateful한 컴포넌트)에서 상태를 전달받는 자식 컴포넌트들이 있는 아래 모습과 같은 상황이 있다고 가정해 봅시다.
리액트의 데이터 흐름은 단방향이기 때문에, 부모 컴포넌트 레이아웃 안에 존재하는 자식 컴포넌트(stateless한 컴포넌트)들은 부모 컴포넌트의 상태를 props로 전달받게 되죠. 그러다 부모 컴포넌트의 상태값이 변하게 되면, 자식 컴포넌트들은 변화된 상태에 맞게 data 혹은 UI를 변경합니다. 즉, 리랜더링 과정을 거치게 됩니다.
여기서 잠깐, 컴포넌트는 다음과 같은 몇 가지 상황에서 리랜더링 됩니다.
자신의 상태(state)값이 변경될 때, 부모 컴포넌트가 리랜더링될 때, 자신이 전달받은 props값이 변경될 때, 강제 업데이트(forceUpdate)함수가 실행될 때
즉, 위와 같은 상황에서 부모 컴포넌트의 상태값이 변한다면, 부모 컴포넌트는 리랜더링 될 것이고, 자식 컴포넌트들 또한 리랜더링 되게 됩니다. 그렇기 때문에 우리는 상태를 관리하는 데 있어서 상태 구성 요소를 최소화하기 위해 노력해야 합니다.
리액트를 사용하면서, 상태 관리를 하는 것은 매우 중요한 요소 중 하나입니다. 리액트로 만들 수 있는 단일 페이지 애플리케이션(SPA, Single Page Application)는 data 혹은 UI의 변화가 복잡, 다양해지는 경우가 많아집니다. 그에 따라 단일 페이지를 이루는 컴포넌트들의 데이터 교류 또한 복잡해지기 때문에 이를 효율적으로 관리할 방법이 필요합니다. 리덕스는 이러한 복잡한 상태 관리를 효율적으로 할 수 있게 도와주는 도구입니다.
만약, 아래 사진과 같은 레이아웃의 SPA가 있다고 가정해 봅시다.
리액트의 데이터 흐름은 단방향이기 때문에, 부모(stateful) 컴포넌트 레이아웃 안에 존재하는 자식(stateless) 컴포넌트들은 부모 컴포넌트의 상태를 props로 전달받게 됩니다. 위와 같은 레이아웃을 다이어그램 형태로 바꾸어 보면 아래와 같은 모습이겠죠.
부모 컴포넌트의 state를 전달받을 자식 컴포넌트들의 깊이가 1층밖에 되지 않으니, 충분히 쉽게 상태를 전달하고 관리할 수 있을 것 같습니다. 하지만, 아래와 같이 자식 컴포넌트의 깊이가 깊어진다면, 이야기가 달라지겠죠.
위와 같은 레이아웃을 다이어그램 형태로 표현하자면, 아래와 같은 모습이 됩니다.
위의 다이어그램으로 부모-자식 관계의 데이터 흐름에 맞게 상태를 전달하는 예를 두 가지 들어보도록 하겠습니다.
위의 형태와 같이 부모 컴포넌트에서 최하위 자식 컴포넌트까지 상태를 전달해 주어야 한다면, 부모-자식-자식-최하위 자식 경로를 통해 상태를 전달해주어야 합니다. 부모와 최하위 자식 사이에 있는 자식 컴포넌트들과는 무관한 상태 변화이더라도 최하위 자식 컴포넌트에게 상태를 전달하기 위해선, 거쳐야 되는 컴포넌트가 되죠. 이러한 형태의 부모-자식 관계의 컴포넌트가 더 깊어진다면 분명 상태 관리를 하는 데 있어 훨씬 더 까다로워질 것같습니다.
또 다른 예를 들어볼까요?
좌측 하단의 자식 컴포넌트에서 발생한 이벤트에 의해 다른 루트에 있는 컴포넌트의 상태 변화를 불러일으켜야 하는 상태에 대한 관리 또한 까다로워질 수 밖에 없겠죠.
이 글 상단에서 언급한 리덕스의 정의처럼, 리덕스는 상태 관리를 효율적 으로 할 수 있도록 도와주는 도구입니다.
"🤔 들어가기 앞서, 리액트 상태 관리에 대한 간략한 설명" 에서 '상태 변화는 리랜더링을 초래하기 때문에 상태 구성 요소를 최소화하는 것이 좋다' 라는 결론을 얻을 수 있었는데요. 하지만, SPA에서 시간의 흐름에 따라 상태 구성 요소는 분명 많아질 수 밖에 없습니다. 리덕스는 결국, 많아진 상태 구성 요소들을 보다 효율적으로 관리할 수 있도록 도와줍니다. 바로 아래와 같이 말이죠.
위와 같이 스토어를 사용하여 상태를 컴포넌트 구조 바깥에 두고 스토어라는 중간자를 통해 상태를 업데이트하거나, 새로운 상태를 전달받습니다.
즉, 리덕스를 사용하면, 위와 같이 상태값을 컴포넌트에 종속시키지 않고, 상태관리를 바깥에서 할 수 있게 해줍니다. (velopert님의 언급을 가져왔습니다.)
전반적인 흐름을 보고 있자니, 아직 생소한 단어들이 많고, 오히려 더 복잡해보입니다. 하나하나 차근차근 공부해보도록 하겠습니다.
우선, 리덕스의 전반적인 흐름에 나오는 용어들의 정의와 역할을 알아보려고 합니다. 리덕스를 사용하다 보면, 중요한 개념들이기 때문에 잘 정리해두는 게 좋겠죠.
상태의 변화가 필요할 때, 우리는 액션을 발생시킵니다. 이 액션은 하나의 객체로 표현됩니다. 가령, 할일 목록을 추가를 한다든지, 삭제를 한다면 추가, 삭제에 관한 액션의 타입 정의가 있어야 합니다.
{
type: "ADD_TODO",
data: {
id: 0,
text: "redux"
}
}
액션은 Type이라는 필드를 필수적으로 가지고 있어야 하고, 추가적으로 필요한 객체의 요소들은 필요에 의해 추가될 수 있습니다. 위의 data 필드와 같습니다.
액션 생성함수라 불리는 Action Creator는 액션을 만드는 함수입니다. 파라미터를 입력받아, 액션 객체 형태로 만들어줍니다.
function addTodo(data) {
return {
type: "ADD_TODO",
data
};
}
이 함수는 data라는 파라미터를 입력받아, 액션을 객체 형태로 반환하는 역할을 합니다.
리듀서는 변화를 일으키는 함수입니다. 이 함수는 이전 상태와 액션을 파라미터로 입력받습니다.
function reducer(state, action) {
...
return alteredState;
}
반환값은 로직에 의해 변화된 상태 값을 반환합니다.
스토어는 컴포넌트 외부에 있는 상태 저장소입니다. 스토어 안에는 현재 상태들, 리듀서, 그리고 몇 가지의 내장 함수를 포함하고 있습니다.
디스패치는 스토어의 내장함수 중 하나로, 액션을 발생시키는 역할을 합니다. 이 카테고리의 해더에 있는 '스토어는 상태 변화의 필요를 어떻게 캐치해?' 질문이 있었죠. 바로 디스패치가 액션을 발생시켜 스토어에게 상태 변화가 필요하다는 것을 알립니다.
그렇게 호출된 액션은 리듀서 함수를 호출시키고, 액션에 맞는 로직대로 상태를 변화시키는 과정을 거치는 것입니다.
추가, 삭제와 같은 각각의 액션타입을 정의합니다. 액션 함수는 각각의 액션 타입과 파라미터를 입력받아 액션을 객체 형태로 반환해줍니다. 상태의 변화가 필요해진다면, 디스패치가 액션을 발생시켜 스토어에게 알립니다. 스토어로 전달된 액션은 스토어의 리듀서 함수를 호출시키고, 호출된 리듀서 함수는 이전 상태와 액션타입을 파라미터로 전달받아 정의된 로직대로 현재 상태값을 변화시켜 변화된 상태를 반환합니다. 반환된 상태는 스토어에 저장됩니다.
리덕스를 안정적으로 활용하기 위해선 3가지 규칙을 지켜주셔야 합니다.
하나의 애플리케이션에는 하나의 스토어를 사용하는 것을 권장합니다.
배열 형태의 상태가 있는데, 로직에 의해 배열에 새로운 값을 넣어주어야 한다면, 기존 상태에 새로운 값을 직접 push하는 것이 아니라, concat과 같은 함수를 통해 새로운 값을 이어붙여 생성한 새로운 배열로 교체하여 업데이트해야 합니다. 이러한 업데이트 방식을 사용하면 불변성이 유지됩니다.
불변성이 유지된 업데이트는 리덕스의 변화 감지 방법인 shallow equality에 적합한 상태 변화이기 때문에 이러한 업데이트 방법을 사용합니다.
순수한 함수라는 것은 동일한 입력을 받았을 때 언제나 동일한 출력을 내는 함수를 말합니다. 우리가 클릭을 통해 액션을 발생시키면, 배경색이 랜덤으로 바뀌는 로직을 작성한다면, 함수의 로직 내에서 랜덤값을 생성해야 합니다. 랜덤값을 생성한다는 뜻은 결국 매번 출력이 바뀐다는 뜻이기 때문에 순수하지 못한 함수입니다.
또한, 네트워크의 요청을 하는 작업도 마찬가지로 순수하지 못한 로직 중 하나입니다.
이 글에서 우리는 리액트의 상태관리를 효율적으로 할 수 있게 도와주는 리덕스라는 도구의 전반적인 개념을 공부했습니다.
리덕스뿐만 아니라 React Context API, MobX 등 상태 관리를 도와주는 도구들이 몇 가지 더 있지만, 리액트의 단짝처럼 리덕스는 가장 많이 사용하는 상태 관리 도구이기 때문에 리액트를 사용한다면 꼭 익혀야 할 도구임이 틀림없습니다.
단지, 생소한 용어들 때문에 아직은 거부감이 들 수 있지만 지금은 이런 게 있구나 정도로 넘어가셔도 좋을 것 같습니다.
백문이 불여일견, 다음 글에서는 직접 리액트와 리덕스를 함께 사용하여 간단한 노트앱을 만들어보고 다시 이 글을 읽으시면 더 도움이 되실 겁니다.
정리 정말 잘하셨네요!!