React 웹 애플리케이션을 만들 때 유용한 디자인 패턴

JaeEun Lee·2024년 7월 6일
1

React

목록 보기
9/10

Layout Components

Layout Components는 애플리케이션의 레이아웃을 정의하는 컴포넌트입니다. 일반적으로 Header, Footer, Sidebar, Main Content 영역 등을 포함합니다.

import React, { FC, ReactNode } from 'react';

type LayoutProps = {
  children: ReactNode;
};

const Layout: FC<LayoutProps> = ({ children }) => {
  return (
    <div>
      <header>Header</header>
      <aside>Sidebar</aside>
      <main>{children}</main>
      <footer>Footer</footer>
    </div>
  );
};

export default Layout;
import React from 'react';
import Layout from './Layout';

const App: React.FC = () => {
  return (
    <Layout>
      <h1>Main Content</h1>
      <p>This is the main content of the application.</p>
    </Layout>
  );
};

export default App;

Controlled Components

Controlled Components는 폼 요소의 상태를 React 컴포넌트가 제어하는 방식입니다. 입력값을 state로 관리합니다.

import React, { useState, ChangeEvent, FC } from 'react';

const ControlledInput: FC = () => {
  const [value, setValue] = useState<string>('');

  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  return (
    <input type="text" value={value} onChange={handleChange} />
  );
};

export default ControlledInput;
import React from 'react';
import ControlledInput from './ControlledInput';

const App: React.FC = () => {
  return (
    <div>
      <h1>Controlled Input Example</h1>
      <ControlledInput />
    </div>
  );
};

export default App;

Custom Hooks

Custom Hooks는 재사용 가능한 로직을 Hook 형태로 추출한 것입니다. 비즈니스 로직이나 상태 관리를 캡슐화할 때 유용합니다.

import { useState, useEffect } from 'react';

const useFetchData = (url: string) => {
  const [data, setData] = useState<any>(null);
  const [loading, setLoading] = useState<boolean>(true);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        const result = await response.json();
        setData(result);
      } catch (error) {
        console.error('Error fetching data:', error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading };
};

export default useFetchData;
import React from 'react';
import useFetchData from './useFetchData';

const DataComponent: React.FC = () => {
  const { data, loading } = useFetchData('https://api.example.com/data');

  if (loading) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <h1>Fetched Data</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
};

export default DataComponent;

Functional Programming

Functional Programming 패러다임을 React에서 활용하는 방법입니다. 주로 순수 함수, 고차 함수 등을 활용합니다.

import React, { FC } from 'react';

const numbers: number[] = [1, 2, 3, 4, 5];

// 순수함수
const double = (num: number): number => num * 2;

const FunctionalComponent: FC = () => {
  const doubledNumbers = numbers.map(double);

  return (
    <div>
      {doubledNumbers.map((num, index) => (
        <div key={index}>{num}</div>
      ))}
    </div>
  );
};

export default FunctionalComponent;
import React from 'react';
import FunctionalComponent from './FunctionalComponent';

const App: React.FC = () => {
  return (
    <div>
      <h1>Functional Programming Example</h1>
      <FunctionalComponent />
    </div>
  );
};

export default App;

서버에서 받아온 데이터를 화면에 맞게 변환하는 경우의 예시

import React, { FC, useEffect, useState } from 'react';

interface User {
  id: number;
  firstName: string;
  lastName: string;
}

interface UserViewModel {
  id: number;
  fullName: string;
}

const transformUser = (user: User): UserViewModel => ({
  id: user.id,
  fullName: `${user.firstName} ${user.lastName}`,
});

const UserList: FC = () => {
  const [users, setUsers] = useState<UserViewModel[]>([]);
  
  useEffect(() => {
    fetch('/api/users')
      .then(response => response.json())
      .then((data: User[]) => {
        const transformedData = data.map(transformUser);
        setUsers(transformedData);
      });
  }, []);
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.fullName}</li>
      ))}
    </ul>
  );
};

export default UserList;

Higher Order Components (HOC)

HOC는 컴포넌트를 인자로 받아 새로운 컴포넌트를 반환하는 함수입니다. 컴포넌트의 로직을 재사용할 때 유용합니다.

import React, { ComponentType, FC } from 'react';

interface WithLoadingProps {
  isLoading: boolean;
}

const withLoading = <P extends object>(Component: ComponentType<P>): FC<P & WithLoadingProps> => {
  return ({ isLoading, ...props }: WithLoadingProps) => {
    if (isLoading) {
      return <div>Loading...</div>;
    }

    return <Component {...props as P} />;
  };
};

type DataProps = {
  data: string;
};

const DataComponent: FC<DataProps> = ({ data }) => {
  return <div>{data}</div>;
};

const DataComponentWithLoading = withLoading(DataComponent);

export default DataComponentWithLoading;
import React from 'react';
import DataComponentWithLoading from './DataComponentWithLoading';

const App: React.FC = () => {
  const [isLoading, setIsLoading] = React.useState<boolean>(true);
  const [data, setData] = React.useState<string>('');

  React.useEffect(() => {
    setTimeout(() => {
      setData('Here is some fetched data!');
      setIsLoading(false);
    }, 3000);
  }, []);

  return (
    <div>
      <h1>Higher Order Component Example</h1>
      <DataComponentWithLoading isLoading={isLoading} data={data} />
    </div>
  );
};

export default App;

통합예제

사용자의 목록을 가져와서 표시하는 간단한 애플리케이션을 만들 것입니다. 각 사용자 항목을 클릭하면 세부 정보를 보여주고, 로딩 상태를 표시하며, 검색 기능을 제공할 것입니다.

시나리오

우리는 사용자의 목록을 가져와서 표시하는 간단한 애플리케이션을 만들 것입니다. 각 사용자 항목을 클릭하면 세부 정보를 보여주고, 로딩 상태를 표시하며, 검색 기능을 제공할 것입니다.

Layout Components

전체 레이아웃을 구성하는 컴포넌트를 정의합니다.

import React, { FC, ReactNode } from 'react';

type LayoutProps = {
  children: ReactNode;
};

const Layout: FC<LayoutProps> = ({ children }) => {
  return (
    <div>
      <header>Header</header>
      <aside>Sidebar</aside>
      <main>{children}</main>
      <footer>Footer</footer>
    </div>
  );
};

export default Layout;

Controlled Components

사용자 검색 입력을 처리하는 컴포넌트를 정의합니다.

import React, { FC, ChangeEvent, useState } from 'react';

interface SearchInputProps {
  onSearch: (query: string) => void;
}

const SearchInput: FC<SearchInputProps> = ({ onSearch }) => {
  const [query, setQuery] = useState('');

  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
    setQuery(event.target.value);
    onSearch(event.target.value);
  };

  return <input type="text" value={query} onChange={handleChange} placeholder="Search users..." />;
};

export default SearchInput;

Custom Hooks

사용자 데이터를 가져오는 커스텀 훅을 정의합니다.

import { useState, useEffect } from 'react';

const useFetchUsers = (query: string) => {
  const [users, setUsers] = useState<any[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchUsers = async () => {
      setLoading(true);
      try {
        const response = await fetch(`https://api.example.com/users?search=${query}`);
        const data = await response.json();
        setUsers(data);
      } catch (error) {
        console.error('Error fetching users:', error);
      } finally {
        setLoading(false);
      }
    };

    fetchUsers();
  }, [query]);

  return { users, loading };
};

export default useFetchUsers;

Functional Programming

사용자 데이터를 변환하고 필터링하는 순수 함수를 정의합니다.

interface User {
  id: number;
  firstName: string;
  lastName: string;
  email: string;
}

const filterUsers = (users: User[], query: string): User[] => {
  return users.filter(user => 
    `${user.firstName} ${user.lastName}`.toLowerCase().includes(query.toLowerCase())
  );
};

const transformUser = (user: User) => ({
  id: user.id,
  fullName: `${user.firstName} ${user.lastName}`,
  email: user.email,
});

export { filterUsers, transformUser };

Higher Order Components (HOC)

로딩 상태를 처리하는 HOC를 정의합니다.

import React, { ComponentType, FC } from 'react';

interface WithLoadingProps {
  isLoading: boolean;
}

const withLoading = <P extends object>(Component: ComponentType<P>): FC<P & WithLoadingProps> => {
  return ({ isLoading, ...props }: WithLoadingProps) => {
    if (isLoading) {
      return <div>Loading...</div>;
    }

    return <Component {...props as P} />;
  };
};

export default withLoading;

전체 통합

모든 패턴을 통합하여 애플리케이션을 구성합니다.

import React, { FC, useState } from 'react';
import Layout from './Layout';
import SearchInput from './SearchInput';
import useFetchUsers from './useFetchUsers';
import withLoading from './withLoading';
import { filterUsers, transformUser } from './utils';

interface User {
  id: number;
  fullName: string;
  email: string;
}

interface UserListProps {
  users: User[];
}

const UserList: FC<UserListProps> = ({ users }) => (
  <ul>
    {users.map(user => (
      <li key={user.id}>
        <strong>{user.fullName}</strong>
        <p>{user.email}</p>
      </li>
    ))}
  </ul>
);

const UserListWithLoading = withLoading(UserList);

const App: FC = () => {
  const [query, setQuery] = useState('');
  const { users, loading } = useFetchUsers(query);

  const filteredUsers = filterUsers(users.map(transformUser), query);

  return (
    <Layout>
      <SearchInput onSearch={setQuery} />
      <UserListWithLoading isLoading={loading} users={filteredUsers} />
    </Layout>
  );
};

export default App;

요약

  • Layout Components: Layout 컴포넌트로 전체 레이아웃을 관리합니다.
  • Controlled Components: SearchInput 컴포넌트로 사용자 입력을 제어합니다.
  • Custom Hooks: useFetchUsers 커스텀 훅으로 사용자 데이터를 fetching합니다.
  • Functional Programming: filterUsers와 transformUser 함수로 데이터를 변환하고 필터링합니다.
  • Higher Order Components: withLoading HOC로 로딩 상태를 처리합니다.
profile
공업철학프로그래머

0개의 댓글