: 어떤 컴포넌트에서 생성한 state를 다른 컴포넌트로 보고자할 때, props를 통해 부모 컴포넌트에서 자식 컴포넌트로 그 값을 보내줬었음
-> 컴포넌트에서 컴포넌트로 state를 보내기 위해서는 반드시 부모-자식 관계가 되어야함
-> 조부모 컴포넌트에서 손자 컴포넌트로 값을 보내고자할 때도 반드시 부모 컴포넌트를 거쳐야만함. 정작 부모컴포넌트에서는 그 값이 필요가 없어도 전달을 위해 불필요하게 거쳐야했음
-> 자식 컴포넌트에서 부모 컴포넌트로 값을 보낼 수 없음
🔥 Redux 사용 시, state를 공유하고자 할때 부모-자식 관계가 아니어도 되고, 중간에 의미없는 컴포넌트를 안넣어도 되고, 자식 컴포넌트에서 만든 State를 부모 컴포넌트에서도 사용할 수 있게 됨
: 앞으로 State를 Local과 Global로 구분지어서 표현할 것임
Local state
: 컴포넌트에서 useState를 이용해서 생성한 state
: 좁은 범위 안에서 생성된 state
Global state
: 컴포넌트에서 생성되지 않고, 중앙화 된 특별한 곳에서 생성된 state
: 중앙 state 관리소에서 state를 생성
🔥 어떤 컴포넌트에서 state가 필요하면 컴포넌트가 어디에 위치하고 있든 상관없이 state를 불러와서 사용할 수 있게 됨
: 특정 컴포넌트에 종속되어 있지 않음
🔥 global state 값들을 관리하는 것을 전역 상태 관리라고 함
: 위에서 말한 '중앙 state 관리소'를 사용할 수 있게 도와주는 패키지/라이브러리
: 중앙에서 관리한다는 개념은 좋지만, 직접 코드로 구현하기는 어려우니 패키지의 도움을 받는 것
: '전역 상태 관리 라이브러리'라고 많이 표현
: 큰 부분인 Store와 그에 포함된 부분인 Reducer로 나뉨
환경설정
: yarn add redux react-redux 로 패키지 설치
: redux - 리덕스와 관련된 코드가 담길 폴더
: config - 리덕스 설정 관련 파일이 담길 폴더
: configStore.js - 중앙 state 관리소인 Store을 만드는 설정 코드들이 담긴 파일
: modules - 우리가 만들 State들의 그룹 ex)todos.js
configStore.js
🙋🏻♀️ props drilling 이란?
🔥 모듈이란, State의 그룹
: counter 프로그램에 필요한 State들이 모여있는 모듈 'counter.js'
: useState 사용해서 괄호 안에 초기값을 지정해주던 것과 같은 이치
: 위 State의 초기값은 객체이고, 그 안에 number라는 변수에 초기값 0을 할당한 것
: 초기값은 객체뿐만 아니라 원시 데이터, 여러개의 변수도 가능
: 🔥 변화를 일으키는 '함수', 데이터를 실제로 바꾸는 곳
: 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 사용법
🔥 컴포넌트에서 스토어를 조회할 때 react-redux에서 제공하는 useSelector이라는 훅을 사용
-> 콘솔을 보면 counter 모듈의 state가 보임
-> 화살표함수에서 꺼낸 state라는 인자는 현재 프로젝트에 존재하는 모든 리덕트 모듈의 state
-> 어떤 컴포넌트에서도 접근할 수 있는 스토어를 갖게 됨
🔥 컴포넌트에서 number라는 값을 사용하려면
const number = useSelector(state => state.counter.number);
전역 상태관리 흐름
: 구성 - 전역으로 관리되는 데이터 & 그 데이터를 참조/수정하는 데이터 & 수정된 값 렌더링
: 데이터를 참조/수정하는 컴포넌트 = 데이터를 '구독한다'
: 컴포넌트가 데이터를 수정할 수 있는 함수 호출 -> 데이터 수정 -> 수정된 데이터를 구독한 컴포넌트들에게 수정되었다는 신호 보냄
리덕스 상태관리 흐름
: 주황, 초록 컴포넌트가 스토어에서 데이터 구독 중
: 순서
: 구독한 컴포넌트들이 모두 바뀐 상태를 볼 수 있어야하는 것이 중요!
: 가져오기, 수정하기(바꿔줘, 바뀜, 알려줌)
action : state가 변하는 것
store : action, reducer을 저장하는 어플리케이션에 존재하는 단 하나의 객체, 수시로 state를 확인해 view에게 변경된 사항을 알려줌
dispatch : 스토어의 내장함수, 액션객체를 리듀서로 보내는 전달자 함수, reducer에게 action을 발생하라고 시키는 것
-> store에서 reducer 함수를 실행시켜 state를 업데이트
subscibe : action이 dispatch 될 때마다 전달해준 함수를 호출
middleware : action을 dispatch 했을 때 reducer에서 이를 처리하기에 앞서, 사전에 지정된 작업들을 실행
-> 하나의 어플리케이션은 하나의 스토어만 가짐
-> 동일한 파라미터로 호출된 리듀서는 언제나 같은 패턴의 결과값을 반환해야 함
-> state는 read-only 이므로 고유값이 수정되지 않고 새로운 state를 만들어 이를 수정하는 방식으로 업데이트 (불변성 유지)
: 기존 useState의 setNumber 사용해서 number에 +1 하기
: 리덕스에서는 리듀서가 값을 수정해주므로
{ type : "PLUS_ONE" };
: '명령' = '내가 어떻게 바꾸길 원한다' = action
: 행동을 코드로 나타내면 객체로 만듦 => action 객체
🔥 액션 객체는 반드시 type이라는 key를 가져야 함
: 액션 객체를 리듀서에게 보냈을 때 리듀서는 객체 안에서 type이라는 key를 보기 때문 (디스패치를 통해 전달받은 액션객체 검사)
: 조건이 일치하면 새로운 상태값 만듦
-> 모듈에 있는 state 변경을 위해 그에 해당하는 액션 객체를 모두 만들어줘야 함
: 🔥 액션객체를 리듀서로 보내기위해서는 새로운 훅 -> useDispatch
: 컴포넌트 안에서 먼저 dispatch 변수 생성하는 코드 작성
: 🔥 dispatch는 함수라 꼭 () 붙여줘야 함!
: 🔥 dispatch의 () 안에 액션객체를 넣어줌(파라미터로 사용)
-> 🔥 액션객체 type의 value는 대문자로 작성
: onClick 이벤트 핸들러 추가
: 마우스를 클릭했을 때 dispatch가 실행, ()안에 있는 액션객체가 리듀서로 전달
: 리듀서가 액션객체를 받아 상태를 바꾸는 순서
: PLUS_ONE 이라는 action.type의 case를 추가
-> dispatch로 전달받은 action의 type이 "PLUS_ONE"일 때 return절 실행
: useSelector을 이용해 state값 조회
-> 리덕스 state도 값이 변경되면 useSelector를 하고 있는 컴포넌트들도 모두 다시 리렌더링
액션객체의 value(PLUS_ONE, MINUS_ONE)가 counter 모듈 안에 있다는 것을 강조하기 위해 counter/PLUS_ONE, counter/MINUS_ONE 이라는 value로 바꾸려면 counter/App.js와 counter.js에 가서 일일이 바꿔야함.
-> 앞으로는 하드코딩하지 않고 액션객체를 한 곳에서 관리할 수 있도록 '함수'와 액션 value를 상수로 만들 것임
: 액션 객체를 만드는 함수 만들기 -> Action Creator
= 액션을 만드는 생성자
: 모듈 파일 안에서 생성
: 액션 객체의 type value로 상수를 생성해서 관리
🔥 action value를 상수로 선언
🔥 action 객체를 반환하는 함수 생성
: export가 붙는 이유는 plusOne()이 밖으로 나가서 사용될 예정이기 때문
: type에는 위에서 만든 상수 사용해서 액션객체를 반환
-> 변경된 리듀서
: case문에도 문자열이 아닌 선언한 상수를 넣어줌
: 생성한 Action Creator를 컴포넌트에서 어떻게 사용할까?
💡 dispatch() 안에는 반드시 객체만 들어가는거 아니에요?
: {type: "PLUS_ONE"} === plusOne()
이기 때문
: const one = () => {return 1;}
으로 함수를 만들었을 때 one() === 1
: counter 모듈에서 한걸음 나아가 1씩 증가/감소하는 것이 아닌, 증가/감소시킬 숫자를 카운터 프로그램을 사용하는 사용자가 직접 정할 수 있게 하려면?
: 이전에는 리듀서에 "1을 더해"라고 임의의 값을 명령으로 정해서 리듀서에 액션 객체를 보냄
: 이제는 N을 더해
라고 리듀서에 보내야 함 -> ~을
이라는 목적어가 생김 -> 목적어도 액션객체에 담아 같이 보내줘야 함
🔥 이렇게 액션객체에 같이 담아 보내주는 N을 더해
을 payload라고 함
-> 액션객체에 payload를 같이 담아서 보내줌
{ type: "ADD_NUMBER", payload: 10}
: payload가 추가된 액션객체
: type 뿐만 아니라 payload라는 key와 value를 같이 담는다
: state를 변경하는데 있어 리듀서에게 어떤 값을 보내줘야한다면 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) 구현된 기능 테스트
: 리덕스 모듈을 개발하는 개발자마다 구성요소를 제 각각 구현한다면? 협업 시 수많은 파일 중에 내가 필요로하는 구성요소를 찾을 수 없다면?
-> 패턴화하여 작성하는 것을 제안 -> Ducks 패턴
-> 모듈 파일 1개에 Action Type, Action Creator, Reducer가 모두 존재하는 작성 방식