본문 바로가기
React

리액트 학습 정리

by ahj 2022. 9. 12.

JS Tips

Object에서 점(.) 표기법 말고 괄호([key]) 방식은 동적인 parameter 등으로 값을 받아와서 접근할때 유용

let obj = {
	name: "이름",
	age: 25
}
function getName(key) {
	return obj[key];
}
console.log(getName("name"));

object 내부 property 바꿀 때, delete로 지울 수 도 있겠지만, 메모리를 계속 잡고 있기 때문에, property 수정 or 추가 하듯이 obj.key = null 이런 식으로 제거 해주자

객체 안에 있는 함수 property는 method라고 부름

in 연산자를 통해 object 내부에 있는 key인지(property) 확인 가능

// 비구조화 할당
let obj = {one:"one", two: "two", name: "이름"};
let {name: myName, one: oneOne, two, abc ="four"} = obj;
console.log(oneOne, two, myName, abc);

비동기 원리(non-blocking)

  • 함수가 호출 되고서 콜스택에 쌓였을 때, 비동기 함수의 경우에 JS 엔진이 Web APIs로 넘긴다. 이 Web APIs에서 본인 대기 시간만큼 대기한다. → 이렇게 비동기 함수가 Call Stack에 쌓여서 기다리지 않기 때문에 다음 동기 처리되는 놈들은 처리가 된다. 이렇게 비동기 함수를 호출한 또다른 함수는 당연히 제거가 되겠지→ 비동기 함수의 대기 시간이 지나고서 사라지고 해당 비동기 함수가 갖고 있던 Callback 함수가 Callback Queue로 옮겨지게 된다.→ Callback Queue에 있는 Callback 함수들은 Event Loop에 의해서 Call Stack으로 옮겨질 수 있다. Call Stack으로 옮겨진다는 것은 실제로 실행이 된다는 것→ Event Loop가 옮겨주는 방식 : Event Loop는 항상 Call Stack에 Main Context외의 함수가 남아있나 확인을 한다. 아무것도 없다면 그때는 Callback 함수를 실행할 수 있다는 것이므로 넘겨준다.

비동기 3가지 상태

  1. Pending(대기 상태)
  2. -resolve→Fulfuilled(성공)
  3. -reject→Rejected(실패)

Promise 객체는 비동기 작업 그 자체

이정환 강의 동기, 비동기, Promise 부분 다시 보기 ⇒ 좋음

Async & await

async를 붙여주면 Promise를 반환한다.

async 를 붙여준 함수의 return 값은 Promise 객체의 resolve 의 결과값이 된다.

await가 붙어 있는 함수의 호출은 그 함수가 끝나기 전까지 아래 코드를 수행하지 않는다. await 붙어 있는 줄은 다 동기적으로 수행되는 것. async가 붙은 함수 내에서만 사용 가능

fetch

fetch(링크) : API를 호출 할 수 있도록 돕는 내장함수. Promise<Response>를 return


내 개인 공부

배열 얕은 복사로 새로운 배열 받으려면 arr.slice() 쓰자

for…in은 상위 객체까지 접근해서 가져오기 때문에 배열에서 쓰는거 비추


Node

Module

// calc.js
const add = (a, b) => a + b;
const sub = (a, b) => a - b;

module.exports = {
	moduleName : "calc module",
	add : add,
	sub : sub
};

//-----------------------------------------
// index.js
const calc = require("./calc"); // 동일 폴더 내 calc.js의 module을 가져온다.

console.log(calc); // module.exports 해준 {} 객체와 동일하게 출력

즉, Node.js에서는 module.exports를 통해 내보낸 module을 경로 + 내장함수 require을 통해 가져와 사용할 수 있다. ⇒ 이것이 node.js가 기본적으로 제공하는 common js라는 module system. 이외에도 es모듈

모듈 시스템이란 간단히 말해 모듈을 내보낼 수 있고, 불러와 사용할 수 있는 기능들을 제공하는 시스템

node.js의 내장함수이기 때문에 Vanilla JS에서는 불가

Package

간단히 말해 다른 사람이 만들어 놓은 node 모듈을 패키지라고 한다.

package.json이란 만들 package의 정보를 기록하는 환경 설정 파일 정도.

main에 기록된 index.js 같은 거를 진입 파일이라고 하는데 → 기능들을 나눠 개발하다보면 여러개로 나눠져서 뭘 사용할 지 모를 수 있는데 하나로 묶어서 이거 사용하면 된다 알려주는 게 진입파일이라 생각하자

npmjs : node.js 패키지 쇼핑몰

package.json은 간단한 정보

package-lock.json은 정확한 버전 정보 등


React

Why React?

기본적 특징 3가지만 우선

  1. Shotgun Surgery를 막기 위해 (중복 코드 발생 방지) → 재사용 되어야 하는 요소들을 컴포넌트화
    React는 Component 기반의 UI 라이브러리
  2. 명령형(절차지향) 프로그래밍은 하나하나 다 입력해줘야 한다. 간단한 프로그램도 매우 길어질 수 있다. : jQuery
    반면, 선언형 프로그래밍은 목적을 바로 말하고 거기에 맞춰 프로그래밍 할 수 있다. : React
  3. 가상 돔 (Virtual DOM)을 사용.가상 돔을 활용하면 가상DOM 에다가 계산을 다 해놓고 한번에 update할 부분만 모아서 한번에 실제 DOM에 update 시키다. 간단하게는 5번 연산할 것을 1번으로 줄여줄 수도 있는 것
    물론 Buffer를 사용해서 해결하는 방법도 있지만 구현이 까다롭다. 고수준 JS, CS 지식 요
    DOM을 직접 수정하면(Vanilla 등으로 개발 할 때처럼), 하다못해 append 명령어 같은거로 요소 추가 하나하나마다 DOM 수정부터 렌더링 과정까지 다 하고 계산하기 때문에 성능상에 issue가 발생할 수 있다.

Create React App

얘는 사실 boiler plate다. 그런데 이거 안해주면 webpack이나 babel 같은 설정을 다 직접 해줘야 함,,

webpack : 다수 JS를 하나의 파일로 합쳐주는 module bundler

babel : jsx를 사용할 수 있도록 해주는 library

npx : 일회용 → create react app과 같은 명령어들은 버전에 민감하고 업데이트가 빈번하기 때문에 npm 을 이용해서 설치하면 오류 발생할 수 있다. npm을 매번 최신버전으로 업데이트 해줘야 하기 때문. 이를 해결하기 위해 나온 것이 npx. npx는 항상 최신버전을 가져와서 설치해준다.

관련 좋은 글

https://ljh86029926.gitbook.io/coding-apple-react/undefined/npm-npx

깃헙 같은 곳에서 받아온 package의 경우 node_module 재설치하려면 npm i 만 해주면 된다.

jsx : JavaScript Xml (eXtensible Markup Language).

State, Props 관련

Component는 state가 바뀌면 rerender가 일어나는데, component 함수가 다시 호출되는 것이다.

여러개의 state를 하나의 component가 가져도 문제되지 않는다.

props에서 defaultProps를 사용해서 오류를 방지할 수도 있다. 부모에서 실수로 내려주지 않더라도

state가 바뀌면 함수가 다시 호출되니까 자식 component도 다시 호출되고 props로 전달된 state값이 자식에 적용도 된다

정리

  1. 본인이 가진 state가 변경되면 함수가 다시 호출되면서 rerender가 된다.
  2. 나에게 내려오는 props가 바뀔때마다 rerender가 된다.
  3. 부모가 rerender가 되면 나도 rerender된다.
  4. Props로 Component도 내려줄 수 있다.
return ( 
    <Container> 
    	<Child /> 
    </Container> 
)

이렇게 Container 안에 Child 넣어주면

const Container = ({children}) => { 
    return ( 
        <div>{children}</div>
    )
}

감싸준 Component에서 children이라는 prop으로 가져와서 사용할 수 있다.

막간 Tip

계속 코드만 짜기보다는 가끔 컴포넌트 트리를 그려보면서 작업을 하면 도움이 많이 된다.

리액트는 같은 level끼리는 data를 주고 받을 수 없다.

State 끌어올리기

setDate등을 prop으로 받은 Component에서 부모의 state, data를 변경시켜주면, data를 prop으로 받고 있던 자식 component에서도 rerender 등 변화 발생

state 끌어올리기, 단방향 data 흐름, 역방향 data 흐름

Lifecycle

탄생(Mount, ComponentDidMount), 변화(Update, ComponentDidUpdate), 죽음(Unmount, ComponentWillUnmount)

→ 아쉽게도 Class React Component Only로 Class Component에서만 사용 가능

원래 state 같은 것들도 사용하지 못한다. 그런데 우리는 useState등을 통해서 state를 잘 사용해왔다.

중요 사실: React18을 사용하고 React.StrictMode 하에서는 mount가 2번 된다


Hooks

use 키워드를 사용해 원래 class형 component가 근본적으로 가지고 있는 기능을 함수형 component에서 Hooking해서(낚아채서) 사용할 수 있도록 해주는 놈 ⇒ React Hooks

⇒ react hook는 사실 19년도 6월에 정식 출시된 기능이다.

class형 컴포넌트는 길어지는 코드 길이 문제, 중복코드, 가독성 문제가 있다.


useEffect

Lifecycle 관리 할 수 있도록 돕는 녀석

useEffect(() => {}/*Callback함수*/, []/*Dependency Array(의존성 배열)*/);
// 의존성 배열 내 값이 하나라도 변화하면 콜백함수가 수행된다.
// 의존성 배열 자리에 빈 배열[]을 넣어두면 mount 되는 시점에 동작하는 코드
// 아무것도 넣어두지 않으면 update 될때마다 동작하는 코드
// [특정 값]을 넣어두면 특정값 바뀔때마다 동작

구조 그리면서 리렌더 여부 확인


성능 최적화

useMemo : 값 재사용

memoization을 통한 값기억 성능 최적화

useEffect를 사용하는 것처럼 dependency array를 target으로 의존성 배열이 바뀌기 전까지 useMemo를 해둬서 return 해준 값들은 그냥 memoization 해두고 사용할 수 있다.

⇒ 정리: useMemo 해둔 값은 memoization 해둔 것이고 함수가 아니라 값으로 가져다 사용

React.memo : Component 재사용

함수형 컴포넌트에게 업데이트 조건 걸기

인자로 전달된(감싼) 컴포넌트를 강화된 컴포넌트로 돌려주는 고차 컴포넌트

고차 컴포넌트 : 인자로 컴포넌트를 받아서 강화된 컴포넌트로 돌려주는 함수

React.memo는 전달받은 props들의 값이 바뀌지 않으면 렌더링하지 않게 메모이제이션 해주는 성능 최적화 기법 중 하나

https://ko.reactjs.org/docs/react-api.html#reactmemo

물론 자기 자신의 state가 바뀌면 re-render된다. 부모의 변화에 대해서 리렌더하지 않겠다 뿐이다.

어떤 Component를 최적화할 것인가?

devtools를 이용해서 어떤 부분이 rerender되는지 확인하면서 찾아보기. useEffect랑 console을 통해 확인해줄 수 있겠지만 모든 컴포넌트마다 확인하고 지우기 힘들잖아

useMemo vs useCallback

useMemo는 값을 반환, useCallback은 함수를 반환

useCallback

memoization된 콜백 함수를 반환

dependency 배열이 변하지 않으면 첫번째 인자 자리에 있는 함수를 재사용할 수 있게 해주는 훅(달리 말하자면, 첫번째 인자 자리 함수가 재생성되지 않도록)

const [data, setData] = useState([]/*mount 되는 시점의 data의 상태*/);
const onCreate = useCallback(
    (author: string, content: string, emotion: number) => {
      const created_date = new Date().getTime();
      const newItem: DiaryInfo = {
        author,
        content,
        emotion,
        created_date,
        id: dataId.current,
      };
      dataId.current += 1;
//      setData([newItem, ...data]); // 이렇게 하면 기존 data를 날리고 새로운 data만 만들어준다.
      setData((data) => [newItem, ...data]); // useCallback 안에서는 이렇게 data를 다시 전달해줘야 한다.
    }/*첫번째 인자: 함수 자리*/,
    [] /*두번째 인자: dependencies(deps) 배열 자리*/
  );

이렇게 해야하는 이유는 dependency 배열 자리에 []만 전달해서 mount 된 이후로는 첫번째 인자 자리의 함수가 재생성되는 일이 없다. 즉, onCreate가 …data로 가져와서 기억하고 사용하고 있는 data의 상태는 [] 상태이기 때문에 빈배열에다가 newItem만 추가해주는 꼴이 되는 것이다.

이렇듯 함수가 컴포넌트가 재생성될때 재생성되는 이유가 있다. 현재의 state값을 참조할 수 있어야 하기 때문이다(주석으로 막아둔 상황에서는 useCallback을 통해 재사용되다보면 data의 최신상태를 참조할 수 없다).

물론 deps에 [data]로 전달해도 되지만, 이렇게 하면 또 data가 변경되면 onCreate가 재생성되는 낭비가 발생한다.

onCreate는 재생성되지 않기를 바라는데, 그런데 재생성되지 않으면 최신의 data 상태를 가져올 수 없게되는 이상한 동작 상황 발생

setState()의 인자로 값 뿐만 아니라 함수도 전달이 가능하다. 그래서 setState의 인자로 전달한 함수의 인자로 data를 넣어주면 되는 것이다.

이렇게 setState함수에 함수를 전달하는 것을 함수형 업데이트라고 표현 ⇒ 이렇게 deps를 비워놔도 setState의 함수의 인자를 통해 최신의 state를 가져올 수 있다.


useReducer

state 관련 함수들을 Component 밖에서 사용하는 것은 쉬운일이 아니다. 하지만 하나의 컴포넌트 안에서 여러 함수들을 쓰다보면 코드가 길어지고 이는 바람직하지 않다.

⇒ 이렇게 컴포넌트에서 상태변화 로직을 분리하기 위해 사용하는 것이 useReducer

사용은 useState 쓰듯이 쓰면 되는데

const [state, dispatch/*상태를 변화시키는 액션 발생 함수*/] = useReducer(reducerFunc/*일어난 상태 변화를 처리해주는 함수*/, initState);
dispatch와 함께 전달되는 action 객체(상태변화를 설명할 객체)에는 꼭 type이라는 property가 들어있다. 이 action 객체가 reducer로 날아간다.
reducer가 처리해준다.switch 문 등을 통해서 처리

useContext

Props Drill : 부모에서 자식 방향으로만 Props를 전달하는 리액트 특성 상 발생

이런 Drilling의 문제를 해결하기 위해 등장한 useContext

같은 Provider 하에 있지 않으면 같은 Context하에 있는 것이 아니다.

같은 Context 안에 있으면 ContextProvider가 공급하고 있는 모든 데이터들을 어디서든 가져다 쓸 수 있다.


실전 PJT

  1. 폰트 세팅
  2. 레이아웃 세팅
  3. 이미지 어셋 세팅
  4. 공통 컴포넌트 세팅

Page Routing

Router : 데이터의 경로를 실시간으로 지정해주는 역할

Single Page Application

react router 검색 → https://reactrouter.com/

페이지 이동시 그냥 a 태그를 사용해서 페이지 이동을 구현하면 MPA랑 똑같이 새로고침 화면만 띄우게 된다.

사용하는 React Router가 6버전이라 이전 버전으로 써있는 글 들이 있으면 감안해서 찾고 볼것

React Router V6

  1. Path Variable ⇒ useParams
  2. Query String ⇒ useSearchParams
  3. Page Moving ⇒ useNavigate

OFL : Open Font License → Google Web Fonts → Nanum Pen Script, Yeon Sung(배민)추천

이미지 등의 asset 파일들은 public/assets에 저장

버튼 같은 UI를 공통 컴포넌트화 하는데에는 깊은 고민이 필요하다. ⇒ 그 UI가 어떤 기준으로 얼마만큼 변화하게 되는가를 찾아서 패턴화하는 과정이 필요하다.

  1. 상태 관리 세팅하기
    프로젝트 전반적 사용 데이터 state 관리 로직 작성
  2. 프로젝트 State Context 세팅하기
    State Context 생성 Provider 공급
  3. 프로젝트 Dispatch Context 세팅하기
    Dispatch 함수 Context Provider 공급

기초 공사 할 때 상태 관리 어떻게 할지 결정


개꿀팁

배열등을 깊은 복사(deep copy)할 때 JSON.parse(JSON.stringify())로 하면 간단해진다(!)

const array = [1,2,3,4,5];
const copiedArray = JSON.parse(JSON.stringify(array));

이렇게 하면 매우 간단히 deep copy ⇒ • 다른 방법에 비해서 성능적으로 느리다


개발할 때도 작은 단위 단위마다 테스트 해볼 것

개발 하고서 확인 할 때 React extensions으로 확인하기

컴포넌트 최상위 태그의 className은 컴포넌트 이름하고 맞춰주기

join(" ")을 이용해서 html tag의 className 만들어주기도 기억하기! ⇒ 이를 통해 동적 class이름 부여 가능!

const env = process.env;
env.PUBLIC_URL = env.PUBLIC_URL || ""; // 오류 있으면 이렇게 작성해도 됨

작업의 순서를 확인하기 위해 항상 프로토타입 확인, 맨 위 header, 구성 요소들 박스 치면서 확인하기. 그러고서 순서대로 하면 좋겠지

만들다가도 공통되는 컴포넌트들이 보이면 공통 컴포넌트로 만들어버리기

컴포넌트의 기능을 분할해야 하는 상황이 벌어질 수 있는데, 사전에 다 알지 못하는 경우가 있음. 그 때마다 옮겨보는 경험도 굿

import 부터 옮기면 에러를 덜 마주할 수 있음.

const EmotionItem = ({
  emotion_id,
  emotion_img,
  emotion_descript,
  onClick,
  isSelected,
}: EmotionInfo & { onClick: Function } & { isSelected: boolean }) => {
};

type union 할 때 이런식으로 하기

textarea css 줄 때 resize : vertical 이런식으로 해서 가로로 늘리는거 방지 확인

DirayEditor 에서 date State의 초기값이 다음과 같이 설정되어 있는데요
const [date, setDate] = useState(getStringDate(new Date()));
여기서 getStringDate 함수에 오류가 있었습니다.
getStringDate함수가 선언된 src/util/date.js 파일을 보시면
date.toISOString 메서드를 이용하여 현재 시간을 yyyy-mm-dd 형식으로 변환하고 있는데 이는 세계별 시간대 차이 때문에 적절하지 않은 해결 방법이였습니다. 이는 UTC 시간대와 KST 시간대가 9시간 차이가 발생하기 때문이며, 타 지역대에도 지역별 차이가 발생합니다.
따라서 정확히 현재 시간을 yyyy-mm-dd형식으로 변환하기 위해 date.js 파일의 getStringDate함수를 아래와 같이 수정하면 정상 동작 합니다
export const getStringDate = (date) => {
  let year = date.getFullYear();
  let month = date.getMonth() + 1;
  let day = date.getDate();
  if (month < 10) {
    month = `0${month}`;
  }
  if (day < 10) {
    day = `0${day}`;
  }
  return `${year}-${month}-${day}`;
};
 
강의 수강에 차질을 빚게 하여 죄송합니다.

변경된든 로직을 처리하는 한가지 통로 : reducer

number로 받아올 때 parseInt등으로 변환하는 습관 들이는거 추천

최적화2

  • setState 등의 useState의 상태 변화 함수는 리렌더링이 일어나도 동일한 ID를 보장. 기본적으로 useCallback 처리가 되어서 나오는 함수라 생각하면 됨. 그래서 굳이 handler 함수 만들 필요 없는 구간에서는 useCallback까지 처리해서 힘들게 쓰지 말고, 당연히 재사용되는 상태변화 함수 그 자체를 내려주면 좀 더 편하게 최적화 가능
  • list 등에 포함되어 있는 item들은 최신순, 오래된 순 등으로 다시 정렬하는 등의 작업을 할 때 리렌더가 계속 일어나면 위험하다. 대부분 이미지등을 포함하고 있기 때문에 동영상이 있거나 텍스트가 얼마나 길어질지 모르기도 하고 ⇒ 페이지 버벅임 발생 가능

'React' 카테고리의 다른 글

[React] props  (0) 2021.11.28
[React] 왜 React인가?  (0) 2021.11.28
[React] 향후 일요스터디의 방향?  (0) 2021.10.31

댓글