[Redux] 음악 클릭시 플레이바 생성하기

ceres·2020년 3월 29일
0

TIL

목록 보기
20/34

이번 프로젝트에서 재생 버튼을 클릭하면 재생바가 홈페이지 화면 위에 나타나도록 구현해야했다.
홈페이지는 최상단 컴포넌트였고, 재생버튼은 하단에 있는 컴포넌트여서 둘이 같은 state를 감지하게 하기 위해서는 redux를 사용해야 했다.
참조: 승현님 블로그 ,예리님 블로그

간단 redux 과정

action -> (dispatch) -> reducer -> store
action: 버튼 클릭 등 행위를 뜻함
reducer: 어떤 action 인지 확인 후 store로 보내줘
store: 상태 upate 함

1. 리덕스 설치

npm install --save redux react-redux

react-redux는 redux를 react에서 사용하기 편하게 만들어진것이다.
원래 state의 변화를 감지하거나, action을 발행하려면 store.dispatch(), store.subscribe() 등의 메서드를 매번 사용해야 하는데, react-redux로 component에서 store를 쉽게 읽고, action을 쉽게 dispatch할 수 있도록 해준다.

2. 폴더구조

src 폴더에 store라는 폴더를 만들고 내부에 Actions와 Reducers 폴더를 만들었다.

3. 최상위 컴포넌트 Provider로 래핑하기

react app에서 store를 사용하려면 Provider를 사용해야 한다. 가장 최상위 컴포넌트를 Provider로 래핑하고 속성에 store를 전달한다.

//index.js 파일
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from 'react-redux'
//store import
import { createStore } from 'redux'
import { rootReducer } from 'store/rootReducer'
import Routes from "./Routes";
import 'styles/reset.css';

ReactDOM.render(
<Provider store={createStore(rootReducer)}>
    <Routes />  
</Provider>, 
document.getElementById("root")
);

index.js 예리님 버전 (안쓴 부분은 위와 동일한것)

//index.js
import rootReducer from 'store/rootReducer';
//import {rootReducer}, import rootReducer차이: 
//전자는 store/rootReducer 파일에서 rootReducer 을 import 해온다는 의미이고
//후자는 store/rootReducer 파일을 변수명 rootReducer로 지정해서 import 해오는 것이다.
const store = createStore(rootReducer); //이부분은 reducer 만든 후 다시 설명

<Provider store={store}>
    <App />
  </Provider>

위에서 createStore(rootReducer)이 부분을 보면 createStore함수에 reducer를 인자로 준것을 알 수 있다. 이 reducer가 무엇인지 알아보자.

4. Reducer

리듀서는 action을 실행시키는 순수함수로 store가 가지고 있는 state를 변화시키기 위해 사용된다.
리듀서는 이전의 state와 action을 인자로 받고, 이 action의 내용(type)에 따라 state를 변화시킨다.

(state, action) => nextState

아래는 Modal 이라는 reducer를 정의한 내용이다.

//src/store/Reducers/showModals.js
import {SHOW_MODAL, CLOSE_MODAL} from 'store/types'
export default function Modal(state = false, action) {
    switch (action.type) {
        case SHOW_MODAL:
            return {...state, showModal:true};
        //이전에 받아올 state가 없다면 ...state는 쓰지 않아도 된다.
        case CLOSE_MODAL: 
            return {...state, showModal:false};
        default:
            return state;    
    }
}
  • 각 action type 별로 새로운 state를 생성하여 return하면 된다.
    SHOW_MODALCLOSE_MODAL은 action type이고, showModal은 state이다.
    (여기서 내가 헷갈린 부분! SHOW_MODAL은 action type이지 action 생성 함수 이름이 아니다!)
    SHOW_MODAL 이라는 action type이 전달되면 showModal 이라는 상태를 true로 변경하고, CLOSE_MODAL 이라는 action type이 전달되면 showModal 이라는 상태를 false로 변경한다.

  • 위에서 초기 state 값을 false로 지정하였는데, 상황에 따라 state값을 주지 않거나 초기상태로 줘도 된다.

function showModal(state = false, action) //state false로 지정
function showModal(state = null, action) //state 없을 경우 null로 지정
function showModal(state = INITIAL_STATE, action) //state 초기 상태로 줌

combine reducer

combineReducers는 리덕스에서 제공하는 메서드로, 각 리듀서를 하나로 모아주는 역할을 한다.
index.js 파일에 각각의 reducer를 import 할 필요 없이 이 파일 한개만 import 해주면 된다.
이번 프로젝트에서는 reducer을 한개만 만들었기 때문에 딱히 하나로 모아줄 내용은 없었다. 아래코드에도 showModal 한개만 적혀있다. reducer가 여러개 있는 경우 컴마(,)로 구분후 나열해주면 된다. 당연히 각각 import 도 해줘야한다.

//src/store/rootReducer.js
import {combineReducers} from 'redux';
import showModal from 'store/Reducers/showModal'
//import reduce명 from '위치'

export const rootReducer = combineReducers({
    showModal //,reducer명  이렇게 컴마를 찍은 후 나열해주면 된다.
});
  • 예리님꺼 (es6문법 아님)
    es6 문법 특징을 몰라서 예리님 코드를 보면서 헷갈렸다.
    es6 문법을 따로 정리할 시간은 없으니 여기다 정리해서 퉁쳐야지.
const rootReducer = combineReducers({
  auth: authReducer,
  modal: ModalReducer
 //{}는 객체형태이기 때문에 원래는 key: value 형식이어야 한다. 
 //그래서 (:) 표기가 있는 것이었다....! es6문법은 축약이 가능해서 쓰지 않아도 된다. 
});
export default rootReducer;

5. store

리덕스는 createStore라는 함수로 store를 생성한다. store는 app에 오직 하나만 있다. 이 유일한 store를 사용해 app 전체의 상태를 관리한다.

리덕스의 스토어를 생성하는 경우에는 리듀서가 필요하다. 위에서 생성한 리듀서를 index.js에 넣어야한다.

//index.js
// 첫 번째 매개변수에 리듀서를 전달했다.
const store = createStore(rootReducer);

6. Action 생성

상태 변화를 일으킬 수 있는 현상이다. 예를 들면 사용자의 입력, 웹 요청 완료 등이다. 이번 프로젝트에서는 사용자가 플레이 버튼을 누르는 행동이다. 객체형식으로 되어있고 꼭 타입을 지정해줘야 한다.

//src/store/Actions/index.js
//Action 마다 파일을 만들어 줘도 된다. 여기서는 action 2개를 한파일에 넣었다.(index.js) 
import {SHOW_MODAL} from 'store/types'
import {CLOSE_MODAL} from 'store/types'

//action1
export const showModalAction = () => {
    return {
        type: SHOW_MODAL,
        payload: {
            showModal: true
          //title: '모달제목' 등을 넣어줘도 된다.
        }
      //payload는 옵션사항이다. 이 코드에서는 오히려 안쓰는것 이 낫다!! 
    };
};

//action2
export const closeModalAction = () => {
    return {
        type: CLOSE_MODAL,
        payload: {
            showMdal: false
        }
    };
};

Action의 특징

  • 객체 형태이다.
  • 반드시 type 프로퍼티를 가져야만 한다. 일반적으로 type은 해당 액션을 설명하는 문자열이다.
  • action은 payload를 가질 수도 있다. payload는 해당 액션에 부가적인 데이터를 전달하고 싶을 때 사용한다. payload가 아닌 다른 다른 프로퍼티명을 사용해도 된다.

Action type.js

액션의 타입을 export 하기 위해 만든 파일이다. 따로 파일을 만들지 않고 action 생성 함수를 만들때 넣어도 된다. 하지만 따로 만드는 것이 유지보수에 편하다고 한다.

//src/store/types.js
export const SHOW_MODAL = "SHOW_MODAL"
export const CLOSE_MODAL = "CLOSE_MODAL"

7. 상태관리가 필요한 컴포넌트에 설정하기: action발행, state 변화 감지

원래 action을 dispatch(발행)하고 싶다면 store.dispatch() 메서드를 사용한다. 하지만 react-redux에서는 dispatch를 사용하지 않는다. 대신 특정 컴포넌트에서 store의 상태를 얻을 때 connect함수를 사용한다. connect는 Provider 컴포넌트 하위에 존재하는 컴포넌트들이 Store에 접근하게 만드는 역할을 한다.

이번 프로젝트에서 NewTrackBox컴포넌트에 있는 playIcon을 누르면 home컴포넌트에서 TidalPlayBar가 나오게 하기 위해 상태관리가 필요했다.

NewTrackBox 컴포넌트

//1. 액션생성함수, connect 함수 import 해주기
import {showModalAction} from 'store/Actions/index'
import {connect} from 'react-redux'


//2. 액션 생성 함수 실행
const NewTrackBox = (props) => {
  const IsClickedPlay =() => {
       props.showModalAction();
    }
  
  return(
  <PlayIcon onClick={IsClickedPlay}/>
)
}
//3. mapStateToProps 정의
const mapStateToProps = state => {
    return {showModal: state.showModal};
}
//4. connect 함수 사용
export default connect(mapStateToProps, {showModalAction}) (NewTrackBox)
  • 액션생성함수 실행
    클릭이벤트로 액션 생성함수를 실행시켜주면 NewTrackBox에 액션이 발생했다는 것을 store가 connect함수로 알게되고 상태값을 reducer를 통해 바꿔주게 된다.

  • connect 함수 사용

    • connect(첫번째 인자, 두번째 인자)(현재 컴포넌트) 형식으로 현재 컴포넌트를 연결
    • 첫번째 인자 (mapStateToPropse) :store에 있는 state를 현재 컴포넌트(NewTrackBox)에게 props로 전달해주는 역할, state를 인자로 받는다.
      • 두번째 인자 (mapDispatchToProps) : dipatch한 액션생성함수를 현재 컴포넌트(NewTrackBox)에게 props로 전달해주는 역할. 쉽게 생각하면 현재 컴포넌트에 쓰고 싶은 액션 생성 함수를 넣으면된다. import 받아온 액션생성 함수를 객체 형태로 넣어주자. (만약 import한 액션생성 함수가 없다면 null이나 빈객체 형태로 적으면 된다.)
  • mapDispatchToProps (예리님 블로그 예시. 구지 이렇게 안써도 됨)

    • connet함수의 두번째 인자
      • 액션생성 함수를 (dispatch) 현재 컴포넌트(NewTrackBox)에서 props로 쓸 수 있도록 전달하는 역할
      • 위에서 액션생성함수를 객체형태로 넣어준것과 기능은 동일하지만 쓰는게 훨씬 복잡하다.
      • mapDispatchToProps를 사용하려면 밑의 예리님 예제를 보자. mapDispatchToProps정의를 해준 후 connect함수 두번째 인자에 mapDispatchToProps 를 넣으면 된다.
    //예리님 블로그 예제 
    const mapDispatchToProps = dispatch => ({
      showModal: () => dispatch(showModal()),
      closeModal: () => dispatch(closeModal()),
    });
    export default connect( mapStateToProps, mapDispatchToProps)(Main);

home 컴포넌트

//1. connect 함수 import 해주기
import {connect} from 'react-redux'

//2.사용 : showMdal이 true일 경우 TidalPlayBar가 나오도록 했다. 
//mapStateToProps 에서 가져온 showModal을 인자로 넣어준 후 사용해야한다.
const Home = ({showModal}) => {
  return (
      {showModal && <TidalPlayBar />}
  );
};
//3.mapStateToProps 정의
const mapStateToProps = state => {
  return {showModal: state.showModal};
}
//4.connect함수 (import한 액션 생성 함수가 없기 때문에 두번째 인자는 빈 객체로 두었다.)
export default connect(mapStateToProps, {}) (Home);

redux를 완전히 이해하고 작성한 블로그가 아니기 때문에 틀린 부분이 있을 수 있습니다.
혹시 틀린부분을 발견하신다면 댓글 남겨주면 감사하겠습니다.

0개의 댓글