[React] Redux

Jinny·2023년 11월 10일
1

React

목록 보기
6/23
post-thumbnail

1. 상태 관리 라이브러리의 필요성

1) useState의 불편함

컴포넌트에서 생성한 상태를 다른 컴포넌트로 보낼 때
Props를 통해 부모 컴포넌트에서 자식 컴포넌트로 보내주었다.

만약 조부모 👉 손자 컴포넌트로 값을 전달할 때
부모 컴포넌트도 손자 컴포넌트에게 전달하기 위해
불필요하게 거쳐가야 한다. (조부모 👉 부모 👉 손자)
또한 자식 컴포넌트에서는 부모 컴포넌트로 값을 보내지 못하는 불편함이 있다.

2) Redux의 등장

Redux는 중앙 데이터 관리소로 state로 관리한 데이터들을 말그대로 중앙에서 관리한다.

  • State를 공유할 때 부-모 관계가 아니여도 가능하고 또한 불필요한 컴포넌트를 거칠 필요가 없다.
  • 따라서 자식 컴포넌트에서 만든 State도 부모 컴포넌트에서도 사용할 수 있다.

➡️ 모든 컴포넌트들이 중앙 데이터를 접근해서 조회하고 업데이트할 수 있다.

2. Redux

  • 리덕스는 전역상태 관리 라이브러리이다.
  • 중앙 state 관리소를 가지고 있어 모든 state는 이 곳에서 생성된다.

2.1 Local State vs Global State

  • Local State (지역 상태)
    : 컴포넌트에서 useState를 통해 생성한 state로
    즉, 좁은 범위 안에서 생성된 state를 뜻한다.
  • Global State (전역 상태)
    : 컴포넌트에서 생성되지 않으며 중앙화된 곳(중앙 state 관리소)에서 state들이 생성된다.
    ➡️ 어디서든 접근이 가능하다.

3. Redux 설정

3.1 Redux 설치

yarn add redux react-redux

3.2 저장소 생성 및 조회

Redux를 통해 카운터 초기값을 만들고 저장소를 통해
값을 출력해보자.

<파일 구조>
📁src 👉 📁 redux
📁redux 👉 📁 config 👉 📜 configStore.js
📁 redux 👉 📁 modules 👉 📜 counter.js

📁 config : Redux 설정 관련 폴더
📁 modules : state의 그룹
📜configstore.js : 중앙 state 관리소 설정 코드

  1. 카운터의 초기 상태값과 리듀서 함수를 작성한다.
    초기값 상태를 설정할 때 객체로 적어준다.

counter.js

//초기값 설정
const initalState = {
    number:0
};

//리듀서 함수 작성
// 👉액션의 타입에 따라 상태를 제어하는 함수
const counter = (state = initalState, action) => {
    switch(action.type) {
      default: 
        return state;
    }
};

export default counter;
  • count의 초기값은 number라는 상태에 0으로 설정한다.
  • 리듀서 함수를 작성할 때 상태(state)액션(action)을 인자로 받아온다.
  • 인자로 받는 action 객체는 type과 payload의 key를 가지고 있다.
  • 액션의 타입에 따라 상태를 달리 설정할 수 있다.
  • 액션의 타입의 예로는 '추가', '삭제', '수정'이 있다.
    👉 위 코드는 action을 따로 지정하지 않으면 초기값을 반환하도록 한다. (default문)
    👉 리듀서는 action의 타입에 따라 state를 변경하는 함수이다.
  1. 중앙 데이터 관리소를 만들고 설정한다.

configStore.js

//중앙 데이터 관리소를 설정하는 코드
import { createStore, combineReducers } from 'redux';
import counter from './../modules/counter';

//기본 리듀서 
const rootReducer = combineReducers({
    //생성된 리듀서들을 넣어줌
    counter
});
const store = createStore(rootReducer);

export default store;
  • 루트 리듀서에 생성된 리듀서들을 넣어주면 된다.
  • key:value 형태로 넣어주는데 count:count와 같이 동일한 이름이면 count로 축약 가능하다.
  1. index.js에서 store를 사용하기 위한 코드를 작성한다. 리액트 엄격모드를 지우고 react-redux에서 제공하는 Provider로 App 컴포넌트를 감싸준다.
import { Provider } from 'react-redux';
import store from './redux/config/configStore';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <Router>
      <App />
    </Router>
  </Provider>
);
  • export한 store를 import한다. (configStore.js)
  • export default한 경우, import할 때 중괄호를 사용하지 않는다.
    ➡️ App 컴포넌트에서 중앙데이터 관리소를 사용할 수 있다.
  1. 이제 스토어에 접근해 count의 값을 읽어온다.
    React-Redux에 제공해주는 useSelector를 이용한다.
import { useSelector } from 'react-redux';
import './App.css';

export default function App() {
  const counter = useSelector((state) => {
    return state.counter;
  })
  return (
    <div className="App">
    </div>
  );
}
  • 매개변수 state는 중앙 저장소에 저장된 state들을 뜻한다.
  • 만약 2번에서 count 외에도 다른 리듀서를 추가했으면 모든 state들을 확인할 수 있다.

👉 state를 출력한 값이다.
👉 현재 count 리듀서만 추가되어 있기 때문에 count 상태만 확인 가능하다.
여러 state들 중에서 count만 접근하고 싶다면 state.count를 통해 값을 가져올 수 있다.

3.3 dispatch를 통한 저장소 데이터 업데이트

  • Store : 중앙 데이터 관리소로 state(전역 상태)reducer(상태를 제어하는 함수)가 존재한다.
  • 이벤트가 발생하면 dispatch를 통해 액션 객체를 가지고 store에 전달한다. 이때 action은 type과 payload라는 key를 가진 객체이다. 즉, 중앙 데이터에게 어떤 액션을 해달라고 요청하는 것과 같다.
  • store에 있는 reducer가 액션의 타입에 따라 state를 제어할 수 있게 된다.

카운터에서 +,- 버튼 클릭 이벤트를 통해 count 값을
저장소에 업데이트 해보자.

  1. 우선 카운터에서 count 값을 제어하는 함수를 통해
    액션 타입을 따로 지정해준다.

counter.js

const initialState = {
    number: 0
};

const counter = (state = initialState, action) => {
    switch (action.type) {
        case 'added':
            return { number: state.number + 1 };
        case 'subtracted':
            return { number: state.number - 1 };
        default:
            return state;
    }
};
  • count의 초기값을 initialState 변수에 지정했으므로
    count 함수에 있는 state 인자는 {number:0} 값을 가지게 된다.
  • 액션 타입은 더한다는 의미에서 'added', 뺀다는 의미로 'subtracted'를 추가한다.
  • 객체인 상태를 업데이트할 때 새로운 객체로 반환해줘야 한다.
  1. App.jsx에서 dispatch를 통해 store에게 액션 객체를 전달해줘야 한다.
import { useDispatch, useSelector } from 'react-redux';
import './App.css';

export default function App() {
  //store에 접근하여 count의 값을 읽어온다.  
  const counter = useSelector((state) => {
    return state.counter;
  })
  //디스패치 생성 
  const dispatch = useDispatch();

  //액션 객체 전달
  const handleAdd = () => {
    dispatch({ type: 'added' });
  }

  const handleMinus = () => {
    dispatch({ type: 'subtracted' });
  }
  return (
    <>
      <div>
        현재 카운트 {counter.number}
      </div>
      <button onClick={handleAdd}>+</button>
      <button onClick={handleMinus}>-</button>
    </>
  );
}

쉽게 말하면 + 버튼을 누르면 1씩 더하라는 의미로 'added'라는 액션객체의 타입을 전달한다.

업로드중..업로드중..

➡️ +,- 버튼을 통해 데이터가 업데이트 되는 것을 확인할 수 있다.

3.4 Action Value Creator

만약 카운터 외에도 다른 모듈에서도 덧셈, 뺄셈의 액션이 있을 수 있어 액션 타입을 작성할 때
세분화된 이름을 작성하는 것이 좋다.

만약 액션 타입의 명칭을 변경하게 되면 📜counter.js와
📜 App.jsx에서 각각 타입들을 다시 변경해줘야 하는 번거로움이 있다.

👉만일 몇백개의 컴포넌트에서 수정하는 과정에서
실수할 수 있기 때문에 액션 타입과 값을 변수에 저장하는 습관을 가지는 것이 좋다!

counter.js

//액션 타입 변수에 저장
export const COUNTER_ADDED = 'counter/added';
export const COUNTER_SUBSTRACTED = 'counter/substracted';

const initialState = {
   number: 0
};


const counter = (state = initialState, action) => {
   switch (action.type) {
       case COUNTER_ADDED:
           return { number: state.number + 1 };
       case COUNTER_SUBSTRACTED:
           return { number: state.number - 1 };
       default:
           return state;
   }
};

export default counter;

APP.jsx에서 이용하기 위해 내보내는 export 키워드를 쓴다.

App.jsx

mport { COUNTER_ADDED, COUNTER_SUBSTRACTED } from './redux/modules/counter';

export default function App() {
  //store에 접근하여 count의 값을 읽어와보자. 
  const counter = useSelector((state) => {
    return state.counter;
  })
  const dispatch = useDispatch();

  const handleAdd = () => {
    dispatch({ type: COUNTER_ADDED });
  }

  const handleMinus = () => {
    dispatch({ type: COUNTER_SUBSTRACTED });
  }
  
}

👉 추후에 카운터 액션 타입을 변경할 상황이 오면
counter.js에서 액션 타입을 지정한 변수에만 값을 변경하면 된다.

또한 액션 객체 자체를 함수에 저장해서 활용할 수 있다.

export const added = () => {
  return {type:COUNTER_ADDED}
}

말 그대로 action value를 반환하는 함수이다.
dispatch를 통해 액션 객체를 전달할 때 저장된 함수로
접근 가능하다.

import {added} from './redux/modules/counter';
dispatch(added());

3.5 redux payload

action 객체에서 특정 값을 전달할 때 payload를 이용한다.

예를 들어, input에서 원하는 숫자만큼 올린 다음,
➕ 버튼을 누르면 해당 숫자만큼 증가하고 ➖ 버튼을 누르면 해당 숫자만큼 감소하도록 해보자.

App.jsx

import {addedN, subtractedN } from './redux/modules/counter';
export default function App() {
  const [num, setNum] = useState(0);
 
  const counter = useSelector((state) => {
    return state.counter;
  })
  const dispatch = useDispatch();

  const handleAdd = () => {
    dispatch(addedN(num))
  }

  const handleMinus = () => {
    dispatch(subtractedN(num))
  }
  return (
    <>
      <div>
        현재 카운트 {counter.number}
      </div>
      <input type='number' value={num} onChange={(e) => setNum(+e.target.value)} /> <br />
      <button onClick={handleAdd}>+</button>
      <button onClick={handleMinus}>-</button>
    </>
  );
}
  • 버튼을 클릭하면 store에게 import한 액션 객체들을 보내줄 때 input에서 입력한 숫자(num)를 인자로 전달해줘야 한다.

counter.js

const ADDED_N = 'counter/added_n';
const SUBSTRACTED_N = 'counter/substracted_n'

//액션객체를 리턴하는 함수 생성
export const addedN = (payload) => {
  //payload는 input에서 입력한 숫자 값
    return {
        type: ADDED_N,
        payload
    }
}

export const subtractedN = (payload) => {
    return {
        type: SUBSTRACTED_N,
        payload
    }
}

//리듀서 함수
const counter = (state = initialState, action) => {
    switch (action.type) {
        case ADDED_N:
            return { number: state.number + action.payload };
        case SUBSTRACTED_N:
            return { number: state.number - action.payload };
        default:
            return state;
    }
  export default counter;
};
  • 액션 객체를 리턴하는 함수를 생성하고 이때 payload를 통해 input에 있는 숫자 값(num)을 받아올 수 있다.
  • 상태를 제어하는 함수에서 전달 받은 데이터는 action.payload로 값을 접근할 수 있다.

3.6 Ducks pattern

  1. Reducer 함수를 export default
  2. Action create 함수들을 export
  3. 액션 타입은 reducer/action_type 형태로 작성

모듈 파일 1개에 Action Type, Action Creator, Reducer가
모두 존재하는 방식이다.

0개의 댓글