리액트 SPA, 프롭스, JSX 등

  1. 화살표 함수 (Arrow Function)
    화살표 함수는 기존의 함수 표현식보다 문법이 간결하여 코드를 더 깔끔하게 작성할 수 있습니다. 기본 구조는 () => {} 형태입니다.

기존

const addTraditional = function(a, b) {
  return a + b;
};
console.log(addTraditional(5, 3));  // 출력: 8

단일 표현식 화살표 함수: function 키워드 없이 함수를 더 간결하게 표현할 수 있으며, 단일 표현식에서는 {}와 return 키워드가 필요 없습니다.

const addArrow = (a, b) => a + b;
console.log(addArrow(5, 3));  // 출력: 8

다중 행 화살표 함수: 함수 본문이 여러 줄의 코드를 포함할 때는 {}를 사용하고, 값을 반환하려면 return 문을 명시해야 합니다.

const sampleLogging = () => {
  const name = "르탄이";
  const age = 20;
  console.log(`안녕, ${name}`);    
  return age + 1;
}
  1. 삼항 연산자 (Conditional Operator)
    삼항 연산자는 조건 ? 참일 때 값 : 거짓일 때 값 형태로 작성되며, 간단한 조건문을 한 줄로 처리할 수 있게 해줍니다.
const score = 85;
const grade = score > 70 ? '합격' : '불합격';
console.log(grade);  // 출력: '합격'
  1. 단축 평가 (Short Circuit Evaluation)
    JavaScript에서 &&와 || 연산자는 조건의 결과에 따라 다른 코드를 실행할 수 있는 단축 평가를 지원합니다.

논리 OR(||): 첫 번째 피연산자가 falsy (false, 0, "", null, undefined, NaN)일 때 두 번째 피연산자를 평가합니다.

function getUsername(user) {
  return user.name || '신원미상';
}
console.log(getUsername({ name: '르탄이' })); // 출력: 르탄이
console.log(getUsername({})); // 출력: 신원미상

논리 AND(&&): 첫 번째 피연산자가 truthy일 때만 두 번째 피연산자를 평가합니다.

let loggedIn = true;
let username = '르탄이';
loggedIn && console.log('환영합니다! ' + username); // 출력: 환영합니다! 르탄이
  1. 옵셔널 체이닝 (Optional Chaining)
    옵셔널 체이닝은 중첩된 객체의 속성에 안전하게 접근할 수 있도록 해주는 JavaScript의 기능입니다. ?. 연산자를 사용하면 객체의 속성이 undefined 또는 null인 경우에도 스크립트가 중단되지 않고, 대신 undefined를 반환합니다. 이는 중첩된 객체 구조에서 어떤 부분이 존재하지 않아도 에러 없이 프로그램을 계속 실행할 수 있게 해줍니다.
const user = {
  profile: {
      name: "르탄이",
      details: {
          age: 30,
          location: "서울시 강남구"
      }
  }
};
console.log(user.profile?.details?.age); // 출력: 30

이 코드에서 user.profile?.details?.age는 profile, details, age 각각이 존재하는지 확인합니다. 만약 중간에 profile 또는 details가 null이나 undefined라면, 즉시 undefined를 반환하고 더 이상의 접근을 시도하지 않습니다. 이러한 방식으로, 존재하지 않는 속성에 접근하려고 할 때 발생할 수 있는 타입 에러를 방지합니다.

  1. Null 병합 연산자 (??)
    Null 병합 연산자 ??는 좌변의 표현식이 null 또는 undefined 일 때만 우변의 표현식을 평가합니다. 이 연산자는 다른 falsy 값들 (0, false, "" 등)을 유효한 값으로 취급하여 그대로 유지합니다. 이는 좌변의 값이 명시적으로 '비어있지 않은' 경우에만 우변의 기본값을 사용하고자 할 때 유용합니다.
let userLocation = null;
console.log(userLocation ?? 'Unknown location');  // 출력: Unknown location

이 경우, userLocation은 null이므로 ?? 연산자의 우변인 'Unknown location'이 평가되어 출력됩니다.

const temperature = 0;
console.log(temperature ?? 25);  // 출력: 0

여기서 temperature는 0이라는 값이 있으므로 falsy 값이지만 null이나 undefined는 아닙니다. 따라서 ?? 연산자는 temperature의 값을 그대로 사용하고, 25는 무시됩니다.


모듈

ES6 모듈은 코드의 재사용성을 높이고, 의존성 관리를 개선하며, 전역 스코프의 오염을 방지하는 등 여러 가지 이점을 제공합니다. 모듈은 기본적으로 코드 조각을 캡슐화하고, 필요할 때 다른 자바스크립트 파일에서 쉽게 재사용할 수 있게 해줍니다.

캡슐화는 모듈 내부의 세부 구현을 숨기고, 외부에서는 제한된 인터페이스만을 통해 모듈과 상호 작용할 수 있도록 함을 의미합니다.

  1. 모듈의 정의
    ES6 모듈은 특정 기능을 담은 재사용 가능한 코드 블록입니다. 이 모듈은 export 키워드를 사용해 다른 파일에서 사용할 수 있도록 함수, 객체, 또는 원시값을 내보낼 수 있습니다.
  2. 모듈 사용 방법
    모듈에서 정의된 기능을 사용하려면 import 구문을 사용해 해당 기능을 불러와야 합니다. 예를 들어, math.js라는 파일에서 여러 수학 함수를 내보냈다면, app.js 파일에서 이를 불러와 사용할 수 있습니다.
// math.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;

// app.js
import { add, multiply } from './math.js';

console.log(add(2, 3));  // 출력: 5
console.log(multiply(2, 3));  // 출력: 6
  1. 모듈 사용의 이점
  2. 1 종속성 관리
    전통적인 태그 방식에서는 파일 로딩 순서에 따라 종속성을 수동으로 관리해야 했습니다. 하지만 모듈 시스템을 사용하면, 각 모듈이 필요로 하는 종속성을 내부적으로 선언하므로, 파일 로딩 순서에 대해 신경 쓸 필요가 없습니다. 모듈 로더(예: Webpack)가 자동으로 종속성을 해석하고 필요한 순서대로 파일을 로드합니다.

 

3.2 코드 캡슐화와 충돌 방지
모듈은 각각 독립된 스코프를 가집니다. 따라서 모듈 외부에서는 모듈 내부의 변수나 함수에 직접 접근할 수 없습니다. 이는 전역 변수의 오염을 방지하고, 다른 모듈과의 이름 충돌을 예방할 수 있습니다.

 

3.3 효율적인 코드 로딩
모듈 시스템을 통해 필요한 기능만을 선택적으로 불러오고, 코드 스플리팅을 사용하여 애플리케이션의 초기 로딩 시간을 단축할 수 있습니다. 사용자의 현재 요구에 따라 필요한 코드만 동적으로 로드하는 지연 로딩(lazy-loading)은 대규모 애플리케이션에서 특히 유용합니다.

// 동적으로 모듈 로드
button.addEventListener('click', event => {
  import('./heavyModule.js')
    .then(module => {
      module.heavyFunction();
    })
    .catch(err => {
      console.error("모듈 로딩에 실패했습니다.", err);
    });
});

고급

  1. 이름 바꾸기와 기본 내보내기
    1-1. 별칭 사용
    모듈에서 내보낸 항목을 가져올 때 충돌을 방지하거나, 더 명확한 이름을 사용하고 싶을 때 별칭을 지정할 수 있습니다. 이는 as 키워드를 사용하여 구현됩니다.
// utils.js 파일에서 square 함수를 내보냅니다.
export const square = x => x * x;

// app.js 파일에서는 square 함수를 sqr이라는 이름으로 가져옵니다.
import { square as sqr } from './utils.js';
console.log(sqr(4));  // 출력: 16

1-2. 기본 내보내기와 가져오기
모듈당 하나의 주요 기능을 내보낼 때, export default를 사용하여 기본값으로 설정할 수 있습니다. 이렇게 내보낸 모듈은 가져올 때 특정한 이름을 사용하지 않고도 가져올 수 있으며, 가져올 때 원하는 이름을 자유롭게 지정할 수 있습니다.

// math.js
export default function multiply(x, y) {
  return x * y;
}

// app.js
import myMultiply from './math.js';
console.log(myMultiply(6, 7));  // 출력: 42
  1. 전체 모듈 내용 가져오기
    2-1. 전체 내용 가져오기
    모듈에서 내보낸 모든 기능을 한 번에 가져오고 싶을 때, import * as 구문을 사용합니다. 이 방법은 모듈 내의 모든 내보내기를 하나의 객체로 가져오므로, 객체의 속성으로 각 기능을 사용할 수 있습니다
// math.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;

// app.js
import * as MathFunctions from './math.js';

console.log(MathFunctions.add(10, 5));       // 출력: 15
console.log(MathFunctions.multiply(10, 5));  // 출력: 50

Promise는 비동기 작업의 결과를 다루기 위해 사용되는 객체입니다. 여기서 '비동기 작업'이란 바로 결과를 알 수 없고, 결과를 기다려야 하는 작업을 말합니다. 예를 들어, 서버에서 데이터를 받아오거나, 파일을 읽는 작업 등이 이에 해당합니다.

 

Promise의 기본 구조
Promise는 마치 실생활에서의 약속과 같습니다. 약속이 이행될 수도 있고, 어길 수도 있는 것처럼, Promise도 성공(fulfilled)하거나 실패(rejected)할 수 있습니다. 그리고 약속의 결과를 기다리는 것처럼, Promise의 결과도 기다립니다.

 

Promise의 세 가지 상태
Pending (대기): Promise가 시작됐지만, 아직 성공도 실패도 하지 않은 상태입니다.
Fulfilled (이행됨): Promise가 성공적으로 완료되어 결과값을 반환한 상태입니다.
Rejected (거부됨): Promise가 실패하거나 오류가 발생한 상태입니다.

 

사용 방법
Promise를 만들 때는 두 가지 함수인 resolve와 reject를 사용합니다. 이 함수들은 Promise 내부에서 호출되며, 각각 성공과 실패의 상황을 처리합니다.

const myPromise = new Promise(function(resolve, reject) {
  const success = true;  // 실제 코드에서는 이 부분을 실제 조건으로 대체합니다.
  if (success) {
    resolve('Success!');  // 성공했을 때 'Success!'라는 값을 반환
  } else {
    reject('Error!');  // 실패했을 때 'Error!'라는 오류 메시지 반환
  }
});

myPromise.then(function(value) {
  console.log(value);  // 'Success!' 출력
}).catch(function(error) {
  console.error(error);  // 'Error!' 출력
});
  1. Promise 생성: new Promise()를 통해 Promise 객체를 만들고, 내부에는 성공(resolve)하거나 실패(reject)할 조건을 작성합니다.
  2. 성공 처리(then): myPromise.then()을 사용하여, Promise가 성공했을 때 실행할 함수를 정의합니다. 여기서 value는 resolve 함수에서 보낸 'Success!' 값을 받습니다.
  3. 실패 처리(catch): myPromise.catch()을 사용하여, Promise가 실패했을 때 실행할 함수를 정의합니다. 여기서 error는 reject 함수에서 보낸 'Error!' 값을 받습니다.

체이닝 (Chaining)
Promise는 .then()을 여러 번 연결하여 사용할 수 있습니다. 이렇게 하면 여러 비동기 작업을 순차적으로 처리할 수 있습니다. 만약 어떤 단계에서 오류가 발생하면, .catch()가 그 오류를 잡아내고 처리합니다.


async 함수는 JavaScript에서 비동기 작업을 더욱 쉽게 처리할 수 있도록 도와주는 강력한 기능입니다. async 키워드를 사용하면, 함수 내의 비동기 코드를 마치 동기 코드처럼 간단하고 이해하기 쉽게 작성할 수 있습니다. 이를 통해 비동기 프로그래밍의 복잡성을 크게 줄일 수 있습니다.

 

async 함수의 기본
async 함수는 항상 Promise를 반환합니다. 함수 내에서 어떤 값이든 반환하면, 그 값은 Promise로 감싸져서 반환됩니다. 이는 async 함수가 내부적으로 비동기 작업의 완료를 나타내는 Promise를 자동으로 생성하기 때문입니다.

 

("Promise로 감싸져서 반환된다"는 표현은 함수에서 비동기 작업의 결과를 Promise 객체로 만들어 반환한다는 의미입니다. Promise 객체는 미래에 얻을 결과를 대표합니다. 이 객체를 통해 나중에 결과값을 받거나, 오류가 발생했을 때 그 오류를 처리할 수 있습니다.)

async function fetchData() {
  return 'Data loaded';
}

 

위 함수는 'Data loaded'라는 문자열을 반환합니다. 비록 우리가 Promise 객체를 직접 만들지 않았지만, async 키워드 덕분에 이 함수는 자동으로 그 값을 Promise로 감싸서 반환합니다. 즉, 이 함수는 다음과 같은 함수와 동일합니다.

async function fetchData() {
  return Promise.resolve('Data loaded');
}

 

함수 사용 방법
async 함수에서 반환된 Promise는 then 메소드를 사용하여 접근할 수 있습니다. then 메소드는 Promise가 성공적으로 완료되었을 때 실행할 콜백 함수를 받습니다.

fetchData().then(console.log); // 'Data loaded'

 

여기서 fetchData() 함수를 호출하면, 함수는 'Data loaded'라는 값을 갖는 Promise를 반환합니다. 그리고 then(console.log)는 이 Promise가 이행되었을 때, 즉 데이터가 로드되었을 때 로그를 출력하는 역할을 합니다.

=
async 함수의 진정한 힘은 비동기 작업, 예를 들어 네트워크 요청이나 파일 I/O와 같은 작업을 처리할 때 더욱 두드러집니다. 이러한 경우에 await 키워드를 사용하여 비동기 작업의 완료를 기다릴 수 있습니다. await는 async 함수 내에서만 사용할 수 있으며, Promise가 완료될 때까지 함수의 실행을 일시 중지합니다.

async function fetchDataFromServer() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  return data;
}

await 사용하는 경우


코드 실행을 일시 중지:
await 키워드는 Promise의 결과가 준비될 때까지 함수의 실행을 일시 중지합니다. 이는 해당 Promise가 성공적으로 이행되거나 거부될 때까지 기다립니다.

 

결과 직접 할당:
await을 사용하면 Promise의 결과를 변수에 직접 할당할 수 있습니다. 이는 결과를 처리하기 위해 .then()을 사용할 필요가 없다는 것을 의미합니다. 코드가 더 깔끔하고 이해하기 쉬워집니다.


리액트

 

라이브러리 vs 프레임워크

프레임워크는 개발자가 프로젝트의 구조를 따라가도록 요구하며, 애플리케이션의 흐름을 크게 제어합니다. 프레임워크는 '제어의 역전(Inversion of Control)' 원칙을 따르기 때문에, 프레임워크가 애플리케이션의 흐름을 주도합니다.

반면에, 라이브러리는 특정 기능을 수행하는 도구 모음으로, 개발자가 필요할 때 선택적으로 사용할 수 있습니다. 라이브러리를 사용할 때는 개발자가 애플리케이션의 흐름을 완전히 제어하며, 필요한 기능을 라이브러리에서 가져와 사용합니다.

React.js가 라이브러리인 이유

  1. 선택적 통합 (Selective Integration):
    • React는 UI 구성에 집중된 라이브러리입니다. 이는 React가 웹 페이지나 앱의 전체적인 구조를 관리하려고 하지 않고, 오직 뷰(view) 층만을 담당한다는 의미입니다.
    • 개발자는 React를 필요한 부분에 통합하고, 애플리케이션의 다른 부분은 다른 라이브러리나 프레임워크와 자유롭게 조합할 수 있습니다.
  2. 제어의 유지 (Control Retained):
    • React 사용 시, 개발자는 애플리케이션의 구조와 흐름을 자신이 완전히 제어할 수 있습니다. React는 단지 UI 렌더링 방법을 제공할 뿐, 애플리케이션의 전체적인 구성 방법을 강제하지 않습니다.
    • 상태 관리나 라우팅 같은 기능은 React의 일부가 아니며, 필요에 따라 Redux나 React Router와 같은 다른 라이브러리를 통해 관리할 수 있습니다.
  3. 단순성과 집중성 (Simplicity and Focus):
    • React는 UI 생성에 필요한 최소한의 기능만을 제공합니다. 이는 학습 곡선을 낮추고 개발자가 특정 문제에 집중할 수 있게 해줍니다.
    • React는 "최소한의 라이브러리" 철학을 따르며, 개발자가 애플리케이션의 다른 부분을 처리하기 위해 필요한 도구를 자유롭게 선택하게 합니다.
  4. 커뮤니티와 생태계 (Community and Ecosystem):
    • React의 생태계는 매우 방대하며 다양한 라이브러리와 도구를 제공합니다. 이 도구들은 React와 함께 사용할 수 있지만, React 자체의 일부는 아닙니다. 이러한 유연성은 React가 특정 기능에 집중하면서도 다양한 개발 요구를 충족할 수 있게 해줍니다.

이러한 특성 덕분에 React는 개발자들 사이에서 매우 인기가 있으며, 복잡한 사용자 인터페이스를 효과적으로 개발할 수 있는 강력한 도구로 자리 잡았습니다.

 

React의 인기 (npm trends)

npm trends는 npm(노드 패키지 매니저)에 등록된 패키지들의 다운로드 수를 통해 그 인기를 측정합니다. React.js는 이런 통계에서도 높은 다운로드 수를 기록하며, SPA 아키텍처를 구현하는 데 있어 가장 인기 있는 라이브러리 중 하나로 자리 잡았습니다. React의 인기는 그 효율성, 성능, 그리고 광범위한 커뮤니티 지원 덕분입니다.

UI가 필요한 곳이면 어디든 (웹, 모바일, VR)

React는 웹 애플리케이션을 만드는 데 사용되는 라이브러리입니다. 그러나 공식 홈페이지에서는 "The library for web and native user interfaces"라고 표현하며, 웹 뿐만 아니라 네이티브 애플리케이션에서도 사용될 수 있음을 강조합니다. 이는 React Native를 통해 모바일 애플리케이션 개발에도 활용될 수 있으며, 심지어 VR (Virtual Reality) 환경에서도 사용될 수 있다는 점을 의미합니다.

 


 

SPA(Single Page Application)는 웹 개발에서 중요한 개념으로, 전통적인 MPA(Multi-Page Application) 방식과 대비되는 현대적인 웹 애플리케이션 설계 방식입니다. SPA는 페이지 로딩 중에 사용자와의 상호작용이 끊기지 않도록 하나의 페이지 내에서 필요한 모든 사용자 인터페이스를 동적으로 렌더링합니다. React, Vue, Angular와 같은 현대적인 JavaScript 프레임워크와 라이브러리들이 이를 구현하는 데 자주 사용됩니다.

SPA의 작동 원리

초기 로딩

  • SPA는 애플리케이션을 처음 접속할 때 단 하나의 HTML 페이지를 로드합니다. 이 페이지는 필요한 JavaScript, CSS 등의 정적 자원을 포함하고 있습니다.

인터랙션과 데이터 통신

  • 사용자가 애플리케이션 내에서 다양한 인터랙션을 수행할 때, SPA는 전체 페이지를 새로 불러오는 대신 필요한 데이터만을 서버로부터 비동기적으로 요청합니다. 이는 주로 AJAX, Fetch API, 또는 GraphQL과 같은 기술을 통해 이루어집니다.
  • 서버는 요청받은 데이터를 JSON 형태로 클라이언트에게 응답합니다.

화면 렌더링

  • 클라이언트는 응답받은 데이터를 JavaScript를 통해 처리하고, DOM을 업데이트하여 사용자 인터페이스를 동적으로 변경합니다. 이 과정에서 페이지의 일부분만이 변경되므로, 사용자는 끊김 없이 부드러운 경험을 할 수 있습니다.

SPA와 MPA의 비교

MPA

  • MPA는 사용자가 새로운 페이지로 이동할 때마다 서버에서 HTML 페이지를 새로 생성하여 브라우저로 전송합니다. 이 과정에서 전체 페이지 새로고침이 발생하며, 사용자는 로딩 동안 대기해야 합니다.
  • SEO 최적화에 유리하며, 각 페이지마다 독립적인 URL을 가질 수 있습니다.

SPA

  • SPA는 한 번의 페이지 로드 후 모든 사용자 인터랙션을 JavaScript를 통해 클라이언트 사이드에서 처리합니다. 서버로부터 추가 HTML을 받는 대신, 필요한 데이터만을 JSON 형식으로 요청하고 받아서 처리합니다.
  • 페이지 전체를 새로 불러올 필요가 없어 사용자 경험이 개선되지만, 초기 로딩에서는 필요한 자원을 한꺼번에 불러와야 하기 때문에 처음에는 다소 지연될 수 있습니다.

SPA의 이점

  • 사용자 경험 향상: 페이지의 부분적 업데이트로 인해 빠르고 부드러운 상호작용이 가능합니다.
  • 리소스 효율성: 초기 로딩 이후 추가적인 페이지 로딩 없이 필요한 데이터만을 불러와 리소스 사용을 최적화할 수 있습니다.
  • 개발 및 유지보수의 용이성: 프론트엔드와 백엔드가 명확히 분리되어 개발 및 유지보수가 용이합니다.

Hashed Routing의 원리

Hashed Routing은 SPA에서 페이지를 이동하는 것처럼 보이게 하는 라우팅 기술 중 하나입니다. URL의 # 뒤에 오는 부분을 사용하여, 서버에 새 페이지를 요청하지 않고 페이지 내에서 필요한 부분만을 업데이트합니다.

동작 방식

  1. 내비게이션 선택: 사용자가 웹 페이지의 내비게이션 메뉴를 클릭합니다.
  2. URL 업데이트: 클릭 시 URL은 #과 함께 변경됩니다. 예를 들어, 메인 페이지에서 'About' 섹션으로 이동하면 URL이 http://example.com/#about으로 변합니다.
  3. 콘텐츠 변경: 브라우저는 URL의 해시 부분을 감지하고, 해당하는 페이지 콘텐츠로 화면을 변경합니다. 이 과정에서 전체 페이지를 새로 로드하지 않습니다.

Hashed Routing의 이점

  • 빠른 페이지 전환: 전체 페이지를 새로 로드할 필요 없이, 필요한 부분만 업데이트하기 때문에 더 빠른 사용자 경험을 제공합니다.
  • 서버 부하 감소: 모든 페이지 콘텐츠 요청이 서버로 가지 않기 때문에 서버 부하가 줄어듭니다.
  • 개발 용이성: 페이지 관리가 한 곳에서 이루어지므로, 개발과 유지보수가 용이합니다.

SPA의 라우팅을 위한 현대적 접근: Browser Routing

Hashed Routing 외에도, Browser Routing이라는 더 현대적이고 깔끔한 방식을 사용할 수 있습니다. 이 방식은 URL에서 #을 사용하지 않고, 서버 설정을 통해 모든 요청을 단일 HTML 파일로 리다이렉트하면서도, 브라우저의 히스토리 API를 활용해 페이지를 깔끔하게 관리할 수 있습니다.

React에서의 Routing

React는 라이브러리로서, 라우팅 기능을 내장하고 있지 않습니다. 대신, react-router-dom과 같은 라이브러리를 사용하여 SPA의 라우팅을 구현합니다. React의 라우팅은 주로 Browser Routing 방식을 사용하여 사용자 친화적이고, SEO 친화적인 URL 구조를 제공합니다.

 


hash 값을 기준(=hashed)으로 페이지를 이동한다(=routing)

 

SPA의 동작 원리

  1. 초기 로딩: 사용자가 웹사이트에 처음 접속할 때, 서버로부터 단 하나의 HTML 파일을 받아옵니다. 이 파일에는 애플리케이션을 실행하는데 필요한 모든 JavaScript, CSS 등의 리소스가 포함되어 있습니다.
  2. 동적 업데이트: 사용자가 애플리케이션 내에서 다른 페이지나 섹션으로 이동할 때, 전체 페이지를 다시 로드하는 대신 필요한 데이터만 서버로부터 가져와서 현재 페이지를 동적으로 업데이트합니다. 이 과정에서 페이지 전체가 새로고침되지 않으므로 사용자 경험이 매끄럽습니다.

Hashed Routing

Hashed Routing은 SPA의 라우팅 방식 중 하나로, URL의 해시(#)를 이용하여 다른 뷰나 상태로의 이동을 처리합니다.

동작 방식

  • 내비게이션 클릭: 사용자가 내비게이션 링크를 클릭하면, URL의 해시 부분이 변경됩니다. 예를 들어, #about으로 변경될 수 있습니다.
  • 화면 변화: 해시 변경을 감지하고 해당 해시에 맞는 콘텐츠를 불러와서 메인 콘텐츠 영역을 업데이트합니다. 이 모든 과정은 페이지를 새로 로드하지 않고 수행됩니다.

해시값의 역할

  • 해시값은 브라우저에 의해 서버로 전송되지 않습니다. 따라서 서버는 URL의 해시 이전 부분만을 받아 처리하고, 나머지 해시 부분은 순전히 클라이언트 측에서만 해석되어 사용됩니다.

Browser Routing

또 다른 현대적인 SPA 라우팅 방식은 Browser Routing입니다. 이 방식은 브라우저의 히스토리 API를 사용하여 URL의 경로를 직접 조작합니다. 이는 해시(#)를 사용하지 않으므로 URL이 더 깔끔해지고, 사용자에게 전통적인 웹 경험을 더 가깝게 제공합니다.

동작 방식

  • 경로 변경: 사용자가 내비게이션 링크를 클릭하면, URL 경로가 변경됩니다. 예를 들어, /about으로 변경될 수 있습니다.
  • 서버 요청: 서버에는 실제로 페이지 요청이 가지 않고, 모든 요청을 처리할 수 있도록 서버가 설정됩니다. 즉, 어떤 경로로 접속하더라도 같은 HTML 파일을 제공합니다.
  • 클라이언트 렌더링: URL 변경에 따라 적절한 컴포넌트를 불러와 화면에 렌더링합니다.

React에서의 라우팅 처리

React는 라우팅을 위해 react-router-dom 같은 라이브러리를 사용하여, 위에서 설명한 Browser Routing 방식을 주로 채택합니다. 이를 통해 사용자는 클린 URL을 경험할 수 있고, 개발자는 사용자 친화적인 웹 애플리케이션을 구축할 수 있습니다.


왜 경로 별칭이 필요한가요?

프로젝트에서 파일들은 폴더 안에 정리되어 있고, 이 파일들은 서로 정보를 공유하면서 작동합니다. 예를 들어, 한 파일에서 다른 파일의 함수나 데이터를 사용하고 싶을 때, 그 파일을 '임포트'(불러오기) 해야 합니다. 이때 파일의 위치를 정확히 알려주어야 하는데, 이 위치를 지정하는 방법이 바로 '경로'입니다.

 

예를 들어, src/components/Button.jsx 파일에서 src/utils/helpers.js 파일을 불러올 때, 보통 다음과 같은 코드를 작성합니다

import { calculateSomething } from '../../utils/helpers';

이 코드는 Button.jsx에서 두 폴더 위로 올라간 후, utils 폴더 안의 helpers.js 파일을 찾아 그 안에 있는 calculateSomething 함수를 사용하겠다는 뜻입니다. 프로젝트가 커질수록 이런 경로가 길고 복잡해질 수 있습니다.

 

경로 별칭 설정하기

경로 별칭을 설정하면, 이런 복잡한 경로를 간단한 이름으로 대체할 수 있습니다. 예를 들어, 프로젝트의 모든 파일에서 src 폴더를 @로 대체한다고 설정해보겠습니다.

  1. vite.config.js에서 설정하기: Vite는 현대적인 프론트엔드 도구로, 프로젝트 설정을 이 파일에서 관리합니다.
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      find: "@",
      replacement: "/src"
    }
  }
});

여기서 @src 폴더를 대신합니다. 이제 src 폴더 안의 어떤 파일도 @를 사용하여 쉽게 참조할 수 있습니다.

 

 

jsconfig.json에서 설정하기: 이 파일은 코드 에디터가 프로젝트를 어떻게 이해해야 하는지 알려줍니다.

{
  "compilerOptions": {
    "baseUrl": ".", // 기본 URL을 현재 디렉토리로 설정
    "paths": {
      "@/*": ["./src/*"] // "@"로 시작하는 모든 경로를 "src" 디렉토리로 매핑
    }
  }
}

이 설정으로, 코드 에디터는 @src 폴더를 가리킨다는 것을 알게 됩니다. 이제 파일을 불러올 때 @를 사용해서 간편하게 접근할 수 있습니다.

 

 

경로 별칭을 사용한 후의 예시를 보면, 앞서 본 복잡한 임포트 문장을 다음과 같이 간단하게 쓸 수 있습니다

import { calculateSomething } from '@/utils/helpers';

여기서 @는 src 폴더를 의미합니다. 이렇게 하면 파일 구조가 변경되어도 경로를 일일이 수정할 필요가 없어, 코드 관리가 훨씬 쉬워집니다.


React에서 컴포넌트는 웹 애플리케이션의 작은 조각들로 생각할 수 있습니다. 예를 들어, 웹사이트에 버튼, 헤더, 푸터, 사이드바 같은 여러 부분이 있다면, 각 부분을 개별적으로 만들고 관리할 수 있게 해주는 게 바로 컴포넌트입니다. 각 컴포넌트는 독립적으로 작동하며 필요에 따라 다른 부분과 조합해서 사용할 수 있습니다.

컴포넌트는 왜 유용할까요?

컴포넌트를 사용하면, 웹 페이지의 특정 부분만을 쉽게 수정하거나 업데이트할 수 있습니다. 예를 들어, 모든 페이지에 나타나는 헤더의 디자인을 변경하고 싶다면, 헤더 컴포넌트만 수정하면 되고, 다른 부분은 그대로 유지할 수 있습니다. 이는 매우 효율적이죠.

컴포넌트의 종류

React에는 크게 두 가지 타입의 컴포넌트가 있습니다

  1. 함수형 컴포넌트:
    • 이름 그대로 JavaScript의 함수입니다. 이 함수는 웹 페이지에 표시될 내용을 반환합니다.
    • 간단하고 쓰기 쉬워서 많이 사용됩니다. 특히 최신 기능인 Hooks를 사용할 수 있어서 더 많이 쓰이고 있어요.
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return <div>Hello</div>;
}

 

클래스형 컴포넌트:

  • React의 기능을 더 많이 사용할 수 있는 형태로, 데이터 관리와 복잡한 상호작용을 다루기에 적합합니다.
  • render 함수를 통해 UI를 반환합니다.
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

= 간단히 말해서, React 컴포넌트는 웹 페이지의 블록이나 조각 같은 것입니다. 이 조각들을 재사용하고 조합하여 멋진 웹 애플리케이션을 만들 수 있습니다.

 

 

 

주어진 코드 예제를 단계별로 나눠서 설명해 보겠습니다.

코드 구성 이해하기

1. 코드의 시작: Import와 Export

코드의 최상단에서는 필요한 리소스나 라이브러리를 가져오고(import), 만들어진 컴포넌트를 다른 파일에서 사용할 수 있도록 내보내기(export) 합니다.

import { useState } from "react";  // React의 useState 훅을 가져옴
import reactLogo from "./assets/react.svg";  // 로컬 파일로 저장된 React 로고를 가져옴
import viteLogo from "/vite.svg";  // 로컬 파일로 저장된 Vite 로고를 가져옴
import "./App.css";  // 스타일시트 파일 가져옴
  • useState는 React의 상태 관리 훅으로, 컴포넌트의 상태를 관리할 수 있게 해줍니다.
  • 이미지 파일과 CSS 파일은 컴포넌트의 스타일과 외관을 결정하는데 사용됩니다.

2. 컴포넌트 정의: 함수형 컴포넌트

App이라는 이름의 함수형 컴포넌트를 정의합니다. 이 함수는 컴포넌트의 로직과 UI를 정의합니다.

function App() {
  const [count, setCount] = useState(0);  // 상태(count)와 그 상태를 업데이트하는 함수(setCount)

  return (
    // JSX 코드: HTML과 유사하게 보이지만 JavaScript를 확장한 문법입니다.
    <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.jsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  );
}
  • JSX 반환: return 문에서 JSX를 사용하여 컴포넌트가 화면에 어떻게 표현될지 정의합니다.
  • 버튼 클릭 이벤트: 버튼을 클릭할 때 setCount 함수를 호출하여 count 상태를 업데이트합니다.

3. 주의사항

  • 컴포넌트 이름: 컴포넌트의 이름은 항상 대문자로 시작해야 합니다 (App).
  • 파일 및 폴더 명명 규칙: 파일은 컴포넌트 이름과 같이 대문자로 시작하는 카멜케이스로, 폴더는 소문자로 시작하는 카멜케이스로 작성합니다.

React에서 컴포넌트 간의 부모-자식 관계는 웹 애플리케이션을 구조화하는 데 중요한 역할을 합니다. 이 개념을 이해하면 React 프로젝트를 효율적으로 관리할 수 있습니다. 아래에서 이 개념을 쉽게 풀어서 설명하고, 실습 예제를 통해 이를 실제로 적용하는 방법을 알아보겠습니다.

컴포넌트의 부모-자식 관계 이해하기

1. 컴포넌트 정의

  • 컴포넌트: React에서 컴포넌트는 UI의 재사용 가능한 부분입니다. HTML 요소들과 로직을 포함할 수 있으며, 각 컴포넌트는 독립적으로 작동합니다.
  • 부모 컴포넌트: 다른 컴포넌트를 포함하는 컴포넌트입니다. 예를 들어, App 컴포넌트가 Child 컴포넌트를 포함하고 있다면, App은 부모 컴포넌트입니다.
  • 자식 컴포넌트: 다른 컴포넌트 내부에 포함되는 컴포넌트입니다. 위의 예에서 Child는 자식 컴포넌트입니다.

2. 컴포넌트 연결

  • 컴포넌트를 부모-자식 관계로 연결하면, 자식 컴포넌트의 UI가 부모 컴포넌트의 UI 내에 포함되어 표시됩니다.

실습: 컴포넌트 연결하기

실습 목표: App 컴포넌트(할아버지), Parent 컴포넌트(엄마), Child 컴포넌트(자식)를 만들고 서로 연결해보기.

 

1. App.js 파일 수정

import React from "react";

// 자식 컴포넌트 정의
function Child() {
  return <div>나는 자식입니다.</div>;
}

// 부모 컴포넌트 정의
function Parent() {
  return (
    <div>
      <h2>나는 엄마 컴포넌트입니다.</h2>
      <Child />
    </div>
  );
}

// 할아버지 컴포넌트 정의 및 내보내기
function App() {
  return (
    <div>
      <h1>나는 할아버지 컴포넌트입니다.</h1>
      <Parent />
    </div>
  );
}

export default App;

설명

  • Child 컴포넌트: 가장 기본적인 컴포넌트로, 단순 텍스트 "나는 자식입니다."를 반환합니다.
  • Parent 컴포넌트: 자식 컴포넌트를 포함하고 있으며, 추가적으로 "나는 엄마 컴포넌트입니다."라는 텍스트를 표시합니다.
  • App 컴포넌트: 최상위 컴포넌트로, 부모 컴포넌트를 포함하고 전체 구조를 나타냅니다.


JSX는 React를 사용할 때 매우 중요한 문법입니다. 이를 이해하면 React로 강력하고 인터랙티브한 웹 애플리케이션을 만드는 데 큰 도움이 됩니다.

JSX란 무엇인가요?

JSX는 JavaScript XML의 약자로, HTML과 비슷하게 생긴 코드를 JavaScript 내에서 사용할 수 있게 해주는 문법입니다. JSX는 React에서 컴포넌트의 구조를 정의하는 데 사용됩니다. 단순한 JavaScript 객체를 반환하는 함수처럼 작동하여, React가 화면에 UI를 그릴 수 있도록 합니다.

 

const element = <h1>Hello, world!</h1>;

위 코드에서 <h1> 태그는 실제 HTML이 아니라 JSX를 사용한 것입니다. 이 코드는 브라우저에서 직접 실행될 수 없기 때문에, Babel 같은 도구를 사용하여 일반 JavaScript 코드로 변환해야 합니다.

변환 과정

JSX는 직접 브라우저에서 해석될 수 없습니다. 따라서 JSX를 일반 JavaScript로 변환하는 과정이 필요합니다.

변환 전 JSX

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

 

변환 후 JavaScript

const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

React.createElement() 함수는 브라우저가 이해할 수 있는 JavaScript 코드로 JSX를 변환합니다. 이 함수는 태그 이름, 속성, 그리고 자식을 인자로 받아 React 엘리먼트를 생성합니다.

JSX의 주요 규칙 및 실습

 

1. 태그는 꼭 닫아야 합니다: HTML과 같이 모든 태그는 닫혀야 합니다. 예를 들어, <input> 태그는 JSX에서 <input />와 같이 자체적으로 닫혀야 합니다.

function App() {
  return (
    <div className="App">
      <input type='text' />
    </div>
  );
}

 

 

2. 하나의 엘리먼트만 반환: 각 컴포넌트나 함수는 하나의 엘리먼트만 반환할 수 있습니다. 여러 요소를 반환하고 싶다면, <div>나 <> (Fragment)로 감싸야 합니다.

function App() {
  return (
    <div className="App">
      <p>안녕하세요! 리액트 반입니다 :)</p>
      <input type="text" />
    </div>
  );
}

 

3. JavaScript 표현식 사용하기: JSX 내에서 JavaScript 값을 사용하려면 중괄호 {}를 사용합니다. 이는 변수 값을 삽입하거나, 조건문, 반복문 등을 실행할 때 사용됩니다.

function App() {
  const cat_name = 'perl';
  return (
    <div>
      hello {cat_name}!
    </div>
  );
}

 

 

4. 스타일과 클래스: JSX에서 HTML 클래스 속성은 className으로 사용되며, 인라인 스타일은 객체 형태로 적용됩니다.

function App() {
  const styles = {
    color: 'orange',
    fontSize: '20px'
  };

  return (
    <div className="App">
      <p style={styles}>orange</p>
    </div>
  );
}

Props란 무엇인가요?

Props는 "properties"의 줄임말로, React 컴포넌트 간에 데이터를 전달하는 수단입니다. Props를 통해 부모 컴포넌트가 자식 컴포넌트에 데이터를 전달할 수 있습니다. Props는 일반적으로 읽기 전용이며, 컴포넌트 내에서 수정할 수 없습니다.

Props의 특징

  1. 단방향 데이터 흐름: Props는 항상 부모 컴포넌트에서 자식 컴포넌트로만 흐릅니다. 이는 데이터 흐름을 예측 가능하게 만들어, 애플리케이션을 더 쉽게 이해하고 디버그할 수 있도록 돕습니다.
  2. 읽기 전용: Props는 자식 컴포넌트에서 변경할 수 없습니다. 컴포넌트 내부 상태를 변경하려면, 상태 관리를 사용해야 합니다.

Props 사용 예시

아래 예시에서는 Mother 컴포넌트에서 Child 컴포넌트로 이름 데이터를 전달하는 방법을 보여줍니다.

function App() {
  return <GrandFather />;
}

function GrandFather() {
  return <Mother />;
}

function Mother() {
  const name = '홍부인';
  return <Child motherName={name} />; // Props로 name을 전달
}

function Child(props) {
  return <div>연결 성공: {props.motherName}</div>; // Props를 통해 데이터 출력
}

Props 전달 및 수신

  1. 전달하기: Mother 컴포넌트는 Child 컴포넌트로 name 값을 motherName이라는 이름의 prop으로 전달합니다.
  2. 받기: Child 컴포넌트는 매개변수로 props를 받고, props.motherName을 사용하여 값을 화면에 표시합니다.

JSX에서 Props 사용하기

JSX 내에서 Props를 사용하려면 중괄호 {}를 사용합니다. 이를 통해 JavaScript 표현식을 JSX 내부에 포함시킬 수 있습니다.

Prop Drilling

Prop Drilling은 한 컴포넌트에서 여러 계층을 거쳐 다른 컴포넌트로 props를 전달하는 것을 의미합니다. 이는 때때로 복잡해질 수 있으며, 상태 관리 라이브러리(예: Redux)를 사용하여 해결할 수 있습니다.

function App() {
  return (
    <div className="App">
      <FirstComponent content="Who needs me?" />
    </div>
  );
}

function FirstComponent({ content }) {
  return (
    <div>
      <SecondComponent content={content} />
    </div>
  );
}

function SecondComponent({ content }) {
  return (
    <div>
      <ThirdComponent content={content} />
    </div>
  );
}

function ThirdComponent({ content }) {
  return <ComponentNeedingProps content={content} />;
}

function ComponentNeedingProps({ content }) {
  return <h3>{content}</h3>;
}

Props Children은 React에서 컴포넌트 간에 데이터를 전달하는 또 다른 방법으로, 자식 컴포넌트로 정보를 전달하는 메커니즘입니다. 여기서는 Props Children에 대한 개념과 사용 방법에 대해 자세히 살펴보겠습니다.

1. children이란?

Children은 부모 컴포넌트에서 자식 컴포넌트로 전달되는 데이터를 의미합니다. 부모 컴포넌트에서 자식 컴포넌트를 감싸는 태그 사이에 작성된 내용이 children으로 자식 컴포넌트에게 전달됩니다.

예를 들어, 아래 코드에서 <User> 컴포넌트에 전달된 "안녕하세요"가 children으로 전달됩니다.

function App() { return <User>안녕하세요</User>; }

 

 

'리액트' 카테고리의 다른 글

가계부 만들기 2  (0) 2024.05.24
가계부 만들기 1  (0) 2024.05.23
리액트로 todo list 만들기 2  (0) 2024.05.16
리액트로 todo list 만들기 1  (0) 2024.05.14
리액트 객체, 배열, 연산자  (0) 2024.05.10