본문 바로가기

프로그래밍

[리액트를 다루는 기술] 13. 리덕스로 리액트 애플리케이션 상태관리

728x90



1. 왜? 사용


- 소규모 프로젝트에서는 컴포넌트의 state 기능을 사용해도 충분함

-> 상태 객체가 너무 복잡하고 큼

-> 최상위 컴포넌트에서 상태관리를 하는 매서드를 너무 맣이 만들게됨

-> 하위 컴포넌트에 props를 전달하려면 여러 컴포넌트를 거쳐야 함


- store.subscribe: 함수를 스토어에 구독시킬때

- store.getState: 상태를 가져올때


* react-redux가 컴포넌트에서 리덕스를 구독하는 역할을 대신해줌



2. 이번 프로젝트는?


- 생서과 제거 버튼이 있음

- 생성을 누르면 원이 생김

- 원의 색상은 랜덤

- 마우스 좌,우 클릭을 통해 숫자와 색상을 변경

- 그렇다면 상태는 숫자와 색상 두개를 원 객체 마다 가지고 있지 않을까?

-> index, num, color


3. 라이브러리

- yarn add react-redux redux



4. 디렉터리 구조

1) 액션: 액션 타입과 액션 생성자 파일

2) 컴포넌트,: 뷰가 어떻게 생길지 담당하는 프레젼테이셔널 컴포넌트 모음

3) 컨테이너: 스토어에 있는 상테를 props로 받아오는 컨테이너 컴포넌트를 저장

4) 리듀서: 스토어의 기본 상태값, 상태의 업데이트를 담당하는 리듀서 파일들을 저장

5) 라이브러리: 이부 컴포넌트에서 함께 사용하는 파일 저장



5. 프리젠테이셔널 컴포넌트, & 컨테이너 컴포넌트


- 멍청한 컴포넌트와 똑똑한 컴포넌트로도 불림



6. 프리젠테이셔널 컴포넌트


- 오직 뷰만 담당

- DOM 엘리먼트와 스타일이 있을 수 있음

- 프리젠테이션 컴포넌트와 컨테이너 컴포넌트가 있을 수 있음

- 리덕스에 직접 접근할 권한이 없음

- 오직 props로만 데이터를 가지고 옴

- 대부분은 state가 없음, 있다고 해도 UI에 관련된 것임

- 주로 함수형 컴포넌트로 작성


7. 컨테이너 컴포넌트

- 프리젠테이션 컴포넌트와 컨테이너 컴포넌트를 관리 담당

- 내부에 직접적으로 DOM 엘리먼트를 사용 안함

- 스타일도 없음

- 상태를 가지고 있을때가 많음, 리덕스에 직접 접근할 수 있음



8. Counter 컴포넌트

- 숫자와 색상값, 더하기, 배깨, 색상변경 함수를 props로 전달 받음

- 마우스 좌클릭: 더하기

- 마우스 우클릭: 빼기

- 마우스 더블클릭: 색상 변경하기


* e.preventDefault()을 우클릭에 사용하는 이유? 메뉴가 열리는 것을 방지


9. import PropType from 'prop-types';


Counter.propTypes = { number: PropTypes.number, ... }

-> 이거 해당 값의 타입을 만족 시키기 위해서 정의해 놓은 거 같음.



10. 리덕스 > 액션생성

- 액션은 객체이다.

-> 객체(오브젝트), 인스턴스, 클래스 차이점:

-> 클래스가 설계도이고 인스턴스는 찍어서 나온거고 객체는 그냥 ?? 머였지?   오브젝트는 컨셉이다. 만들기 전이다.


 - 모든 액션 객체에는 type 값이 필수로 있어야함


{ type: "SET_COLOR" }


- 추가할 사항이 있으면 다음과 같이 함


{ type: "SET_COLOR", color: "black" }


- type은 액션 이름과도 같음

- 리듀서가 이값을 전달 받아서 실행해야함


- 이 값들은 따로 만들어서 관리하면 좋음




11. ACtionTypes 준비


- ActionTypes.js


export const INCREMENT = 'INCREMENT';

-> 앞에 export를 붙이면 import * as types from './ActionTypes'


- 액션을 선언할때는 대문자로 선언



12. 액션 생성 함수 만들기


- 액션을 만들때마다 객체를 만드는 것은 번거롭다.

- 액션을 만들어내는 함수를 만들자.


() => ( { } )  화살표 함수는 function() { return{ } } 함수와 동일



- index.js > import * as types from './ActionTypes'

- export const increment  = () => ({ type: types.INCREMENT  })

-> 이게 왜 필요한지 곰곰히 생각해 보았다.: 자동완성 때문인가?

- 파라미터를 가지고 있는 값 표현

- export const setColor = (color) => ({ type: types.SET_COLOR, color})

-> 파라미터가 더 많아지면 예를덜어 shape이라는게 있으면 color,shape로 적나?

- 정리: 1. 액션 타입 선언  2. 액션 생성 함수 만들기

13. 리듀서 생성

- 액션 type에 따라 변화를 일으키는 함수
- 최초 변화를 일으키기 전에 상태를 가지고 있어야 함
- 초기 상태 선언
const initialState = { color: 'black', number: 0 };

- 리듀서는 state와 action을 파라미터로 가지고 있음

- 리듀서 함수 내부에서 action.type에 따라 상태에 다른 변화를 일으킴

- state를 직접 변형하면 안됨

- state 값에 새 상태 객체를 만드는 방식으로 진행해야함



* 이해 한 부분 정리

1. 리듀서 파라미터는 상태와 액션을 보낸다.

2. 상태가 undefined면 initialState를 값을 갖는다.

3. 액션 타입에 따라 switch로 함수 호출

4. 상태가 변환 경우 기존 값에 덮어 쓰기 안하고 새 상태를 만들어서 진행

5. 리듀서는 액션을 리덕스에서 받아서 스토어의 변형을 일으키기 위해서 필요함


function counter (state = initialState, action){

switch(action.type){

case types.INCREMENT:

return {

... state,

number = number + 1

};

}

}  


14. 스토어 생성

- 스토어는 리덕스에서 가장 핵심적인 인스턴스임

- 현재 상태가 내장됨

- 상태를 업데이트 할때마다 구독 중인 함수를 호출


- 프로젝트의 엔트리 포인트 index.js


- 리덕스 관련 불러오기 

-> import { createStore } from 'redux';

-> import reducer from './reducers';

- 스토어 생성

-> const store = createStore(reducers);


- 다음단계: 이 스토어를 리액트 컴포넌트로 전달



15. Provider 컴포넌트로 리액트 앱에 store 연동


- Provider : react-redux 라이브러리에 내장된 리액트 어플리케이션에 손쉽게 스토어를 연동할수 

있도록 도와주는 컴포넌트임


- import { Provider } from ' react-redux '

- ReactDOM.redner( 

< Provider store={ store} >

<APP/>

</Provider>,

document.getElementById('root')

);


16. 컨테이너 컴포넌트


- 스토어가 연동됨

- 컨테이너 컴포넌트와 스토어를 연결


- react-redux 라이브러리의 connect 함수를 이용해서 연결

- connect 함수는 파라미터가 3개

-> mapStateToProps

-> mapDispatchToProps

-> mergeProps

- 파라미터는 함수형태임,

- 꼭 필요한 것은 아님

- 컴포넌트에서 사용할 props를 반환


1)   mapStateToProps: store.getState() 결과값인 state를 파라미터로 받아 컴포넌트의 props로 사용할 객체를 반환: state에서 props?

2)  mapDispatchToProps: dispatch를 파라미터로 받아 액션을 디스패치하는 액션을 디스패치하는 함수들을 안에 넣어서 반환 -> 디스패치에서 함수?

3)  mergeProps: state와 dispatch가 동시에 필요한 함수를 props로 전달하려고 할때 사용 -> 일반적으로 잘 사용하지 않음


- connect 함수를 호출하고 나면 또 다른 함수를 반환

-> 이때 반환하는 함수의 파라미터로 리덕스에 연결시킬 컴포넌트를 넣으면 mapStateToProps와 mapdispatchToProps에 정의한 값을 props로 받아오는 새 컴포넌트를 만듬


- store 안에 state  값을 props  값에 연결

-> const mapStateToProps = (state ) => ({

color : state.color,

number: state.number

});


- 액션 생성함수를 이용하여 액션을 생성, 

- 해당 액션을 dispatch하는 함수를 만든 후 이를 props로 연결

-> const mapDispatchToProps = (dispatch) => ({

on increment = dispatch( actions.increment())

})

* actions -> import * as actions from './actions'



- counter 컴포넌트의 container 컴포넌트 

- counter 컴포넌트를 애플리케이션 데이터와 묶는 역할을 함


const container = connect(

mapStateToPros,

mapDispatchToProps

)(Counter);


-> 이렇게 하면 counter 컴포넌트의 props로 들어간다.



-> 이해한 부분을 정리해 보자.

상태를 만들고

-> 액션을 만들기: 정의수준

-> 리듀서 만들어서 액션에 대응해서 행동을 정의

-> 디스패치되어서 액션에 변화를 주게됨

-> 구독한 함수에게 영향을 미침



 

17. 서브리듀서 생성

- 두개로 나누고 하나로 합침: 서브(2) + 루트(1)
- color.js와 number.js 두개로 구분

- import { combineReducers } from 'redux'
- combinReducer를 실행하고 나면
- 나중에 store 형태를 파라미터로 객체 모양대로 만듬

numberData: {
number: 0
},

colorData:{
color: 'black'
}

}

* 일단 이렇게 마든다고 함


const reducers = combineReducers({

numberData: number,

colorData: color

})


-> const mapStateToProps = (state ) => ({

color : state.ColorData.color,

number: state.numberData.number

});




18. 크롬 개발자 도구 툴: redux dev tools 


- 스토어를 생성하는 코드를 수정

-> const store = createStore( reducers, window.divToolsExtensioin && window.devToolsExtension());

- 현재 리덕스 상태가 어떤지

- 방금 디스패치한 액션은 무엇인지

- 액션으로 어떤값을 바꾸었는지


19. Actions 수정

- create, remove 버튼 생성

- 액션 생성함수 수정: 특정 카운터를 조작할 수 있도록 수정 index 값을 액션 객체에 포함


export const increment = (index) =>({ 

type: types.INCREMENT,

index

})


- increment(3) 이면 index가 3인 카운터값을 1씩 올린다.


20. 리듀서 수정


- const initialState = {  counters = [ { color: black, number:0}  ]   };


- 생성: 기존 배열 가지고 추가로 하나 더 생성 , 전개 연산자(...)를 사용하거나 slice 함수로 배열을 잘라서 새로 생성해야함

- 삭제: 끝에서 하나 지움



switch (action.Type){

(...)

case types.INCREMENT:

return {

counter: [

... counters.slice(0, action.index), -> 선택한 인덱스의 전 아이텐들 

{

...counters[action.index], -> 기존 객체에 

number: counters[action.index].number + 1 -> 새 number  값 덮어쓰기

},

... counters.slice(action.index+1, actioni.index.length) -> 선택한 index의 다음 아이템들

]

}

}




21. slice 함수

.slice()는 배열의 일부분을 선택하여 새로운 배열을 만듭니다.

array.slice( start, end )



22. counterList > counter

- counter: 화면과 액션이 어울려져 있는 곳


- counterListContainer 

-> mapStateToProps: 리덕스 스토어에 있는 값들을 props로 전달

-> mapDispatchToProps: 액션 생성 함수들을 연결


















가우넷 구름 USB 충전식 양면 대용량 보조배터리 손난로, 단일 상품, 화이트



"파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음"