리액트 기초반 3주차(2)

2022. 2. 2. 01:11코딩공부/React

리덕스

전역상태관리를 위한 전역 상태 저장소

자식 컴포넌트는 부모 컴포넌트의 state를 맘대로 조작할 수 없어요.

왜냐면 데이터는 부모에서 자식으로 흐르게 하기로 했으니까요.(데이터는 단방향으로!)

 

리덕스는 여러 컴포넌트가 동일한 상태를 보고 있을 때 굉장히 유용합니다!

또, 데이터를 관리하는 로직을 컴포넌트에서 빼면, 컴포넌트는 정말 뷰만 관리할 수 있잖아요! 코드가 깔끔해질테니, 유지보수에도 아주 좋겠죠. 🙂

 

상태관리 흐름도

 

  • (1) 리덕스 Store를 Component에 연결한다.
  • (2) Component에서 상태 변화가 필요할 때 Action을 부른다.
  • (3) Reducer를 통해서 새로운 상태 값을 만들고,
  • (4) 새 상태값을 Store에 저장한다.
  • (5) Component는 새로운 상태값을 받아온다. (props를 통해 받아오니까, 다시 랜더링 되겠죠?)

구독 : 컴포넌트들이 스토어에서 데이터를 받아가는 것

액션 : 데이터를 바꿔주고싶다고 전달(action을 dispatch했다)

 

리덕스 패키지 설치

yarn add redux react-redux

리덕스 공식문서

https://ko.redux.js.org/introduction/getting-started/

 

Redux 시작하기 | Redux

소개 > 시작하기: Redux를 배우고 사용하기 위한 자료

ko.redux.js.org

 

리덕스의 개념과 용어

1. State

리덕스에서는 저장하고 있는 상태값("데이터"라고 생각하셔도 돼요!)를 state라고 불러요.

딕셔너리 형태({[key]: value})형태로 보관합니다.

 

2. Action

상태에 변화가 필요할 때(=가지고 있는 데이터를 변경할 때) 발생하는 것입니다.

객체이다

// 액션은 객체예요. 이런 식으로 쓰여요. 
// type은 객체 이름같은 거예요! 저희가 정하는 임의의 문자열을 넣습니다.
// data의 뒷부분은 이 데이터로 바꿀거야!

{type: 'CHANGE_STATE', data: {...}}

 

3. ActionCreator

액션 생성 함수라고도 부릅니다. 액션을 만들기 위해 사용합니다.

액션을 리턴해줌! 전역데이터 바꾸기위해 함

//이름 그대로 함수예요!
const changeState = (new_data) => {
// 액션을 리턴합니다! (액션 생성 함수니까요. 제가 너무 당연한 이야기를 했나요? :))
	return {
		type: 'CHANGE_STATE',
		data: new_data
	}
}

 

4. Reducer

리덕스에 저장된 상태(=데이터)를 변경하는 함수입니다.

우리가 액션 생성 함수를 부르고 → 액션을 만들면 → 리듀서가 현재 상태(=데이터)와 액션 객체를 받아서 → 새로운 데이터를 만들고 → 리턴해줍니다.

// 기본 상태값을 임의로 정해줬어요.
const initialState = {
	name: 'mean0'
}

function reducer(state = initialState, action) {
	switch(action.type){

		// action의 타입마다 케이스문을 걸어주면, 
		// 액션에 따라서 새로운 값을 돌려줍니다!
		case CHANGE_STATE: 
			return {name: 'mean1'};

		default: 
			return false;
	}

 

5. Store

우리 프로젝트에 리덕스를 적용하기 위해 만드는 거예요!

스토어에는 리듀서, 현재 애플리케이션 상태, 리덕스에서 값을 가져오고 액션을 호출하기 위한 몇 가지 내장 함수가 포함되어 있습니다.

생김새는 딕셔너리 혹은 json처럼 생겼어요. 내장함수를 어디서 보냐구요? → 공식문서에서요! 😉

 

6. Dispatch

액션을 발생 시키는 역할을 합니다.

// 실제로는 이것보다 코드가 길지만, 
// 간단히 표현하자면 이런 식으로 우리가 발생시키고자 하는 액션을 파라미터로 넘겨서 사용합니다.
dispatch(action);

 

리덕스의 3가지 특징

1. store는 한 프로젝트에 한개만 쓴다!

2. store의 state(데이터)는 오직 action으로만 변경할 수 있다!

데이터가 마구잡이로 변하지 않도록 불변성을 유지해주기 위함입니다.

리덕스에 저장된 데이터 = 상태 = state는 읽기 전용

액션으로 가지고 있던 값을 수정하지 않고, 새로운 값을 만들어서 상태를 갈아끼웁니다!

3. 어떤 요청이 와도 리듀서는 같은 동작을 해야한다!

리듀서는 순수한 함수여야 한다는 말입니다. 순수한 함수라는 건,

- 파라미터 외의 값에 의존하지 않아야하고,

- 이전 상태는 수정하지(=건드리지) 않는다. (변화를 준 새로운 객체를 return 해야합니다.)

- 파라미터가 같으면, 항상 같은 값을 반환

- 리듀서는 이전 상태와 액션을 파라미터로 받는다.

 

덕스 구조

  • 보통 리덕스를 사용할 때는, 모양새대로 action, actionCreator, reducer를 분리해서 작성합니다. (액션은 액션끼리, 액션생성함수는 액션생성함수끼리, 리듀서는 리듀서끼리 작성합니다.)
  • 덕스 구조모양새로 묶는 대신 기능으로 묶어 작성합니다. (버킷리스트를 예로 들자면, 버킷리스트의 action, actionCreator, reducer를 한 파일에 넣는 거예요.)

덕스 구조 참고 링크

https://github.com/erikras/ducks-modular-redux

 

GitHub - erikras/ducks-modular-redux: A proposal for bundling reducers, action types and actions when using Redux

A proposal for bundling reducers, action types and actions when using Redux - GitHub - erikras/ducks-modular-redux: A proposal for bundling reducers, action types and actions when using Redux

github.com

 

모듈 만들기

// widgets.js

// Actions
// Action 타입을 정해주는 부분
const LOAD = 'my-app/widgets/LOAD';
const CREATE = 'my-app/widgets/CREATE';
const UPDATE = 'my-app/widgets/UPDATE';
const REMOVE = 'my-app/widgets/REMOVE';

// Reducer
export default function reducer(state = {}, action = {}) {
// 파라미터 = {} 기본값을 준다. 
// 파라미터에 값이 안들어왔을때 발생할 수 있는 오류 방지
switch (action.type) {
// do reducer stuff
default: return state;
}
}

// Action Creators
export function loadWidgets() {
return { type: LOAD };
}

export function createWidget(widget) {
return { type: CREATE, widget };
}

export function updateWidget(widget) {
return { type: UPDATE, widget };
}

export function removeWidget(widget) {
return { type: REMOVE, widget };
}

// side effects, only as applicable
// e.g. thunks, epics, etc
export function getWidget () {
return dispatch => get('/widget').then(widget => dispatch(updateWidget(widget)))
}

 

리덕스와 컴포넌트 연결하기

// index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {BrowserRouter} from "react-router-dom";
// 우리의 버킷리스트에 리덕스를 주입해줄 프로바이더를 불러옵니다!
import {Provider} from "react-redux";
// 연결할 스토어도 가지고 와요.
import store from "./redux/configStore";

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

// If you want to start measuring performance in your app, pass a function to
// log results (for example: reportWebVitals(console.log)) or send to an
// analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

컴포넌트에서 리덕스 데이터 사용하기

1. 리덕스 훅

// useDispatch는 데이터를 업데이트할 때,
// useSelector는 데이터를 가져올 때 씁니다.
import {useDispatch, useSelector} from "react-redux";

2. 데이터를 가져올 컴포넌트에서 redux 데이터 가져오기

useSelector 사용

// BucketList.js

import React from "react";
import styled from "styled-components";

import {useHistory} from "react-router-dom";
// useDispatch는 데이터를 업데이트할 때,
// useSelector는 데이터를 가져올 때 씁니다.
import {useDispatch, useSelector} from "react-redux";


const BucketList = (props) => {
    let history = useHistory();

    // 여기에서 state는 리덕스 스토어가 가진 전체 데이터예요.
    // 우리는 그 중, bucket 안에 들어있는 list를 가져옵니다.
    const my_lists = useSelector((state) => state.bucket.list);

    return (
        <ListStyle>
            {
                my_lists.map((list, index) => {
                    return (
                        <ItemStyle
                            className="list_item"
                            key={index}
                            onClick={() => {
                                history.push("/detail");
                            }}>
                            {list}
                        </ItemStyle>
                    );
                })
            }
        </ListStyle>
    );
};

const ListStyle = styled.div `
display: flex;
flex-direction: column;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
`;

const ItemStyle = styled.div `
padding: 16px;
margin: 8px;
background-color: aliceblue;
`;

export default BucketList;

 

3. App.js에서 redux 데이터 추가하기

useDispatch 사용

import React from "react";
import styled from "styled-components";
import {Route, Switch} from "react-router-dom";
// useDispatch를 가져와요!
import { useDispatch } from "react-redux";
// 액션생성함수도 가져오고요!
import { createBucket } from "./redux/modules/bucket";

// BucketList 컴포넌트를 import 해옵니다. import [컴포넌트 명] from [컴포넌트가 있는 파일경로];
import BucketList from "./BucketList";
import Detail from "./Detail";
import NotFound from "./NotFound";



function App() {

    const [list, setList] = React.useState(["영화관 가기", "매일 책읽기", "수영 배우기"]);
    const text = React.useRef(null);
    const dispatch = useDispatch();

    const addBucketList = () => {
        // 스프레드 문법! 기억하고 계신가요? :) 원본 배열 list에 새로운 요소를 추가해주었습니다.
        // setList([
        //     ...list,
        //     text.current.value
        // ]);

        // 액션 객채 {type: "", data}
        dispatch(createBucket(text.current.value));
    };

    console.log(list);
    return (
        <div className="App">
            <Container>
                <Title>내 버킷리스트</Title>
                <Line/> {/* 컴포넌트를 넣어줍니다. */}
                {/* <컴포넌트 명 [props 명]={넘겨줄 것(리스트, 문자열, 숫자, ...)}/> */}
                <Switch>
                    <Route path="/" exact render={(props) => (<BucketList list={list}/>)}/>
                    <Route path="/detail" component={Detail}/>
                    <Route component={NotFound}/>
                </Switch>
                {/* 스위치 안에 있는 경로들을 하나씩 하나씩 비교하고 전부 안맞으면 
                패스 지정 안한 마지막 경로가 뜸 */}

            </Container>
            {/* 인풋박스와 추가하기 버튼을 넣어줬어요. */}
            <Input>
                <input type="text" ref={text}/>
                <button onClick={addBucketList}>추가하기</button>
            </Input>
        </div>
    );
}

...

export default App;

 

상세페이지에서 버킷리스트 내용 띄워보기

1. 몇 번째 상세에 와있는 지 알기 위해, URL 파라미터를 적용하자(몇번째 것을 눌렀는지 알려줘야함)

App.js에서 클릭해도 몇번째를 눌렀는지 알아야하는 것은 Detail 컴포넌트

// App.js
<Route exact path="/detail/:index" component={Detail} />
//BucketList.js
...
      {my_lists.map((list, index) => {
        return (
          <ItemStyle
            className="list_item"
            key={index}
            onClick={() => {
              history.push("/detail/"+index);
            }}
          >
            {list}
          </ItemStyle>
        );
      })}
...

 

2. 상세페이지에 내용 띄우기

//Detail.js
// 리액트 패키지를 불러옵니다.
import React from "react";
// 라우터 훅을 불러옵니다.
import {useParams} from "react-router-dom";
// redux hook을 불러옵니다.
import { useSelector } from "react-redux";

const Detail = (props) => {
  // 스토어에서 상태값 가져오기
  const bucket_list = useSelector((state) => state.bucket.list);
  // url 파라미터에서 인덱스 가져오기
  const params = useParams();
  const bucket_index = params.index;

  return <h1>{bucket_list[bucket_index]}</h1>;
};

export default Detail;

 

'코딩공부 > React' 카테고리의 다른 글

리액트 심화반 1주차  (0) 2022.02.05
리액트 기초반 4주차  (0) 2022.02.03
리액트 기초반 3주차(1)  (0) 2022.02.01
리액트 기초반 2주차(2)  (0) 2022.01.31
리액트 기초반 2주차(1)  (0) 2022.01.29