no image
state, useState
리액트의 state는 일반 자바스크립트 객체이고 랜더링을 유발하는 변수라고 볼 수 있다. state 값이 변경되면 리액트는 해당 컴포넌트를 다시 랜더링 한다. useState() 함수를 통해 state를 사용할 수 있다. useState는 배열을 반환하는데 [state, setFunction] 형태이다. useState의 인자는 state의 초기값이다. 여기서 setFunction은 state의 값을 변경하는 데 사용된다. import React, { useState } from 'react'; function ToggleComponent() { // useState를 사용하여 상태 초기화 const [state, setState] = useState('yes'); // 초기 상태는 'yes' // 상태..
2024.03.06
no image
Zustand 상태 관리 라이브러리
리액트에는 상태(state) 라는 개념이 있다. 상태에 대해서는 나중에 다룰 예정인데, 일단은 state는 컴포넌트 내부에서 변경되는 값을 다루는 객체이자, 변화하면 리랜더링 되고 컴포넌트에 반영되는 변수 정도로 이해해두자. 리액트는 state 값을 추적하고 있다. state의 변화를 감지해서 리랜더링을 진행한다. 추후에 서술하겠지만, 그냥 state 를 변경한다고 해서 변화를 감지하는 것이 아니고, setState함수를 통해 주소값이 변경돼야 상태가 변한 것으로 인지한다. 나중에 상태 관련해서 글 쓸 예정 그리고 한 컴포넌트가 아니라 전역적으로 관리해야 하는 상태를 위해 위해 상태 관리 라이브러리를 사용한다. useContext()도 있지만 불필요한 리랜더링을 야기할 수 있다. 이러한 상태 관리를 위해..
2024.01.29
no image
React Router, Outlet
과거의 웹사이트는 페이지가 바뀔때마다 새로운 페이지를 로드해야 했다. 직관적으로 생각해봐도 사용자 경험을 저하시킬 수 밖에 없다. 싱글 페이지 애플리케이션(spa)는 이를 개선하기 위해 만들어졌다. 전체 페이지를 갱신하는 것이 아니라 필요한 부분만 랜더링하여 갱신해준다. 라우터는 사용자가 입력한 URL에 따라 필요한 데이터를 보여주는 역할을 한다. 근데, 앞서 말했듯이 경로마다 새로운 페이지를 가져오는 것이 사용자 경험을 저하시킨다면, 싱글 페이지 애플리케이션의 특성을 살려서, 모든 요소를 랜더링으로 바꾸면 라우터를 쓸 필요도 없지 않나? 맞는 말이지만, 그러면 URL이 하나이기 새로고침 하면 초기 페이지로 이동하고, 뒤로가기도 사용하기 힘들다. 때문에 (프로젝트 관리와 유지를 위해서라도) 경로를 나누어..
2024.01.29
[블로그 프로젝트] forwardRef를 사용해서 Input 컴포넌트 만들기
함수형 컴포넌트는 레퍼런스가 없기 때문에 ref를 사용할 수 없다. 때문에 함수형 컴포넌트에 ref를 통해 데이터를 전달하려면 forwardRef() 를 사용해야 한다. const InputBox = forwardRef((props: Props, ref) => { // state:properties // const { label, type, value, placeholder, error, icon, message} = props; const { onChange , onButtonClick, onKeyDown} = props; // event handler: 키 이벤트 처리 함수 // const onKeyDownHandler = (event: KeyboardEvent) => { if(!onKeyDown) ..
2024.01.27
no image
리액트 Ref, state와의 차이
const [fooBar, setFooBar] = useState() Ref는 Reference의 약자로 리액트에서 DOM 노드나 React 엘리먼트에 접근하는 방법을 제공한다. 요소를 getElementById('root')로 DOM에 접근하는 것과 비슷한 역할을 한다. import { useRef } from 'react'; const ref = useRef(0); userRef 훅을 통해서 컴포넌트에 ref를 추가할 수 있다. 그리고 useRef는 다음과 같은 객체를 반환한다. { current: 0 // The value you passed to useRef } current속성에 DOM 요소를 자바스크립트 객체로 받아온다. current 속성을 통해서 ref의 현재 값에 액세스 할 수 있고, 읽..
2024.01.27
no image
[블로그 프로젝트] DTO 정의
서버에서 ResponseEntity를 통해 오는 응답을 받을 DTO를 프론트에서 정의해준다. 타입스크립트의 장점을 살려 타입을 맞춰준다. 서버에서 오는 ResponseDto enum을 통해 code를 매크로로 정의한다 (VO) Code는 ResponseCode, message는 string 타입으로 형을 맞춰준다. 서버에서 json객체로 오는 response는 구조화 대입을 통해 RespoonseDto에 할당된다.
2024.01.14
no image
[블로그 프로젝트] 리액트(타입스크립트) 프로젝트 생성, index.html, App.tsx, index.tsx
npx create-react-app projectName --template typescript 타입스크립트를 사용하는 리액트 프로젝트를 생성 package.json 과 함께 기본 디렉토리 구조가 생선된다. public경로 안데 index.html 파일을 볼 수 있다. 큰 내용 없는 html 파일이다. 중요한건 "root" id 를 달고 있는 div 태그이다. 이후에 살펴보겠지만 해당 요소에 리엑트 엘리먼트가 표시된다. 프로젝트 진행을 위한 디렉토리 구조이다. index.tsx를 살펴보자 import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import..
2024.01.12

state, useState

sumjo
|2024. 3. 6. 01:36

리액트의 state는 일반 자바스크립트 객체이고 랜더링을 유발하는 변수라고 볼 수 있다.

state 값이 변경되면 리액트는 해당 컴포넌트를 다시 랜더링 한다.

 

useState() 함수를 통해 state를 사용할 수 있다.

useState는 배열을 반환하는데 [state, setFunction] 형태이다. useState의 인자는 state의 초기값이다.

여기서 setFunction은 state의 값을 변경하는 데 사용된다.

import React, { useState } from 'react';

function ToggleComponent() {
  // useState를 사용하여 상태 초기화
  const [state, setState] = useState('yes'); // 초기 상태는 'yes'

  // 상태를 'no'로 업데이트하는 함수
  const changeStateCorrectly = () => {
    setState('no'); // 올바른 방법: setState를 사용하여 상태 업데이트
  };

  // 잘못된 방법으로 상태를 변경하려고 시도하는 함수 (실제로는 작동하지 않음)
  const changeStateIncorrectly = () => {
    state = 'no'; // 잘못된 방법: 직접 상태를 수정하려고 시도
    console.log(state); // 이 코드는 실제로는 React 상태 업데이트 원칙에 어긋나며, 실행되지 않음
  };

  return (
    <div>
      <p>Current state: {state}</p>
      <button onClick={changeStateCorrectly}>Change State Correctly</button>
      <button onClick={changeStateIncorrectly}>Change State Incorrectly (Won't Work)</button>
    </div>
  );
}

export default ToggleComponent;

 

위에 changeStateIncorrectly에서 state 값에 직접 할당을 하는 방식은 오류가 난다.

state는 직접 변경할 수 없고 useState에서 반환받은 setState를 통해 업데이트 해야 한다. state는 기본적으로 Immutable 객체이다.

 

리액트는 setState가 호출되면 랜더링큐에 컴포넌트가 들어가게 된다. 함수형 컴포넌트라면 함수의 반환값이 jsx 컴포넌트로 변화 되는 것이다. 리액트는 가상돔 트리를 만들어 이전의 트리와 차이를 비교한다.

여기서 state의 '차이'를 감지할 때 값이 아닌 콜스택의 주소값을 비교 즉 '얕은 비교'를 통해 변화를 인지한다.

때문에 state는 불변성을 지켜야 빠른 랜더링과 사이드 이펙트를 방지할 수 있다.

 

출처 : https://velog.io/@mollog/React%EC%97%90%EC%84%9C%EC%9D%98-%EA%B0%80%EC%83%81%EB%8F%94-%EA%B0%9C%EB%85%90

기존의 가상 돔과 변화된 버전의 가상돔을 비교하고 차이점을 commit한 후 다시 브라우저에 적용하는 모습이다.

 재귀적으로 만들어진 트리이기 때문에 하위 컴포넌트들도 자동으로 다시 호출되고 하위 컴포넌트도 변화를 감지한다.

 

import React, { useState } from 'react';

function CounterExample() {
  // useState를 사용하여 count 상태를 초기화
  const [count, setCount] = useState(0);

  function handleClick() {
    // 첫 번째 setCount는 현재 count 값(스냅샷)에 1을 더한다.
    setCount(count + 1);
    // 두 번째 setCount는 동일한 스냅샷(count의 현재 값)에 1을 더한다.
    setCount(count + 1);
    // 세 번째 setCount도 동일하게 처리된다.
    setCount(count + 1);
  }

  return (
    <div>
      <button onClick={handleClick}>Increase Count</button>
      <p>Count: {count}</p>
    </div>
  );
}

export default CounterExample;

 

state는 스냅샷으로 제공된다.

떄문에 setCount에서 count + 1을 하는 모든 count 는 스냅샷을 찍었던 시점의 값으로 저장된다.

리액트는 batcing을 통해 state를 변경하는 작업을 일괄적으로 처리한다.

리액트에는 상태(state) 라는 개념이 있다.

상태에 대해서는 나중에 다룰 예정인데,

일단은 state는 컴포넌트 내부에서 변경되는 값을 다루는 객체이자, 변화하면 리랜더링 되고 컴포넌트에 반영되는 변수 정도로 이해해두자.

리액트는 state 값을 추적하고 있다. state의 변화를 감지해서 리랜더링을 진행한다.

추후에 서술하겠지만, 그냥 state 를 변경한다고 해서 변화를 감지하는 것이 아니고, setState함수를 통해 주소값이 변경돼야 상태가 변한 것으로 인지한다. 나중에 상태 관련해서 글 쓸 예정

그리고 한 컴포넌트가 아니라 전역적으로 관리해야 하는 상태를 위해 위해 상태 관리 라이브러리를 사용한다.

useContext()도 있지만 불필요한 리랜더링을 야기할 수 있다.

 

이러한 상태 관리를 위해 여러가지 라이브러리가 있는데, Redux, Recoil 등이 유명한 것으로 안다.

내가 써볼 라이브러리는 Zustand 이다.

사용은 어렵지 않다.

npm install zustand

받아준다 늘 그렇듯이.

 

참고 : https://github.com/pmndrs/zustand

 

GitHub - pmndrs/zustand: 🐻 Bear necessities for state management in React

🐻 Bear necessities for state management in React. Contribute to pmndrs/zustand development by creating an account on GitHub.

github.com

zustand 예시 및 설명이 잘 돼있다.

ts 사용 예시이다.

import { User } from "types/interface";
import { create } from "zustand";

interface LoginUserStore {
	loginUser: User | null;
	setLoginUser: (loginUser: User) => void;
	resetLoginUser: () => void;
};

const useLoginUserStore = create<LoginUserStore>((set) => ({
	loginUser: null,
	setLoginUser: (loginUser) => set(state => ({...state, loginUser })),
	resetLoginUser: () => set(state => ({...state, loginUser: null})),
}));

export default useLoginUserStore;

creat 를 통해 store 를 만들어 준다.

내부적으로 set 함수를 통해 상태를 변경해준다.

state를{...state}로 넣어준 이유는

요약하자면

리액트와 똑같이 state는 불변으로 유지해야 한다.

즉 초기에 할당한 값 자체를 바꾸면 안되고, 변경을 위해서 항상 새로운 할당을 진행한다.

immutable 유지를 위해 전개 연산자를 통해 state를 복사해준 뒤 상태를 변경한다.

하지만 밑에까지 보면 상태를 병합해주기 때문에 skip할 수 있다는 것도 쓰여있는데, 자세히는 모르겠다.

 

import { useLoginUserStore } from 'stores'

const  { setLoginUser, resetLoginUser } = useLoginUserStore();

const { loginUser } = useLoginUserStore();

 

이렇게 import 해온 뒤

다른 파일에서 loginUser의 상태를 참조하거나,

set함수를 통해 상태를 변경할 수 있다.

'React' 카테고리의 다른 글

state, useState  (0) 2024.03.06
React Router, Outlet  (0) 2024.01.29
[블로그 프로젝트] forwardRef를 사용해서 Input 컴포넌트 만들기  (0) 2024.01.27
리액트 Ref, state와의 차이  (0) 2024.01.27
[블로그 프로젝트] DTO 정의  (0) 2024.01.14

React Router, Outlet

sumjo
|2024. 1. 29. 08:13

과거의 웹사이트는 페이지가 바뀔때마다 새로운 페이지를 로드해야 했다.

직관적으로 생각해봐도 사용자 경험을 저하시킬 수 밖에 없다.

싱글 페이지 애플리케이션(spa)는 이를 개선하기 위해 만들어졌다.

전체 페이지를 갱신하는 것이 아니라 필요한 부분만 랜더링하여 갱신해준다.

 

라우터는 사용자가 입력한 URL에 따라 필요한 데이터를 보여주는 역할을 한다.

근데, 앞서 말했듯이 경로마다 새로운 페이지를 가져오는 것이 사용자 경험을 저하시킨다면,

싱글 페이지 애플리케이션의 특성을 살려서, 모든 요소를 랜더링으로 바꾸면 라우터를 쓸 필요도 없지 않나?

맞는 말이지만, 그러면 URL이 하나이기 새로고침 하면 초기 페이지로 이동하고, 뒤로가기도 사용하기 힘들다.

때문에 (프로젝트 관리와 유지를 위해서라도) 경로를 나누어 웹페이지를 구성할 필요가 있다.

그리고 이를 라우팅 이라고 한다. 

덧붙이자면 우리가 사용할 라우터 또한 사실 페이지 경로에 따라 필요한 컴포넌트만 랜더링 해서 보여주는 것이다.

 

리액트는 기본적으로 html파일이 하나만 있는 SPA 라이브러리이고 내장된 route 기능이 없다.

그래서 라우팅을 위해서는 다른 라이브러리가 필요한데, react-router-DOM 라이브러리를 사용해볼것이다.

 npm install react-router-dom

react-router-dom 을 설치해준다.

 

먼저 최상단을 <BrowserRouter> 태그로 감싸주어야 한다.

index.tsx

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

 

 

App.tsx

import { Route, Routes } from 'react-router-dom';

return (
    <Routes>
      <Route element={<Container />}>
        <Route path={MAIN_PATH()} element={<Main />} / >
        <Route path={AUTH_PATH()} element={<Authentication />} / >
        <Route path={SEARCH_PATH(':searchWord')} element={<Search />} / >
        <Route path={USER_PATH(':userEmail')} element={<UserP />} / >
        <Route path={BOARD_PATH()}>
          <Route path={BOARD_WRITE_PATH()} element={<BoardWrite/>}/>
          <Route path={BOARD_DETAIL_PATH(':boardNumber')} element={<BoardDetail/>}/>
          <Route path={BOARD_UPDATE_PATH('boardNumber')} element={<BoardUpdate/>} />
        </Route> 
      <Route path="*" element={<h1>Not Found</h1>} />
      </Route>
    </Routes>
  );
}

<Routes> 태그는 모든 <Route>의 상위 경로에 존재해야 한다.

<Route> 태그의 인자

path에는 경로를 전달하고 element로는 경로에 따라 랜더링 해줄 엘리멘트(화면)을 전달한다.

경로를 변수로 바꿔놓은 상태라 알아보기 힘들 수 있는데,

예시를 들자면

MAIN_PATH = http://localhost:3000/

BOARD_PATH() = http://localhost:3000/board

BOARD_WRITE_PATH(:boardNumber) = http://localhost:3000/:boardNumber <- 쿼리스트링

 

이런식으로 경로에 따른 화면을 랜더링 해준다.

 

Outlet

Outlet 태그는 부모 엘리먼트에서 사용해 자식 엘리먼트를 랜더링 하고 싶을 때 사용한다.

function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>

      {/* This element will render either <DashboardMessages> when the URL is
          "/messages", <DashboardTasks> at "/tasks", or null if it is "/"
      */}
      <Outlet />
    </div>
  );
}

function App() {
  return (
    <Routes>
      <Route path="/" element={<Dashboard />}>
        <Route
          path="messages"
          element={<DashboardMessages />}
        />
        <Route path="tasks" element={<DashboardTasks />} />
      </Route>
    </Routes>
  );
}

App에 DashboardMessages와 DashboardTasks를 <Route path="/" element={<Dashboard>} 로 감싸고 있는 것을 볼 수있다.

Dashboard를 보면 div태그 안에 <Outlet /> 을 확인할 수 있다.

해당 요소는 URL이 "/messages"일 때 <DashboardMessages>를 렌더링하고, "/tasks"일 때는 <DashboardTasks>를 렌더링합니다. 만약 URL이 "/"일 경우에는 null을 렌더링합니다.

설명처럼, Outlet 태그 자리에 자식 엘리먼트가 랜더링이 된다고 알아두면 될 듯 하다. 이를 활용해서 Header와 Footer를 랜더링 할 것이다.

 

간단한 원리

깊게 공부한 건 아니지만 뇌피셜을 곁들인 원리를 써보자면

 

1. BrowserRouter는 HistoryAPI를 사용해서 사용자가 방문한 모든 경로를 객체로 만들어 하위 <Routes>에 전달한다.

 

2. HistoryAPI는 stack구조이기 때문에 경로가 바뀌면 stack의 최상단이 변경되면서 <Routes>가 참조하던 객체도 새로운 

것으로 변경된다.

 

3. 이로 인해 Routes 컴포넌트가 리랜더링 되고 하위 라우팅 관련 컴포넌트들도 리랜더링 된다.

 

 

 

함수형 컴포넌트는 레퍼런스가 없기 때문에 ref를 사용할 수 없다.

때문에 함수형 컴포넌트에 ref를 통해 데이터를 전달하려면 forwardRef() 를 사용해야 한다.

 

const InputBox = forwardRef<HTMLInputElement, Props>((props: Props, ref) => {

	//         state:properties 			//
	const { label, type, value,  placeholder, error, icon, message} = props;
	const { onChange , onButtonClick, onKeyDown} = props;

	//  event handler: 키 이벤트 처리 함수 			//
	const onKeyDownHandler = (event: KeyboardEvent<HTMLInputElement>) => {
		if(!onKeyDown) return;
		onKeyDown(event);
	}

	//       render: Input Box 컴포넌트 			//
	return (
		<div className='inputbox'>
			<div className='inputbox-label'>{label}</div>
			<div className={error ? 'inputbox-container-error' : 'inputbox-container'}>
				<input  ref={ref} type = {type} className='input' placeholder={placeholder} value={value} onChange={onChange} onKeyDown={onKeyDownHandler}/>
				{onButtonClick !== undefined && (
				<div className='icon-button' onClick={onButtonClick}>
					{icon !== undefined && (
					<div className={`icon ${icon}`}></div>
					)}
				</div>
				)}
			</div>
			{message !== undefined && <div className='inputbox-message'>{message}</div>}
		</div>
	)
})

export default InputBox

 

위와 같이 forwardRef를 통해 인자로 props와 함께 ref를 받아서 처리할 수 있다.

props의 각 변수와 함수를 input 요소에 맵핑 시켜준다.

onKeyDownHandler를 통해 keyEvent 처리 함수도 만들어준다.

 

//				event handler: 이메일 인풋 키 다운 이벤트 처리				//
const onEmailKeyDownHandler = (event: KeyboardEvent<HTMLInputElement>) => {
	if (event.key !== 'Enter') return;
	if (!passwordRef.current) return;
	passwordRef.current.focus();
}

//				event handler: 패스워드 키 다운 이벤트 처리				//
const onPassworKeyDownHandler = (event: KeyboardEvent<HTMLInputElement>) => {
	if (event.key !== 'Enter') return;
	onSignInButtonClickHandler();
}



return (

..
..

</div>
	<InputBox ref={emailRef} label = '이메일주소' type = 'text' placeholder='이메일 주소를 입력해주세요.' error={error} value={email} onChange={onEmailChangeHandler} onKeyDown={onEmailKeyDownHandler}/>
	<InputBox ref={passwordRef} label = '패스워드' type = {passwordType} placeholder='비밀번호를 입력해주세요.' error={error} value={password} onChange={onPasswordChangeHandler} icon={passwordButtonIcon} onButtonClick={onPasswordButtonClickHandler} onKeyDown={onPassworKeyDownHandler}/>
</div>
)

 

로그인 카드 컴포넌트의 일부이다.

InputBox에 props와 함께 ref를 전달하고 이메일 입력에서 keyDown(Enter)이벤트 발생 시 비밀번호 입력창으로 포커스를 옮겨주고

비밀번호 입력후 다시 Enter를 누를 시 로그인 버튼 클릭 이벤트 함수를 호출해서 로그인 동작이 일어난다.

const [fooBar, setFooBar] = useState()

Ref는 Reference의 약자로 리액트에서 DOM 노드나 React 엘리먼트에 접근하는 방법을 제공한다.

<div id='root'> 요소를 getElementById('root')로 DOM에 접근하는 것과 비슷한 역할을 한다.

 

 

import { useRef } from 'react';

const ref = useRef(0);

userRef 훅을 통해서 컴포넌트에 ref를 추가할 수 있다.

그리고 useRef는 다음과 같은 객체를 반환한다.

{ 
  current: 0 // The value you passed to useRef
}

 

current속성에 DOM 요소를 자바스크립트 객체로 받아온다.

current 속성을 통해서 ref의 현재 값에 액세스 할 수 있고, 읽기와 쓰기 모두 가능하다.

ref.current = ref.current + 1 같은 식으로 사용할 수 있는 것이다.

이 부분이 state 와의 차이점이다.

const [fooBar, setFooBar] = useState()

위와 같이 set함수를 통해서 fooBar를 초기화시켜야 한다.

state의 사용 목적을 생각해보면 알 수 있다. state는 변화가 있을 때 랜더링을 해야하는 정보에 사용한다. 리액트는 state의 변화를 내부 값의 변화가 아닌 주소값이 이전과 달라졌을 때 감지한다. 때문에 set함수를 통해 초기화 하는 것이다.

그렇다면, 이 차이를 통해서 ref의 목적을 예상해볼 수 있겠다. ref는 변화가 발생하지만 새로운 랜더링을 하고싶지 않을 때 사용할 수 있다.

또한 state는 라이프사이클을 가지는 변수에 가깝고 ref는 위에서 말했듯 dom에 접근하기 위해 사용한다.

공식문서에서는 특정 정보를  '기억'하고싶지만 새로운 랜더링을 촉발하지 않도록 할 때 ref의 사용을 권한다.

 

import React, { useRef, KeyboardEvent } from 'react';

    const App = () => {
      const passwordRef = useRef<HTMLInputElement>(null);

      const onKeyDownHandler = (event: KeyboardEvent<HTMLInputElement>) => {
        if (event.key === 'Enter') {
          passwordRef.current?.focus();
        }
      };

      return (
        <div>
          <input placeholder="아이디 입력" onKeyDown={onKeyDownHandler} />
          <input ref={passwordRef} placeholder="비밀번호 입력" />
        </div>
      );
    };

export default App;

 

많이 사용하는, 로그인 창에서 엔터키를 눌렀을 때 비밀번호 입력 요소로 마우스 포커스가 이동하는 예시 코드이다.

다음 글에서 로그인창, 검색창 등에 사용할 Input 컴포넌트를 생성하면서 ref와 forwardRef를 사용해서 만들어보자.

 

서버에서 ResponseEntity를 통해 오는 응답을 받을 DTO를 프론트에서 정의해준다.

타입스크립트의 장점을 살려 타입을 맞춰준다.

서버에서 오는 ResponseDto

enum을 통해 code를 매크로로 정의한다 (VO)

Code는 ResponseCode, message는 string 타입으로 형을 맞춰준다.

서버에서 json객체로 오는 response는 구조화 대입을 통해 RespoonseDto에 할당된다.

npx create-react-app projectName --template typescript

타입스크립트를 사용하는 리액트 프로젝트를 생성

 

 

package.json 과 함께 기본 디렉토리 구조가 생선된다.

 

public경로 안데 index.html 파일을 볼 수 있다.

 

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- 구글 inter font -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter&display=swap" rel="stylesheet">

    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

큰 내용 없는 html 파일이다.

중요한건 "root" id 를 달고 있는 div 태그이다.

이후에 살펴보겠지만 해당 요소에 리엑트 엘리먼트가 표시된다.

프로젝트 진행을 위한 디렉토리 구조이다.

 

index.tsx를 살펴보자

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

 

React와 ReactDom을 import 해준다.

getElementById()를 통해서 아까 index.html의 <div id ="root"> 요소를 가져온다.

createRoot 함수는 공식 문서를 참고하면

DOM 요소 내부에 콘텐츠를 표시하기 위한 React 루트를 생성한다.

간단히 말하자면 호출한 요소 자리에 랜더링 한다는 의미이다.

root.render 를 통해서 말이다. App.tsx를 import하여 랜더링 해주는 모습을 볼 수 있다.

즉, App.tsx에 화면에 나타날 컴포넌트들을 정의하면 되겠다.

 

App.tsx(예시)

//          component: Application 컴포넌트 			  //
function App() {
  return (
    <Routes>
      <Route element={<Container />}>
        <Route path={MAIN_PATH()} element={<Main />} / >
        <Route path={AUTH_PATH()} element={<Authentication />} / >
        <Route path={SEARCH_PATH(':searchWord')} element={<Search />} / >
        <Route path={USER_PATH(':userEmail')} element={<UserP />} / >
        <Route path={BOARD_PATH()}>
          <Route path={BOARD_WRITE_PATH()} element={<BoardWrite/>}/>
          <Route path={BOARD_DETAIL_PATH(':boardNumber')} element={<BoardDetail/>}/>
          <Route path={BOARD_UPDATE_PATH('boardNumber')} element={<BoardUpdate/>} />
        </Route> 
      <Route path="*" element={<h1>Not Found</h1>} />
      </Route>
    </Routes>
  );
}

export default App;

컴포넌트를 표시하고 라우트를 설정해주었다.

 

이외 디렉토리는 내가 만든 디렉토리이다.

간단한 설명

apis - 서버 요청에 대한 메소드 및 path 정리

components - 리액트 컴포넌츠

layouts - header 와 footer를 포함한 컨테이너

types - dto와 props 등의 데이터 타입 선언

views - 각 페이지 view