TIL_220608_리액트

설탕유령·2022년 6월 8일
0
post-custom-banner

state와 props
props

  • Component가 부모 Component로 부터 받아온 데이터
  • 읽기 전용이며 수정하면 안됨
  • 순수 함수처럼 동작해야함(받은 값을 수정하지 않고, 같은 값을 넣으면 동일한 결과를 내주는 함수)
    state
  • Component가 가지고 있는 데이터
  • 함수형 컴포넌트는 useState() 훅을 이용해 상태값을 가질 수 있음
  • state는 직접 수정해선 안됨
  • state는 비동기적으로 업데이트

컴포넌트가 돔에 그려지려면
const root = ReactDOM.createRoot(document.getElementById('root'));
를 사용함
이를 루트 DOM 노드라고 부름
html에

로 대상을 지정함

ReactDOM.createRoot(someElement).render()

  • render()는 루트 돔 노드에 리액트 엘리먼트를 전달
  • 리액트 엘리먼트는 기본적으로 불변 객체
  • render()는 엘리먼트에 변화가 필요한 경우 필요한 부분만 업데이트

Hook이란 함수형 컴포넌트에서도 상태 값을 관리하거나 여러 React 기능을 사용하게 하기 위함
훅을 사용하는데 규칙이 있음

  • 오직 함수형 컴포넌트에서만 쓸 수 있음(React Function 내에서 사용 가능)
  • 컴포넌트의 최상위에서만 호출 할 수 있음(반복문, 중첩문 내에서는 호출 할 수 없음)

[자주 사용하는 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: 특정 함수에 매개변수로 전달된 함수

  • A()가 B()를 콜백으로 받았다면 A()안에서 B를 실행할 수 있음
  • 콜백 패턴은 자바스크립트가 비동기 처리를 하기 위한 패턴 중 하나
  • 전통적인 콜백 패턴은 콜백 헬로 불리는 중첩 문제가 생기기 쉬움

콜백 헬

  • 꼬리에 꼬리를 무는 비동기 처리가 늘어나고, 호출이 계속 중첩되고 깊어지고 관리는 어려워짐
  • 멸망의 피라미드라고 부르기도 함

프라미스: 비동기 연산이 종료된 이후 결과를 알기 위해 사용하는 객체

  • 프라미스를 쓰면 비동기 메서드를 마치 동기 메서드 처럼 값을 반환 가능
  • 콜백 헬로 인해서 도입한 비동기 처리 패턴
  • 비동기 처리 시점을 좀 더 명확하게 표현할 수 있음

프라미스 생성

  • Promise 생성자 함수를 통해 생성
  • 비동기 작업을 수행할 콜백 함수를 인자로 전달받아서 사용
// 프라미스 객체를 만듭니다. 
// 인자로는 (resolve, reject) => {} 이런 excutor 실행자(혹은 실행 함수라고 불러요.)를 받아요.
// 이 실행자는 비동기 작업이 끝나면 바로 두 가지 콜백 중 하나를 실행합니다.
// resolve: 작업이 성공한 경우 호출할 콜백
// reject: 작업이 실패한 경우 호출할 콜백
const promise = new Promise((resolve, reject) => {
	if(...){
		...
		resolve("성공!");
	}else{
		...
		reject("실패!");
	}
});

프라미스의 상태 값

  • pending: 비동기 처리 수행 전(resolve, reject가 아직 호출되지 않음)
  • fulfilled: 수행 성공(resolve가 호출된 상태)
  • rejected: 수행 실패(reject가 호출된 상태)
  • settled: 성공 or 실패(resolve나 reject가 호출된 상태)

프라미스 후속 처리 메서드

  • 프라미스로 구현된 비동기 함수는 프라미스 객체를 반환
  • 프라미스로 구현된 비동기 함수를 호출하는 측에서는 이 프라미스 객체의 후속 처리 메서드를 통해 비동기 처리 결과를 받아서 처리해야함
  • then(성공시, 실패시): then의 첫 인자는 성공 시 수행, 두번째 인자는 실패시 실행
// 프라미스를 하나 만들어 봅시다!
let promise = new Promise((resolve, reject) => {
	setTimeout(() => resolve("완료!"), 1000);
});

// resolve
promise.then(result => {
	console.log(result); // 완료!가 콘솔에 찍힘
}, error => {
	console.log(error); // resolve 대신 reject인 경우 실행
});

async

  • 함수 앞에 async를 붙여서 사용
  • 항상 프라미스를 반환(프라미스가 아닌 값이라도, 프라미스로 감싸서 반환

await

  • async의 짝꿍(async 없이 사용 불가)
  • 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초 후에 완료!가 콘솔에 찍힐거예요.
}

[상태관리]
상태관리는 전역 데이터 관리
렌더링과 연결된 데이터를 컴포넌트끼리 주고 받는게 번거로우니 전역 데이터를 만들고 관리

형제 컴포넌트 끼리 데이터를 주고 받는 방식

  • 부모 컴포넌트에서 state를 생성하고 자식 컴포넌트들에게 props로 데이터와 state를 변경할 함수를 넘겨줌
  • 자식 컴포넌트는 props로 받아온 값을 참조해서 쓰고, 값 변경이 필요한 경우는 넘겨받은 함수로 해당 값을 변경해줌

[ContextAPI()]
React.CreateContext()를 이용해 데이터를 저장할 공간을 만듬

const MyStore = React.createContext();

Context.Provider를 통해 데이터를 주입
Provider는 Context를 구독한 컴포넌트들에게 데이터 변경 사항을 알려줌

<MyStore.Provider value={state}>
  <Component1 />
  <Component2 />
</MyStore.Provider>

Context.Consumer

  • Consumer는 컴포넌트가 context를 구독하게 해줌
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

  • 리덕스 Store를 Component에 연결
  • Component에서 상태 변화가 필요할 때 Action을 호출
  • Reducer를 통해서 새로운 상태 값 생성
  • 새 상태값을 Store에 저장
  • Component는 새로운 상태값을 받아옴(Props를 통해 받아오며 다시 랜더링 됨)

리덕스는 상태관리 라이브러리
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;
profile
달콤살벌
post-custom-banner

0개의 댓글