JavaScript를 더 깊게 공부하고자 "모던 자바스크립트 Deep Dive"를 읽으며 공부하고 있습니다. 그리고 각 파트를 상세히 기록하며 정리할 예정인데 그 전에 기본적인 것들을 전체적으로 정리할 예정입니다. 드루아 시리즈는 각 언어나 개념들의 전체를 훑으며 그 흐름을 이해하는 데 도움이 되고자 작성하는 시리즈입니다. 잘 부탁드립니당 :)
해당 글은 코딩앙마의 자바스크립트 강좌를 기반으로 작성되었습니다.
변수
먼저 변수를 선언하는 과정에서 지켜야 하는 것들이 있는데요. 이는 다음과 같습니다.
변수는 문자, 숫자, $, _만 사용
첫글자에 숫자는 올 수 없음
예약어 사용 불가
상수는 보통 대문자로 작성
읽기 쉽고, 이해 가능하게 작성
그리고 이러한 변수는 var, let, const 총 3가지를 통해 선언할 수 있습니다. 각각의 차이점을 알기 위해선 호이스팅이란 개념에 대해 이해해야 합니다.
호이스팅이란 코드를 실행하기 전에 함수, 변수, 클래스나 import 선언문을 최상단 스코프로 끌어올리는 것처럼 보이는 현상입니다. 이렇게 보이는 이유는 JS는 코드를 실행하기 전에 선언된 변수와 함수 등을 먼저 체크하기 때문입니다.
이제 var, let, const의 차이점에 대해 알아봅시다.
var
호이스팅 시 변수의 선언과 초기화를 같이 실행 , 그렇기에 var는 선언하기 전에 사용힐 수 있습니다.(undefined로 초기화 되기 때문에 선언 전에 사용하면 undefined을 가져옵니다.)
함수 스코프 : 함수를 기준으로만 어휘적 환경이 설정 (전역변수와 지역변수의 경계 모호)
재할당 재선언 가능
let
호이스팅이 일어나지만 초기화되지는 않습니다.
블록 스코프 : 블록{}을 기준으로 어휘적 환경이 설정 (전역변수와 지역변수의 경계 확실)
TDZ(Temporal Death Zone) 적용 : 호이스팅이 됐지만 초기화 전까지는 해당 변수에 접근 불가
위의 이유로 선언하기 전에는 사용 불가
재할당만 가능
const
let과 모두 동일하지만 선언, 초기화, 할당이 동시에 일어나기 때문에
재선언 재할당 불가능
선언 : 변수를 JS 엔진에 알려줌
초기화 : 선언된 변수에 기본 값을 할당(변수를 메모리에 등록하고 초기값 할당) 즉, 선언 후 첫 번째 값을 할당할 때 발생
할당 : 초기화 이후의 값 변경
생성자 함수
JS에서는 객체를 자주 유용하게 사용합니다. 그렇기에 비슷한 객체를 여러개 만들어야 하는 상황이 생기고, 이 때 사용하는 것이 생성자 함수입니다.
만들던 과정 중에 생각이 든건 s3를 백에서 사용하는데 지금 의미가 없었다...하핳 사전 과제는 프론트인데 백에서 s3를 사용할리가 없기 때문이다. 그래서 일단 데이터 호출한 부분까지만 정리해 올리고 목업 데이터를 이용한 기능 구현에 초점을 맞춰서 프론트 단에서만 개발을 할 예정이다.
JSX에서 변수 삽입할 때 사용하는 중괄호 안에는 if문이나 for문은 들어가지 못한다. 그래서 배열을 편하게 html에 보여주고 싶을때, map 함수와 filter 함수를 이용해 편하게 보여줄 수 있다.
- map() 함수
예를 들어서 나열을 하고 싶은 arr이라는 배열이 있을때, for문을 사용하지 않고 map을 이용한다면 편하게 정렬할 수 있다.
let arr = [1, 2, 3, a, b, c];
arr.map((val, index, arr) => {
retrun 보여주고 싶은 값
});
여기서 각 콜백 함수에 대해 설명하자면
val : 현재 돌고 있는 값
index : 돌고 있는 값의 인덱스값
arr : 현재 돌고 있는 배열 전체
그리고 보통 리액트에서 사용할때는 객체가 배열에 들어가 있는 경우가 많기 때문에 단일로 써서 각 객체에서 원하는 값을 불러낸다. 여기서 중요한 점은 key값을 설정해줘야 오류가 나지 않는다. 아래와 같은 객체를 가진 배열이 있다고 가정하자. 그럼 map은 다음과 같이 사용할 수 있다.
//리액트의 한 컴포넌트라고 가정하자exportdefaultfunction 컴포넌트이름 () {
//여기에 옆에 있는 객체를 가진 배열도 존재한다고 가정한다.return (<><ul>
{list.map((listData) => {
return <likey={listData.id}> {listData.alpha} </li>
})}
</ul></>)
}
따로 아이디가 존재하지 않다면 인덱스를 가지고 오는 콜백함수를 키값으로 넣을 수 있다.
- filter() 함수
보통 map 함수를 이용해 배열을 나열하기 전에 어떠한 조건을 기준으로 배열을 가공할 때 사용한다. 즉, filter 함수로 리턴된 배열을 map함수로 나열한다고 생각하면 편하다. 왜냐하면 filter 함수는 map 함수처럼 html 자체를 반환해주지는 않고, 그냥 조건에 맞는 배열 자체를 보내주기 때문이다. 사용법은 다음과 같다.
//리턴한 배열을 받기 위해서 변수를 선언하어 사용하는 경우가 많다.
arr.filter((val, index, arr)=> {
return 조건
})
문법 [sort함수]
sort 함수를 통해 문자나 숫자를 정렬할 수 있다.
문법 [구조분해할당]
지금까지는 변수를 선언할때 보통
let num = [1, 2, 3, 4];
이런 식으로 했었는데, 선언을 할때부터 각 값을 지정할 수 있다.
let [a, b, c, d] = [1, 2, 3, 4];
이를 활용하여서 어떠한 객체 파일을 보냈을때 받은 데이터에서 원하는 값만 사용하고 싶은 경우 구조분해 할당을 통해 원하는 값만 뽑아서 사용할 수 있다.
ex)
보낸 객체
const data = {
name: result.name,
pw: result.pw,
}
받은 곳에서 이름만 사용하고 싶음
const data = req.body.data //(경우에 따라 params가 올 수도 있다.)
let { name } = data;
Component
MVC 모델의 view를 독립적으로 구성해 재사용할 수 있고, 새로운 컴포넌트도 만들 수 있다. 간단히 말하자면 하나로 이어진 html 파일은 구분하기와 읽기가 어렵기 때문에 개별적으로 컴포넌트를 만들어준 후에 App.js에서 import해서 가져올 수 있다. 이 과정에서 props를 넘겨주면 컴포넌트끼리 데이터를 간편하게 주고 받을 수 있다.
컴포넌트는 원래 함수형 컴포넌트롸 클래스형 컴포넌트가 있다. 예전에는 state와 라이프 사이클을 쓸 수 있는 클래스형
컴포넌트를 많이 사용했지만 현재는 Hook으로 state와 라이프사이클 등 다양한 부분들을 함수형 컴포넌트에서도 사용할 수 있게 되면서 함수형 컴포넌트를 많이 사용하게 됐다. 각각의 컴포넌트 구조를 알아보자
- 클래스형 컴포넌트
클래스형 컴포넌트는 리액트에서 컴포넌트를 불러오고 상속을 받아서 사용을 한다. 그래서 랜더 안에 리턴을 넣어서 사용한다.
import {Component} from"react";
class 컴포넌트이름 extendsComponent{
render() {
//삼항연산자 한 번 써보자const classes = 'dd';
return (
<><div>{classes === 'dd' ? "dd씨 반가워요" : "넌 뭐야"}
{/* 참이라면 앞의 것을 출력해주고 거짓이라면 뒤의 것을 출력해준다. */}
<div> 반가워!! </div></>
)
}
export default 컴포넌트이름;
}
- 함수형 컴포넌트
함수형 컴포넌트는 위에서 따로 불러올 필요 없이 바로 함수로 만들어서 사용할 수 있다. 그리고 함수 특성을 이용해 작성 동시에 앞에 export default를 적어서 바로 export 해 줄 수 있다.
exportdefaultChild(props) {
return (<><div>{props}</div>//배열일 경우 {props[원하는인덱스값]}
<div>{props.children}</div>// 위에서 보낸 값 말고 <>이사이의 값</>을 가져온다.
</>)
}
//구조분해할당 방식exportdefaultChild({name, children}) {
return (<><div>{name}</div><div>{children}</div></>)
}
- Ts 사용법
근데 이를 TypeScript에서 쓰려면 부모 컴포넌트와 자식 컴포넌트에서 타입을 모두 지정해줘야 오류가 나지 않고 잘 전달된다.
import Child from'./Child';
import {useState} from'react';
exportdefaultParents() {
const[name, setName] = useState<string>('내이름');//타입 지정해줘야함
conconst[nameArr, setArr] = useState<string[]>(['내이름', '너이름', '우리이름']);
//배열일 경우 타입지정return (<><Childname={name}setName={setName}name={nameArr}setName={setArr} >넌 할 수 있어</Child>
//자식 컴포넌트에서 어떤 타입으로 받아야할지 모르겠다면,
//여기서 해당 변수위에 마우스를 올리면 어떠 타입인지 알 수 있다.
</>)
}
위와 같이 보내주었다면 개별로 타입을 지정해주는 것보다 interface로 만들어 지정해주는 것이 편하다.
import { Dispatch, SetStateAction } from'react'
interface PROPS {
name : string,
nameArr : string[],
setName : Dispatch<SetStateAction<string>>,
setArr : Dispatch<SetStateAction<string[]>>,
//원래 ()=>{} 이 방식으로 보내도 됐었는데 계속 오류가 나서 찾아보니까 저렇게 하는거였다.
children : React.ReactNode,
}
exportdefaultChild({name, nameArr, setName, setArr, chidren}: PROPS){
const change = () => {
setName('너이름');
}
return (<><p>{name}</p> //이름
<p>{nameArr.map((val. valindex) => {
return <pkey={valindex}> {val} </p>
})}</p> // 배열 출력
<p> {children} </p><buttononClick={change}>이름변경</button></>)
}
이렇게 사용할 수 있다.
props [객체를 가진 배열에 id를 추가한 후 id에 맞는 값을 가져와 모달창에 출력하는 방법](+Ts)
처음에는 객체를 가지지 않고 그냥 문자열을 가진 배열에서 인덱스를 뽑아서 보낼려고 했는데, 이유는 모르겠지만 계속 되지 않아서 객체를 가진 배열로 만들어 id와 내용을 넣어서 제목에 onClick 이벤트를 넣고 클릭을 했을때, 해당 id를 받아와서 자식 컴포넌트로 보내주는 방식을 사용했다. 이 과정에서 확실히 알게 된것은 타입 지정을 미리 해줘야한다는 것이다.
배열 안에 있는 객체임으로 객체에 들어가는 변수들에 대한 타입 지정을 interface로 해준 뒤 그 인터페이스이름[] 이렇게 타입을 넣으면 된다. 실제 코드를 쳐보도록 하겠다.
App.js
//실제 내가 실습 중인 코드는 다른 내용들이 다 포함되어있어서//위에서 말한 기능만 보여주는 코드를 짜보도록 하겠다.import { useState } from'react';
import Modal from'./Modal';
//2. 만든 객체를 보고 interface를 만들어주자
interface Title {
id: number;
title: string;
text: string;
}
exportdefaultParents() {
//1. 객체를 먼저 만들어주고 그에 맞는 interface를 만들어주자//3. 만든 interface를 useState에 적용해준다. Title[]const [title, setTitle] = useState<Title[]>([
{
id: 1,
title: '제목1'text: '내용1'
},
{
id: 2,
title: '제목2'text: '내용2'
},
{
id: 3,
title: '제목3'text: '내용3'
},
]);
//4. 모달 창은 삼항연산자나 단축평가를 이용해 앞의 값이 true라면 창이 열리도록 할것이다.// 그리고 버튼을 눌렀을때 id 값도 같이 들어와야한다.// 모달을 열고 닫을 boolean을 담은 useState와 클릭이벤트가 일어났을때 값을 변경할 함수를 만들자// 또한 id의 값을 가져갈 useState도 만들어주자const[modal, setModal] = useState<boolean>(false);//처음엔 닫혀있음으로 false값이다.const[id, setId] = useState<number>(0);//값을 안써주면 오류가 난다. 0을 기본값으로 쓰자//5. 버튼을 눌렀을때 id값을 가져오는 부분을 만들고 id를 파라미터로 받아서 사용해주자const modalBtn = (id) => {
if(modal === false){
setModal(true);
setId(id);
} else {
setModal(false);
}
};
return (<>
{/* 5. 화살표함수 방식으로 바꿔서 val.id를 하면 id를 받을 수 있다.*/}
{title.map((val)=>{
return <pkey={val.id}onClick={() => modalBtn(val.id)}> {val} </p>
})}
{/* 6. 가져온 모달창에 props를 보내주는 것과
modal state에 맞춰 열고 닫히는 코드를 짜보자 */}
//삼항연산자
{modal ? <ModaltitleObj={title}id={id}></Modal> : null}
//단축평가
{modal && <ModaltitleObj={title}id={id}></Modal>}
</>)
}
위에서 업급했듯 저렇게 props를 받기 위해선 자식 컴포넌트에서 type을 다 설정해줘야한다.
Modal.js
//1. 부모에서 사용한 interface와 props에 적용할 interface를 만들어주자
interface Title {
id: number;
title: string;
text: string;
}
interface PROPS {
title: Title[];
id: number;
}
exportdefaultModal({title, id}: PROPS){
//2. 값을 받아와서 타입 설정을 해준 뒤에 인덱스 값이므로 id-1로 불러와서 넣어준다.
retrun (<><h4>제목 : {title[id-1].title}</h4><p>날짜 : 10월 16일 발행(까먹고 추가 못함)</p><p>내용 : {title[id-1].text} </p></>)
}
이렇게 만들어주면 각 제목을 누를때만다 제목에 대한 내용이 나오는 모달창을 구현할 수 있다.
Hook
훅은 클래스형 컴포넌트에서만 가능했던 상태관리(state)와 생애주기(라이프 사이클)을 함수형 컴포넌트에서도 할 수 있도록 하는 기능이다.
훅의 중요한점은 컴포넌트 안에서만 호출이 가능하다는 것이다. 대표적인 것들을 설명하자면
- useState() : 상태관리를 해주는 가장 기본적인 훅이다.
- useEffect() : 클래스형에서 사용하는 라이프 사이클을 사용할 수 있도록 해준다.
- useContext() : 리액트에서 사용하고자 하는 데이터를 전역적으로 처리할 수 있다. 컴포넌트가 많은 큰 프로젝트에서 props를 사용하면 매우 번거로운데, 이에 대한 해결책 중 하나이다. 하지만 useContext보다는 리덕스를 더 많이 사용하는 추세이다.
- useReducer() : 복잡한 컴포넌트의 상태로직을 리듀서 함수로 관리해준다.
- useMemo() : 메모이제이션을 통해 함수의 리턴 값을 재사용할 수 있게 해주는 훅
- useCallback() : 함수를 메모이제이션하여 불필요한 렌더링을 줄이게 해주는 훅
- useRef() : 참조(reference)를 생성하고 관리할 수 있는 훅 (DOM 접근, 변수 보존 등)
Hook [useState]
- 기본
위에서 import로 선언을 해주고,
let/const [실제 값, 값변경함수] = useState(실제값 초기화);
위와 같은 방식으로 사용한다. 그리고 타입 스크립트로 지정해줄려면
let/const [실제 값, 값변경함수] = useState<string, number 등등>(실제값 초기화);
만약에 컴포넌트가 사라지거나 특정값이 사라질때 어떤 작업을 수행하고 싶다면, 중괄호 안에 return () => {}; 를 이용해 해당 함수 안에 넣으면 수행하게 할 수 있다.
간단히 요약하자면 useEffect(랜더링 이후 수행할 함수, 어떤 상황에 할 것인지 나타냄( or [] or [특정값]) )
Hook [useRef()]
훅 useRef는 html의 요소를 가지고 오기 위해 사용한다. 근데 이를 ts에서 사용하기가 진짜 빡셌다. 특정 요소에서 원하는 값을 가지고 올려고 할때 형식이 맞지 않아서 가지고 올 수 없기 때문에 요소에 적는 ref에도 형식 지정을 해줘야한다..ㅠㅠ
기본적인 사용법은 useRef로 myref하나 만들어주고 이를 원하는 요소에 ref={}로 넣어준뒤 myref.current로 가져올 수 있는 다. ts에서도 똑같이 해주면 되는데 형식 지정을 해줘야한다.
프로젝트를 하면서 겪은 것을 올리도록 하겠다.
에러
'MutableRefObject<HTMLTextAreaElement | undefined>' 형식은 'LegacyRef<HTMLTextAreaElement> | undefined' 형식에 할당할 수 없습니다. 'MutableRefObject<HTMLTextAreaElement | undefined>' 형식은 'RefObject<HTMLTextAreaElement>' 형식에 할당할 수 없습니다. 'current' 속성의 형식이 호환되지 않습니다. 'HTMLTextAreaElement | undefined' 형식은 'HTMLTextAreaElement | null' 형식에 할당할 수 없습니다. 'undefined' 형식은 'HTMLTextAreaElement | null' 형식에 할당할 수 없습니다.ts(2322) index.d.ts(159, 9): 필요한 형식은 여기에서 'DetailedHTMLProps<TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>' 형식에 선언된 'ref' 속성에서 가져옵니다.
해결법
이 에러는 TypeScript에서 useRef의 반환 값과 textarea 요소의 ref 속성의 타입이 일치하지 않아 발생합니다. TypeScript에서는 기본적으로 useRef를 사용하면 current 속성이 HTMLTextAreaElement | null 형식을 가집니다. 반면 textarea 요소의 ref 속성은 HTMLTextAreaElement | undefined일 수 있습니다.
이 문제를 해결하려면 useRef와 textarea 요소의 타입이 일치하도록 타입을 지정해야 합니다. HTMLTextAreaElement | null 형식을 HTMLTextAreaElement | undefined 형식으로 변경하거나 그 반대로 변경할 수 있습니다. 아래는 두 가지 방법 중 하나를 선택하여 문제를 해결하는 방법입니다:
1. useRef를 HTMLTextAreaElement | undefined 형식으로 변경: