코딩 애플 React Part 2 - (3) Redux

신승준·2022년 7월 17일
0

장바구니 페이지 만들기 & Redux 1: Redux Toolkit 설치

  • tr을 쓰면 가로 행 하나가 생긴다.
  • th는 세로 열 하나를 생성시킨다.
  • thead랑 tbody
    • 둘 다 없어도 되긴 한다.
    • thead
      • table의 head, 제목 부분(카테고리)이다.
    • tbody
      • table의 body, 표의 내용이 오는 곳이다.
  • 장바구니 데이터는 App, Detail에서도 사용할 수 있다. 따라서 App에다가 선언해주는 것이 좋은데, 그렇게 되면 하위에서 사용하기 까다로워질 수 있다. 계속 전달해야 되니까.
    • Redux를 이용한다.
      • props 없이 state 공유가 가능해진다.

  • Redux
    • 하나의 JavaScript 파일을 만들어서 거기에 state를 저장하는 방식이다.
    • 이 파일에서 state를 꺼내서 사용하면 된다. 이런 방식으로 App이든 Detail이든 Cart에서든 상위 state를 사용할 수 있게 된다.
    • Redux는 큰 회사에서는 거의 필수로 사용 중이다.
    • package.json에서 react, react-dom이 18.1.0 이상 버전이어야 한다.
    • npm install @reduxjs/toolkit react-redux 입력하여 설치

  • Redux를 쓰기 위한 셋팅 방법
  1. src 폴더 안에 store.js 파일을 생성한다.
  2. store.js 파일에 다음 코드 입력
import { configureStore } from '@reduxjs/toolkit';

export default configureStore({
    reducer: {
        
    }
})
  1. index.js에서 다음과 같이 Provider로 감싼다.
...

import { Provider } from "react-redux";
import store from './store.js';

...

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
    <React.StrictMode>
        <Provider store={store}>
            <BrowserRouter>
                <App />
            </BrowserRouter>
        </Provider>
    </React.StrictMode>
);
  • 위 코드를 보면 이제 App이라는 컴포넌트는, state들을 저장하고 있는 저장소인 store를 제공하는 Provider로 감싸져 있기 때문에
    • App과 App의 자식 컴포넌트들은 store의 state들을 사용할 수 있게 된다.



Redux 2: store에 state 보관하고 쓰는 방법

Redux를 쓰는 이유

  • 컴포넌트간 state 공유가 편해진다.
    • props 전송이 필요 없어진다.



  • Redux store에 state 보관하는 방법
    • createSlice(), useState랑 비슷한 용도
    • name과 initialState라는 key 값은 고정이다. name_1 등 이상하게 쓰면 안된다.(규격임)
createSlice({
    name: 'state이름 ~~',
    initialState: '값'
})

  • reducer에 꼭 등록해줘야 한다.
    • reducer 안에 등록되는 것들은 꼭 reducer를 붙여줘야 한다.

  • store.js 최종
import { configureStore, createSlice } from "@reduxjs/toolkit";

// useState랑 비슷한 용도
let user = createSlice({
    name: "user",
    initialState: "kim",
});

let stocks = createSlice({
    name: 'stocks',
    initialState: [10, 11, 12]
})

export default configureStore({
    reducer: {
        user: user.reducer,
        stocks: stocks.reducer
    },
});

  • Cart.js에서 써보도록 하겠다.
...

import { useSelector } from "react-redux";

...

function Cart() {
    let state = useSelector((state) => {return state});
    let temp_user = useSelector((state) => {return state.user});
    let temp_stocks = useSelector((state) => state.stocks);
    
    console.log(state);
    console.log(state.user);
    console.log(state.stocks);
    
    console.log(temp_user);
    console.log(temp_stocks);
    
    return (
        <div>
            <Table>
                <thead>
                    <tr>
                        <th>#</th>
                        <th>{temp_user}</th>
                        <th>수량</th>
                        <th>변경하기</th>

...

  • Redux를 쓰기 위해서는 외부 라이브러리 설치, 셋팅하는 문법, state를 만들었으면 등록하는 등 코드가 더 길어질 수 있다.
    • 간단한 것은 props를 쓰는 것이 좋을 수 있다.
    • 하지만 컴포넌트가 10개, 20개 되면 Redux가 필요해질 수 있다.
  • Redux를 쓴다고 해서 모든 state들을 store에 저장할 필요는 없다.
    • 공유할 필요가 없으면 그냥 useState로 해주는 것이 훨씬 간단하다.

  • 과제
    Cart.js
import { Table } from "react-bootstrap";
import { useSelector } from "react-redux";

function Cart() {
    // let state = useSelector((state) => {return state});
    // let temp_user = useSelector((state) => {return state.user});

    let cartItems = useSelector((state) => state.cartItems);

    console.log(cartItems);

    return (
        <div>
            <Table>
                <thead>
                    <tr>
                        <th>#</th>
                        <th>상품명</th>
                        <th>수량</th>
                        <th>변경하기</th>
                    </tr>
                </thead>
                <tbody>
                    <Item></Item>
                </tbody>
            </Table>
        </div>
    );
}

function Item(props) {
    let cartItems = useSelector((state) => state.cartItems);
    
    return (
        <>
            {cartItems.map(function(element, index) {
                return (
                    <tr>
                        <td>{index + 1}</td>
                        <td>{element.name}</td>
                        <td>{element.count}</td>
                        <td>안녕</td>
                    </tr>
                );
            })}
        </>
    );
}

export default Cart;

store.js

import { configureStore, createSlice } from "@reduxjs/toolkit";

// useState랑 비슷한 용도
let user = createSlice({
    name: "user",
    initialState: "kim",
});

let temp = [
    { id: 0, name: "White and Black", count: 2 },
    { id: 2, name: "Grey Yordan", count: 1 },
];

let cartItems = createSlice({
    name: "cartItems",
    initialState: temp,
});

export default configureStore({
    reducer: {
        user: user.reducer,
        cartItems: cartItems.reducer,
    },
});



Redux 3: store의 state를 변경하는 방법

  • Redux에서 state를 변경하는 방법
  1. state를 수정해주는 함수 만들기
let user = createSlice({
    name: "user",
    initialState: "kim",
    reducers: {
        // state를 수정하는 함수(setState와 유사)
        changeName(state) {
            return `john ${state}`;
        },
        function2(state) {
            return;
        }
    }
});
  1. 만든 함수를 export
...

export let { changeName, function2 } = user.actions;

...

user.actions는 user의 reducers이다.

  1. 만든 함수를 import 해서 사용
import { useDispatch, useSelector } from "react-redux";
import { changeName } from "../store";

...

function Item() {
    let cartItems = useSelector((state) => state.cartItems);
    let dispatch = useDispatch();
    
...

                        <button onClick={() => {
                            dispatch(changeName());
                        }}>+</button>
                    </tr>
                );
            })}
        </>
    );
}

...

useDispatch는 store.js에 요청을 보내주는 함수이다.

  • redux는 소프트웨어의 사이즈가 커지면 버그를 방지하기에 좋다.



Redux 4: state가 object/array일 경우 변경하는 방법

  • object의 경우 다음과 같이 변경
let user = createSlice({
    name: "user",
    initialState: {name: "kim", age: 20},
    reducers: {
        changeName(state) {
            return {name: "park", age: 20} 
          	// 다음과 같이 작성해도 됨.
            // state.name = "park";
        }
    }
})

state.name = "park"으로 써도 immer.js의 도움을 받아 state를 변경할 수 있다.
array로 가능하다.


  • 다음과 같이 object 혹은 array로 작성하면 immer.js의 도움을 받아 return문을 쓰지 않는 등 state를 변경하기 쉬워서, 변수 1개라도 일부러 object 혹은 array로 작성하기도 한다.
let user = createSlice({
    name: "user",
    initialState: {name: "kim", age: 20},
    reducers: {
        changeName(state) {
            // return {name: "park", age: 20} 
            state.name = "park";
        },
        changeAge(state) {
            state.age += 1;
        }
    }
})
  • state 변경함수에 파라미터 넣기
    • 꼭 다음과 같이 payload(화물)를 써줘야 함.
    • 화물이 도착한다는 의미
    • 주로 인자를 action으로 작명한다.
let user = createSlice({
    name: "user",
    initialState: {name: "kim", age: 20},
    reducers: {
        changeName(state) {
            // return {name: "park", age: 20} 
            state.name = "park";
        },
        changeAge(state, x) {
            state.age += x.payload;
        }
    }
})
  • state가 생각보다 길 수 있다.
    • 따라서 src 안의 store라는 폴더 안에 저장하기도 한다.
      store.js
import user from './store/userSlice.js'

src/store/userSlice.js

import { createSlice } from '@reduxjs/toolkit';

let user = createSlice({
    name: "user",
    initialState: {name: "kim", age: 20},
    reducers: {
        changeName(state) {
            // return {name: "park", age: 20} 
            state.name = "park";
        },
        changeAge(state, action) {
            state.age += action.payload;
        }
    }
})

export let { changeName, changeAge } = user.actions;
export default user;

위와 같이 옮겼으면, 경로 수정을 잊으면 안된다.


주의!!

export let { increaseCount } = cartItems.actions;

위와 같이 1개의 변경 함수를 export 한다고 하더라도 {}로 감싸줘야 한다.




React에서 자주 쓰는 if문 작성 패턴들

  • 컴포넌트 안에서 쓰는 if(else)문
function component() {
    if (true) {
        return <div>I'm true</div>;
    }

    return <div>I'm false</div>;
}
  • JSX안에서(component의 return문) 쓰는 삼항 연산자(ternary operator)
    • 밖에 div 등의 태그로 감싸져야 한다.
function component2() {
    let temp = true;
    
    return (
        <div>
            {temp === false ? <div></div> : <div></div>}
        </div>
    )
}

다음과 같이 중첩도 가능하다. 하지만 가독성이 좋지 않아 지양하는 것이 좋다.

function component2() {
    let temp = false;
    let temp2 = true;

    return (
        <div>
            {temp === true ? (
                <div>temp = true</div>
            ) : temp2 === true ? (
                <div>temp2 = true</div>
            ) : (
                <div>temp = false, temp2 = false</div>
            )}
        </div>
    );
}
  • && 연산자로 if 역할 대신하기
    • 왼쪽 조건문이 false가 되면 false && JSX문이 되는 셈이다. 따라서 아무것도 나오지 못하고 null이 된다.
    • 반면 true이면 true && JSX문이 되고, JSX에서는 마지막 JSX문이 남는다.
function component3() {
    let temp = true;
    let temp2 = false;
    
    return (
        <div>
            {temp === true && <div>temp = true</div>}
            {temp2 === true && <div>false가 연산에 끼어 있으면 null이 반환된다.</div>}
        </div>
    )
}
  • switch / case 조건문
    • 괄호를 줄이는 데에 용이하다.
function component4() {
    let user = 'seller';
    
    switch (user) {
        case 'seller':
            return <h4>판매자 로그인</h4>
        case 'customer':
            return <h4>구매자 로그인</h4>
        default:
            return <h4>그냥 로그인</h4>
    }
}
  • object/array 자료형 응용
    • state가 변할 때 적절하게 tabUI에서 해당하는 정보를 보여줄 수 있다.
const tabUI = {
    info: <div>상품정보</div>,
    shipping: <div>배송관련</div>,
    refund: <div>환불약관</div>
}

function component5() {
    let [state, setState] = useState('info');
    
    return (
        <div>
            {tabUI[state]}
        </div>
    )
}
profile
메타몽 닮음 :) email: alohajune22@gmail.com

0개의 댓글