- App에서 사용하는 데이터를 아래의 컴포넌트에서도 사용하고 싶을 때 보통 props를 사용한다
- 그런데 값을 props로 계속해서 넘겨주면 중간 컴포넌트는 값을 넘겨주기만 하는 역할만 하게된다
- 이러한 react의 상태(state)관리를 돕는 방법을 두가지 학습했다
- 기본 기능인 context Api와 설치 라이브러리인 redux이다
- 리액트에서 기본적으로 제공하는 useContext()라는 hook을 사용한다
- context를 사용하면 컴포넌트 재사용이 어려워질 수 있기 때문에 꼭 필요할 때만 사용해야한다
- 새로운 context를 생성하고 export해준다
- createContext(초기값)으로 생성하는데 보통 초기값으로는 null을 설정하고 이후
전달할 때 값을 부여해서 전달한다
import { createContext } from "react";
export const AgeContext = createContext(null);
- 값을 사용할 컴포넌트를 context의 .provider로 감싸준다
- value라는 이름의 props로 전달하려는 값을 전달한다
<ThemeContext.Provider value={"dark"}>
<Box />
</ThemeContext.Provider>
- 전달받은 컴포넌트는 useContext(context이름)을 통해 곧바로 value값에 접근할 수 있다
- 위의 코드의 경우 Box컴포넌트에서 다음과 같이 dark라는 값을 받는다
export default function Box() {
const context = useContext(ThemeContext);
console.log(context);
return (
<>
<div>{context}</div>
</>
);
}
- state와 setter함수를 app.js에서 설정하고 context의 value로 해당 state를 전달하는 것도 가능하다
- 아래의 경우 Profile컴포넌트는 useContext(AgeContext)를 const { age, setAge }에 저장하여 곧바로 사용할 수 있다
const { age, setAge } = useState(20);
<AgeContext.Provider value={{ age, setAge }}>
<Profile />
</AgeContext.Provider>
- 다만 이러한 state값을 app.js에서 설정하기 보다는 provider를 다른 파일에서 설정하고 import해서 사용하는 것이 더 깔끔하고 바람직하다
- 수업에서는 provider 폴더를 생성하여 state를 생성하고 값을 전달할 컴포넌트를 children으로 받아 실제 context사이에 해당 컴포넌트를 감싸는 코드를 리턴하도록했다
- app.js구조
<AgeProvider>
<Profile />
</AgeProvider>
- AgeProvider구조(즉, Profile을 children으로 받는것)
export default function AgeProvider(props) {
const { children } = props;
const [age, setAge] = useState(20);
return (
<AgeContext.Provider value={{ age, setAge }}>
{children}
</AgeContext.Provider>
);
}
- react의 상태관리 라이브러리로 가장 많이 사용된다
- 현재는 redux외에 recoil도 자주 사용되는 추세라고 한다
- 이전에 학습한 useReducer와 작동과정이 거의 유사하다
- 설치명령: redux, react-redux, @reduxjs/toolkit 설치
- 수업에서는 store폴더에 reducer를 통합하는 index.js파일과 reducer들을 모아둔 module폴더를 위치시키고, app을 import하는 index.js에서 store로 불러와 사용했다
- root.render에 app컴포넌트를 provider로 감싸 하위 컴포넌트 전역에서 store에 접근할 수 있도록한다
- 통합된 rootReducer를 configureStore를 사용해 store로 설정한다
const store = configureStore({ reducer: rootReducer });
root.render(
<Provider store={store}>
<App />
</Provider>
);
- combineReducers()를 통해 {}객체형태로 키: 리듀서를 저장한다
- 통합된 rootReducer를 export하여 위의 index.js에서 store로 사용하게 된다
//여러개의 리듀서를 하나로 합쳐주는 combineReducers
const rootReducer = combineReducers({
isData: isDataReducer,
counter: counterReducer,
money: moneyReducer,
});
export default rootReducer;
- reducer는 action을 받아 state를 변경한다
- reducer함수는 첫번째인자로 state, 두번째인자로 action을 받는다
- state의 초기값 선언도 함수선언시 인자를 적으면서 적는다
- action 설정은 reducer를 사용하는 컴포넌트에서 설정하지않고, 받을 값을 함수형태로 Reducer파일에서 설정후 import하여 사용하는것이 오타 등으로 인한 오류를 방지할 수 있어 좋다
- 예시코드는 입금과 출금에 따라 money의 값을 변경하는 reducer로 action으로는 문자열 입금 혹은 출금값(type)과 금액(payload)를 받는다. payload는 사용하는 컴포넌트에서 input의 value를 전달한다
//초기값 0으로 설정
const initialState = 0;
const DEPOSIT = "money/deposit";
const WITHDRAW = "money/withdraw";
//컴포넌트에서 deposit과 withdraw함수를 사용하게된다
//dispatch(deposit(payload))의 형태
export const deposit = (payload) => ({ type: DEPOSIT, payload: payload });
export const withdraw = (payload) => ({ type: WITHDRAW, payload: payload });
export default function moneyReducer(moneyState = initialState, action) {
if (action.type === WITHDRAW) {
return moneyState - action.payload;
} else if (action.type === DEPOSIT) {
return moneyState + action.payload;
} else {
return moneyState;
}
}
- reducer를 컴포넌트에서 사용할 때는 두가지 훅이 사용된다
- 1. useSelector(): 현재 state값을 가져오기 위한 훅이다 인자로 콜백함수를 받는다. 기본적으로 (state)=> state값을 받아 저장하면 모든 state를 가져올 수 있지만 해당방법은 지양하고 state.reducer이름으로 접근하여 특정 state값을 가져오는 것이 좋다. 여기서 reducer의 이름은 rootReducer생성 시 사용한 키 값을 의미한다
- 2. useDispatch(): dispatch를 사용하기위해 불러오는 훅이다. useReducer와 마찬가지로 특정 이벤트 발생 시 호출해 action을 전달하면된다
- 예시코드는 onClick이벤트 후 dispatch를 사용하며, 값 전달과 이후 focus등의 기능을 주기 위해 ref와 개별실행함수를 사용하였다
export function Practice1() {
const money = useSelector((state) => state.money);
const numberRef = useRef();
const dispatch = useDispatch();
function depositFunc() {
dispatch(deposit(Number(numberRef.current.value)));
numberRef.current.value = "";
numberRef.current.focus();
}
function withdrawFunc() {
dispatch(withdraw(Number(numberRef.current.value)));
numberRef.current.value = "";
numberRef.current.focus();
}
return (
<div>
<h2>코딩온 은행</h2>
<h3>잔액: {money}원</h3>
<input type="number" ref={numberRef} step={100} />
<button onClick={depositFunc}>입금</button>
<button onClick={withdrawFunc}>출금</button>
</div>
);
}