[유데미x스나이퍼팩토리] 프로젝트 캠프 : Next.js 1기 -2주차

ms_de10·2024년 6월 9일
0

가상돔(Virtual Dom)

리액트에서는 기존의 DOM 트리의 문제인 DOM이 깊어질 수록 구성 요소 변경에 대한 작업 비용이 많이 들기에 가상돔 이라는 새로운 개념을 만들어 기존의 DOM을 복사하여 리액트 내부 메모리에 저장하고, 리액트에서 어떤 코드의 변경 사항이 발생하면 이렇게 복사한 DOM을 사용해서 변경 사항을 처리하며, 최종적으로 실제 DOM 과 비교하여 변경된 부부만 업데이트 한다.

![[Pasted image 20240609105912.png|500]]

React CSS

리액트 컴포넌트를 스타일링 하는 방법에는 4가지 방식이 있다.

  • Inline styling
  • External Stylesheet
  • CSS module
  • CSS in JS Libaries

인라인 스타일(Inline styling)

인라인 스타일은 말 그대로 HTML 태그의 style 속성을 사용해서 CSS 스타일을 지정하는 방식을 말한다.
리액트에서는 style 속성을 객체 형식의 값으로 할당해야하고, font-size와 같은 케밥 케이스 형식의 속성은 카멜 케이스 형식으로 fontSize로 작성해야 한다.

const App = () => {
return (
<div>
<h1
	style={{
		fontSize: "30px",
		color: "#ed4848",
		textDecoration: "line-through",
	}}
>		
	Hello World!
	</h1>
</div>

);
};
export default App;

외부 스타일(External Stylesheet)

외부 스타일은 별도의 CSS 파일에 CSS 코드를 작성하고, 리액트 컴포넌트 파일과 연결해서 사용하는 방법을 말한다.

App.tsx

import "./App.css";
const App = () => {
return (
<div>
	<h1 className="title">Hello World!</h1>
</div>
	);

 };

export default App;

App.css

.title {
	font-size: 30px;
	color: #ed4848;
	text-decoration: line-through;
}

CSS Moudules

모듈 방식을 사용해서 특정 컴포넌트에만 종속되는 CSS 코드를 작성하기 위한 방법, 외부 스타일 방법과 비슷하지만, 파일명이 .module.css 라는 특징이 있음

App.moduele.css

.title {
	font-size: 30px;
	color: #ed4848;
	text-decoration: line-through;
}

App.tsx

import styles from "./App.module.css";
const App = () => {
return (
	<div>
		<h1 className={styles.title}>Hello World!</h1>
	</div>
	);
};

export default App;

Tailwind CSS

Utility-First 컨셉을 가진 CSS 프레임워크

Utility-First: 미리 셋팅된 유틸리티 클래스를 활용하여 HTML 코드 내에서 CSS를 적용시키는 것

Bootstrap 프레임 워크와 비슷하게 생겼으며 HTML 코드 내에서, CSS 스타일을 만들 수 있게 해주는 CSS 프레임워크

설치

npm install -D tailwindcss
npx tailwindcss init

tailwind.config.js

export default {
  content: ["./src/**/*.{js,jsx,ts,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
};

vite.config.js

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "tailwindcss";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  css: {
    postcss: {
      plugins: [tailwindcss()],
    },
  },
});

CSS in JS Libaries

CSS-in-JS는 JavaScript 내에서 CSS 스타일을 작성하고 관리하는 기법을 의미합니다. 이 방법은 전통적인 CSS 파일과는 다르게, 스타일을 JavaScript 코드 내에서 정의하고 컴포넌트와 함께 번들링하는 접근 방식입니다.

App.tsx

import styled from "styled-components";

const HelloWorld = styled.h1`
	font-size: 30px;
	color: #ed4848;
	text-decoration: line-through;
	&:hover{
		color:blue;
	}
`;

  

const App = () => {
return (
<div>
	<HelloWorld>Hello, World!</HelloWorld>
</div>
	);
};


export default App;

컴포넌트의 데이터 전달

리액트에서 props는 컴포넌트 간에 데이터를 전달하는 방법, props는 부모 컴포넌트가 자식 컴포넌트에 전달하는 읽기전용 데이터이다. props 는 컴포넌트가 상위 컴포넌트로부터 받는 입력값이라고 할 수 있으며, 컴포넌트는 이를 이용해 렌더링 결과를 동적으로 변경할 수 있다.

문법

<Component 속성={} />

사용법

App.tsx

import Greeting from "./components/Greeting";

const App = () => {
	return (
		<div>
			<Greeting name="철수" />
			<Greeting name="영희" />
			<Greeting name="바둑이" />
		</div>
);
};

export default App;

Greeting.tsx

cont Greeting = (props:{name : string}) => {
	return <h1>Hello, {props.name} </h1>
}
export default Greeting

props는 읽기 전용 데이터로 자식 컴포넌트에서 직접 수정이 불가능하다
타입스크립트에서는 props의 타입을 검사할 수 있다.

Props Children

부모에서 자식 컴포넌트로 데이터를 전달하는 또 하나의 방법

type ChildProps ={
	children: React.ReactNode; //리액트 노드 타입의 자식요소를 받음
}

const App = () => {
	return <User>안녕하세요</User> //props children으로 데이터 전달
}

// 자식 컴포넌트에서 children값 받기
const User(props:ChildProps) {
	const {children} = props //props를 구조 분해 할당으로 받음
	return <div>{children}</div>  //안녕하세요
}

const default App;

예시

type ButtonProps = React.ComponentProps<'button'> & {
	children: React.ReactNode;
	style?: string
	onClick?: ()=> void; // 클릭 이벤트 핸들러
}

const Button = (props: ButtonProps) => {
	const {children, style, ...rest} = props;
	return (
		<button className = {style} {...rest}>
			{children}
		</button>
	)
}
export defulat Button;

리액트 훅

useState

import { useState } from "react";
const App = () => {
  const [count, setCount] = useState(0); // 초기값 count가 0으로
  const clickHandler = () => {
    console.log(count);
    // count++; 직접 접근 불가능!!
    setCount(count + 1);
  };
  return (
    <>
      <h1>Count : {count}</h1>
      <button onClick={clickHandler}>증가</button>
    </>
  );
};

export default App;

useState의 타입은 내부적으로 제네릭을 사용하는데 이는 상태의 타입을 지정하는데 사용된다. 즉 처음부터 useState를 호출할 때 상태의 초기값으로 사용해 타입을 추정하여 지정한다.

const [count, setCount] = useState<number>(0)

예시

const App = () => {
	const [inputStr, setInput] = useState('')
	const onChnageHandler = (value: string) => {
		setInput(value);
	};
	return (
		<>
			<h1>{inputStr}</h1>
			<input
				type='text'
				value={inputStr}
				onChnage={(e)=>setINput(e.target.value)}>
			</input>
		</>
	)

}

onChnage 안에 콜백함수 형태로 전달 받는 이유는 매개변수로 전달 받을 값이 있을때 콜백함수 형태로 넘겨준다. 바로 값이 나오면 안되고 이벤트 발생 시점에 함수가 실행되어야 하기 때문

useRef

리액트 컴포넌트 내에서 DOM 요소에 접근할 때 사용됨, 이 훅을 사용하면 함수 컴포넌트 내에서 DOM 요소를 선택하고 조작할 수 있다.

const inputEl = useRef<HTMLinputElement>(null);
const onButtonClick = () => {
	inputEl.current?.focus()
}
return (
	<div>
		<input ref={inputEl} type='text />
		<button onClick={onButtonClick}> Focus the input</button>
	</div>
)

예시

import React, { useRef, useState } from "react";
import "./Login.css";
const Login = () => {
const passwordEl = useRef<HTMLInputElement>(null);
const [isType, setIsType] = useState<string>("register");
const [data, setData] = useState({
name: "",
email: "",
password: "",
agree: false,

});

const onChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {

setData((prev) => ({
...prev,
[e.target.name]:
e.target.type !== "checkbox" ? e.target.value : e.target.checked,
}));

};

const onSubmitHandler = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (data.password.length < 4) {
alert("비밀번호는 4자리 이상이어야 합니다.");
passwordEl.current?.focus();
return;

}

if (isType === "register") {
alert(
`이름:${data.name}, 이메일:${data.email}, 비밀번호:${data.password} 가입 완료!`
);
} else {
alert(`이메일:${data.email}, 비밀번호:${data.password} 로그인 완료!`);
}

};

// 입력 필드 유효성 검사
const isFormValid = () => {
if (isType === "register") {
return (
data.name.trim() !== "" &&
data.email.trim() !== "" &&
data.password.trim() !== "" &&
data.agree

);

}

return (
data.email.trim() !== "" && data.password.trim() !== "" && data.agree

);

};

  

return (
<div className="login">
<h2 className="login-title">
{isType === "register" ? "Login" : "Sign"} Into App
</h2>
<p className="login-subtitle">Please enter your details to continue.</p>
<form action="" onSubmit={onSubmitHandler}>
<div className="login-input">
{isType === "register" && (
<input
type="text"
name="name"
placeholder="Enter Your Name"
autoComplete="off"
value={data.name}
onChange={onChangeHandler}
/>

)}

<input
type="email"
name="email"
placeholder="someone@example.com"
autoComplete="off"
value={data.email}
onChange={onChangeHandler}
/>

<input
ref={passwordEl}
type="password"
name="password"
placeholder="Enter Password"
value={data.password}
onChange={onChangeHandler}

/>

  

<div className="login-checkbox">
<input
type="checkbox"
id="agree"
name="agree"
checked={data.agree}
onChange={onChangeHandler}
/>

<label htmlFor="agree"></label>
<span>
I agree with <em>terms</em> and <em>policies</em>
</span>
</div>
</div>
<div className="login-button">
<button
type="submit"
className="signin-btn"
disabled={!isFormValid()}
>

Sign In
</button>
<button
type="button"
className="login-btn"
onClick={() =>
setIsType((prev) => (prev === "register" ? "login" : "register"))
}

>

{isType === "register" ? "Go To Login" : "Go To Sing up"}
</button>
</div>
</form>
</div>
);
};

export default Login;

useReducer

조금 더 복잡한 상태 관리를 할 때 사용하는 리액트 훅이다.

const [todos, dispatch] = useReducer(TodoReducer, []);

useReducer(외부 컴포넌트에서 처리할 로직, 초기값 )

  • todos: 컴포넌트에서 사용할 state
  • dispatch: reducer함수를 실행 즉 TodoReducer를 실행시키며, 컴포넌트 내에서 state의 업데이트를 일으키기 위해서 사용하는 함수
  • TodoReducer: 컴포넌트 외부에서 state를 업데이트하는 로직을 담당하는 함수, 현재의 state 와 action 객체를 인자로 받아서, 기존의 state를 대체할 새로운 state를 반환
  • 두 번재 인자: 초기값
  • action: 업데이트를 위한 정보를 가지고 있으며, dispatch의 인자가 된다
dispatch({type: 'ADD_TODO'})  // {type: 'ADD_TODO'} -> action

reducder 함수

import {v4 as uuidv4} from 'uuid';

export type Todo = {
id: string;
text: string;
completed: boolean;
};

  

export type Action = {
type: 'ADD_TODO' | 'TOGGLE_TODO' | 'DELETE_TODO';
payload: string; //Partial은 모든 속성을 선택적으로 만듭니다.

};

// [{id, text,completed}]
export const TodoReducer = (state: Todo[], action: Action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, {id: uuidv4(), text: action.payload, completed: false}];
case 'TOGGLE_TODO':
return state.map((todo) =>
todo.id === action.payload
? {...todo, completed: !todo.completed}
: todo
);
case 'DELETE_TODO':
return state.filter((todo) => todo.id !== action.payload);
  
default:
return state;
}
};


const 

TodoItem

import React from 'react';
import Checkbox from './html/Checkbox';
import {Action, Todo} from './reducer/TodoReducer';

  

type TTodoListItemProps = {
todo: Todo;
dispatch: React.Dispatch<Action>;
};

  

const TodoListItem = (props: TTodoListItemProps) => {
console.log('TodoListItem rendering');
const {todo, dispatch} = props;

  

return (
<>
<li className="flex justify-between items-center py-[10px] px-[15px] bg-[rgba(53,56,62,0.05)] border border-[#4f4f4f] rounded-[4px]">
<Checkbox
checked={todo.completed}
onChange={() => dispatch({type: 'TOGGLE_TODO', payload: todo.id})}
>

<span
className={`${todo.completed && 'line-through'} text-[#35383E]`}
>

{todo.text}
</span>
</Checkbox>
<button

className="w-6 h-6 bg-[rgba(53,56,62,0.1)] border border-[#4F4F4F] rounded flex items-center justify-center"
onClick={() => dispatch({type: 'DELETE_TODO', payload: todo.id})}
>

<svg
width="15"
height="16"
viewBox="0 0 15 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.50002 9.81827L12.9548 15.2731L14.7731 13.4548L9.31829 8L14.7731 2.54518L12.9548 0.726901L7.50002 6.18173L2.04519 0.726902L0.226918 2.54518L5.68174 8L0.226919 13.4548L2.04519 15.2731L7.50002 9.81827ZM7.50002 9.81827L9.31829 8L7.50002 6.18173L5.68174 8L7.50002 9.81827Z"
fill="#4F4F4F"
/>

<path
d="M7.50002 9.81827L9.31829 8L7.50002 6.18173L5.68174 8L7.50002 9.81827Z"
fill="#4F4F4F"
/>

</svg>
</button>
</li>
</>
);
};

export default React.memo(TodoListItem);

//reducer를 사용하면 메모제이션이 쉽게 된다.

//dispatch를 하면 콜백을 안 해도 된다.

HTTP 메서드

  • GET: 데이터를 요청하기 위해 사용되는 메소드
  • POST: 데이터를 생성하기 위해 사용되는 메소드
  • PUT: 데이터를 업데이트하기 위해 사용되는 메소드
  • PATCH: 데이터를 업데이트하기 위해 사용되는 메소드
  • DELETE: 데이터를 삭제하기 위해 사용되는 메소드

프로토콜

HTTP 프로토콜: TCP/IP 기반으로 클라이언트와 서버 사이테 이루어지는 요청/응답 프로토콜
QUIC 프로토콜: UDP 기반으로 빠른 전송 속도를 가지는 프로토콜

Fetch API

const App = () => {
	const fetchData = () => {
		fetch('http://localhost:4000/todos')
		.then((res)=>{
			return res.json()
		})
		.then((data)=>{
			console.log(data)
		})
	}

}

Fetch API는 브라우저 내장 api로 Promise 기반으로 작동한다. 응답이 Promise 객체로 돌아오므로 해당 response를 json 형태로 변환하고 then을 사용해 json 형태의 응답에서 data만 받아 할당해야 한다.

fetch Async

async/await 를 사용해 then 상태를 생략할 수 있다. async 함수는 항상 promise를 반환 하고 await은 promise가 해결 될때 까지 기다리고 그 결과값을 리턴한다.

const fetchDataAsync = async() => {
	try{
		const res = await fetch('http//localhost:4000/todos')
		const data = await res.json();
		setTodos(data)
	}catch(err) {
		console.log(err)
	}
}


//fetch 데이터를 더 간략하게 표현
//const res = await(await fetch('http:localhost:4000/todos')).json()

Axios

axios 외부 라이브러리 Promise 기반이며 위의 방법들보다 더 간결하고 많은 기능을 사용할 수 있다. 자동으로 json 데이터를 직렬화/역직렬화 한다.

const axiosGet = () => {
	axios.get('http://localhost:4000/todos')
	.then((res)=> {
		console.log(res)
		setTodos(res.data)
	})
}

Axios Async

async/await를 사용하면 axios 요청을 처리하면 코드가 간결해진다.

const axiosAsyncGet = async () => {
	try {
		const res = await axios.get('http;//localhost:4000/todos');
		setTodos(res.data)
	}
}

useEffect

사이드 이펙트 - 컴포넌트 외부에 있는 데이터를 읽거나수정하는 작성을 수행할 수 있도록 하는 hook

useEffect(()=>{
	// 부수 효과를 수행하는 코드
 종속성 배열을 비워둔거면 이안에 있는 코드가 렌더링 영향 없이 한번만 실행된다.
fetchData();

return ()=> {
	언마운트 될 때 실행된다. 즉 컴포넌트가 화면에서 사라질 때 실행된다.
}
})

컴포넌트의 렌더링

리액트에서 상태를 변경해도 1씩 증가하는 이유는 상태 업데이트가 비동기적으로 수행되고, 여러 상태 업데이트가 하나의 렌더링 주기에 배치되기 때문에 리액트의 상태 업데이트 매커니즘은 성능 최적화를 위해 여러 상태 변경을 배치하여 한 번의 렌더링 주기 동안 처리 한다.

메모이제이션

비용이 큰 연산에 대한 결과를 저장해 두고, 이 저장된 값을 반환하는 훅

  • React.memo(): 컴포넌트를 메모이제이션
  • useCallback() : 함수를 메모이제이션
  • useMemo() : 값 자체를 메모이제이션

React.memo()

import React from "react";

const Display = ({ count }: { count: number }) => {
console.log("display rendered");

  
return (
<div>
<h1>COUNT : {count}</h1>
</div>
);
};

  

export default React.memo(Display);

컴포넌트의 메모이제이션 count 값이 바뀌지 않는 이상 재렌더링 되지 않는다.
React.memo는 컴포넌트의 props가 변경되지 않았을 때만 렌더링을 방지한다. 그러나 부모 컴포넌트가 렌더링될 때 마다 자식 컴포넌트에 새로운 콜백함수를 전달하는 경우, 자식 컴포넌트는 새로운 콜백 함수를 받게 되므로 재렌더링될 수 있다.
그래서 이걸 해결하기 위해 부모 컴포넌트에서 콜백 함수가 아닌 메모이제이션 된 콜백 함수를 전달해야 한다.

useCallback()

const onClickhandler = useCallback(()=>{
 setCount(count + 1)
},[count])

두번째 인자로 의존성 배열을 준다. 재렌더링 할때도 함수를 다시 만들지 말고 재활용한다. 즉 App 컴포넌트는 루트 컴포넌트이므로 매번 재렌더링 되는데 이 때 해당 함수는 제외된다.
그리고 종속성 배열을 전달하여 값이 변경될 때에만 콜백함수가 생성되도록 한다.
아니면 이전 상태값을 참조하는 것도 방법이다 e.g) setCount((prev)=> prev +1)

useMemo

cont initialItems = new Array(29_999_999).fill(0).map((-,1) => {
return{
	id: 1,
	selected: i === 29_999_998,
}
})

첫번째 인수로 어떠한 값을 반환하는 생성 함수를, 두 번째 인수로 해당함수가 의존하는 값의 배열을 전달한다. useMemo는 렌더링 발생 시 의존성 배열의 값이 변경되지 않으면 함수를 재실행하지 않고 이전에 기억해둔 값을 반환한다.

상태관리 라이브러리

ZUSTAND

zustand는 리액트 상태 관리를 위한 라이브러리이다. 다른 상태관리 라이브러리와는 다르게 직관이적이며 심플하다.
단일 스토어 패턴을 사용해 상태를 관리하는데 전역 상태를 하나의 객체에 저장하고 필요한 컴포넌트에서 import 해 사용한다. 액션은 상태 객체에 변경을 줄 수 있는 함수이다. 상태 객체의 메서드로 액션이 정의된다.

설치

npm install zustand

store 설정

import create from 'zustand'

//zustand 스토어 생성
const useStore = create((set)=> {
	//인자로 set을 받음
	count: 0,
	increment: () => set((state)=> ({count: state.count + 1}))
	decrement: () => set((state)=> ({count: state.count - 1})),
})

export default useStore

사용법

import useStore from './store'

const App () = ()=> {
	const count = useStore((state)=> state.count)
	const increment = useStore((state) => state.increment)
	const decrement = useStore((state)=> state.decrement)
return(
	<div>
		<h1>Counter</h1>
		<p>Count: {count}</p>
			<button onClick={increment}>Increment</button>
			<button onClick={decrement}>Decrement</button>
	</div>	

)
}
export default App;

zustand는 비동기적인 작업을 처리할 코드를 작성할 수 있지만 메모이제이션을 통해 성능 최적화를 하기 위해서는 필요한 상태나 액션만 선택해서 사용해야한다. 그렇지 않으면 메모이제이션 적용이 안 된다.

const Counter () = () => {
	const count = useStore((state)=> state.count) // count만 선택
	const increment = useStore((state)=> state.increment) // increment만 선택

}

count 와 increment 중 필요한 부부만 재렌더링이 된다.

React-Router

설치

npm install react-router-dom
  • 해시 라우터: URL에 #이 붙는 형식 /#/login
  • 브라우저 라우터: URL에 슬래시(/)가 붙어서 구성되는 형식

사용법

import { BrowserRoute, HashRouter } from "react-router-dom";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);
<Routes>
  <Route element={<Home />} path="/" />
  <Route element={<Cart />} path="/cart" />
</Routes>

동적 라우터 설정해서 사용

<Routes>
  ...  
  <Route element={<CartDetail />} path="/cart/:id" />
  ...
</Routes>

쿼리 스트링 처리

일반 경로나 동적 경로는 모두 쿼리 스트링을 가질 수 있다.
쿼리 스트링이나 URL 뒤에 ? 나 &로 추가적인 정보가 붙는 형식을 말한다.

const CartDeatil = () => {
	const [searchParams] = useSearchParams();
	console.log(searchParams.get(key))
	return <div>CartDetail</div>
}

export default CartDetail;

react-route 6.4 버전

createBrowerRouter 메서드안에 컴포넌트와 경로를 넣고 변수에 할당하여 그 변수를 RouterProver prop 라우터 객체로 전달해준다.

const router = createBrowerRouter([
	{
		element: <DefaultLayout />,
		children: [
			{

		path: '/',
		element: <Home />,
		},

	{

		path: '/login',
		element: <Login />,
		},

		{
		path: '/register',
		element: <Register />,
		},
	],
},

])

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
	<RouterProvider router={router} />
</React.StrictMode>

);

Next.js

react는 라이브러리라 부름 -파일의 구조나 폴더 구조 개발자가 직접 세팅을 함 ex) 라우팅 - 직접 라우팅 설치해서 설정해야함
vue.js,Nuxt.js, Next.js는 프레임워크라 부름 - 정해진 규칙을 따라야 함

![[Pasted image 20240607171102.png | 500]]

cd [폴더이름] -> 폴더로 이동
./ 현재 폴더
../ 상위폴더
../../ 상위 폴더 두번

라우팅 - URL을 통해 다른페이지 이동

라우터 - 라우팅을 관리

라우트 - 어떤페이지와 연결됐는지

![[Pasted image 20240607160352.png | 500]]

Next.JS 설치하기

Next.js (No boilerplate)

npm 초기화

npm init - y

Next.js 패키지 설치

npm install react@latest next@latest react-dom@latest

Package.json

"start" : "next dev"

보일러 플레이트(boilerplate)

보일러 플레이트(boilerplate)란 주로 프로그래밍이나 법률 문서에서 반복적으로 사용되는 표준화된 코드나 텍스트를 의미, 이 용어는 원래 철강 산업에서 철판을 의미하는 단어였으나 다양한 분야에서 공통적으로 사용되는 표현이나 코드 블록을 지칭

대표적으로 CRA, create-vite

Next.js 보일러 플레이트

npx create-next-app@latest

라우팅(Routing)

  • 라우팅은 웹 애플리케이션에서 사용자가 URL을 통해 다른 페이지로 이동하는 것을 의미합니다.
  • 사용자가 애플리케이션 내에서 다른 경로로 이동할 때, 해당 경로에 대한 새로운 콘텐측 ㅏ표시됩니다.
  • 예를 들어서 사용자가 '/about' 으로 이동하면 'About US' 페이지가 표시됩니다.

라우터(Router)

  • 라우터는 이러한 라우팅을 관리하고 처리하는 기능을 제공합니다.
  • Next.js 에서는 내장된 라우팅 시스템을 사용하여 페이지 간의 이동을 처리합니다.
  • 일반적으로 라우터는 사용자의 URL을 해석하고 해당 URL에 맞는 컴포넌트를 렌더링합니다.

라우트(Route)

  • 라우트는 URL과 특정 컴포넌트 간의 매핑을 나타냅니다.
  • 에를 들어 /about 경로에 대한 라우트는 "About us" 페이지 컴포넌트와 연결됩니다.
  • 라우트는 일반적으로 페이지의 경로와 해당 경로에 표시할 컴포넌트를 정의하는데 사용됩니다.

App Router

정의 Next.js 14에서 새롭게 도입된 라우팅 시스템
![[Pasted image 20240607172336.png ]]

Next.js는 App routing 방식과 pages routing 방식 둘 다 지원한다.
14 버전에서는 App routing 방식으로 변경 되었다.

기본적인 Route

app/
|-- blog/
	|-- page.tsx --- /blog
|-- about/
	|-- page.tsx ---/about

앱 라우터는 루트에 app 폴더 생성하면 자동으로 라우트가 생성이 된다. app 폴더 하위에 blog 페이지를 만들고 page.tsx 컴포넌트를 생성하면 /blog

중첩(nested Route)

app/
|- blog/
	|-- page.tsx - /blog
	|-- first/
		|-- page.tsx -- /blog/first

다이나믹 경로

동적(dynamic) 경로를 사용하면 특정 세그먼트가 동적으로 변경되는 경로를 지정 할 수 있음

app/
|--blog/
	|--page.tsx- /blog
	|--[id]/
		|--comment/
		|--[reviewId]/
			|-- page.tsx - /blog/2/comment/1

포괄적인 경로(catch all segments)

특정 경로 이하의 모든 경로를 포괄적으로 허용하는 라우트 지정 방식

app/
|-docs/
	|--[...slug]
		|--page.tsx -/docs/1 or /docs/1/api or /docs/1/api/ -- 모든경로 커버
const Docs =({
	params,
}): {
 params: {
 slug: string[];
 }
}) => {
	console.log(params) // docs 이후의 경로가 배열로 들어감 ex/
}

프라이빗 폴더

app
|-- _utils
	|-page.tsx- 접근 불가
	form-date.ts

not-found 페이지 설정하기

루트 경로에 not-found.tsx 파일은 경로를 찾지 못했을 때 보여줄 페이지다. next.js에서는 not-found 파일을 특정 그룹에 커스텀해서 작성할 수 없다.

layout

루트 레이아웃은 모든 페이지에 공통적으로 사용되는 header와 footer가 포함된다.

export const metadata = {
	title: {
		// %s는 하위 layout에 metadata에서 title을 받아와서 적용이 된다.
		template: '&s | funcoding'
		default : 'Next.js | funcoding'
	},
	description: 'First Next.js'
}

const RootLayout = ({children} : {children: React.ReactNode})=> {
		return (

// 이래야 html body 태그가 생성된다.
<html lang="en">
	<body>
		<h1>Root Header</h1>
			{children}
		<Link href="/blog">로그인</Link>
		<h1>Root Footer</h1>
		</body>
</html>
);
}

usePathname

현재 페이지의 경로를 가져오는 리액트 라우터의 훅이다

const pathname = usePathname()

하이드레이션(hydration)

next.js 는 두 가지 컴포넌트가 존재하며 두 번의 렌더링을 하는데 서버 컴포넌트는 서버 측에서 실행되는 리액트 컴포넌트로 SSR을 지원하며 초기 HTML 을 생성한다. 생성된 HTML을 클라이언트로 전달해 웹페이지의 초기 로딩 시간을 줄이고 SEO를 향상 시킨다.

클라이언 컴포넌트는 브라우저에서 실행되는 리액트 컴포넌트로 CSR을 지원해 서버에서 초기 HTML을 전달 받고 이후에 클라이언트에서 JS를 적용하며 초기 html에 살을 붙이는 역할을 한다 하여 뼈대에 수분을 공급하는 기능인 하이드레이션이라고 불리는 개념이다.

use client

use client를 쓰면 해당 컴포넌트는 클라이언트 컴포넌트이다.

폰트 최적화

import {Roboto} from 'next/font/google'

const roboto = Rotobo({weight:['400','700'], subsets:['latin']})
<h1 className = {roboto.className}> hello, world! <h1>

본 후기는 본 후기는 [유데미x스나이퍼팩토리] 프로젝트 캠프 : Next.js 1기 과정(B-log) 리뷰로 작성 되었습니다.

0개의 댓글

관련 채용 정보