✅ 이번주 배운 내용
자바스크립트 기본문법, 타입스크립트 기초, 리액트 기초
자바스크립트 기본 문법
템플릿 문자열
const name = '철수';
const helloTemplate = `${name}야, 머해`;
console.log(helloTemplate);
템플릿 문자열은 기존의 큰 따옴표나 작은 따옴표 대신에 백틱 기호로 문자열을 정의하는 방법을 말한다.
템플릿 문자열은 변수를 대입할 수 있다는게 가장 큰 장점이다.
화살표 함수
es6에서 새롭게 등장한 함수 선언 방법이다.
1.일반함수
function printHello() {
retrun 'hello';
}
console.log(printHello()); //hello
2.화살표 함수
매개변수가 없을 때
const printHello = () => {
retrun("hello");
}
console.log(printHello())
매개변수가 있을 때
const printHello = name => `${name}, 안녕하세요`
console.log(printHello('경웡'));
매개변수가 2개 있을 때
const printHello = (name, dayType) => {
if(dayType === 'morning') return `${name}, 좋은아침이예요`
else if(dayType === 'lunch') return `${name}, 좋은 점심이예요!`
else if(dayType === 'night') return `${name}, 좋은 저녁이예요!`
}
console.log(printHello('철수', 'morning'));
3. 비구조화 할당
일반 문법 - 배열
const likeFoods = ['apple' ,'banana', 'orange'];
const food1 = likeFoods[0];
const food2 = likeFoods[1];
const food3 = likeFoods[2];
console.log(food1, food2, food3);
비구조화 할당 배열
const likeFoods = ['apple' ,'banana', 'orange'];
const [food1, food2, food3] = likeFoods;
console.log(food1, food2, food3);
일반 문법 - 객체
const animal = {
animalName: '강아지',
animalType: '고양이',
animalAge : 20,
animalGender : 'male'
};
const animalName = animal.animalName;
const animalType = animal.animalType;
const animalAge = animal.animalAge;
const animalGender = animal.animalGender;
console.log(animalName);
console.log(animalType );
console.log(animalAge );
console.log(animalGender );
비구조화 할당 - 객체
const animal = {
animalName: '강아지',
animalType: '고양이',
animalAge : 20,
animalGender : 'male'
};
const {animalName, animalType, animalAge, animalGender} = animal;
console.log(animalName);
console.log(animalType );
console.log(animalAge );
console.log(animalGender );
Spread 연산자
전개연산자라고도 한다. ...을 이용해서 표현하는 연산자.
배열
깊은 복사
const arr1 = [10, 20, 30];
const arr2 = arr1;
console.log(arr1 === arr2); // true
배열은 참조 자료형이고 별도의 주소값을 가지고 있기 때문에 arr1이 가지고 잇는 배열의 주소값을 arr2도 가지게 된다.
그래서 하나만 바꿔도 자신이 바라보고있는 배열의 주소 위치가 같이때문에 같이 변한다.
const arr1 = [10, 20, 30];
const arr2 = arr1;
arr1.push(40); // 배열에 40추가
console.log(arr1); // [10, 20, 30, 40]
console.log(arr2); // [10, 20, 30, 40]
하나의 데이터가 변경될 때 다른 쪽의 데이터도 변경되는 복사를 얕은복사라고 한다.
깊은 복사를 하고싶다면 spread연산을 이용하면 된다.
const arr1 = [10, 20];
const arr2 = [...arr1];
arr1.push(30);
console.log(arr1); // [10, 20, 30]
console.log(arr2); // [10, 20]
arr2에 arr1 배열이 전개되어 새로운 배열로 다시 생기기 때문에 arr2는 arr1의 배열 변수의 주소값을 가지고 있지않는다.
따라서 arr1의 값을 변경해도 arr2는 영향을 받지않음
이를 깊은복사라고한다.
다차원 배열일때는 spread 연산자를 사용해서 깊은 복사를 할 수없다.
객체도 깊은 복사를 할 수 있다,.
const obj1 = { name: "철수" };
const obj2 = obj1;
obj1.age = 20;
console.log(obj1); // { name: '철수', age: 20 }
console.log(obj2); // { name: '철수', age: 20 }
const obj1 = { name: "철수" };
const obj2 = { ...obj1 };
obj1.age = 20;
console.log(obj1);// { name: '철수', age: 20 }
console.log(obj2);// { name: '철수' }
병합
const obj1 = { name: "철수" }
const obj2 = { age: "20" }
const combined3 = { name: obj1.name, age: obj2.age }
const combined4 = { ...obj1, ...obj2 }
내가 병합하려는 객체 속성이 겹칠수 있다. 뒤에 있는 속성 값이 앞에 있는 속성값을 덮는다.
함수의 인자로 사용하는 예
전개 연산자 앞에 매개변수를 써야한다. 뒤에쓰면 오류남
function add(a, b, ...rest) {
return rest.reduce((acc,current) => acc+current,0)
}
타입스크립트
타입스크립트는 값에 타입을 지정하는 언어이다.
값 => 기본 자료형, 참조 자료형
기본 자료형
number,string,boolean,null,undefined,symbol
let,const : 변수명: 타입 = 값;
let uname: string = "홍길동";
let num: number = 10;
let isTrue: boolean = true;
let nul: null = null;
let und: undefined = undefined;
let sym: symbol = Symbol('key');
참조 자료형
object, array, function
let user: {
name: string,
age: number;
isTrue:boolean
} = {
name: "이름름",
age: 30,
isTrue:true
}
let animal: {
name: string;
age: number;
isLive:boolean
} = {
name: 'pubby',
age: 3,
isLive:true
}
배열
let arr:number[] = [1, 2, 3];
let arr2:string[] = ['a', 'b', 'c'];
let arr3:boolean[] = [true, false];
let arr4:null[] = [null, null];
let arr5: { name: string; age: number }[] = [
{name:"영희", age:20}
]
함수 타입
매개변수 + 반환 값에 타입을 지정해주는 것.
function(n1:매개변수 타입):반환타입{}
함수 표현식
타입이 두가지 있다
1. 함수 선언문럼 매개변수와 반환값의 타입을 지정하는 방법
2. 변수에 타입을 지정하는 방법
함수표현식은 변수에 함수를 담는것
값으로 평가 될 수 있는 식을 말한다.
const add = function add(n1:number, n2:number):number {
return n1 + n2;
}
add(10, 20)
함수선언문
function printHello(): void{
console.log("hello")
}
아무것도 반환하지 않을때는 void를 사용한다.
이름이 있느논ㅁ은 기명함수
기명함수 -> 권장 -> 디버깅이 쉬움
브라우저가 렌더링 되는 과정
렌더링 과정
- 브라우저는 Html, css, js, 이미지, 폰트, 파일등 렌더링에 필요한 리소스를 요청하고 서버로부터 응답을 받는다
- 브라우저의 렌더링 엔진은 응답받은 html 과 css를 파싱하여 DOM과 CSSSOM를 생성하고 이들을 결합하여 렌더트리를 형성한다.
- 브라우저의 자바스크립트 엔진은 응답 받은 자바스크립트를 파싱하여 AST(Abstract Syntax Tree)를 생성하고 바이트 코드로 변환하여 실행한다. 이때 자바스크립트는 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있다. 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합된다.
HTML, CSS파싱과 DOM, CSSOM생성
- 서버는 요청시 바이트(2진수)로 저장된 Html 파일을 응답한다.
- 응답된 바이트 형태의 html문서는 meta 태그의 charset 어트리뷰트에 의해 지정된 인코딩 방식을 기준으로 문자열로 반환한다.
- 문자열로 된 html 파일을 읽어 문법적 의미를 갖는 코드의 최소 단위인 토큰을로 분해한다.
- 각 토큰들을 객체로 변환하여 노드(node)를 생성한다.
- HTML요소의 중첩관계를 반영하여 모든 노드 트리 자료구조로 구성한것이고 이것이 DOM(Document Object Model)이다.
렌더링 엔진은 HTML을 한줄씩 순차적으로 파싱하여 DOM을 생성해 나간다. 생성중 CSS를 로드하는 Link태그나 style 태그를 만나면 DOM을 일시중지하고 CSS 파일을 HTML과 동일한 파싱 과정을 통해 CSSOM(CSS Object Model)을 생성한다.
자바스크립트도 CSS파싱 과정과 마찬가지로 렌더링 엔진은 DOM을 생성해 나가다 srcipt 태그를 만나면 DOM 생성을 일시중단하고 자바스크립트 코드를 파싱하기 위해 자바스크립트 엔진에 제어권을 넘긴다. 파싱이 종료되면 렌더링 엔진으로 제어권이 넘어가 DOM 생성을 재개한다.
참고
컴포넌트 트리 구조
컴포넌트가 그려지는 순서
리액트는 컴포넌트 트리구조를 탐색할때 깊이 우선 탐색( Depth First Search,DFS)이라는 알고리즘으로 탐색한다.
깊이 우선 탐색은 하나의 경로를 따라 가능한 깊이까지 탐색한 후 다시 다른 경로를 탐색한다.
컴포넌트의 이벤트
JSX의 이벤트는 DOM 엘리먼트에서 이벤트를 처리하는 방식과 매우 유사하다.
- 리액트의 이벤트에서는 소문자 대신 캐멀 케이스를 사용한다.
- JSX를 사용하여 문자열이 아닌 함수로 이벤트 핸들러를 전달한다.
컴포넌트 데이터 전달(props)
리액트에서 Props는 컴포넌트 간에 데이터를 전달하는 방법이다.
props는 부모 컴포넌트가 자식 컴포넌트에 전달하는 읽기전용 데이터 이다.
Children Props
리액트에서 children prop은 컴포넌트의 속성 중 하나로 컴포넌트의 자식 요소들을 포함한다.
이를 통해 부모 컴포넌트는 자식 컴포넌트를 동적으로 렌더링할 수 있다.
children prop은 모든 react 컴포넌트에 기본적으로 존재하며, 주로 컴포넌트 간의 구성 요소를 더 쉽게 재사용하고 동적으로 컨텐츠를 전달할 때 유용하다.
재사용 컴포넌트 타입 지정
<Button><BsTrashFill />전송</Button> {/* children을 활용해서 데이터를 전달하고싶을 경우에는 이런식으로 사용해야한다. */}
<Button ><BsTrashFill />전송</Button>
<Button text="로그인"></Button> {/* 이런식으로 작성하면 에러는 안나더라도 안티패턴임 */}
<Button text="삭제" />
{/* Props는 여러개 내가 원하는만큼 보낼 수 있지만 children은 하나만 보낼 수 있다 */}
type TButtonProps =React.ComponentPropsWithoutRef<"button">
// ComponentPropsWithoutRef 사용하면 태그에 해당하는 속성을 다 포함시킬수있다.
// 표준속성이 아니라면 에러가 뜬다.
import React from "react";
import { twMerge } from "tailwind-merge";
type TButtonProps = React.ComponentPropsWithoutRef<"button">
// CComponentPropsWithoutRef를 쓰면 태그에 해당하는 속성을 다 포함시킬수있다.
const Button = (props: TButtonProps) => {
const { children, className, ...rest } = props; // 이렇게하면 칠드런을 제외한 부모 컴포넌트에서 넘어오는 프롭스는 rest라는 변수에 담기게 된다.
return (
<>
<button className={twMerge(` w-[77px] h-[44px] text-sm text-white font-medium rounded-lg`, className)} {...rest} >
{children}
</button>
</>
);
}
export default Button
twMerge 라이브러리를 설치하여 사용할 경우 porps로 내려주는 className들중 겹치는걸 제외시켜 적용해준다.
type InputProps = Omit<React.ComponentPropsWithoutRef<"input">, "type" & {
type: "text" | "password" | "email" | "number" | "date"
}>
omit이라는 타입을 사용해서 코드 퀄리티를 더 높일 수 있다
Omit이라는 타입은.. 따로 찾아볼것..
type TButtonProps = React.ComponentPropsWithoutRef<"button">
이렇게만 써버리면 버튼에 사용되는 200개가 넘는 한번에 지정하게 된다 ( 뭘쓸지 모르니 다 갖다 넣겠다는 뜻)
하지만 실무에선 문제가 되지않음.
하지만!!! 굳이 200개를 다 쓸 필요가 없으니 위 코드처럼 omit타입을 사용해서 사용하는 타입만 지정해줘도 되는것 같다.
강사님 맥락상 그말이 맞는것같음
type ProfileCardProps = {
[key:string]:string;
} & {onClick: () => void}
이렇게 쓸순 있으나
순수 html 태그를 조작하는게 아니라 여러가지 태그들이 사용되는 복잡한 태그 같은 경우에는 사용자가 넘겨줄때 뭘 넘겨줬는지 모호하다.
컴포넌트의 정보전달을 위해 값을 전달하는것이라면 적합하지 못하다
인덱스 시그니처는 너무 포괄적이라는뜻.
순수 html일때 하나하나 다 받는건 안티패턴이셈.
type TCheckBoxProps = Omit<React.ComponentPropsWithoutRef<"input">, "type" & {
type: "checkbox";
}>
const CheckBox = (props: TCheckBoxProps) => {
const { children, ...rest } = props;
const uid = useId()
return (
<>
<div className="flex items-center gap-2">
<input
type="checkbox"
id={uid}
{...rest}
/>
<label htmlFor={uid}>{children}</label>
</div>
</>
);
};
export default CheckBox;
질문중에 id랑 htmlFor도 프롭스로 넘겨줘도 되지않냐라는 질문이 있엇는데
id,htmlFor를 따로 프롭스로 받아서 써도 되지만 useId라는 훅을 사용해서 처리할 수 도 있다.
조건부 렌더링
const App = () => {
const isLogin = false;
return (
<>
{isLogin ? <h1>로그인</h1> : <h1>로그인 안댐</h1>}
</>
);
}
export default App
조건부 렌더링 삼항연산자는 Html 코드가 간단할때는 좋지만 여러줄일 경우 가독성이 떨어지는 단점이 있다.
const num = 10;
num>2 ? '크다' : '작다' // 한번쓴경우
이런식으로 삼항연산자를 두번 이상 쓸 경우 안티패턴이다.
num>2 (num > 5 ? '5보단 큼': '5보단작아') :'작다';
두번 이상 쓸거면 차라리 If문을 쓰셈
논리 연산자 &&를 사용한 조건부 렌더링
//앞이 참이여도 참고지수로 판단하기 어려우면 그냥 출력해준다
{isLogin && <h1>logged in</h1>}
반복 렌더링
const App = () => {
const fruits = ["사과", "바나나", "체리", "사과"]
return (
<>
<h1>내가 제일 좋아하는 과일</h1>
<ul>
{fruits.length > 0 && fruits.map((fruits, index) => <li key={index}>{fruits}</li>)}
</ul>
</>
);
}
export default App
배열일경우엔 map을 사용해서 쓸수있다.
컴포넌트에서 이미지 불러오는 방법
- 외부 이미지 파일을 사용하는 방법
<img src ="외부 링크"/> - 리액트에선 보통 src/assets/images 폴더에 이미지를 넣어 사용한다
<img src={img} alt=''/>
<img src='/1.png' alt=''/>
public이랑 assets에 넣는게 무슨 차이햐
퍼블릭에 넣으면 누구나 그 이미지에 접근이 가능하게 된다 -> 공개적으로 노출이 됨 (접근이란 리소스적 접근을 말한다)
리액트에서 assets라는 폴더를 만들고 이미지 이름을 지정해줄 수있다
import Logo from './1.png'
단점은 이미지마다 임포트를 해주어야한다.
그래서 어떻게 써야합니까요
일반적으로 웹사이트에서 쓰는 이미지는 퍼블릭 폴더에 이미지를 넣고 쓴다
하지만 이론적으론 import해서 쓰는게 맞다.
png,jpg 보다는 webp가 압축률이 좋아서 webp를 사용하는걸 권장한다.!
리액트 훅
useState
리액트에서 상태 변수를 선언하고 관리하는 데 사용하는 훅이다. 가장 기본이며 많이 사용하는 중요한 훅이다.
const [state, setState] = useState(initialState);
const App = () => {
const [count, setCount] = useState(0);
const onClickHandler = () => {
setNum(10) // 값으로 저기한거
setNum((prev) => prev + 1) // 본인 값을 매개변수로 받는다 항상 최신 상태를 보장한다. 즉 동기적인 업데이트를 보장함 만약 이전 상태를 참조 할 필요가 없는데 이 함수패턴을 쓰면 안티패턴이다.
}
}
const onClickAddCount = () => {
setCount((prev) => prev + 1)
console.log(count) // 이렇게 했을경우 set카운트 내에서만 동기 처리가 돼서 최산상태를 유지하지만 console.log까지 동기로 기다리지 읺아서 콘솔엔 0이 찍힌다
}
const [formState, setFormState] = useState({
email: '',
password: '',
name: '',
}) // state를 일괄적으로 객체로 관리하는 방법
회원가입 폼이나 로그인 폼을 작성할 경우 일괄로 관리하는 방법도 있다.
또는 커스텀훅을 만들어 관리하는 방법도 있다.
const App = () => {
const [email, onChangeEmail] = useInput("");
return(
<>
<input
type="email"
name="email"
value={email}
onChange={onChangeEmail} />
</>
)
}
import { ChangeEvent, Dispatch, SetStateAction, useState } from "react"
type UseInputReturn = [
string, (e: ChangeEvent<HTMLInputElement>) => void,
Dispatch<SetStateAction<string>>
]
function useInput(initialValue: string):UseInputReturn {
const [value, setValue] = useState(initialValue)
const onChangeValue = (e: ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value)
}
return[value, onChangeValue,setValue]
}
export default useInput
useRef
useRef는 리액트에서 html요소에 접근하거나 컴포넌트의 렌더링에 영향없이 값을 유지하고 싶을 때 사용한다.
const ref = useRef(initialValue);
회고
- 이번주 수업은 기초부터 다시 복습한다는 생각으로 들었는데 강사님께서 자세히 설명을 해주시고 실무에선 어떻게 쓰이는지도 알려주셔서 유용했다.
- 새롭게 배운건 재사용 컴포넌트에서 omit이라는 타입을 사용해서 코드퀄리티를 높이는 방법을 새롭게 배워서 얼른 써보고싶은 마음이 든다!
- 아직 Ref에 관해선.. 이해도가 조금 떨어지는것 같다. 대충 이해한 나의 잘못이다.
- 리액트 딥다이브를 사자.
본 후기는 본 후기는 [유데미x스나이퍼팩토리] 프로젝트 캠프 : React 2기 과정(B-log) 리뷰로 작성 되었습니다.