React란?

이수현·2022년 5월 23일
0

React

목록 보기
1/4

📚 React란?

React : UI를 만들기 위한 Javascript 라이브러리

Component?

공식문서에 따르면 개념적으로 컴포넌트는 JavaScript의 함수와 유사하다고 한다.
"props"라고 하는 임의의 입력(파라미터)를 받은 후, 화면에 어떻게 표시되는지를 기술하는 "React 엘리먼트"를 반환한다.

컴포넌트의 종류(Class & Function)

공식문서에 따르면 컴포넌트를 정의하는 가장 간단한 방법은 함수형으로 작성하는 것이다.

  • 주의할 사항으로 컴포넌트의 이름은 모두 대문자로 시작해야된다.
function Login(props) {
	return <h1>{props.userName}님 반갑습니다.</h1>;
}
// 이 Login 함수는 props라는 객체를 파라미터로 받은 후 사용하여 React 엘리먼트를 반환하므로 유효한 React 컴포넌트이다.
// 또한 컴포넌트의 이름은 모두 대문자로 시작해야된다.
class Login extends React.Component {
	render() {
    	<h1>{this.props.userName}님 반갑습니다.</h1>;
    }
}

위 두 가지 유형의 컴포넌트는 동일하다.

  • 이전까지는 <div/> <h1> 등 DOM 태그만을 사용해 React 엘리먼트를 나타냈지만, React 엘리먼트는 사용자 정의 컴포넌트로도 나타낼 수 있다.(위에서 만든 컴포넌트를 이용)
  • 예를 들면 위에 정의된 Login() 컴포넌트를 사용하여 아래와 같이 사용할 수 있다.
function Login(props) {
	return <h1>{props.userName}님 반갑습니다.</h1>;
}

const element = <Login userName="suhyeon" />;
const root = createRoot(document.getElementById('root'));
root.render(element);

위 코드에서 Login 사용자 정의 컴포넌트 안에 userName에 값을 전달하면 props객체로 function Login(props) 함수에 전달돼서 props.userName으로 suhyeon을 꺼내서
<h1>suhyeon님 반갑습니다.</h1>;이 반환돼서 element에 할당된다.

컴포넌트 합성

그리고 사용자 정의 컴포넌트는 또 다른 컴포넌트 안에서 사용될 수 있다.

function Login(props) {
	return <h1>{props.userName}님 반갑습니다.</h1>;
}

function App() {
  return (
    <div>
      <Login userName="Sara" />
      <Login userName="Cahal" />
      <Login userName="Edite" />
    </div>
  );
}

위 코드에서 함수형 컴포넌트 App안에서 Login 컴포넌트가 사용되는 것을 볼 수 있다.

  • 결국에 App 함수가 반환하는 값은 아래와 같다.
<div>
      <h1>Sara님 반갑습니다.</h1>
      <h1>Cahal님 반갑습니다.</h1>
      <h1>Edite님 반갑습니다.</h1>
</div>

컴포넌트 추출

공식문서에도 복잡한 구조의 컴포넌트를 여러 개의 작은 컴포넌트로 나누는 것을 두려워하지 말라고 나와있는데, 두렵다..

  • 예제 코드를 쓰면서 최대한 이해해보자.
function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

크게 크게 생각해보자.

  • 우선 댓글 컴포넌트가 있고, 그 안에 유저정보, 댓글 내용, 댓글 날짜 3개로 분류할 수 있다.
  • 그리고 유저 정보 안에서 유저 사진과 유저 이름 2개로 또 분류할 수 있다.
  • 의미있게 분류한다면 유저 정보안에서도 유저 사진을 따로 컴포넌트르 분리하여 재사용성을 높일 수 있다.
  • 그렇다면 큰 틀인 댓글 컴포넌트 안에 유저 정보 컴포넌트가 들어가는데, 유저 정보 컴포넌트 안에도 유저 사진 컴포넌트가 들어가는 구조로 분리해보자.
function Avatar(props) {
	return (
      <img src={props.author.avatarUrl} alt={props.author.name} />
    );
}

function UserInfo(props) {
	return (
    	<div className="UserInfo">
        	<Avatar author={props.user} />
        	<div className="UserInfo-name">
              {props.user.name}
        	</div>
        </div>
    );
}

function Comment(props) {
	return (
    	<div className="Comment">
      		<UserInfo user={props.author} />
      		<div className="Comment-text">
        		{props.text}
      		</div>
      		<div className="Comment-date">
        		{formatDate(props.date)}
      		</div>
    	</div>
    );
}

JSX?

JSX란 JavaScript의 확장 문법이다.

  • 템플릿 언어와 비슷해 보이지만, Javascript의 주요 기능들을 모두 사용할 수 있다.

  • React.createElement()의 호출을 통해 일반 JavaScript 객체인 "React Element"로 컴파일 된다.

  • React에서는 본질적으로 렌더링 로직이 UI 로직(이벤트가 처리되는 방식, 시간에 따라 state가 변하는 방식, 화면에 표시하기 위해 데이터가 준비되는 방식 등)과 연결된다는 사실을 받아들인다.

  • 별도의 파일에 마크업과 로직을 넣어 기술을 인위적으로 분리하는 대신, 둘 다 포함하는 "Component"라고 부르는 느슨하게 연결된 유닛으로 관심사를 분리한다.

  • 물론 React에서 JSX가 필수는 아니지만 JavScript 코드 안에서 UI 관련 작업을 할 떄 시각적으로 더 도움이 된다.

예시 코드

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};

const element = (
  <h1>
    Hello, {formatName(user)}!
  </h1>
);

ReactDOM.render(
  element,
  document.getElementById('root')
);

위 코드를 보면 fisrtName과 lastName 프로퍼티를 가지고 있는 객체가 자바스크립트 문법으로 선언되어 있다.

그리고 user객체를 파라미터로 받아서 firstName과 lastName을 공백을 가진 문자열로 반환해주는 formatName라는 함수가 선언되어 있다.

그런데 변수 element에는 <h1>와 그 사이에 문자열과 {} 괄호 사이에 formatName(user)라는 함수를 호출하고 있다.

마지막 코드는 ReactDOM.render() 함수 안에 매개변수로 element와 document.getElementById('root');로 넣어 주고 있다.
=> 최근 리액트 18버전에서 ReactDOM.render()는 아래와 같이 대체되었다.

const root = createRoot(document.getElementById('root'));
root.render(element);

==> createRoot()메서드는 제공된 컨테이너에 대한 React 루트를 만들고 루트를 반환합니다.
==> 그리고 반환 받은 루트에 element를 렌더링 하는 것이다.

위에서 보여진 element같은 변수가 JSX 문법이다. Javascript 문법과 마크업이 섞여있는 형태이다. 아래와 같이 조건문 뿐만 아니라 반복문 안에서도 사용하고, 변수에 할당하고, 인자로서 받아들이고, 함수로부터 반환할 수 있다.

JSX 문법에서 {}중괄호 안에 JavaSCript 표현식을 사용할 수 있다.

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

Router?

React Router는 단순히 URL을 컴포넌트에 매칭하는 것이 아니다. Router는 URL에 매핑되는 전체 UI를 구축하는 것이므로 이전보다 더 많은 개념이 포함될 수 있다.

Matching

초기 렌더에서 그리고 history stack이 변경되면 React Router는 route config과 위치를 일치시켜 렌더링할 matches를 제공한다

  • 'matches' : 현재 위치와 일치하는 경로의 배열(또는 경로 구성의 분기)이다. 이 구조는 중첩 경로를 활성화한다.
  • route config : route match의 분기를 만들기 위해 현재 위치에 대해 순위를 매기고 match(중첩 포함)할 route 객체의 트리이다.

Routes 정의

route config는 다음과 같은 경로 트리이다.

<Routes>
  <Route path="/" element={<App />}>
    <Route index element={<Home />} />
    <Route path="teams" element={<Teams />}>
      <Route path=":teamId" element={<Team />} />
      <Route path=":teamId/edit" element={<EditTeam />} />
      <Route path="new" element={<NewTeamForm />} />
      <Route index element={<LeagueStandings />} />
    </Route>
  </Route>
  <Route element={<PageLayout />}>
    <Route path="/privacy" element={<Privacy />} />
    <Route path="/tos" element={<Tos />} />
  </Route>
  <Route path="contact-us" element={<Contact />} />
</Routes>

<Routes>컴포넌트는 이것의 props.children을 통해 재귀하여 해당 props를 제거하고 다음과 같은 객체를 생성한다.

let routes = [
  {
    element: <App />,
    path: "/",
    children: [
      {
        index: true,
        element: <Home />,
      },
      {
        path: "teams",
        element: <Teams />,
        children: [
          {
            index: true,
            element: <LeagueStandings />,
          },
          {
            path: ":teamId",
            element: <Team />,
          },
          {
            path: ":teamId/edit",
            element: <EditTeam />,
          },
          {
            path: "new",
            element: <NewTeamForm />,
          },
        ],
      },
    ],
  },
  {
    element: <PageLayout />,
    children: [
      {
        element: <Privacy />,
        path: "/privacy",
      },
      {
        element: <Tos />,
        path: "/tos",
      },
    ],
  },
  {
    element: <Contact />,
    path: "/contact-us",
  },
];
  • 사실 <Routes>대신에 useRoutes()를 대신 사용할 수 있다. <Routes>가 하는 일을 여기까지이다.
  • 보다시피 경로는 :teamId/edit와 같은 여러 세그먼트 또는 :teamId와 같은 세그먼트를 정의할 수 있다.
  • route config의 분기 아래에 있는 모든 세그먼트는 최종 경로 패턴을 만들기 위해 함께 추가된다.

Match Params

  • :teamId 세그먼트를 확인해보자. 이것은 우리가 경로 패턴의 동적 세그먼트라고 부르는 것이다. 즉, 정적으로 URL(실제 문자)과 일치하지 않지만 동적으로 일치한다.
  • :teamId에 대해 모든 값을 채울 수 있다. /teams/123 또는 /teams/cupcakes 모두 일치한다.(파싱된 값을 URL params 라고 한다.)
  • 따라서 이 경우 teamId 매개변수는 "123" 또는 "cupcakes"가 된다. 조금 더 밑에 있는 렌더링 섹션에서 App에서 사용하는 방법을 살펴보자.

Ranking Routes

route config의 모든 분기의 모든 세그먼트를 추가하면 앱이 응답하는 다음 경로 패턴이 생성됩니다.

[
  "/",
  "/teams",
  "/teams/:teamId",
  "/teams/:teamId/edit",
  "/teams/new",
  "/privacy",
  "/tos",
  "/contact-us",
];

그런데 만약 URL이 /teams/new인 경우를 고려해보자.

  • /teams/new, /teams/:teamId 둘 다 매치될 것이다.
  • React Router는 여기 결정을 내려야 한다. 단 하나만 있을 수 있다.
  • 클라이언트 측과 서버 측 모두에서 많은 라우터는 정의된 순서대로 패턴을 처리한다.(즉, 처음 매치되는 것이 처리되는 것이다.)
  • React Router는 이러한 패턴을 보면 /teams/new가 URL /teams/new와 일치하기를 원한다는 것을 직관적으로 알 수 있다.
  • 일치할 때 세그먼트, 정적 세그먼트, 동적 세그먼트, 별 패턴 등의 수에 따라 경로의 순위를 지정한다.
  • 그리고 가장 구체적인 일치 항목을 선택한다.

Pathless Routes

위에서 아래와 같은 이상한 경로를 발견했을 것이다.

  • path가 없는데 어떻게 route가 될까?
  • 이것은 React Router에서 "route"라는 단어가 꽤 느슨하게 사용되는 곳이다.
  • <Home/><LeagueStandings/>는 인덱스 경로이고 <PageLayout/>은 레이아웃 경로이다.
    • 인덱스 경로 : 부모 URL의 부모 outlet에서 렌더링되는 경로가 없는 자식 경로이다.
    • 레이아웃 경로 : 경로가 없는 상위 경로로, 특정 레이아웃 내에서 하위 경로를 그룹화하는 데만 사용된다.
  • 어느 쪽도 실제 매칭과 관련이 없다.
<Route index element={<Home />} />
<Route index element={<LeagueStandings />} />
<Route element={<PageLayout />} />

Route Matches

<Route path=":teamId" element={<Team/>}/>에 대한 일치는 다음과 같다.

{
  pathname: "/teams/firebirds",
  params: {
    teamId: "firebirds"
  },
  route: {
    element: <Team />,
    path: ":teamId"
  }
}
  • pathname은 이 경로와 일치하는 URL 부분을 보유한다(이 경우 전체 경로).
  • params는 일치하는 모든 동적 세그먼트에서 구문 분석된 값을 보유한다.
  • param의 개체 키는 세그먼트 이름에 직접 매핑된다. :teamId는 params.teamId가 된다.
  • 우리의 경로는 트리이기 때문에 단일 URL은 트리의 전체 분기와 일치할 수 있습니다. URL /teams/firebirds를 고려하면 다음 경로 분기(>>가 표시된)가 됩니다.
<Routes>
>  <Route path="/" element={<App />}>
    <Route index element={<Home />} />
>    <Route path="teams" element={<Teams />}>
>      <Route path=":teamId" element={<Team />} />
      <Route path=":teamId/edit" element={<EditTeam />} />
      <Route path="new" element={<NewTeamForm />} />
      <Route index element={<LeagueStandings />} />
    </Route>
  </Route>
  <Route element={<PageLayout />}>
    <Route path="/privacy" element={<Privacy />} />
    <Route path="/tos" element={<Tos />} />
  </Route>
  <Route path="contact-us" element={<Contact />} />
</Routes>

React Router는 이러한 경로와 URL에서 일치하는 배열을 생성하여 경로 중첩과 일치하는 중첩 UI를 렌더링할 수 있다.

[
  {
    pathname: "/",
    params: null,
    route: {
      element: <App />,
      path: "/",
    },
  },
  {
    pathname: "/teams",
    params: null,
    route: {
      element: <Teams />,
      path: "teams",
    },
  },
  {
    pathname: "/teams/firebirds",
    params: {
      teamId: "firebirds",
    },
    route: {
      element: <Team />,
      path: ":teamId",
    },
  },
];

Rendering

다음 개념은 렌더링이다. 앱 항목이 다음과 같다고 생각해보자.

const root = ReactDOM.createRoot(
  document.getElementById("root")
);
root.render(
  <BrowserRouter>
    <Routes>
      <Route path="/" element={<App />}>
        <Route index element={<Home />} />
        <Route path="teams" element={<Teams />}>
          <Route path=":teamId" element={<Team />} />
          <Route path="new" element={<NewTeamForm />} />
          <Route index element={<LeagueStandings />} />
        </Route>
      </Route>
      <Route element={<PageLayout />}>
        <Route path="/privacy" element={<Privacy />} />
        <Route path="/tos" element={<Tos />} />
      </Route>
      <Route path="contact-us" element={<Contact />} />
    </Routes>
  </BrowserRouter>
);
  • /teams/firebirds URL을 다시 예로 들어 보자. <\Routes>는 위치를 경로 구성과 일치시키고 일치 세트를 가져오고 다음과 같이 React 요소 트리를 렌더링한다.
<App>
  <Teams>
    <Team />
  </Teams>
</App>
  • 부모 경로의 요소 내에서 렌더링된 각 일치는 정말 강력한 추상화다.
  • 대부분의 웹사이트와 앱은 다음과 같은 특징을 공유한다.
  • 상자 안의 상자, 상자 안의 상자, 각각 페이지의 하위 섹션을 변경하는 탐색 섹션이 있다.

Outlets

  • 중첩 요소 트리는 자동으로 발생하지 않는다.
  • <Routes>는 첫 번째 일치 요소를 렌더링한다.
  • 다음 매치 요소는 <Team>이다. 이를 렌더링하려면 App에서 <Outlet>을 렌더링 해야한다.
function App() {
  return (
    <div>
      <GlobalNav />
      <Outlet />
      <GlobalFooter />
    </div>
  );
}
  • Outlet 컴포넌트는 항상 다음 매치 요소를 렌더링 한다.
  • 즉, <Teams><Team>을 렌더링할 Outlet이 필요하다.
  • URL이 /contact-us인 경우 요소 트리는 다음과 같이 변경된다. <ContactForm />
  • 왜냐하면 contact form이 main <App> 경로 밑에 없기 때문이다.
  • URL이 /teams/firebirds/edit이면 요소 트리는 다음과 같이 변경된다.
<App>
  <Teams>
    <EditTeam />
  </Teams>
</App>

outlet은 일치하는 새 자식으로 자식을 교체하지만 부모 레이아웃은 유지된다. 미묘하지만 컴포넌트를 청소하는 데 매우 효과적입니다.

Index Routes

  • /teams에 대한 route config를 기억해보자.
<Route path="teams" element={<Teams />}>
  <Route path=":teamId" element={<Team />} />
  <Route path="new" element={<NewTeamForm />} />
  <Route index element={<LeagueStandings />} />
</Route>

-URL이 /teams/firebirds인 경우 요소 트리는 다음과 같다.

<App>
  <Teams>
    <Team />
  </Teams>
</App>
  • 그러나 URL이 /teams인 경우 요소 트리는 다음과 같다.
<App>
  <Teams>
    <LeagueStandings />
  </Teams>
</App>
  • 인덱스 경로는 부모 경로에 있는 그들의 부모 경로의 outlet에 렌더링된다.
  • 자식 경로의 경로 중 하나에 있지 않은 경우 <Outlet>은 UI에 아무 것도 렌더링하지 않는다.
  • 만약 모든 teams이 왼쪽 목록에 있는 경우 비어있는 outlet은 오른쪽에 빈 페이지가 있음을 의미한다.
  • UI는 공간을 채울 무언가가 필요하다.(인덱스 경로가 도와준다)
  • UI에 따라 무조건 필요하지느 않겠지만, 상위 경로에 어떤 종류의 지속적인 탐색이 있는 경우 사용자가 아직 항목 중 하나를 클릭하지 않았을 때 공간을 채우는 인덱스 경로를 원할 가능성이 크다.

Layout Routes

  • /privacy는 아직 일치하지 않은 경로 구성의 일부이다. 일치하는 경로를 강조 표시(>> 표시)하여 경로 구성을 다시 살펴보자.
<Routes>
>  <Route path="/" element={<App />}>
    <Route index element={<Home />} />
    <Route path="teams" element={<Teams />}>
      <Route path=":teamId" element={<Team />} />
      <Route path=":teamId/edit" element={<EditTeam />} />
      <Route path="new" element={<NewTeamForm />} />
      <Route index element={<LeagueStandings />} />
    </Route>
  </Route>
>  <Route element={<PageLayout />}>
>    <Route path="/privacy" element={<Privacy />} />
    <Route path="/tos" element={<Tos />} />
  </Route>
  <Route path="contact-us" element={<Contact />} />
</Routes>

렌더링된 결과 요소 트리는 다음과 같다.

<App>
  <PageLayout>
    <Privacy />
  </PageLayout>
</App>
  • PageLayout 경로는 확실히 이상하다.
  • 매칭에 전혀 참여하지 않기 때문에 우리는 이것을 레이아웃 경로라고 부른다.
  • 동일한 레이아웃에서 여러 자식 경로를 더 간단하게 래핑하기 위해서만 존재한다.
  • 이것을 허용하지 않으면 두 가지 다른 방식으로 레이아웃을 처리해야 한다.

URL이 변경되면 이를 "Navigation"이라고 합니다. React Router에서 탐색하는 방법에는 두 가지가 있다.

  • <Link>
  • navigate
  • Link는 탐색의 기본 수단이다.
  • <Link>를 렌더링하면 사용자가 클릭할 때 URL을 변경할 수 있다.
  • React Router는 브라우저의 기본 동작을 방지하고 기록 스택에 새 항목을 푸시하도록 기록에 알린다.
  • 위치가 변경되고 새 일치 항목이 렌더링된다.
  • 중첩 경로는 단순히 레이아웃을 렌더링하는 것이 아니다.
  • 그들은 또한 "상대 링크"를 활성화합니다. 이전의 teams 경로를 고려하자..
  • <Teams>컴포넌트는 아래처럼 렌더링 될 것 이다.
<Route path="teams" element={<Teams />}>
  <Route path=":teamId" element={<Team />} />
</Route>
<Link to="psg" />
<Link to="new" />
  • 링크되는 전체 경로는 /teams/psg 및 /teams/new이다.
  • 렌더링되는 경로를 상속한다.
  • 이렇게 하면 경로 구성 요소가 앱의 나머지 경로에 대해 실제로 알 필요가 없다.

이 함수는 useNavigate Hook에서 반환되며 프로그래머가 원할 때마다 URL을 변경할 수 있도록 한다. 시간 초과 시 수행할 수 있다.

let navigate = useNavigate();
useEffect(() => {
  setTimeout(() => {
    navigate("/logout");
  }, 30000);
}, []);
  • 또는 양식이 제출된 후 수행할 수 있다.
<form onSubmit={event => {
  event.preventDefault();
  let data = new FormData(event.target)
  let urlEncoded = new URLSearchParams(data)
  navigate("/create", { state: urlEncoded })
}}>
  • <Link>와 마찬가지로 중첩된 대상 값에서도 작동한다. navigate("psg");
  • <Link> 대신에 사용하려면 좋은 이유가 있어야 한다.
  • 링크와 양식을 제외하고 URL을 변경해야 하는 상호 작용은 거의 없다.
  • URL은 접근성 및 사용자 기대치를 둘러싼 복잡성을 유발하기 때문이다.

Review

아래 코드를 해석해보자.

const root = ReactDOM.createRoot(
  document.getElementById("root")
);
root.render(
  <BrowserRouter>
    <Routes>
      <Route path="/" element={<App />}>
        <Route index element={<Home />} />
        <Route path="teams" element={<Teams />}>
          <Route path=":teamId" element={<Team />} />
          <Route path="new" element={<NewTeamForm />} />
          <Route index element={<LeagueStandings />} />
        </Route>
      </Route>
      <Route element={<PageLayout />}>
        <Route path="/privacy" element={<Privacy />} />
        <Route path="/tos" element={<Tos />} />
      </Route>
      <Route path="contact-us" element={<Contact />} />
    </Routes>
  </BrowserRouter>
);
  1. <BrowserRouter>는 히스토리를 생성하고 초기 위치를 상태로 설정하고 URL을 구독한다.

  2. <Routes>는 하위 경로를 재귀하여 route config을 빌드하고, 해당 경로를 위치와 일치시키고 일부 경로 일치 항목을 만들고 첫 번째 일치 항목의 경로 요소를 렌더링한다.

  3. 각각의 부모 경로에서 <Outlet />을 렌더링 한다.

  4. outlet은 route matches에서 다음 일치를 렌더링 한다.

  5. 사용자가 링크를 클릭한다.

  6. 그 링크는 navigate()를 호출한다.

  7. 히스토리는 URL을 변경하고 <BrowserRouter>에 알린다.

  8. <BrowserRouter>가 다시 렌더링되고 1번에서 다시 시작한다.

0개의 댓글