https://react-bootstrap.github.io/
Bootstrap 설치 및 CSS 파일 import
npm install react-bootstrap bootstrap
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
crossorigin="anonymous"
/>
컴포넌트를 import 해야 하는 것도 잊지 말것!
import { Button } from 'react-bootstrap';
장바구니 페이지를 만들기 위해 Cart.js
파일을 생성했다.
장바구니 데이터를 state에 보관해두고 데이터 바인딩을 해보자.
그런데 이 state가 Cart
외에 다른 컴포넌트에서도 필요하다면 어디에 만들어야 할까? 👉 당연히 App
(최상위 컴포넌트)에 만들어야 하는데, props 전송하는 게 귀찮다!
이때, 사용하는 것이 Redux이다.
리덕스를 사용하면 모든 컴포넌트들이 state를 직접 빼서 사용할 수 있다. (= props를 전송할 필요가 없다.)
React, React DOM 버전이 18.1.0 이상이어야 설치가 가능하다. (package.json
에서 확인 가능)
npm install @reduxjs/toolkit react-redux
store.js
생성 👉 쉽게 말해서, state를 보관하는 통이라고 생각하면 된다.
import { configureStore } from '@reduxjs/toolkit'
export default configureStore({
reducer: { }
})
index.js
에 store를 사용하겠다고 해야 한다. 👉 <Provider store={store}>
이제 store.js
에 있던 state를 App
에서 가져다 쓸 수 있다. (<Provider>
로 감싸져 있는 모든 자식이 state를 가져다 쓸 수 있는 것)
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>
);
reportWebVitals();
store
에 state
보관state 하나를 'slice'라고 부른다.
createSlice()
를 상단에서 import 해온 다음, { name : 'state이름', initialState : 'state값' }
를 넣으면 state가 하나 생성된다.
let user = createSlice({
name: 'user',
initialState: 'kim'
})
이렇게 생성을 했으면, 등록을 해야 사용할 수 있다.
state 등록은 configureStore()
안에 하면 된다. { 작명 : createSlice만든거.reducer }
. 여기에 등록한 state는 모든 컴포넌트가 자유롭게 사용할 수 있다.
export default configureStore({
reducer: {
user: user.reducer
}
})
어떻게 사용?
이제 Cart.js
에서 useSelector()
를 이용하면 된다. useSelector()
는 Redux store를 가져와주는 함수이다.
let a = useSelector((state) => {return state});
console.log(a);
console.log(a.user);
🤔
useSelector()
를 편하게 쓰려면?
state
: store 안에 있던 모든 state를 뜻한다.let a = useSelector((state) => {return state});
user라는 항목만 저장해서 가져다 쓸 수도 있다.let a = useSelector((state) => {return state.user});
축약해서 사용 가능!let a = useSelector((state) => state.user);
간단한 프로젝트에서는 props를 전송하는 것이 코드가 더 간편하다.
그러나 컴포넌트가 많아지면 Redux를 사용하는 것이 더 나을 수 있다! 또한, 반드시 모든 state를 store에 관리해야 하는 것은 아니다. (공유가 필요하지 않은 state라면 필요한 컴포넌트에서 생성해도 된다.)
Cart.js
에서 사용하기장바구니 데이터를 store에 저장하고 Cart.js
에서 가져와서 출력해보자.
// 장바구니 데이터
[
{id : 0, name : 'White and Black', count : 2},
{id : 2, name : 'Grey Yordan', count : 1}
]
let data = useSelector((state) => {return state.data});
...
<tbody>
{
data.map((item, i) =>
<tr key={data[i].id}>
<td>{data[i].id}</td>
<td>{data[i].name}</td>
<td>{data[i].count}</td>
<td>변경</td>
</tr>
)
}
</tbody>
...
store
의 state
변경🤔 Redux state 변경 step
1. state를 변경해주는 함수를 만든다.
2. 만든 함수를 export한다.
3. 원할 때 그 함수를 실행해달라고store.js
에 요청한다. 👉dispatch(state변경함수())
기존에 저장된 데이터
let user = createSlice({
name: 'user',
initialState: 'kim'
})
state를 수정해주는 함수 만들기
기존 state인 kim을 John kim으로 변경해준다.
let user = createSlice({
name: 'user',
initialState: 'kim',
reducers: {
changeName() {
return 'John kim';
}
}
})
만약, 기존 state가 필요하다면, 다음처럼 하면 된다. (changeName()의 인자로 들어간 state는 기존 state를 의미한다.)
let user = createSlice({
name: 'user',
initialState: 'kim',
reducers: {
changeName(state) {
return 'John' + state;
}
}
})
만든 함수를 export하기
user.actions
: state 변경 함수가 남는다.
다음 코드는 오른쪽 자료를 변수로 빼는 문법이라고 생각하면 된다.
export let { changeName } = user.actions;
만든 함수 import해서 사용
이제 이름을 바꾸고 싶을 때 함수를 호출하면 된다.
버튼을 누르면 'kim'에서 'John kim'으로 변경되도록 버튼에 onClick
을 줘서 만들 수 있다.
useDispatch()
: store.js로 요청을 보내주는 함수이다.
let state = useSelector((state) => {return state});
let dispatch = useDispatch();
...
<td><button onClick={() => dispatch(changeName())}>+</button></td>
...
🤔 이렇게 복잡한 이유..?
🤔 그래서 이 방식이 뭐가 좋은 거지?
컴포넌트가 많은 상황에서 효율적이다.
user
를 여러 컴포넌트에서 가져다 쓰는데, 각 컴포넌트에서 직접 수정을 할 수 있는 상황이라고 가정해보자. 갑자기 'kim'이 '123'으로 바뀌었다면, 모든 컴포넌트를 뒤져서 어디서 바뀐 건지 알아내야 한다.
하지만 수정함수를 호출하는 식일 때는 '123'으로 바뀌었으면store.js
만 보면 된다.
state
가 array/object
인 경우 변경array/object의 경우 return문을 사용하지 않고 직접 수정해도 state 변경이 된다. (state.name = "park";
)
그래서 문자 하나만 필요하더라도 일부러 {}
안에 담기도 한다.
// store.js
let user = createSlice({
name: 'user',
initialState: {
name: 'kim',
age: 20
},
reducers: {
changeName1(state) {
state.name = "park";
}
}
})
export let { changeName1 } = user.actions;
// Cart.js
let state = useSelector((state) => {return state});
let dispatch = useDispatch();
...
<button onClick={() => dispatch(changeName1())}>1</button>
age
가 1씩 증가// store.js
increaseAge(state) {
state.age += 1;
}
// Cart.js
<button onClick={() => {
dispatch(increaseAge());
}}>버튼</button>
근데 경우에 따라 증가하게 할 숫자가 다르다면 다음처럼 새로운 함수를 계속 만들어야 할까?
// store.js
increaseAge1(state) {
state.age += 1;
},
increaseAge2(state) {
state.age += 5;
},
increaseAge3(state) {
state.age += 10;
}
No.
함수에 파라미터를 하나 더 주면 된다.
// store.js
increaseAge(state, action) {
state.age += action.payload;
}
// Cart.js
<button onClick={() => {
dispatch(increaseAge(5));
}}>버튼</button>
🤔
action
action.type
하면 state 변경함수 이름이 나온다.action.payload
하면 파라미터가 나온다.