state와 props
props
컴포넌트가 돔에 그려지려면
const root = ReactDOM.createRoot(document.getElementById('root'));
를 사용함
이를 루트 DOM 노드라고 부름
html에
ReactDOM.createRoot(someElement).render()
Hook이란 함수형 컴포넌트에서도 상태 값을 관리하거나 여러 React 기능을 사용하게 하기 위함
훅을 사용하는데 규칙이 있음
[자주 사용하는 Hook]
useState(): 상태 관리를 위한 훅
useState()는 값을 넣어두고 참조하기 위한 변수(state 변수), 값을 바꿔주기 위한 함수를 반환
const [someValue, setValue] = useState("hi");
useEffect(): 컴포넌트의 사이드 이펙트 관리를 위한 훅
useEffect(() => {
if(is_loaded){
window.alert("hi! im ready! ٩(๑•̀o•́๑)و");
}
return () => {
window.alert("bye!");
}
}, [is_loaded]);
useEffect(() => {} {});
구조에서 첫번째 인자는 컴포넌트가 화면에 그려질 때 실행할 함수
두번째 인자는 디펜던시 어레이라고 하며, 배열에 넣은 값이 변했을 때 첫번째 인자를 다시 실행(빈 값으로 두면 Effect는 처음 그려질때만 수행됨)
return 부분을 clean up이라고 부르며 컴포넌트가 화면에서 사라질 때 마지막으로 정리하는 용도
useCallBack(): 함수를 메모이제이션 하기 위한 훅
메모이제이션이란 메모리 어딘가에 두고 다시 사용한다는 뜻
함수형 컴포넌트가 리렌더링되면 컴포넌트 안에 선언해둔 함수나 인라인으로 작성한 함수를 다시 생성함
useCallBack()은 함수를 메모리제이션해서 여러번 만들지 않게 함
주로 자식 컴포넌트에게 전달해주는 콜백 함수를 메모이제이션할 때 사용
const myNewFunction = useCallback(() => {
console.log("hey!", need_update);
}, [need_update]);
사용 구조는 useEffect와 비슷함
useRef(): ref객체를 다루기 위한 훅
도플갱어 박스 같은 존재
어떤 값을 넣어주면 그 값으로 초기화된 변경 가능한 ref 객체를 반환
원본이 아닌 똑같이 생긴 다른 값이라 변경도 됨
변경해도 리렌더링이 일어나지 않음
[Promise]
동기와 비동기
callback: 특정 함수에 매개변수로 전달된 함수
콜백 헬
프라미스: 비동기 연산이 종료된 이후 결과를 알기 위해 사용하는 객체
프라미스 생성
// 프라미스 객체를 만듭니다.
// 인자로는 (resolve, reject) => {} 이런 excutor 실행자(혹은 실행 함수라고 불러요.)를 받아요.
// 이 실행자는 비동기 작업이 끝나면 바로 두 가지 콜백 중 하나를 실행합니다.
// resolve: 작업이 성공한 경우 호출할 콜백
// reject: 작업이 실패한 경우 호출할 콜백
const promise = new Promise((resolve, reject) => {
if(...){
...
resolve("성공!");
}else{
...
reject("실패!");
}
});
프라미스의 상태 값
프라미스 후속 처리 메서드
// 프라미스를 하나 만들어 봅시다!
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("완료!"), 1000);
});
// resolve
promise.then(result => {
console.log(result); // 완료!가 콘솔에 찍힘
}, error => {
console.log(error); // resolve 대신 reject인 경우 실행
});
async
await
async function myFunc(){
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("완료!"), 1000);
});
console.log(promise);
let result = await promise; // 여기서 기다리자!하고 신호를 줍니다.
console.log(promise);
console.log(result); // then(후처리 함수)를 쓰지 않았는데도, 1초 후에 완료!가 콘솔에 찍힐거예요.
}
[상태관리]
상태관리는 전역 데이터 관리
렌더링과 연결된 데이터를 컴포넌트끼리 주고 받는게 번거로우니 전역 데이터를 만들고 관리
형제 컴포넌트 끼리 데이터를 주고 받는 방식
[ContextAPI()]
React.CreateContext()를 이용해 데이터를 저장할 공간을 만듬
const MyStore = React.createContext();
Context.Provider를 통해 데이터를 주입
Provider는 Context를 구독한 컴포넌트들에게 데이터 변경 사항을 알려줌
<MyStore.Provider value={state}>
<Component1 />
<Component2 />
</MyStore.Provider>
Context.Consumer
function App() {
// Context의 Value는 App 컴포넌트에서 관리하자!
const [name, setName] = useState("민영");
return (
<MyStore.Provider value={{ name, setName }}>
<MyStore.Consumer>
{(value) => {return <div>{value.name}</div>}}
</MyStore.Consumer>
</MyStore.Provider>
);
}
useCOntext()를 사용하면 좀더 단순하게 사용 가능
// MyStore.Provider를 찾는데 성공할 컴포넌트
const SomeComponentInProvider = () => {
const { name, setName } = useContext(MyStore); // useContext에 createContext한 Store를 넣어줌
return (
<div>
{name}
<button onClick={() => setData("perl")}>바꾸기</button>
</div>
);
}
context를 만들 때 나온 값과 함수가 있음
값은 가져다 쓰기 위함이고 함수는 값을 고쳐쓰기 위함
import React, { useState, useContext } from "react";
// Context를 만들기
const MyStore = React.createContext();
const MyStoreConsumer = () => {
const { name, setName } = useContext(MyStore);
return (
<div>
{name}
<button onClick={() => setName("perl")}>바꾸기</button>
</div>
);
};
function App() {
// Context의 Value는 App 컴포넌트에서 관리
const [name, setName] = useState("민영");
return (
<MyStore.Provider value={{ name, setName }}>
<MyStoreConsumer />
</MyStore.Provider>
);
}
export default App;
[Redux]
상태관리의 흐름도: Store, Action, Reducer, Component
리덕스는 상태관리 라이브러리
State: 리덕스에서 저장하고 있는 상태값(데이터)
Action: 상태에 변화가 필요할 때 발생
// 액션은 객체예요. 이런 식으로 쓰여요. type은 이름같은 거예요! 저희가 정하는 임의의 문자열을 넣습니다.
{type: 'CHANGE_STATE', data: {...}}
ActionCreator: 액션 생섬 함수, 액션을 만들기 위해 사용
//이름 그대로 함수예요!
const changeState = (new_data) => {
// 액션을 리턴합니다! (액션 생성 함수니까요. 제가 너무 당연한 이야기를 했나요? :))
return {
type: 'CHANGE_STATE',
data: new_data
}
}
Reducer: 리덕스에 저장된 상태(데이터)를 변경하는 함수
액션 생성 함수 -> 액션 생성 -> 리듀서가 현재 상태와 액션 객체를 받음 -> 새로운 데이터 생성 -> 리턴
// 기본 상태값을 임의로 정해줬어요.
const initialState = {
name: 'mean0'
}
function reducer(state = initialState, action) {
switch(action.type){
// action의 타입마다 케이스문을 걸어주면,
// 액션에 따라서 새로운 값을 돌려줍니다!
case CHANGE_STATE:
return {name: 'mean1'};
default:
return false;
}
}
Store: 리듀서, 애플리케이션 상태, 리덕스에서 값을 가져오고 액션을 호출하기 위한 몇 가지 내장 함수 포함
dispatch: 액션을 발생 시키는 역할을 함
리덕스가 필요 없는 경우
[Redux Tool Kit]
리덕스 Toolkit(이하 RTK)는 리덕스를 더 쉽게 사용하기 위한 용도
대표적인 리덕스의 문제 3가지
RTK를 사용 시 액션 타입, 액션 생성 함수, 리듀서, 기초값을 한번에 묶어서 사용 가능
[store 만들기]
import 필요 사항
import { createStore, combineReducers, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import { createBrowserHistory } from "history";
import { connectRouter } from "connected-react-router";
import User from "./modules/user";
root reducer 만들기
const rootReducer = combineReducers({
user: User,
});
미들 웨어 설정
const middlewares = [thunk];
// 지금이 어느 환경인 지 알려줘요. (개발환경, 프로덕션(배포)환경 ...)
const env = process.env.NODE_ENV;
// 개발환경에서는 로거라는 걸 하나만 더 써볼게요.
if (env === "development") {
const { logger } = require("redux-logger");
middlewares.push(logger);
}
크롬확장 프로그램 redux devTools 사용
https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd
미들웨어 묶기
const enhancer = composeEnhancers(
applyMiddleware(...middlewares)
);
미들웨어와 루트 리듀서를 엮어서 스토어를 만듬
let store = (initialStore) => createStore(rootReducer, enhancer);
export default store();
[slice 만들기]
import
import { createAction, handleActions } from "redux-actions";
import { produce } from "immer";
필요한 액션 생성
const SET_POST = "SET_POST";
const ADD_POST = "ADD_POST";
const setPost = createAction(SET_POST, (post_list) => ({post_list}));
const addPost = createAction(ADD_POST, (post) => ({post}));
initialState 만들기
const initialState = {
list: [],
}
// 게시글 하나에는 어떤 정보가 있어야 하는 지 하나 만들어둡시다! :)
const initialPost = {
user_info: {
id: 0,
user_name: "mean0",
user_profile: "https://mean0images.s3.ap-northeast-2.amazonaws.com/4.jpeg",
},
image_url: "https://mean0images.s3.ap-northeast-2.amazonaws.com/4.jpeg",
contents: "고양이네요!",
comment_cnt: 10,
insert_dt: "2021-02-27 10:00:00",
};
리듀서 작성
// reducer
export default handleActions(
{
[SET_POST]: (state, action) => produce(state, (draft) => {
}),
[ADD_POST]: (state, action) => produce(state, (draft) => {
})
},
initialState
);
export 진행
// action creator export
const actionCreators = {
setPost,
addPost,
};
export { actionCreators };
[redux hook으로 데이터 구독]
// pages/PostList.js
...
import {useSelector} from "react-redux";
...
const PostList = (props) => {
const post_list = useSelector((state) => state.post.list);
...
[redux hook으로 데이터 수정]
...
import {actionCreators as userActions} from "../redux/modules/user";
import { useDispatch } from "react-redux";
const Login = (props) => {
const dispatch = useDispatch();
const [id, setId] = React.useState('');
const [pwd, setPwd] = React.useState('');
const changeId = (e) => {
setId(e.target.value);
}
const changePwd = (e) => {
setPwd(e.target.value);
}
const login = () => {
dispatch(userActions.login({user_name: "perl"}));
}
return (
<React.Fragment>
<Grid padding={16}>
<Text type="heading">로그인 페이지</Text>
</Grid>
<Grid padding={16}>
<Input value={id} onChange={changeId} placeholder="아이디를 입력하세요."/>
<Input value={pwd} onChange={changePwd} type="password" placeholder="비밀번호를 입력하세요."/>
</Grid>
<Button __click={() => {login();}}>로그인</Button>
</React.Fragment>
)
}
export default Login;