[새싹] 현대IT&E 231123 기록 - React

최정윤·2023년 11월 23일
0

새싹

목록 보기
28/67
post-custom-banner

컴포넌트

  • CRUD(create, read, update, delete) 기능을 구현한다.
    App.js
import logo from './logo.svg';
import './App.css';
import React, { useState } from 'react';

function Header(props) {
  console.log('props', props)
  return (
    <header>
      <h1><a href="/" onClick={(event)=>{
        event.preventDedault();
        props.onChangeMode();
      }}>{props.title}</a></h1>
    </header>
  )
}

function Nav(props) {
  const lis = []
  for(let i=0; i<props.topics.length; i++) {
    let t = props.topics[i];
    lis.push(
    <li key={t.id}>
      <a 
      id={t.id} 
      href={'/read/'+t.id} 
      onClick={(event)=>{
        event.preventDefault();
        props.onChangeMode(Number(event.target.id));
      }}>{t.title}</a>
      </li>);
  }
  return(
    <nav>
      <ol>
        {lis}
      </ol>
    </nav>
  );
}

function Article(props) {
  return(
    <header>
      <article>
        <h2>{props.title}</h2>
        {props.body}
      </article>
    </header>
  )
}

function Create(props) {
  return(
    <article>
      <h2>Create</h2>
      <form onSubmit={(event)=>{
        event.preventDefault();
        const title = event.target.title.value;
        const body = event.target.body.value;
        props.onCreate(title.body);
      }}>
        <p><input type="text" name="title" placeholder="title"/></p>
        <p><textarea name="body" placeholder="body"></textarea></p>
        <p><input type="submit" value="Create"></input></p>
      </form>
    </article>
  )
}

function Update(props) {
  const [title, setTitle] = useState(props.title);
  const [body, setBody] = useState(props.body);
  return <article>
    <h2>Update</h2>
    <form onSubmit={event=>{
      event.preventDefault();
      const title = event.target.title.value;
      const body = event.target.body.value;
      props.onUpdate(title, body);
    }}>
      <p><input type="text" name="title" placeholder='title' value={title} onChange={event=>{
        setTitle(event.target.value);
      }}></input></p>
      <p><textarea name="body" placeholder="body" value={body} onChange={event=>{
        setBody(event.target.value);
      }}></textarea></p>
      <p><input type="submit" value="Update"></input></p>
    </form>
  </article>
}

function App() {
  const [mode, setMode] = useState('WELCOME');
  const [id, setId] = useState(null);
  const [nextId, setNextId] = useState(4);
  const [topics, setTopics] = useState([
    {id:1, title:'html', body:'html is ...'},
    {id:2, title:'css', body:'css is ...'},
    {id:3, title:'javascript', body:'javascript is ...'}
  ]);
  let content = null;
  let contextControl = null;
  if(mode === 'WELCOME') {
    content = <Article title="Welcome" body="Hello, Web"></Article>
  } else if(mode === 'READ') {
    let title, body = null;
    for(let i=0; i<topics.length; i++){
      console.log(topics[i].id, id);
      if(topics[i].id === id) {
        title = topics[i].title;
        body = topics[i].body;
      }
    }
    content = <Article title={title} body={body}></Article>
    contextControl = <>
      <li><a href={'/update/'+id} onClick={event=>{
        event.preventDefault();
        setMode('UPDATE');
      }}>Update</a></li>
      <li><input type="button" value="Delete" onClick={()=>{
        const newTopics = []
        for(let i=0; i<topics.length; i++) {
          if(topics[i].id !== id) {
            newTopics.push(topics[i]);
          }
        }
        setTopics(newTopics);
      }}></input></li>
    </>
  } else if (mode === 'CREATE') {
    content = <Create onCreate={(title, body)=>{
      const newTopic = {id:nextId, title:title, body:body}
      const newTopics = [...topics]
      newTopics.push(newTopic);
      setTopics(newTopics);
      setMode('READ');
      setId(nextId);
      setNextId(nextId+1);
    }}></Create>
  } else if(mode === 'UPDATE'){
    let title, body = null;
    for(let i=0; i<topics.length; i++){
      if(topics[i].id === id) {
        title = topics[i].title;
        body = topics[i].body;
      }
    }
    content = <Update title={title} body={body} onUpdate={(title, body)=>{
      console.log(title, body);
      const newTopics = [...topics];
      const updatedTopic = {id:id, title:title, body:body}
      for(let i=0; i<newTopics.length; i++) {
        if(newTopics[i].id === id) {
          newTopics[i] = updatedTopic;
          break;
        }
      }
      setTopics(newTopics);
    }}></Update>
  }
  return (
    <div className="App">
      <Header title="React" onChangeMode={()=>{
        setMode('WELCOME');
        // alert('Header');
      }}></Header>
      <Nav topics={topics} onChangeMode={(id)=>{
        setMode('READ');
        setId(id);
        // alert(id);
      }}></Nav>
      {content}
      <ul>
        <li><a href="/create" onClick={(event)=>{
          event.preventDefault();
          setMode('CREATE');
        }}>Create</a></li>
        {contextControl}
        <li><a href="/update">Update</a></li>
      </ul>
    </div>
  );
}
export default App;

React Router DOM

  • 리액트 라우터가 없을 경우 1장 실습과 같이 조건에 따라 서로 다른 컴포넌트를 출력해야 할 경우 직접 컴포넌트와 props, state 등을 이용하여 처리를 해야 한다.
  • 리액트 라우터를 이용하면 위의 과정을 거치지 않고 간편하게 구현할 수 있다.
  • 설치
    • 새로운 프로젝트를 생성한다.
    npx create-react-app react-router-dom
    • 리액트 라우터를 설치 및 확인을 한다.
    react-router-dom-example>npm install react-router-dom
    react-router-dom-example>npm list
    • 실행한다.
    react-router-dom-example> npm start

리액트 라우터

  • 리액트 라우터는 클라이언트 내에서 자원을 탐색하는 클라이언트측 라우팅(ClientSide Routing)을 지원한다.
  • 라우팅(Routing)은 지정된 자원을 경로를 이용하여 탐색하는 과정이다. 일반적으로 서버에 존재하는 자원에 접근할 때 많이 이용된다. 최근에는 실제 물리적으로 존재하지 않는 서비스를 찾아가는 탐색 과정도 포함된다.
  • 리액트 라우터는 리액트 내에 컴포넌트 같은 자원에 경로를 설정할 수 있는 다양한 방법을 제공한다. 라우트 정보를 가지고 있는 라우팅 테이블(Route table)과 비슷하다.
  • React Router는 react-router-dom와 react-router-native를 제공한다. 목적에 적합한 패키지를 선택하는 것을 추천한다.
  • 다양한 경로를 관리할 수 있는 다양한 라우터와 라우터 컴포넌트, 기본 컴포넌트, 후
    크(Hooks) 등을 제공한다.
  • 2021년에 발표된 v6은 리액트 v16.8 이상을 지원한다.
  • 최근에 유행하는 SPA(Single Page Application)제작에 필수적인 기술이다.
  • Page 새로 고침을 피하기 위해 'a 태그보단 Link 컴포넌트 사용을 권장한다.
  • SPA(Single Page Application)
    • 단일 페이지로 구성된 어플리케이션이다.
    • 빠르고 자연스러운 화면 전환을 할 수 있고, 재사용 높은 컴포넌트를 이용하여 구성하기 수월하다.
    • UI 처리에 관련된 서버 요청이 줄어 들어 네트워크 트래픽 및 서버 자원의 사용을 줄일 수 있다.
    • 전체적인 화면 구현 자원을 한 번에 모두 다운로드 후 출력하게 되어 초기화면이 늦게 출력될 수 있
      다.
    • 크롤링(Crawling) 기반의 검색엔진 최적화(Search Engine Optimization, SEO)에 불리하다.
    • 원활한 유지보수 및 업무 분담을 위해 신중한 설계가 필요하다.

가상환경 설정하기

$ conda env list
$ conda create -n (가상환경이름)
$ conda activate (가상환경이름)

설치 및 설정

  • 리액트의 기본 라이브러리가 아닌 리액트 라우터는 사용하기 위해서 추가적인 패키지 설치가 필요하다.
npm install react-router-dom
  • 리액트 라우터를 사용하기 전에 참조가 필요하다.
import { BrowserRouter, Routes, Route } from 'react-router-dom';
  • 라우팅 대상이 되는 컴포넌트를 라우팅 태그에 포함시켜 라우팅 태그를 부모로 설정한다.
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);

라우터 컴포넌트(Router Component)

  • 리액트 라우터는 라우팅 기능을 위해 다양한 라우터 컴포넌트(Router Component)를 제공한다. 라우터 컴포넌트는 BrowserRouter, HashRouter, MemoryRouter,
    NativeRouter, StaticRouter 등을 제공한다.
  • 라우터(Router) 컴포넌트는 Router 컴포넌트들의 상위 인터페이스이다.
  • 상위 컴포넌트인 라우터 컴포넌트의 속성은 대부분의 라우트 컴포넌트에서 공통 속성으로 이용될 수 있다.
  • basename 속성 : 공통 상위 루트를 지정한다.
function App() {
  return (
    <BrowserRouter basename="/root">
      <Routes>
        <Route path="/intro" />
      </Routes>
    </BrowserRouter>
  );
}

주요 컴포넌트

  • BrowserRouter
    • 브라우저의 주소창(Address bar, Location bar)에 출력되고 있는 URL을 기준으로 동작한다.
    • HTML5 History API를 이용하여 History Stack을 조작하여 URL과 UI를 동기화 한다.
    • 새로 고침을 하는 경우 서버에 요청이 전달되어 HTTP 404 Not Found 오류가 발생될 수 있다.
      • 서버에서 관리하는 URL이 아니라 발생되는 것으로 서버에서 오류 처리를 설정하여 처리한다.
      • Route 컴포넌트의 'path' 속성을 이용하여 처리한다. 제일 마지막 Route 컴포넌트를 다음과 같이 선언한다.
      <Route path='*' element={<ErrorPage />} /> // ErrorPage 컴포넌트 필요
      <Route path="*" element={<Navigate to="/" />} /> // Navigate 컴포넌트 이용
    • URL을 다양하게 응용하는 동적 페이지에 적합하다.
  • BrowserRouter 구현
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/intro" /> <= "/root/intro"
        <Route path="/product/foods" /> <= "/product/foods"
        {}
      </Routes>
    </BrowserRouter>
  );
}
  • HashRouter
    • URL에 '#'을 이용하여 경로를 지정한다.
    • '#' 은 앵커(Anchor)로 같은 리소스내에 이동하여 처리될 경로를 지정한다. html을 기준으로 동일 html내에 (a name="")로 북마크(bookmark)하면 #북마크로 이동 된다.
    • '#' 이후의 지정된 내용(fragment)은 서버로 전달되지 않아 같은 URL인 경우 서버로 요청 (request)이 전달되지 않아 화면이 갱신(reflash)되지 않는다.
    • URL이 고정적인 정적 페이지에 적합하다.
  • HashRouter 구현
function App() {
  return (
    <HashRouter>
      <Routes>
        <Route path="/intro" /> <= /#/about
        <Route path="/faq" /> <= /#/faq
        {}
      </Routes>
    </HashRouter>
  );
}

기본 컴포넌트

  • 라우터 컴포넌트 외에 기본적으로 제공되는 컴포넌트로 컴포넌트와 함께 사용되는 경우가 많다.

  • Routes

    • 현재 경로를 자식 컴포넌트 경로의 상위경로로 설정한다.
    • 라우트(Route) 컴포넌트들의 부모 컴포넌트로 URL을 이용하여 자식 라우트 컴포넌트를 검색한다. 검색할 때 Route Config를 생성하여 이용한다.
    • Route Config는 라우트 컴포넌트의 트리구조를 이용하여 라우트 컴포넌트의 트리상 위치를 기준으로 route matches를 생성하고 일치하는 URL이 있으면 Match 객체를 전달한다.
      • Match 객체는 URL, URL Params, Pathname 등의 정보를 가지고 있다.
    • 경로가 변경될 때마다 자식 컴포넌트들의 경로와 UI 경로와의 연결을 최적화 한다.
    • Routes가 여러 개 구현되어 있으면 서로의 자식 Route에 접근이 불가능하다.
    import { BrowserRouter, Routes, Route } from 'react-router-dom';
  • Route

    • 리액트 라우터에서 가장 많이 사용되는 컴포넌트이며 중첩을 허용한다.
    • URL 경로에 연결될 컴포넌트를 지정하고 렌더링해 준다.
    • Route 컴포넌트의 중첩으로 허용하여 Tree 구조를 생성한다.
    import { BrowserRouter, Routes, Route } from 'react-router-dom';
    • 대표적인 속성은 다음과 같다

      path: 문자열. 매칭될 URL 경로를 지정한다.
      element: URL에 매칭된 리액트 컴포넌트를 이용하여 리액트 요소를 생성한다.
      Component: URL에 매칭을 이용하여 리액트 요소를 생성한다. RouterProvider를 이용하여 생성한다.
      index: index 라우트로 지정되며 페이지가 없을 경우에도 기본으로 출력된다. 형제 라우트
      들이 있을 경우 기본 라우트로 지정한다.

  • Link

    • 서버에 요청을 보내 화면이 새로 고침 되는 (a href=""/)와 다르게 History API를 이용하여 브라우저 주소만 갱신하여 새로 고침 없이 이동한다.
    import { Link } from 'react-router-dom';
    • Routes 없이 단독적으로 사용 가능하다.
    • 대표적인 속성은 다음과 같다

      to: 문자열. 이동할 URL 경로를 지정한다.

    <ul>
    <li><Link to="/">Home</Link></li>
    <li><a href="/">Home</a></li>
    </ul>
  • NavLink

    • 기본적으로 Link와 비슷한 기능의 컴포넌트이지만, "active", "pending", "transitioning" 상태를 알 수 있다.
    • 인식할 수 있는 상태를 이용하여 CSS 를 적용할 수 있다.
      import { NavLink } from 'react-router-dom';
    • Routes 없이 단독적으로 사용 가능하다.
    • 상태별로 기본 클래스명이 지정되어 있다.
      • isActive : "active" , isPending: "pending", isTransitioning: "transitioning"
    • 대표적인 속성은 다음과 같다

      style: CSS를 선언 한다.
      className: 클래스 명을 지정한다.
      to: 이동할 URL을 지정한다

    // class명 이용
    <NavLink to="/abut" className={
    ({isActive}) => isActive ? "activated" : "deacticated";
    }>About</NavLink>
    
    // Class 객체 이용
    const activeStyle = { color: 'red' }
    const deactiveStyle = { color: 'gray' }
    <NavLink to="/abut" className={
    ({isActive}) => isActive ? activeStyle : deactiveStyle;
    }>About</NavLink>
  • Navigate

    • useNavigate hook의 속성 및 기능이 동일한 Wrapper 컴포넌트이다. 렌더링이 일어나면 현재 경로를 변경한다.
      import { Navigate } from 'react-router-dom';
    • 다른 리액트 컴포넌트와 잘 호환되는 useNavigate 사용을 권장한다.
    • 대표적인 속성은 다음과 같다.

      replace: true 이면 이전 페이지로 이동할 수 없다.
      state: useLocation 등에서 사용될 상태 객체를 전달한다.
      to: 이동할 URL을 지정한다.

    <Navigate to="/users" state={User} replace={true} />
  • Outlet

  • 부모 컴포넌트에서 선언된 라우터를 자식 컴포넌트에서 사용할 경우 이용된다.

  • 중첩된 라우트의 부모의 라우터를 자식 컴포넌트들에서 공통적으로 사용할 때 사용된다.

import { Outlet } from 'react-router-dom';
function App() {
  return (<Link to="/"> HOME </Link>, Link to="/about"> ABOUT </Link>
    <Routes>
      <Route path="/" element={<Menu />}>
        <Route path="home" element={<Home />} />
        <Route path="about" element={<About />} />
      </Route>
    </Routes>);
}
function Menu() {
  return (
    <div>
      <h1>Menu</h1>
      <Outlet />
    </div>
  );
}
  • Path Pattern
    • Star Segment
      • "*" 를 이용하여 지정한다. 지정된 하위 모든 경로 패턴을 매칭시킨다.
path="/article/*"
/article
/article/1/2/3
/article/list/123
  • Optional Segment
    • 모든 세그먼트는 필수이나, 생략 가능한 세그먼트를 지정한다.
path="/:articleId?/list"
/1/list
/2/list
/list
  • React URL 파라미터(URL Parameter, Dynamic Segment)
    • URL을 이용하여 부가 정보를 보내는 방법이다.
    • ":" 로 시작되며 파라미터(parameter)명을 지정한다.
path="/board/list/:boardId"
path="/board/:boardCode/list/:boardId"
path="/:articleId"

실습

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
// import App from './App';
import reportWebVitals from './reportWebVitals';
import { HashRouter, Routes, Route, Link, NavLink } from 'react-router-dom';

ReactDOM.createRoot(document.getElementById('root')).render(
<HashRouter>
  <App />
</HashRouter>
);

function Home() {
  return (
    <div>
      <h2>Home</h2>
      Home...
    </div>
  );
}

var contents = [
  {id:1, title:'HTML', description:'HTML is...'},
  {id:2, title:'JS', description:'JS is...'},
  {id:3, title:'React', desctiption:'React is...'},
];

function Topic() {
  return (
    <div>
      <h3>Topic</h3>
      Topic...
    </div>
  );
}

function Topics() {
  var lis = [];
  for (var i=0; i<contents.length; i++) {
    lis.push(
      <li key={contents[i].id}>
        <NavLink to={"/topics/" + contents[i].id}>{contents[i].title}</NavLink>
      </li>
    )
  }
  return (
    <div>
      <h2>Topics</h2>
      <ul>
        {lis}
      </ul>
      <Routes>
        <Route path="/:topic_id" element={<Topic />} />
      </Routes>
    </div>
  );
}

function Contact() {
  return(
    <div>
      <h2>Contact</h2>
      Contact...
    </div>
  );
}

function App() {
  return (
    <div>
      <h1>Hello React Router DOM</h1>
      <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/topics">Topics</Link></li>
        <li><Link to="/contact">Contact</Link></li>
      </ul>
      <Routes>
        <Route path="/" element={<Home/>}/>
        <Route path="/topics/*" element={<Topics/>}/>
        <Route path="/contact" element={<Contact/>}/>
        <Route path="/*" element={'Not Found'}/>
      </Routes>
      <Home></Home>
      <Topics></Topics>
      <Contact></Contact>
    </div>
  );
}


// root.render(
//   <BrowserRouter basename="/root">
//     <Routes>
//       <Route path="/intro" />
//     </Routes>
//     {/* <App /> */}
//   </BrowserRouter>
// );

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals

reportWebVitals();

index.css

.active{
  background-color: tomato;
  text-decoration: none;
}
profile
개발 기록장
post-custom-banner

0개의 댓글