Frontend 📚/React

[React] 리액트 기술 면접 핸드북 III - FLUX, 리덕스, state 불변성, 리듀서 불변성, side effect etc.

leejaejae 2024. 6. 30. 20:09

1. FLUX란
2. 리덕스란
3. React에서 state 불변성
4. 리듀서 내부의 불변성과 ...연산자의 단점 해결 방법
5. side effect 문제점


1. FLUX 란 ⭐️⭐️

  • FLUX는 라이브러리나 프레임워크가 아닌 추상적인 개념임
  • MVC 패턴에서 쌍방향적인 데이터 흐름을 강조했던 것과 달리 FLUX 패턴에서는 단방향 데이터 흐름을 강조함

  • MVC 패턴
    • 사용자의 어떠한 행위(액션)가 있을 때, 이를 바탕으로 설계된 컨트롤러를 통해 모델을 변화하고 이를 뷰에 반영함
    • 보여져야 할 뷰가 적거나, 변경될 모델이 많지 않다면 매우 효율적인 구조임
    • 하지만, 모델과 뷰가 복잡하게 얽혀 있다면 어떤 모델이 변화되어 뷰가 변경되었는지 제대로 확인할 수 없는 경우가 생김

  • FLUX 패턴
    • MVC 패턴의 단점을 해결하기 위해 나온 단방향 데이터 흐름을 제어하는 패턴
    • 단방형적 데이터 흐름 구조를 통해 어떤 액션이 디스패처에 의해 어떤 결과를 낳고 변화되는지 명확히 파악하고 알아볼 수 있음
    • 사용자의 행위(액션)은 디스패처에 의해 통제됨
    • 디스패처가 스토어를 업데이트하고 변경된 스토어에 대한 뷰를 리렌더링함
    • 뷰에서는 스토어에 직접 접근하지 않으며, 디스패처로 다시 액션을 보내고 스토어를 업데이트한 뒤, 다시 뷰를 리렌더링

 

2. 리덕스란 ⭐️⭐️

  • 리덕스(Redux)
    • 상태 관리 라이브러리 중 하나로 여러 가지 상태 관리 라이브러리 중 가장 많이 사용됨
    • Store(스토어)라는 변수를 이용해 전역 상태 관리를 하게 됨
    • 전역으로 상태를 관리하기 때문에 props <-> state를 통해 부모 컴포넌트에서 자식 컴포넌트로, 자식의 자식 컴포넌트로 내려주지 않아도 사용할 수 있음
  • 리덕스의 기본 원칙
    • 응용 프로그램의 전역 상태는 단일 저장소 내의 트리에 저장됨
    • 상태(state)는 읽기 전용임
    • 순수 함수(부수 효과를 일으키지 않는 함수)에 의해서 변경되어야 함

 

3. React에서 state의 불변성 유지 ⭐️

  • 객체는 실제 데이터 값이 아닌 참조 값을 가짐
  • 따라서 복사하여 동일한 참조 값을 가지는 객체 중 하나라도 변경된다면, 모든 객체의 내부 값이 변경될 것임
  • `...(spread) 연산자`를 통해 복사할 경우 A와 B는 같은 값을 가지더라도 새로운 객체를 할당 받은 상태됨
  • 따라서 A와 B 내부의 값은 같더라도(같아 보이더라도) 참조하는 객체가 다르기 때문에 무결성을 유지할 수 있음

  • 이미 복사를 한 프린트물은 A 인쇄물에 낙서를 하더라도 B 인쇄물에 영향을 미치지 않는 것과 같음
  • 리액트에서는 데이터를 저장할 때 객체 형식 또는 배열 형식의 데이터를 많이 다루게 되는데, 원본 배열이 변경되는 경우 의도한 동작과 다르게 동작할 수 있으며, 어떤 함수에 의해 부수 효과(side effect)가 발생했는지 찾기 어려울 수 있음

 

4. 리듀서 내부에서 불변성 & 전개(spread) 연산자의 단점 해결 방법 ⭐️ 

  • 컴포넌트는 다음과 같은 총 네 가지 경우에 업데이트함
    • props가 바뀔 때
    • state가 바뀔 때
    • 부모 컴포넌트가 리렌더링될 때
    • this.forceUpdate로 강제로 렌더링을 트리거할 때
  • 리듀서의 initial state에는 서버에서 넘겨받는 정보를 저장하고 전역으로 해당 객체를 사용할 수 있는 저장소 역할을 함
    • Q) 리듀서 내부에서 불변성을 지키는 이유?
    • A) 불변성을 지킴으로써 각각의 고유한 참조값을 가지는 객체를 복사해서 사용함으로써 어떤 함수가 호출됐을 때 같은 객체를 참조한다면 생길 수 있는 불필요한 리렌더링과 부수효과를 줄일 수 있음
  • 하지만 `...(spread) 연산자`를 사용하여 객체를 복사해 사용할 경우 객체의 깊이에 따라, 로직 구성이 매우 어려울 수 있음
    • Q) `...(spread) 연산자`의 단점 해결 방법은?
    • A) immer 라이브러리 사용, produce, drafe 라는 키워드를 사용해 기본의 `...(spread) 연산자`를 사용하지 않고도 불변성을 유지해주며 불필요한 부수효과(side effect)를 막아줌
/* 기존의 ...(spread)연산자 사용 */
const nextState = {
  ...state,
  posts: state.posts.map((post) =>
    post.id === 1
      ? {
          ...post,
          comments: post.comments.concat({
            id: 3,
            text: '새로운 댓글',
          }),
        }
      : post
  ),
};

/* immer 라이브러리 사용 */
const nextState = produce(state, (draft) => {
  const post = draft.posts.find((post) => post.id === 1);
  post.comments.push({
    id: 3,
    text: '와 정말 쉽다!',
  });
});

 

5. 리액트 사용시 부수효과(side effect)로 인해 생기는 문제점 ⭐️⭐️

  • 부수효과
    • 함수가 만들어진 목적과는 다른 효과 또는 부작용
  • 부수효과를 일으키는 함수(불순함수)
/* 코드 참조 (https://maxkim-j.github.io/posts/js-pure-function) */

// http 요청을 보내는 함수 : 순수함수 될 수 없음
const getData = () => {
  axios.get('http://data.url')
  .then(...)
  .catch(...)
}

// 입력 내포한 함수 : 순수함수 될 수 없음
const typeInput = () => {
  const input = prompt("Message");
  return input;
}

// 파라미터를 직접 변경하는 함수 : 순수함수 될 수 없음
const changeParams = (arr, elem) => {
  arr.push(elem);
  return arr;
}
  • 부수효과를 일으키지 않는 함수(순수 함수)
/* 코드 참조 (https://maxkim-j.github.io/posts/js-pure-function) */

const num_arr = [1, 2, 3, 4, 5];

// 매개변수의 값을 직접 변경하는 불순함수
const addSixImpure = (arr) => {
  // 매개변수에 직접 6 추가
  arr.push(6);
  return arr;
};

// 매개변수를 복사한 값을 변경하는 순수함수
const addSixPure = (arr) => {
  // 펼침 연산자로 새로운 배열에 6 추가
  newArr = [...arr, 6];
  return newArr;
};

// 매개변수 arr에 6이 있는지 확인하는 함수
const hasSix = (arr) => {
  if (arr.includes(6)) {
    return true;
  } else {
    return false;
  }
};

const new_arr = addSixImpure(num_arr);
console.log(hasSix(num_arr)); // true
  • addSixPure()addSixInpure()는 언뜻 보면 별 차이가 없어 보이지만 addSixInpure()는 매개변수의 값을 직접 변경하는 불순함수이고, addSixPure()는 매개변수 값을 복사해서 변경하는 순수함수임
    • addSixInpure()는 num_arr을 직접 바꿨기 때문에 함수가 실행되면 num_arr의 값이 [1,2,3,4,5,6]으로 영구히 바뀜. 그래서 hasSix()함수의 결과로는 true를 반환하게 되는 것
    • 하지만 개발자의 의도가 변수 new_arr에 addSix 함수를 호출한 새로운 배열을 할당하고 난 후, 값이 [1,2,3,4,5]인 num_arr에 대해서 6이 있는지 판단하고 싶었던 거였다면 코드는 의도대로 실행되지 않았음

  • 요약!
    • react state는 직접 조작을 피하는 방식으로 부수효과를 방지함(state, props가 변경될 때 리렌더링이 되기 때문에 의도치 않게 부수효과를 가진 함수들로 인해 불필요한 리렌더링이 잦아질 수 있음)
    • Redux의 reducer는 순수함수여야만 하는데, store값을 변경하는 함수가 부수효과를 동반하지 않아야 store 내부의 값들이 안전하게 보호될 수 있기 때문