티스토리 뷰

TIL

2024. 01. 26 Redux 알아보기

윤미주 2024. 1. 27. 02:42

Redux

➡️전역상태 관리 라이브러리

 

State는 2개로 구분지을 수 있다.

Local state = useState이용한 상태(컴포넌트 안에서 사용되는 State)

Global state = 전체적인 상태 (redux)

 

-- yarn 설치방법 --

 yarn add redux react-redux

 

package.json에서 확인해보기

 

 

--  Redux 폴더이름 만들기  --

일반적으로 Redux를 사용할 폴더이름은 아래와 같이 만든다고 한다!

 

⬇️redux폴더 아래

⬇️modules폴더 아래

   ✅actions: 액선타입과 액션 생성자 함수를 정의

   ✅ reducers: 상태의 일부를 관리하는 리듀서 함수 포함

   ✅ selectores: 상태 트리에서 특정 부분을 선택하는 선택자 함수

   ✅ middleware: 사용자정의 미들웨어를 정의하는 파일

⬇️config폴더 아래( 스토어를 생성하고 미들웨어,리듀서와 같은 필요한 설정을 결합하는 파일 )

   ✅ configstore.js: 중앙 state 관리소 (확장자: js)


✅configStore.js 만드는 방법

// configStore.js

import { createStore } from "redux";
import { combineReducers } from "redux"; //reducer를 한번에 묶는 API

const rootReducer = combineReducers({
  modules에서 만드는 파일의 reducer를 여기에 저장
}); //메서드는 () 를하면 호출된다

const store = createStore(rootReducer);

export default store;

 

  ❗ ❗ ❗ ⭐⭐⭐⭐⭐modules에서 만드는 파일의 reducer를 여기에 저장 ⭐⭐⭐⭐⭐ ❗ ❗ ❗

사용하고자 하는 컴포넌트에서 import해서 사용할때 위에 중요하다고 한 부분에 값을 import해서 사용할 수 있음


✅만든 Store 전역으로 설정하는 방법

// index.js

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { Provider } from "react-redux";
import store from "./redux/config/configStore";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

reportWebVitals();

 

사용하고자 하는 파일을 Provider태그로 감싸고 store={store} 값을 전달!!!

 

❗Provider import 필요

❗ store import 필요


 

✅state 만드는 방법

const initialState = {
	number: 0
}

const counter = ( state, action ) => {
	switch(action.type) {
		default: 	
		return state;
	}
}

export default counter;

 


 

-- ① 초기값 --

const initialState = {
	number: 0
}

처음에 항상 초기값을 설정해 주어야 한다. 

 

 

-- ② reducer 함수  --

⚠️ configStore.js 에 들어가야하는 부분 ⚠️

const counter = ( state, action ) => {
	switch(action.type) {
		default: 	
		return state;
	}
}

reducer함수를 만들어주어야 한다.

() 안에는 2개의 인자값을 받고인자값으로는 항상 state, action 이 들어간다.

const counter = ( state=initialState, action ) => { }

state에는 초기값으로 설정해준 값을 주고

 

  switch (action.type) {
    default:
      return state;
  }

action은 객체이며 type과 payload를가지고 있으며, key, value 형태이다.


✅만들어둔 reducer함수 Store에 저장하기

import { createStore } from "redux";
import { combineReducers } from "redux";

import counter from "../modules/counter";
import person from "../modules/user";

const rootReducer = combineReducers({
  counter,
  person: person,
});
const store = createStore(rootReducer);

export default store;

위에서 중요하다고 그렇게 강조했던 부분에 내가 만든 reducer함수가 들어가있다!!!

counter: counter인데 이름이 같으니 생략 가능!!!


✅ Store에 저장한reducer import해서 사용하기

import { useSelector } from "react-redux";

function App() {
  const counter = useSelector((state) => {
    return state.counter;
  });
  const person = useSelector((state) => {
    return state.person;
  });

  const data = useSelector((state) => {
    return state;
  });

  return <div>redux</div>;
}

useSelector라는 매서드를 이용해 Store에 저장한 reducer들을 가져올 수 있다.

사용하고자 하는 파일에서 import를 전혀 하지 않았어도 사용할 수 있다!!

 

🔥주의사항🔥

위 예시에서는 useSelector안에 여러개의 reducer를 가지고 있지만 

useSeletor 안에는 1개의 reducer를 사용하는것이 좋은 방법이다.

 

이유:

useSelector는 얕은 평가를 하기 때문에

여러개의 reducer를 가지고 있는다면 불필요한 평가를 해야한다.

때문에 useSelector 안에서 가지는 reducer의 값은 1개만 넣어주자!

 

또 다른 시도:

처음에는 Store에 있는 값을 다 불러와서 필요한거 사용하면 안되나?

라는 생각이 들어서 return하는 부분에서 아래 코드처럼 사용해보았다.

return <div>너는 {store.person.name} 입니다.</div>;

 

이렇게 되면 store에 가지고 있는 많은 reducer를 통째로 들고 오는 것이 되기 때문에

해당 컴포넌트가 Store에 과한 의존성을 가지게 되며

① 성능에 매우 안좋을 수 있겠다.

 

② 가독성이 떨어질 수 있다. 어떤 reducer를 가져왔는지 명확하게 보여주고 

return에서도 조금더 간단하게 쓸수 있다.

 

③ useSelector를 사용하는 이유는 해당 값에 변동이 없을때 얕은 평가를 하고 

   불필요한 리렌더링을 하지 않게하기 위해 사용하는 것인데 

   굳이 여러개의 reducer를 사용해서 성능을 저하시킬 필요가 전혀 없다.

 

④ 재사용성 부분에서도 store에 있는 특정 부분에만 의존하는 컴포넌트를 사용하는 것이

     재사용성 부분에서 훨씬 좋다. (불필요한 얕은 복사를 할 이유가 없음.)

 

**여러가지 시도를 해보았는데 결론은  useSelector 안에는 1개의 reducer만 사용하자 ^^

   분명 알게되어 다행이지만 시간을 너무 많이 썼으니 누군가 읽는다면 시간낭비 하지 마세요..🥲

 


✅ reducer에 action 값 주기

const initialState = {
  name: "miju",
  age: 10,
  hobby: "game",
};

const person = (state = initialState, action) => {
  switch (action.type) {
    case "GET_AGE":
      return {
        age: state.age + 1,
      };
    default:
      return state;
  }
};

export default person;

초기값 중 변화를 주고 싶은 값을 사용해 return안에 값을 넣어주는데, 

현재 초기값은 객체이므로 return { } 객체안에 필요한 action을 주면 된다.

 

action을 줄때에는 case "type이름" : return { key: stage.key action(ex. +1) } 로 주면 된다.

만약 객체가 아닌 단순 타입일 경우 return state action(ex. +1) 

 


 

✅ reducer를 사용하는 곳에서 action 사용하기

import { useDispatch, useSelector } from "react-redux";

function App() {
  const person = useSelector((state) => {
    return state.person;
  });

  const changeAge = useDispatch();

  return (
    <>
      <div>일년이 지나서 당신의 나이는 {person.age}</div>
      <button
        onClick={() => {
          changeAge({
            type: "GET_AGE",
          });
        }}
      >
        일년 나이먹기
      </button>
    </>
  );
}

 

①   useDispatch를 상수로 선언해준다.

②  사용하고자 하는 곳에서 useDispatch의 상수를 불러오고 (매서드이므로 당연히 상수() 괄호로 호출)

③  초기값을 객체로 주었으므로 {}를 사용해 객체로 type을 넣어준다.

④  만약 객체가 아닌경우는 {}를 이용해 type을 넣어줄 필요가 없다.


✅ reducer   type을 상수로 만들어 주기

// user.js

export const GET_AGE = "GET_AGE";
export const OLD_AGE = "OLD_AGE";

const initialState = {
  name: "miju",
  age: 10,
  hobby: "game",
};

const person = (state = initialState, action) => {
  switch (action.type) {
    case GET_AGE:
      return {
        age: state.age + 1,
      };
    case OLD_AGE:
      return {
        age: state.age - 1,
      };
    default:
      return state;
  }
};

 

case에서 type으로 사용할 이름을 const를 이용해 상수로 만들어주고

case 에서 type 이름을 상수로 변경해 준다. ( 그럼 문자열이 아닌 상수임으로 " " 이런게 필요 없음. )

밖에서도 상수를 사용할 것 이므로 export 를 앞에 붙여주어야 한다.

 

그리고

// App.js

import { useDispatch, useSelector } from "react-redux";
import { GET_AGE } from "./redux/modules/user";

function App() {
  const person = useSelector((state) => {
    return state.person;
  });

  const changeAge = useDispatch();

  return (
    <>
      <div>일년이 지나서 당신의 나이는 {person.age}</div>
      <button
        onClick={() => {
          changeAge({
            type: "GET_AGE",
          });
        }}
      >
        일년 나이먹기
      </button>
      <button
        onClick={() => {
          changeAge({
            type: GET_AGE,
          });
        }}
      >
        일년 나이 버리기
      </button>
    </>
  );
}

export default App;

 

 

import { GET_AGE } from "./redux/modules/user";

reducer를 사용하는 컴포넌트에서 꼭 만들어준 type 상수를 import 해와야한다.

 

그리고 action을 사용하는 곳에서도 상수이므로 " " 필요 없음! 

 

 


번외, 당연한소리하는중..ㅋㅋ

변수로 만들어도 쌉 가능

import { useDispatch, useSelector } from "react-redux";
import { GET_AGE } from "./redux/modules/user";

function App() {
  const person = useSelector((state) => {
    return state.person;
  });

  const changeAge = useDispatch();

  const handleplusone = () => {
    changeAge({
      type: GET_AGE,
    });
  };

  return (
    <>
      <div>일년이 지나서 당신의 나이는 {person.age}</div>
      <button onClick={handleplusone}>일년 나이먹기</button>
      <button
        onClick={() => {
          changeAge({
            type: "OLD_AGE",
          });
        }}
      >
        일년 나이 버리기
      </button>
    </>
  );
}

✅ reducer value(반환되는 action)를 상수로 만들어 주기

//user.js

export const GET_AGE = "GET_AGE";
export const OLD_AGE = "OLD_AGE";

export const userValue = () => {
  return {
    type: GET_AGE,
  };
};

const initialState = {
  name: "miju",
  age: 10,
  hobby: "game",
};

const person = (state = initialState, action) => {
  switch (action.type) {
    case GET_AGE:
      return {
        age: state.age + 1,
      };
    case OLD_AGE:
      return {
        age: state.age - 1,
      };
    default:
      return state;
  }
};

export default person;

 

리덕스에서 권장되는 패턴임으로 번거롭지만 해주자..ㅎㅎ

export const userValue = () => {
  return {
    type: GET_AGE,
  };
};

만약 객체가 아니였다면 return GET_AGE;

 

//App.js

import { userValue } from "./redux/modules/user";

function App() {
  const person = useSelector((state) => {
    return state.person;
  });

  const changeAge = useDispatch();

  const handleplusone = () => {
    changeAge(userValue());
  };

  return (
    <>
      <div>일년이 지나서 당신의 나이는 {person.age}</div>
      <button onClick={handleplusone}>일년 나이먹기</button>
      <button
        onClick={() => {
          changeAge({
            type: "OLD_AGE",
          });
        }}
      >
        일년 나이 버리기
      </button>
    </>
  );
}

 

사용되는 곳에서 마찬가지고 import 필수

import { userValue } from "./redux/modules/user";

 

그리고 원래 action을 반환하던 부분에서 action을 함수로 만들어 import 해주었으니 actio이름() **괄호로 호출**


--  payload --

: 사용자로부터 입력받은 값이나 다른 소스에서 얻은 값을 reducer에 있는 action에 전달한다.

 

**payload를 전달받은  reducer는 action + payload 기반으로 새로운 상태를 계산하고 반환하게 된다!!