React는 SPA 개발의 하나로 Facebook에서 개발한 UI 라이브러리로, 복잡한 UI를 간단한 컴포넌트(Component)로 나누어 관리할 수 있도록 도와줍니다. React는 UI의 상태에 따라 뷰를 자동으로 업데이트하고, 재사용성이 높은 컴포넌트를 통해 개발 생산성을 향상시켜주는 등 많은 이점을 제공합니다. 이러한 React를 사용하여 웹 어플리케이션, 서버 사이드 렌더링, 모바일 애플리케이션 등 다양한 분야에서 활용됩니다.
SPA(단일 페이지 애플리케이션) 개발은 전통적인 웹 개발 방식과는 달리, 한 개의 페이지에서 모든 요청에 대한 처리를 하며 필요한 데이터만 비동기적으로 받아와 화면을 갱신하는 방식으로 동작합니다.
SPA에서는 페이지 이동이 없으며, 모든 컨텐츠는 동적으로 로딩됩니다. 이를 위해 대부분의 SPA 프레임워크(예: React, Angular, Vue 등)는 클라이언트 측 라우팅과 데이터 관리를 지원합니다.
SPA는 사용자 경험(UX)을 향상시킬 수 있으며, 속도가 빠르고 반응성이 높아서 모바일 앱과 유사한 경험을 제공할 수 있습니다. SPA 개발 방식은 API 서버와의 연동을 중요하게 다루며, RESTful API를 사용하여 데이터를 주고받습니다.
하지만, SEO(Search Engine Optimization) 문제나 초기 로딩 속도 등의 문제도 있습니다. SPA는 초기 로딩이 느릴 수 있고, JavaScript를 지원하지 않는 일부 검색 엔진에서는 페이지를 인덱싱하기 어려울 수 있습니다. 이러한 문제를 해결하기 위해 서버 사이드 렌더링, 프리렌더링, 라우팅 처리 등의 기술을 적용하여 보완할 수 있습니다.
라우팅(Routing)은 웹 애플리케이션에서 클라이언트의 요청에 따라 적절한 컨텐츠나 페이지를 반환하는 것을 말합니다. 이를 위해서는 URI(Uniform Resource Identifier)를 기반으로 사용자가 요청한 컨텐츠를 찾아서 반환해야 합니다.
클라이언트 사이드 라우팅은 브라우저에서 처리되며, 페이지 전환 시 전체 페이지를 새로 불러오지 않고, 페이지 내에서 필요한 부분만 동적으로 갱신하는 방식을 말합니다. 이 방식은 페이지 전환의 빠른 응답 속도와 애플리케이션의 부드러운 동작을 보장합니다.
React에서는 react-router-dom
라이브러리를 이용하여 라이팅을 구성합니다.
React 코딩 컨벤션은 코드를 작성하는 방법을 표준화하여 가독성을 향상시키고 유지보수를 용이하게 합니다.
예:App.js, MyComponent.jsx
예: function myFunctionComponent() {}
예: <MyComponent myProp="value" />
예: <div className="my-class" />
예:
{isLoggedIn ? <UserProfile /> : ''} X
{isLoggedIn && <UserProfile />} O
예: /my-account/my-profile
React CRA(Create React App)란, React 앱 개발을 위한 보일러플레이트(프로젝트 템플릿)로, React 프로젝트를 쉽게 시작할 수 있도록 해주는 공식 도구입니다.
React CRA를 사용하면, 프로젝트를 생성하고 개발 서버를 실행하는 것이 쉽고 빠르며, 초기 설정과 구성이 자동으로 이루어지므로 개발자는 바로 애플리케이션의 로직 구현에 집중할 수 있습니다.
React CRA로 시작하는 방법은 다음과 같습니다.
NVM 설치는 window 와 MAC 이 다르므로 설치시 유의해서 설치 합니다.
npx react-create-app [프로젝트 이름]
my-app/
README.md // 프로젝트에 대한 설명을 담은 파일입니다.
node_modules/ // 프로젝트에서 사용하는 모든 라이브러리와 도구들이 설치되는 폴더입니다.
package.json // 프로젝트의 메타데이터와 의존성 정보를 담고 있는 파일입니다.
public/ // 정적 파일들이 위치하는 폴더입니다.
index.html // 프로젝트의 진입점 HTML 파일입니다.
favicon.ico
src/ // 프로젝트의 모든 소스 코드가 위치하는 폴더입니다.
App.css
App.js // create-react-app 명령어로 생성된 기본 앱 컴포넌트입니다.
index.css
index.js // React 앱의 진입점 JavaScript 파일입니다.
logo.svg
.gitignore
Prettier는 코드 포맷팅 툴 중의 하나로, 코드의 가독성을 높이고 일관성 있는 스타일로 코드를 작성할 수 있도록 도와줍니다.
1. prettier 설치하기
npm install --save-dev prettier
module.exports = {
singleQuote: true,
// 문자열은 작은 따옴표로 통일
semi: true,
//코드 마지막에 세미콜른이 자동 생성
tabWidth: 4,
// 들여쓰기 너비는 4칸
trailingComma: 'all',
// 객체나 배열 키:값 뒤에 콤마 생성
printWidth: 160,
// 코드 한줄이 maximum 80칸
arrowParens: 'avoid',
// 화살표 함수가 하나의 매개변수를 받을 때 괄호 생략
};
추가설명 Prettier Options
.prettierignore 파일 생성하기 (옵션)
Prettier를 사용할 때 무시해야 할 파일이 있다면 .prettierignore 파일을 생성하고 해당 파일 목록을 작성합니다.
package.json 파일 수정하기
package.json 파일의 scripts 항목에 Prettier를 실행하는 스크립트를 추가합니다.
"scripts": {
"format": "prettier --write \"src/**/*.js\""
},
ESLint는 ES(Ecma Script)
[Lint](https://ko.wikipedia.org/wiki/%EB%A6%B0%ED%8A%B8_(%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4) (소스 코드를 분석하여 프로그램 오류, 버그, 스타일 오류, 의심스러운 구조체에 표시를 달아놓기 위한 도구) 자바스크립트 코드에서 일관성을 유지하고 잠재적인 오류를 발견하기 위한 정적 분석 도구 중 하나입니다. React 프로젝트에서 ESLint를 적용하기 위해서는 다음과 같은 단계를 따르면 됩니다.
npm install --save-dev eslint
{
"env": {
"browser": true,
"es6": true
},
"extends": [
"plugin:react/recommended",
"airbnb",
"prettier",
"prettier/react"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 2018,
"sourceType": "module"
},
"plugins": ["react", "prettier"],
"rules": {
"react/prop-types": "off",
"prettier/prettier": "error"
}
}
위의 설정 코드에서는 다음과 같은 내용이 포함되어 있습니다:
위의 코드에서는 AirBnb의 JavaScript 스타일 가이드를 기반으로 하고 있습니다. 이 가이드를 따르는 것이 좋은 코드 스타일을 유지하기 위한 방법 중 하나입니다.
React에서 라이프사이클(lifecycle)이란 컴포넌트가 마운트, 언마운트, 업데이트되는 과정에서 호출되는 여러 메서드를 말합니다.
React 16.8 버전 이후부터는 함수형 컴포넌트에서도 Hook을 사용하여 라이프사이클 메서드와 비슷한 작업을 수행할 수 있습니다. 아래는 함수형 컴포넌트에서 useEffect Hook을 사용하여 라이프사이클 메서드와 비슷한 작업을 수행하는 예제 코드입니다.
import React, { useState, useEffect } from 'react';
function MyComponent(props) {
const [count, setCount] = useState(0);
// componentDidMount와 componentDidUpdate 역할
useEffect(() => {
console.log('Component did mount or update');
// 어떤 작업을 수행하고 싶다면 이곳에서 처리합니다.
// 주의할 점은 무한루프에 빠지지 않도록 의존성 배열을 설정해야합니다.
}, [count]);
// componentWillUnmount 역할
useEffect(() => {
return () => {
console.log('Component will unmount');
// 어떤 작업을 수행하고 싶다면 이곳에서 처리합니다.
}
}, []);
const handleClick = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increase count</button>
</div>
);
}
export default MyComponent;
Hook은 React 버전 16.8부터 React 요소로 새로 추가되었습니다.
Hook을 이용하여 기존 Class 바탕의 코드를 작성할 필요 없이 function 형태로 상태 값과 여러 React의 기능을 사용할 수 있습니다.
Hook은 조건문, return문 에 상용할 수 없으며 React Component 내의 전역으로서만 사용가능하다.
컴포넌트의 state(상태)를 관리 할 수 있다.
상태에 따라, 다른 화면 출력
import { useState } from "react";
const App = () => {
const [isMobile, setIsMobile] = useState(false);
console.log(isMobile) // false
setIsMobile(true);
console.log(isMobile) // true
return '';
}
렌더링 이후에 실행할 코드를 만들수 있다.
어떤 변수가 변경될때마다(의존성), 특정기능이 작동하도록 할 수 있다.
import { useEffect } from 'react'
const App = (data) => {
useEffect(() => {
console.log('data Change'); // data 가 변경될 때 마다 해당 console.log 출력
return () => {
console.log('언마운트') // 컴포넌트 초기화에 사용
}
}, [data])
return '';
}
모든 DOM 변경 후 브라우저가 화면을 그리기(render)전에 실행되는 기능을 정할 수 있다.
import { useLayoutEffect } from 'react'
const App = (data) => {
useLayoutEffect(() => {
console.log('data Change'); // data 가 변경될 때 마다 해당 console.log 출력
return () => {
console.log('Layout이 사라짐') // 컴포넌트 초기화에 사용
}
}, [data])
return '';
}
부모컴포넌트와 자식컴포넌트 간의 변수와 함수를 전역적으로 정의할 수 있다.
// newContext.js
import { createContext } from "react" // createContext 함수 불러오기
// context안에 homeText란 변수를 만들고, 공백("") 문자를 저장한다.
const newContext = createContext({
homeText: "",
})
import React from "react";
import Home from "./Home"; // 자식 컴포넌트 불러오기
import { newContext } from "./newContext"; // context 불러오기
const App = () => {
// context에 저장할 정보를 입력한다.
const homeText = "is Worked!"
// NewContext.Provider로 우리가 만든 context를 사용할 부분을 감싸준다.
return (
<newContext.Provider value={{ homeText }}>
<Home />
</newContext.Provider>
);
}
export default App;
// Home.js
import React from "react";
import { useContext } from "react";
import { newContext } from "../newContext";
const Home = () => {
// useContext hook 사용해서, newContext에 저장된 정보 가져오기
const { homeText } = useContext(newContext);
// 불러온 정보 사용하기!!
return (
<p>{homeText}<p>
);
}
export default Home;
React에서 context 없이 변수나 함수를 자식 컴포넌트로 전달하려면 Prop을 이용한 부모자식에서만
가능하므로 자식이 자식컴포넌트가 계속 추가 될수록 계속 prop을 넘겨야 하는데
context 를 이용하면 중간을 처지지 않고 바로 제일 하위 자식으로 전달할 수 있다.
전달하고자하는 컴포넌트에 context를 만들면, 불필요한 호출이 추가될 수 있으므로, context 전용 파일을 만들어야 한다.
[예시 조건]
컴포넌트 : A, B, C, D ( A가 최상위 컴포넌트 )
전달 : A -> D
A 컴포넌트에 context 생성
D 컴포넌트에서 context 불러옴
[실행 과정]
A가 렌더링되며, B, C, D를 차례로 불러옴
D에서 context를 가져오기위해 A를 다시불러옴
A를 다시 불러오면서, 불필요한 중복이 발생함
의존성 배열에 적힌 값이 변할 때만 값,함수를 다시 정의할 수 있다. ( 재랜더링시 정의 안함 )
Memoization : 과거에 계산한 값을 반복해서 사용할때, 그 값을 캐시에 저장하는 것
import { useMemo } from 'react'
const App = () => {
const data = useMemo(() => "data", []);
// 데이터 변수는 의존성 배열(dependency) []에따라 선언된다. ( []사용시, 첫 렌더링 시에 1번만 선언 )
return '';
}
의존성 배열에 적힌 값이 변할 때만 값,함수를 다시 정의할 수 있다. ( 재랜더링시 정의 안함 )
useMemo의 함수버전
import { useCallback } from 'react'
const App = () => {
const data = useCallback(() => console.log(data), []);
// 데이터 변수는 의존성 배열(dependency) []에따라 선언된다. ( []사용시, 첫 렌더링 시에 1번만 선언 )
return '';
}
HTML요소(태그)나 컴포넌트의 메모리주소를 가져와, 객체(레퍼런스) 형식으로 관리할 수 있다.
import React, { useRef } from "react";
const Field = () => {
const inputRef = useRef(null);
function handleFocus() {
inputRef.current.focus();
}
return (
<>
<input type="text" ref={inputRef} />
<button onClick={handleFocus}>입력란 포커스</button>
</>
);
}
useRef로 만든 래퍼런스를 상위 컴포넌트로 전달할 수 있다.
import React, { useRef, forwardRef } from "react";
const Input = forwardRef((props, ref) => {
return <input type="text" ref={ref} />;
});
function Field() {
const inputRef = useRef(null);
function handleFocus() {
inputRef.current.focus();
}
return (
<>
<Input ref={inputRef} />
<button onClick={handleFocus}>입력란 포커스</button>
</>
);
}
forwardRef() 함수를 호출할 때 다음과 같이 익명 함수를 넘기면 브라우저에서 React 개발자 도구를 사용할 때 컴포넌트의 이름이 나오지 않아서 불편할 수가 있는데요.
React 개발자 도구에서 forwardRef() 함수를 사용해도 컴포넌트 이름이 나오게 하는 3가지 방법.
forwardRef()
함수에 익명 함수를 대신에 이름이 있는 함수를 넘깁니다.const Input = forwardRef(function Input(props, ref) {
return <input type="text" ref={ref} />;
});
forwardRef()
함수의 호출 결과로 기존 컴포넌트를 대체합니다.function Input(props, ref) {
return <input type="text" ref={ref} />;
}
Input = forwardRef(Input);
forwardRef()
함수의 호출 결과의 displayName
속성에 컴포넌트 이름을 설정해줍니다.const Input = forwardRef((props, ref) => {
return <input type="text" ref={ref} />;
});
Input.displayName = "Input";
useRef로 만든 래퍼런스의 상태에 따라, 실행할 함수를 정의 할 수 있다.
import {useRef, useImperativeHandle} from 'react'
const ImperativeTextInput = (props, ref) => {
const textInputRef = useRef<TextInput | null>(null);
// ref을 전달받아, 메소드를 만들어준다. ( 다른 ref 호출 가능 )
useImperativeHandle(
ref,
() => ({
focus() {
textInputRef.current?.focus();
},
dismiss() {
Keyboard.dismiss();
},
}),
[],
);
return <TextInput ref={textInputRef} {...props} />;
};
export default forwardRef(ImperativeTextInput);
state(상태) 업데이트 로직을, reducer 함수에 따로 분리 할 수 있다.
import React, { useReducer } from "react";
function init(initialState) {
return { count: initialState };
}
function reducer(state, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + action.payload };
case "DECREMENT":
return { count: state.count - action.payload };
case "RESET":
return init(action.payload);
default:
throw new Error("unsupported action type: ", action.type);
}
}
const Counter = ({ initialCount }) => {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
<h2>{state.count}</h2>
<button onClick={() => dispatch({ type: "RESET", payload: 0 })}>
초기화
</button>
<button onClick={() => dispatch({ type: "INCREMENT", payload: 1 })}>
증가
</button>
<button onClick={() => dispatch({ type: "DECREMENT", payload: 1 })}>
감소
</button>
<button onClick={() => dispatch({ type: "kkkkkkkkk", payload: 1 })}>
에러
</button>
</>
);
};
export default Counter;
사용자 정의한 Hook의 디버깅을 돕기 위해 쓰이며 chrome React Developer Tools 을 통해 사용한 hook 의 데이터 값이나 혹은 label 식으로 입력하여 어떤 Hook 을 썻는지 알 수 있다.
import { useDebugValue, useState } from 'react'
export const useUserInformation = (user) => {
const [user, setUser] = useState(user);
useDebugValue(`useUserInformation Hook ${user}`);
return user;
}