07. Redux

개발자 취준생 밍키·2022년 10월 10일
0

개인공부🤓

목록 보기
16/28
post-custom-banner

useState의 불편함

: 어떤 컴포넌트에서 생성한 state를 다른 컴포넌트로 보고자할 때, props를 통해 부모 컴포넌트에서 자식 컴포넌트로 그 값을 보내줬었음
-> 컴포넌트에서 컴포넌트로 state를 보내기 위해서는 반드시 부모-자식 관계가 되어야함
-> 조부모 컴포넌트에서 손자 컴포넌트로 값을 보내고자할 때도 반드시 부모 컴포넌트를 거쳐야만함. 정작 부모컴포넌트에서는 그 값이 필요가 없어도 전달을 위해 불필요하게 거쳐야했음
-> 자식 컴포넌트에서 부모 컴포넌트로 값을 보낼 수 없음

🔥 Redux 사용 시, state를 공유하고자 할때 부모-자식 관계가 아니어도 되고, 중간에 의미없는 컴포넌트를 안넣어도 되고, 자식 컴포넌트에서 만든 State를 부모 컴포넌트에서도 사용할 수 있게 됨

Global state와 Local state

: 앞으로 State를 Local과 Global로 구분지어서 표현할 것임

  • Local state
    : 컴포넌트에서 useState를 이용해서 생성한 state
    : 좁은 범위 안에서 생성된 state

  • Global state
    : 컴포넌트에서 생성되지 않고, 중앙화 된 특별한 곳에서 생성된 state

: 중앙 state 관리소에서 state를 생성
🔥 어떤 컴포넌트에서 state가 필요하면 컴포넌트가 어디에 위치하고 있든 상관없이 state를 불러와서 사용할 수 있게 됨
: 특정 컴포넌트에 종속되어 있지 않음
🔥 global state 값들을 관리하는 것을 전역 상태 관리라고 함

Redux

: 위에서 말한 '중앙 state 관리소'를 사용할 수 있게 도와주는 패키지/라이브러리
: 중앙에서 관리한다는 개념은 좋지만, 직접 코드로 구현하기는 어려우니 패키지의 도움을 받는 것
: '전역 상태 관리 라이브러리'라고 많이 표현
: 큰 부분인 Store와 그에 포함된 부분인 Reducer로 나뉨

  • 환경설정
    : yarn add redux react-redux 로 패키지 설치
    : redux - 리덕스와 관련된 코드가 담길 폴더
    : config - 리덕스 설정 관련 파일이 담길 폴더
    : configStore.js - 중앙 state 관리소인 Store을 만드는 설정 코드들이 담긴 파일
    : modules - 우리가 만들 State들의 그룹 ex)todos.js

  • configStore.js

  • index.js

🙋🏻‍♀️ props drilling 이란?

✅ 카운터 모듈 만들기

🔥 모듈이란, State의 그룹
: counter 프로그램에 필요한 State들이 모여있는 모듈 'counter.js'

  • 초기 상태값 지정

: useState 사용해서 괄호 안에 초기값을 지정해주던 것과 같은 이치
: 위 State의 초기값은 객체이고, 그 안에 number라는 변수에 초기값 0을 할당한 것
: 초기값은 객체뿐만 아니라 원시 데이터, 여러개의 변수도 가능

  • 리듀서(Reducer)

: 🔥 변화를 일으키는 '함수', 데이터를 실제로 바꾸는 곳
: dispatch(Store 내장함수)된 action을 바탕으로 데이터 수정해줌

: useState에서 변수의 값을 바꾸고 싶을 때 set을 사용했었음
ex) setNumber(number+1)
-> 리덕스에서는 리듀서가 이 역할을 함
ex) "리듀서야 number에 +1를 해줘~" -> 리듀서가 number에 +1 해줌

: 🔥 리듀서 인자 (state = initialState, action)
-> 일단 state에 initialState 넣어야한다는 것 기억!

: 🔥 action의 타입마다 케이스문을 걸어주면 action에 따라서 새로운 값을 돌려줌

카운터 모듈을 스토어에 연결하기

: 아직까지 모듈과 스토어가 따로 분리되어있는 상태이므로 우리가 만든 State를 스토어에서 꺼낼 수 없음

: configStore.js 에 코드 추가
-> 스토어와 모듈 연결 완료!

: a : counter 해도 상관없음!

스토어와 모듈 연결 확인하기

: useSelector 사용법

  1. store에서 꺼낸 값을 할당할 변수 number 선언
  2. useSelector()를 변수에 할당
  3. useSelector의 인자 state에 화살표함수 넣어줌
  4. 화살표 함수의 인자에서 값을 꺼내 return
  5. state가 뭔지 콘솔로 값 찍어보기

🔥 컴포넌트에서 스토어를 조회할 때 react-redux에서 제공하는 useSelector이라는 훅을 사용
-> 콘솔을 보면 counter 모듈의 state가 보임
-> 화살표함수에서 꺼낸 state라는 인자는 현재 프로젝트에 존재하는 모든 리덕트 모듈의 state
-> 어떤 컴포넌트에서도 접근할 수 있는 스토어를 갖게 됨

🔥 컴포넌트에서 number라는 값을 사용하려면
const number = useSelector(state => state.counter.number);

✅ Store State 수정

상태관리 흐름

  • 전역 상태관리 흐름
    : 구성 - 전역으로 관리되는 데이터 & 그 데이터를 참조/수정하는 데이터 & 수정된 값 렌더링
    : 데이터를 참조/수정하는 컴포넌트 = 데이터를 '구독한다'
    : 컴포넌트가 데이터를 수정할 수 있는 함수 호출 -> 데이터 수정 -> 수정된 데이터를 구독한 컴포넌트들에게 수정되었다는 신호 보냄

  • 리덕스 상태관리 흐름

: 주황, 초록 컴포넌트가 스토어에서 데이터 구독 중
: 순서

  1. 리덕스 스토어을 주황, 초록 컴포넌트에 연결
  2. 초록이 스토어의 데이터를 변경하려할 때, 액션을 디스패치한다고 함
  3. 액션을 일으키면 리듀서가 스토어의 데이터를 변경하는 작업 해줌
  4. 새 상태값이 스토어에 저장됨
  5. 스토어가 자신을 구독한 컴포넌트들에게 값이 변경되었다고 신호 보냄
  6. 스토어를 구독하고있는 컴포넌트들은 새로운 상태값을 받아옴
  7. 리렌더링됨

: 구독한 컴포넌트들이 모두 바뀐 상태를 볼 수 있어야하는 것이 중요!
: 가져오기, 수정하기(바꿔줘, 바뀜, 알려줌)

상태관리 흐름 도식화

  1. View에서 action 일어남
  2. dispatch에서 action 일어남
  3. action에 의한 reducer 함수가 실행되기 전 state로 middleware가 작동
  4. middleware에서 명령내린 일을 수행한 뒤, reducer 함수가 실행됨
  5. reducer 함수 실행 결과로 변경된 새로운 실행결과 state를 store에 저장
  6. onChange() 함수를 통해 store의 state를 subscribe하고 있던 UI에 변경된 값을 준다 -> 렌더링

action : state가 변하는 것
store : action, reducer을 저장하는 어플리케이션에 존재하는 단 하나의 객체, 수시로 state를 확인해 view에게 변경된 사항을 알려줌
dispatch : 스토어의 내장함수, 액션객체를 리듀서로 보내는 전달자 함수, reducer에게 action을 발생하라고 시키는 것
-> store에서 reducer 함수를 실행시켜 state를 업데이트
subscibe : action이 dispatch 될 때마다 전달해준 함수를 호출
middleware : action을 dispatch 했을 때 reducer에서 이를 처리하기에 앞서, 사전에 지정된 작업들을 실행

-> 하나의 어플리케이션은 하나의 스토어만 가짐
-> 동일한 파라미터로 호출된 리듀서는 언제나 같은 패턴의 결과값을 반환해야 함
-> state는 read-only 이므로 고유값이 수정되지 않고 새로운 state를 만들어 이를 수정하는 방식으로 업데이트 (불변성 유지)

counter.js 모듈의 state 수정 기능 만들기

: 기존 useState의 setNumber 사용해서 number에 +1 하기

: 리덕스에서는 리듀서가 값을 수정해주므로

  1. 리듀서에게 보낼 명령을 만든다 : "number에 +1 해라!"
  2. 명령을 보낸다
  3. 리듀서에서 명령을 받아 number+1 한다

{ type : "PLUS_ONE" };

: '명령' = '내가 어떻게 바꾸길 원한다' = action
: 행동을 코드로 나타내면 객체로 만듦 => action 객체

🔥 액션 객체는 반드시 type이라는 key를 가져야 함
: 액션 객체를 리듀서에게 보냈을 때 리듀서는 객체 안에서 type이라는 key를 보기 때문 (디스패치를 통해 전달받은 액션객체 검사)
: 조건이 일치하면 새로운 상태값 만듦
-> 모듈에 있는 state 변경을 위해 그에 해당하는 액션 객체를 모두 만들어줘야 함

: 🔥 액션객체를 리듀서로 보내기위해서는 새로운 훅 -> useDispatch
: 컴포넌트 안에서 먼저 dispatch 변수 생성하는 코드 작성
: 🔥 dispatch는 함수라 꼭 () 붙여줘야 함!

: 🔥 dispatch의 () 안에 액션객체를 넣어줌(파라미터로 사용)
-> 🔥 액션객체 type의 value는 대문자로 작성
: onClick 이벤트 핸들러 추가
: 마우스를 클릭했을 때 dispatch가 실행, ()안에 있는 액션객체가 리듀서로 전달

: 리듀서가 액션객체를 받아 상태를 바꾸는 순서

  1. 컴포넌트로부터 dispatch를 통해 액션객체를 전달 받음
  2. action 안에 있는 type을 스위치문을 통해 하나씩 검사해서 일치하는 case 찾음
  3. type과 case가 일치하는 경우에 해당 코드가 실행되고, 새로운 state를 return
  4. 모듈이 새로운 state로 업데이트됨

: PLUS_ONE 이라는 action.type의 case를 추가
-> dispatch로 전달받은 action의 type이 "PLUS_ONE"일 때 return절 실행

: useSelector을 이용해 state값 조회
-> 리덕스 state도 값이 변경되면 useSelector를 하고 있는 컴포넌트들도 모두 다시 리렌더링

✅ Action creator

액션객체의 value(PLUS_ONE, MINUS_ONE)가 counter 모듈 안에 있다는 것을 강조하기 위해 counter/PLUS_ONE, counter/MINUS_ONE 이라는 value로 바꾸려면 counter/App.js와 counter.js에 가서 일일이 바꿔야함.
-> 앞으로는 하드코딩하지 않고 액션객체를 한 곳에서 관리할 수 있도록 '함수'와 액션 value를 상수로 만들 것임

Action creator 만들기

: 액션 객체를 만드는 함수 만들기 -> Action Creator
= 액션을 만드는 생성자
: 모듈 파일 안에서 생성
: 액션 객체의 type value로 상수를 생성해서 관리

🔥 action value를 상수로 선언

🔥 action 객체를 반환하는 함수 생성
: export가 붙는 이유는 plusOne()이 밖으로 나가서 사용될 예정이기 때문
: type에는 위에서 만든 상수 사용해서 액션객체를 반환

-> 변경된 리듀서
: case문에도 문자열이 아닌 선언한 상수를 넣어줌

Action Creator 사용하기

: 생성한 Action Creator를 컴포넌트에서 어떻게 사용할까?

  1. export된 Action Creator import 하기
  2. dispatch()에 있던 액션객체 지우고 Action creator 넣기

💡 dispatch() 안에는 반드시 객체만 들어가는거 아니에요?
: {type: "PLUS_ONE"} === plusOne() 이기 때문
: const one = () => {return 1;} 으로 함수를 만들었을 때 one() === 1

Action Creator 사용하는 이유

  1. 휴먼에러 (오타) 방지
    : 액션 객체의 type value를 상수로 만들어놓았기 때문에, 개발툴에서 자동완성 등의 보조 기능을 지원받을 수 있음
  2. 유지보수의 효율성 증가
    : Action Creator가 100군데에서 쓰이고 있는데, 그것을 바꿔야하는 상황이 오면 단 한번의 수정으로 100군데에 모든 수정사항을 반영할 수 있음
  3. 코드 가독성
    : 모듈 파일에서 Action Creator가 일목요연하게 정리가 되어있으면, 내가 아닌 다른 개발자가 보았을 때 해당 모듈이 가지고 있는 모든 Action들을 한눈에 알 수 있게 됨 -> Action 리스트업의 역할
  4. 리덕스 공식문서에서 추천하는 방법이라

✅ Payload

: counter 모듈에서 한걸음 나아가 1씩 증가/감소하는 것이 아닌, 증가/감소시킬 숫자를 카운터 프로그램을 사용하는 사용자가 직접 정할 수 있게 하려면?

: 이전에는 리듀서에 "1을 더해"라고 임의의 값을 명령으로 정해서 리듀서에 액션 객체를 보냄
: 이제는 N을 더해라고 리듀서에 보내야 함 -> ~을 이라는 목적어가 생김 -> 목적어도 액션객체에 담아 같이 보내줘야 함
🔥 이렇게 액션객체에 같이 담아 보내주는 N을 더해 을 payload라고 함
-> 액션객체에 payload를 같이 담아서 보내줌

{ type: "ADD_NUMBER", payload: 10}

: payload가 추가된 액션객체
: type 뿐만 아니라 payload라는 key와 value를 같이 담는다
: state를 변경하는데 있어 리듀서에게 어떤 값을 보내줘야한다면 payload를 액션객체에 같이 담아서 보냄

payload 기능 구현 작업 순서

1) 사용자가 입력한 값을 받을 input 구현
2) Action Creator 작성
3) 리듀서 작성
4) 구현된 기능 테스트

1) 사용자가 입력한 값을 받을 input 구현
: input과 button 2개를 작성
: input 값을 state로 관리하기 위해 훅을 사용하여 state 사용
: 이벤트핸들러(onChangeHandler)로 input과 연결

2) Action Creator 작성
: payload가 필요한 Action Creator에서는 함수를 선언할 때 매개변수 자리에 payload를 넣어줘야 함
-> Action Creator를 사용하는 컴포넌트에서 리듀서로 보내고자하는 payload를 인자로 넣어줘야하기 때문
-> Action Creator가 액션객체를 생성할때 payload를 같이 담아 생성

3) 리듀서 작성
: 사용자가 컴포넌트에게 action creator로 payload를 담아 보내는 것은 액션객체로 담겨짐
: payload가 담긴 액션객체는 리듀서에서 action.payload 에서 꺼내 사용할 수 있음
-> 기존의 값에 더해줌으로써 기능 구현 가능

4) 구현된 기능 테스트

  1. useSelector 이용해서 Store의 값을 조회하고 그것을 화면상에 렌더링
  2. dispatch 사용위해 선언
  3. 더하기 버튼을 눌렀을때 onClickAddNumberHandler 이벤트핸들러 연결
  4. Action Creator import 후, payload를 담아 dispatch
  5. Action Creator dispatch 후, 그때 Action creator의 인자에 number 넣어줌

Ducks 패턴

: 리덕스 모듈을 개발하는 개발자마다 구성요소를 제 각각 구현한다면? 협업 시 수많은 파일 중에 내가 필요로하는 구성요소를 찾을 수 없다면?
-> 패턴화하여 작성하는 것을 제안 -> Ducks 패턴

  1. Reducer 함수를 export default 하기
  2. Action creator 함수들을 export
  3. Acton type은 app/reducer/ACTION_TYPE 형태로 작성
    cf) 외부 라이브러리 포함 시 UPPER_SNAKE_CASE로만 가능

-> 모듈 파일 1개에 Action Type, Action Creator, Reducer가 모두 존재하는 작성 방식

profile
개발자가 되고싶어요
post-custom-banner

0개의 댓글