반응형

1. useRef

특징 및 사용 목적

  • 값 저장: 컴포넌트가 다시 렌더링되어도 참조 값을 유지할 수 있습니다.
  • DOM 접근: 특정 DOM 요소에 직접 접근하거나 조작할 때 사용됩니다.
  • 렌더링 영향 없음: 값이 변경되어도 컴포넌트를 다시 렌더링하지 않습니다.

주요 사용 예시

  • DOM 요소의 포커스 제어, 크기/위치 확인
  • 이전 상태 값 저장
  • 타이머가 업데이트 될 때마다 불필요한 렌더링 방지
function Timer() {
  const timerRef = useRef(null);
  const [time, setTime] = useState(0);

  const startTimer = () => {
    if (!timerRef.current) {
      timerRef.current = setInterval(() => {
        setTime((prevTime) => prevTime + 1);
      }, 1000);
    }
  };

  const stopTimer = () => {
    clearInterval(timerRef.current);
    timerRef.current = null;
  };

  return (
    <div>
      <p>Time: {time}s</p>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
}

2. useMemo

특징 및 사용 목적

  • 계산된 값의 캐싱: 복잡한 계산 결과를 캐싱하여 성능을 최적화합니다.
  • 종속성 배열: 지정된 종속성 값이 변경될 때만 다시 계산합니다.
  • 렌더링 성능 개선: 렌더링 과정에서 불필요한 계산을 방지합니다.

주요 사용 예시

  • 무거운 연산이나 데이터 변환 최적화
  • 컴포넌트에서 조건에 따라 변경되는 데이터 처리
  • 자식 컴포넌트에 전달할 복잡한 데이터 생성
function Parent() {
  const [count, setCount] = useState(0);

  const data = useMemo(() => {
    console.log('Generating data...');
    return { value: count };
  }, [count]); // `count`가 변경될 때만 데이터 재생성

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <Child data={data} />
    </div>
  );
}

function Child({ data }) {
  console.log('Child rendered');
  return <p>Child received: {data.value}</p>;
}

3. useCallback

특징 및 사용 목적

  • 함수 캐싱: 동일한 함수 객체를 재사용하여 불필요한 렌더링을 방지합니다.
  • 종속성 배열: 지정된 종속성이 변경될 때만 새로운 함수 객체를 생성합니다.
  • 자식 컴포넌트 최적화: React.memo와 함께 사용하여 불필요한 렌더링을 줄입니다.

주요 사용 예시

  • 부모 컴포넌트에서 자식 컴포넌트로 콜백을 전달할 때, 불필요한 재생성으로 인해 자식 컴포넌트가 다시 렌더링되지 않도록 하고 싶을 때.
function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount((prevCount) => prevCount + 1);
  }, []); // 동일한 함수 객체 유지

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

const Child = React.memo(({ onClick }) => {
  console.log('Child rendered');
  return <button onClick={onClick}>Increment Parent Count</button>;
});

비교

Hook 사용 목적 렌더링 영향
useRef 값 저장, DOM 접근 X
useMemo 계산된 값 캐싱 O
useCallback 함수 캐싱, 자식 컴포넌트 최적화 O

useMemo, useCallback 의 차이는 미미하다.

https://www.linkedin.com/feed/update/urn:li:activity:7233818253674889217/

 

최효빈님의 LinkedIn 활동 보기

로그인 또는 회원 가입 후 더 확인해 보세요.

www.linkedin.com

 

useMemo(() => {{원하는함수}}, dependencies)
//useCallback 과 비슷해보이는 모습

 

훅으로 훅 만들기

리액트의 구현 방식 때문에 useReduceruseStateuseRefuseMemo 를 구현할 수 있다.

왜일까?

리액트 공식문서를 찾아보면, useRef, useMemo, useState 는 결국 fiber에 붙은 hook.memoizedState 를 반환하는 것. 또한, setState, dispatch역시 react-reconciler의 dispatch 함수를 어떤 형태로 반환하느냐의 차이다.

useReducer ⇒ useState

function useState<S>(initState: S | (() => S)) : [S, (action: SetStateAction>S>) => void] {
    const [state, dispatch] = useReducer(
        (state: S, action: SetStateAction<S>): S =>
            typeof action === 'function' ? (action as (prevState: S) => S)(state) : action,
        typeof initState === 'function' ? (initState as () => S)() : initState
    );

    return [state, dispatch];
} 

useRef ⇒ useMemo

function useMemo<T>(factory: () => T, deps: React.DependencyList): T {
    const ref = useRef<{ value : T; deps: React.DependencyList | undefined }>){
        value: undefined as T,
        deps : undefined,
    });

    if(!ref.current_deps || !shallowEqual(deps, ref.current.deps)) {
        ref.current.value = factory();
        ref.current.deps = deps;
    }

    return ref.current.value;
}

useMemo ⇒ useCallback

function useCallback<T extends (...args: any[]) => any>(
    callback: T,
    deps: React.DependencyList
): T {
    return useMemo(() => callback, deps);
}
반응형

'Web > React' 카테고리의 다른 글

React 번들링  (1) 2024.12.01
React의 특징  (2) 2024.11.17
반응형

번들링이란?

여러 제품이나 코드, 프로그램을 묶어서 패키지로 제공하는 행위, 웹 애플리케이션을 제공하기 위한 파일 묶음

 

번들링에는 다양한 방법이 있습니다. 대표적으로 Webpack, Vite 등이 있죠

Webpack

Webpack 이란 여러 개의 파일을 하나의 파일로 합쳐주는 모듈 번들러

모듈 번들러

  • HTML, CSS, JS 등의 자원을 전부 각각의 모듈로 보고 이를 조합해 하나의 묶음으로 번들링하는 도구
  • 변수들의 스코프 문제 해결
  • 각 자원을 호출할 떄 생겨나는 네트워크 쪽의 코스트도 신경써야 하기에 등장

등장 배경

  • 웹 앱의 빠른 로딩 속도와 높은 성능을 위해서 필요한 파일들을 묶어 제공
  • 두개의 다른 js 파일에서 같은 변수를 사용할 경우 충돌

빌드와 번들링

  1. 빌드
    • 개발이 완료된 앱을 배포하기 위해 하나의 폴더로 구성하여 준비하는 작업
  2. 번들링
    • 파일을 묶는 작업, 모듈간의 의존성 관계를 파악해 그룹화하는 작업

Webpack 핵심개념

module.exports = {
  target: ["web", "es5"],
  entry: "./src/script.js",
  output: {
    path: path.resolve(__dirname, "docs"),
    filename: "app.bundle.js",
    clean: true
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
        exclude: /node_modules/,
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "src", "index.html"),
    }),
    new MiniCssExtractPlugin(),
  ],
  optimization: {
    minimizer: [
      new CssMinimizerPlugin(),
    ]
  }
};

 

Target

웹팩은 다양한 환경과 target을 컴파일한다.

  • target의 기본값은 web
  • esX 를 넣으면 지정된 ECMAScript 버전으로 컴파일

Entry

개발자가 작성한 코드의 시작점

  • 리액트의 경우 index.js 에서 HTML 엘리먼트 하나에 리액트 코드를 적용하는것 부터 시작
  • Entry 속성은 Entry point 라고 한다.
    • Webpack 은 Entry point 를 기반으로 직간접적 의존하는 다른 모듈과 라이브러리를 찾아낼 수 있음

Output

생성된 번들을 내보낼 위치와 이 파일의 이름을 지정하는 방법을 웹팩에 알려주는 역할

Loaders

loader를 사용하면 웹팩이 다른 유형의 파일을 처리하거나, 유효한 모듈로 변환해 애플리케이션에 사용하거나 디펜던시 그래프에 추가할 수 있다.

Plugins

번들을 최적화하거나 애셋을 관리하고, 또는 환경변수 주입 등 광범위한 작업을 수행한다.

  • require 을 통해 플러그인 요청

Mode

Browser Compatibility

 

Vite

빠르고 만능, Esbuild와 Rollup의 장점을 합쳤다.

  • ES 모듈 시스템을 사용하여 필요한 모듈만 로드하고, 핫모듈 리플레이스먼트 (HMR) 성능이 뛰어나 빠른 개발 서버 속도를 자랑

특징

  • 빠른 개발 서버 : 모듈을 ESM 형식으로 제공해, 필요한 모듈만 즉시 로드
  • HMR : 코드 변경 사항을 즉시 반영하여 브라우저 새로고침 없이도 UI 업데이트 가능
  • 빠른 번들링 : Esbuild를 사용해 번들링 속도 향상

흔히 리액트 프로젝트를 만드는데 create-react-app 혹은 vite 로 생성하게 되는데 이 둘의 차이가 극명하다.

CRA vs Vite

항목 Create React App (CRA) Vite
번들링 도구 Webpack ESBuild 또는 Rollup
빌드 속도 상대적으로 느림 매우 빠름 (ESBuild 기반)
개발 서버 성능 느린 초기 빌드와 Hot Module Replacement(HMR) 속도 빠른 초기 빌드와 즉각적인 HMR
설치 및 구성 자동화된 설정, 시작하기 쉬움 간단한 설정, 플러그인 기반으로 높은 확장성 제공
출력 번들 크기 기본적으로 최적화가 부족할 수 있음 작은 번들 크기, Tree Shaking 및 코드 분할 기본 제공
플러그인 지원 제한적 (Webpack 플러그인에 의존) 풍부한 플러그인 생태계
ESM 지원 제한적 (CommonJS 중심) 기본적으로 ES Module 지원
구성 유연성 제한적 (eject를 해야 커스텀 가능) 유연한 설정 파일 (vite.config.js)로 커스텀 가능
로드 시간 번들 파일 크기에 따라 느릴 수 있음 ESBuild 기반으로 빠른 로드 시간
SSR(서버 사이드 렌더링) 직접 지원하지 않음 내장된 SSR 지원
사용 사례 기존 React 프로젝트에서 간편한 시작을 원할 때 빠른 개발 환경과 최적화된 번들링이 필요한 프로젝트
커뮤니티 더 많은 사용 사례와 기존 자료 빠르게 성장하는 커뮤니티
호환성 오래된 브라우저 및 라이브러리와 높은 호환성 최신 브라우저 중심, 구형 브라우저와의 호환성은 Polyfill 필요

 

결론

CRA는 설정 없이 바로 사용할 수 있으며, React의 공식 지원 도구이지만, 대규모 프로젝트에서는 빌드 속도가 느려질 수 있고, 설정을 변경하려면 eject 명령이 필요하는 단점이 있고,

 

Vite는 매우 빠른 개발 서버와 빌드 성능을 제공하며, 설정 파일을 통해 유연하게 설정을 변경할 수 있고, 다양한 모던 프레임워크를 지원하는 동시에 빠르게 성장하는 도구다.

 

둘 중의 프로젝트에 맞는 번들링 방식을 적용하면 된다.

반응형

'Web > React' 카테고리의 다른 글

[React] useRef, useMemo, useCallback  (1) 2024.12.13
React의 특징  (2) 2024.11.17
반응형

1. 컴포넌트 기반 아키텍처

React는 UI를 독립적이고 재사용 가능한 컴포넌트로 나누어 개발할 수 있습니다. 이렇게 나누어진 컴포넌트는 서로 독립적으로 관리되며, 부모-자식 관계로 데이터를 주고받습니다.

예제: 컴포넌트 분리

const Button =({ label, onClick })=> {
  return (
    <button onClick={onClick}>
      {label}
    </button>
  );
}
function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Count: {count}</h1>
      <Button label="Increment" onClick={() => setCount(count + 1)} />
      <Button label="Decrement" onClick={() => setCount(count - 1)} />
    </div>
  );
}
  • App 컴포넌트는 Button 컴포넌트를 사용해 label, onClick props를 전달 후, 생성한다.
  • Button 컴포넌트는 서로 다른 버튼이지만 하나의 컴포넌트 코드로 재사용할 수 있다.

2. Virtual DOM을 활용한 고성능 렌더링

React는 Virtual DOM을 사용하여 변경 사항을 추적하고, 실제 DOM 조작을 최소화하여 성능을 최적화한다.

예제: Virtual DOM에서의 렌더링 비교

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  console.log('Rendered with count:', count); // Virtual DOM에서 변경 사항 확인

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default Counter;

설명:

  • 상태 변경 시 Virtual DOM에서 새 상태를 반영한 컴포넌트를 생성하고, 이전 Virtual DOM과 비교.
  • 변경된 부분만 실제 DOM에 업데이트하므로 성능이 최적화.
  • 브라우저 콘솔에서 상태 변경 시마다 컴포넌트가 다시 렌더링되는 것을 확인 가.

3. 단방향 데이터 흐름

React는 상위 컴포넌트에서 하위 컴포넌트로만 데이터를 전달합니다. 이를 통해 데이터 흐름이 명확해지고 관리가 쉬워집니다.

예제: 부모-자식 간 데이터 전달

// Child.js: 하위 컴포넌트
import React from 'react';

function Child({ message }) {
  return <h2>{message}</h2>;
}

export default Child;

// App.js: 상위 컴포넌트
import React, { useState } from 'react';
import Child from './Child';

function App() {
  const [message, setMessage] = useState('Hello, React!');

  return (
    <div>
      <Child message={message} />
      <button onClick={() => setMessage('Data Flow Updated!')}>
        Update Message
      </button>
    </div>
  );
}

export default App;

설명:

  • App 컴포넌트가 Child 컴포넌트로 message를 전달한다.
  • 단방향 데이터 흐름으로 인해, 데이터는 항상 부모에서 자식으로만 흐릅니다.
  • Child 컴포넌트로 넘어온 message를 변경하고 싶다면, 부모의 setMessage 를 props로 받아와 실행하면된다.

React의 단점

단점 1: 초기 로딩 시간 해결 (코드 스플리팅)

React 애플리케이션은 초기 로딩 시간 증가 문제가 있지만, 코드 스플리팅을 통해 이를 해결할 수 있습니다.

예제: React의 코드 스플리팅

import React, { Suspense, lazy } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <div>
      <h1>Welcome to My App</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <HeavyComponent />
      </Suspense>
    </div>
  );
}

export default App;

설명:

  • lazy 를 통해 HeavyComponent는 사용될 때만 로드된다.
  • Suspense를 사용하여 로드 중인 상태를 처리할 수 있다.
  • 이를 통해 초기 로딩 시간을 줄이고 성능을 개선할 수 있다.

단점 2: SEO 문제 해결 1(ReactDOMServer)

리액트의 ReactDOMServer 라이브러리를 통해 SSR을 직접 구현.

import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './App';

const app = express();

app.get('*', (req, res) => {
  const appHtml = ReactDOMServer.renderToString(<App />);
  const html = `
    <!DOCTYPE html>
    <html>
      <head><title>React SSR</title></head>
      <body>
        <div id="root">${appHtml}</div>
        <script src="/bundle.js"></script>
      </body>
    </html>
  `;
  res.send(html);
});

app.listen(3000, () => console.log('Server is running on http://localhost:3000'));
  • 서버에서 리액트 컴포넌트를 해석하는 라이브러리 ReactDOMServer 을 이용해 HTML 파일로 변경한다.

단점 2: SEO 문제 해결 2(Next.js 활용)

CSR 방식에서 발생하는 SEO 문제는 Next.js를 사용하여 서버 사이드 렌더링(SSR)을 도입함으로써 해결 가능합니다.

예제: Next.js에서 SSR 사용

// pages/index.js
import React from 'react';

export default function Home({ message }) {
  return (
    <div>
      <h1>{message}</h1>
    </div>
  );
}

export async function getServerSideProps() {
  return {
    props: {
      message: 'This page is rendered on the server!',
    },
  };
}

설명:

  • getServerSideProps를 사용해 서버에서 데이터를 가져와 페이지를 렌더링한다.
  • HTML 콘텐츠가 완전히 생성되어 클라이언트에 전달되므로 SEO와 초기 로딩 문제를 동시에 해결할 수 있다.

반응형

'Web > React' 카테고리의 다른 글

[React] useRef, useMemo, useCallback  (1) 2024.12.13
React 번들링  (1) 2024.12.01
반응형

배경

계절학기에 들어가면서 1학점 짜리 웹 프로젝트 연구를 진행하게 되었다. 주제는 웹 보안을 위한 시큐어 코딩 학습 사이트다.

3주 동안 진행되는 계절학기 특성상 미리 준비하는게 정신건강에 좋기 때문에 미리 웹사이트를 구축해보기로 했다.

 

새로운 기술

우선, 평소에는 react, nodejs 로 웹을 구축했었는데 이번에는 nextjs 를 이용해 구축해보려고 한다. 새로운 기술에 대해 배우는고 공부하는데 도움이 될 것 같아 이렇게 진행하려고 한다.

 

취약점 조사

프로젝트에는 웹보안 하면 가장 기초가되는 4가지 공격을 제외한 공격을 다루라고 명시되어 있다. (sql injection, XSS, path traversal, desdrialization)

사실 내가 생각해도 위의 공격은 너무 소스도 많고 기본적인 문제들이라 날로 먹는 느낌이 들 것 같긴했다.

그렇기에 새로운 취약점을 찾아보았고, OWASP 에서 발표한 TOP10 취약점을 알게 되었다.

 

 

여기서 나는 1번 injection, 8번 CSRF 에 대해 진행해보려고 한다. injection 에도 file upload, os command 를 이용한 취약점에 집중하려고 한다.

 

웹 구현

 

next 프로젝트를 구축한 후 app 폴더의 트리 모습이다. layout.js 를 이용해 헤더와 네비게이션을 고정해놓고 page를 바꾸어가며 출력할 예정이다.

 

app
│ favicon.ico
│ globals.css
│ layout.js
│ page.js

├─components
│ Header.js
│ Nav.js

├─csrf
│ page.js

├─file
│ page.js

├─list
│ page.js

└─os

 

헤더와 네비게이션을 고정하기 위해 다음과 같은 코드를 작성했다.

 

layout.js

import Link from "next/link";
import "./globals.css";
import { Inter } from "next/font/google";
import Nav from "./components/Nav";
import Header from "./components/Header";

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Header/>
        <Nav />
        {children}
      </body>
    </html>
  );
}

Header.js

'use client';
const { default: Link } = require("next/link");

const Header = () =>{
  const toggleDropdown = () => {
    setDropdownVisible(!isDropdownVisible);
  };
    return(
        <div className="header">
            <h3>Secure Coding Lecture</h3>
        </div>
    )
}

export default Header;

Nav.js

'use client';
import { useState } from "react";
const { default: Link } = require("next/link");

const Nav = () =>{
    const [isDropdownVisible, setDropdownVisible] = useState(false);

  const toggleDropdown = () => {
    setDropdownVisible(!isDropdownVisible);
  };
    return(
        <nav className="navbar">
          <ul className="navList">
            <li className="navItem">
              <Link href="/">홈</Link>
            </li>
            <li className="navItem">
              <div onClick={toggleDropdown}>강의
              {isDropdownVisible && (
                <ul className="subNavList">
                  <li className="navItem">
                    <Link href="/file">File Upload Attack</Link>
                  </li>
                  <li className="navItem">
                    <Link href="/os">OS Command Injection</Link>
                  </li>
                  <li className="navItem">
                    <Link href="/csrf">CSRF</Link>
                  </li>
                </ul>
              )}
              </div>
            </li>
            <li className="navItem">
              <Link href="/">설정</Link>
            </li>
          </ul>
        </nav>
    )
}

export default Nav;

위의 코드를 이용하면 다음과 같은 웹이 출력된다.

 

 

 

어려웠던점

  1. Layout.js 내에서 useState 사용불가
    • 이건 내가 next 에 대해 제대로 몰라 생겼던 문제다. next 는 기본적으로 서버 컴포넌트로 작동하는데 useState 는 클라이언트에서만 사용되기에 오류가 발생한다.
    • 따라서, use client 를 최상단에 적어 클라이언트 임을 선언하면 정상작동 된다.
반응형

+ Recent posts