diff --git "a/_posts/2024-07-28-Redux-\354\235\264\354\236\254\354\232\251.md" "b/_posts/2024-07-28-Redux-\354\235\264\354\236\254\354\232\251.md" new file mode 100644 index 0000000..a33f667 --- /dev/null +++ "b/_posts/2024-07-28-Redux-\354\235\264\354\236\254\354\232\251.md" @@ -0,0 +1,169 @@ +--- +layout: post +title: "함수 컴포넌트에서의 Redux 성능 최적화" +author: "이재용" +categories: "프론트엔드 기술블로그" +banner: + image: "https://velog.velcdn.com/images/jjh099/post/3fa558f9-2050-402b-aee7-350c02fc6ef2/image.jpeg" + background: "#000" + height: "100vh" + min_height: "38vh" + heading_style: "font-size: 4.25em; font-weight: bold; text-decoration: underline" +tags: ["redux", "리덕스", "최적화"] +--- +## 들어가며 + +Redux는 리액트 애플리케이션의 강력한 상태 관리 방법을 제공하지만, 잘못 사용하면 성능 이슈가 발생할 수 있습니다. 이번 글에서는 Redux를 사용할 때 적용할 수 있는 성능 최적화 기법에 대해 알아보겠습니다. + +--- + +## **Redux와 함수 컴포넌트에서 발생하는 성능 이슈** + +다음과 같이 크게 2가지 문제를 생각해 볼 수 있습니다. + +- **불필요한 리렌더링** : Redux 상태가 변경될 때마다, 연결된 컴포넌트들은 리렌더링됩니다. 하지만 모든 상태 변경이 해당 컴포넌트와 관련된 것은 아닐 수 있습니다. 이로 인해 불필요한 리렌더링이 발생하여 성능 저하를 일으킬 수 있습니다. +- **비효율적인 상태 선택과 계산** : 컴포넌트에서 필요한 상태를 선택할 때 불필요한 연산이나 깊은 비교가 발생하면 렌더링 성능에 영향을 미칩니다. + +이제, 본격적으로 Redux 성능을 최적화 할 수 있는 방법에 대해 알아봅시다. + +--- + +## **Redux 성능 최적화** + +### **1. React Redux Hooks의 효율적 사용** + +첫 번째 최적화 기법은 `useSelector` 훅을 최적화하는 방법입니다. + +- **`useSelector` 훅의 최적화**: + - `useSelector`는 Redux 스토어의 상태를 선택하여 컴포넌트에서 사용할 수 있게 해주는 훅입니다. 기본적으로 `useSelector`는 참조 비교를 수행하기 때문에, 선택한 상태가 새로운 객체나 배열로 반환되면 내용이 같더라도 리렌더링이 발생할 수 있습니다. + - **해결 방법**: + - `shallowEqual`을 사용하여 얕은 비교를 통해 불필요한 리렌더링을 방지할 수 있습니다. 이는 특히 상태가 객체나 배열일 때 유용합니다. + - 상태가 복잡한 중첩된 객체인 경우, 커스텀 비교 함수를 사용하여 객체 내 특정 값만 비교하도록 최적화할 수 있습니다. + +두 번째로, `createSelector`를 사용한 메모이제이션 기법입니다. + +- **메모이제이션된 셀렉터 사용 (`createSelector`)**: + - `createSelector`는 Redux Toolkit에 포함된 `reselect` 라이브러리의 일부로, 불필요한 계산을 방지하고 성능을 향상시킬 수 있습니다. + - **사용 예시**: + + ```jsx + const selectItems = (state) => state.items; + const selectFilter = (state) => state.filter; + + const selectFilteredItems = createSelector( + [selectItems, selectFilter], + (items, filter) => items.filter((item) => item.type === filter) + ); + + const filteredItems = useSelector(selectFilteredItems); + + ``` + + - `createSelector`는 상태가 변경되지 않으면 이전에 계산된 결과를 반환하여, 불필요한 연산과 리렌더링을 방지할 수 있습니다. + +> **메모이제이션 (Memoiztion)** : +컴퓨터 프로그램이 동일한 계산을 반복적으로 해야할 때, **이전에 계산한 값을 메모리에 저장**하여 **중복적인 계산을 제거**하여 전체적인 실행속도를 빠르게 해주는 기법입니다. +> + +--- + +### **2. Redux Toolkit을 통한 효율적인 상태 관리** + +Redux Toolkit은 Redux의 공식 툴킷으로, 보일러플레이트 코드를 줄이고 일반적인 Redux 작업을 간소화합니다. 기본적으로 Immer와 Redux Thunk를 포함하고 있어, 불변성 관리와 비동기 작업 처리가 수월합니다. + +- **`createSlice`와 `createSelector` 사용**: + - `createSlice`를 사용하면 상태와 리듀서를 간결하게 정의할 수 있으며, `createSelector`와 결합하여 효율적인 상태 관리와 성능 최적화를 할 수 있습니다. + - **사용 예시**: + + ```jsx + const usersSlice = createSlice({ + name: 'users', + initialState: { list: [], status: 'idle' }, + reducers: { + addUser: (state, action) => { + state.list.push(action.payload); + }, + }, + }); + + const selectUsers = (state) => state.users.list; + const selectActiveUsers = createSelector( + [selectUsers], + (users) => users.filter((user) => user.isActive) + ); + + ``` + + - 이를 통해 불필요한 리렌더링과 성능 저하를 방지하며, 애플리케이션의 효율성을 높일 수 있습니다. + +--- + +### **3. 불변성 유지와 Immer의 활용** + +불변성을 유지하는 것은 Redux 성능 최적화에서 중요한 역할을 합니다. Redux Toolkit은 내부적으로 `Immer` 라이브러리를 사용하여 불변성을 자동으로 관리합니다. 이를 통해 상태를 직관적이고 간단하게 업데이트할 수 있습니다. + +- **불변성 유지**: + - 불변성을 유지하면 상태 변화 추적이 용이하고, 예기치 않은 부작용을 방지할 수 있습니다. +- **Redux Toolkit과 Immer**: + - **사용 예시**: + + ```jsx + const todosSlice = createSlice({ + name: 'todos', + initialState: [], + reducers: { + addTodo: (state, action) => { + state.push(action.payload); // Immer가 자동으로 불변성을 유지해줌 + }, + toggleTodo: (state, action) => { + const todo = state.find(todo => todo.id === action.payload); + if (todo) { + todo.completed = !todo.completed; + } + }, + }, + }); + + ``` + + - 이를 통해 명령형 프로그래밍 스타일로 상태를 쉽게 업데이트하면서도 불변성을 유지할 수 있습니다. + +> **Immer** : +상태 변경 시 원본 상태를 복사한 후 변경된 부분만 업데이트하여 새로운 상태를 반환하는 불변성 관리 라이브러리입니다. +직관적인 불변성 관리를 통해, 개발자가 실수로 원본 상태를 수정하는 것을 방지할 수 있습니다. +> + +--- + +### **4. 미들웨어와 캐싱을 활용한 비동기 작업 최적화** + +비동기 작업을 처리할 때의 최적화 기법입니다. Redux Thunk를 사용하면 비동기 로직을 명확하게 관리할 수 있으며, 불필요한 API 호출을 줄이기 위해 캐싱을 활용할 수 있습니다. + +- **Redux Thunk의 활용**: + - 비동기 작업을 관리하고, 필요한 시점에 상태를 업데이트할 수 있습니다. +- **비동기 작업의 성능 최적화**: + - 예를 들어, 동일한 데이터를 반복적으로 요청하는 대신, 한 번 요청한 데이터를 스토어에 저장하고 필요할 때마다 스토어에서 가져오는 방식으로 성능을 최적화할 수 있습니다. + - **사용 예시**: + + ```jsx + const fetchUserById = (userId) => async (dispatch, getState) => { + const cachedUser = getState().users.find(user => user.id === userId); + if (cachedUser) { + return; // 캐시된 데이터가 있으면 추가 API 호출을 방지 + } + const response = await fetch(`/api/users/${userId}`); + const data = await response.json(); + dispatch(usersSlice.actions.addUser(data)); + }; + + ``` + + - 이를 통해 비동기 작업이 성능에 미치는 영향을 최소화할 수 있습니다. + +--- + +## **결론** + +지금까지 리액트의 함수 컴포넌트에서 Redux를 사용할 때 적용할 수 있는 다양한 성능 최적화 기법을 살펴보았습니다. `useSelector`의 최적화, `createSelector`를 활용한 메모이제이션, 불변성 유지, 그리고 Redux Thunk를 통한 비동기 작업 최적화는 모두 애플리케이션의 성능과 사용자 경험을 크게 향상시킬 수 있는 중요한 기법들입니다. + +다양한 최적화 기법들을 상황에 맞게 적용하여, 보다 빠르고 안정적인 애플리케이션을 개발하시길 바랍니다.