본문 바로가기
JavaScript

[React] 라우터, useEffect

by Zㅣ존수빈zz 2023. 1. 25.

# 리액트  라우터란?

- 요청(특히 url)에 적합한 작업을 수행하는 것

 

-  리액트 라우터 모듈 설치

  1. vs code에서 새로운 cmd 터미널을 열어 프로젝트로 이동

  2. npn install react-router-dom

      (npm install + 설치할 패키지)

  3. App.js 제일 위쪽에 import

      import {

          BrowserRouteer as Router, Routes, Route, Link, Outlet, useParams

      } from "react-router-dom"

 


# 라우터 사용 / useParams

- Router 컴포넌트로 전체를 감싸주고 Route를 Routes로 묶어준다

- Link태그를 이용하여 텍스트에 주소를 연결한다

- Route 컴포넌트로 각각의 컴포넌트와 주소를 연결한다

  <Route path='주소' element={<컴포넌트 />} />

  path='*'는 지정한 주소들 외의 모든 주소들을 의미한다. (연결된 컴포넌트가 없는 주소들)

- Post컴포넌트의 주소는 'post/:postId' 이다. 콜론 뒤에 있는 주소는 useParams을 통해 접근할 수 있다.

function App() {
  return (
    <>
      <Router>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/about">About</Link>
            </li>
            <li>
              <Link to="/posts">Posts</Link>
            </li>
          </ul>
        </nav>
        <hr />
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/posts" element={<Posts />} />
          <Route path="post/:postId" element={<Post />} />
          <Route path="*" element={<NotFound />} />
        </Routes>
      </Router>
    </>
  )
}

 

- useParams: react가 제공하는 Hook. url로 전달된 파라미터에 접근하게 한다

  Post 컴포넌트에서 각각 post1과 post2에 접근하기 위해서 useParams으로 postId를 가져온다.

function Home() {
  return <h1>Home</h1>
}

function About() {
  return <h1>About</h1>
}

function Posts() {
  return (
    <>
      <h1>Posts</h1>
      <ul>
        <li>
          <Link to='/post/p1'>Post1</Link>
        </li>
        <li>
          <Link to='/post/p2'>Post2</Link>
        </li>
      </ul>
    </>
  )
}

function Post() {
  const params = useParams();
  const postId = params.postId;
  
  return (
    <>
      <h1>Title</h1>
      <p>{postId}</p>
    </>
  )
}

function NotFound() {
  return <h1>404 NotFound</h1>
}

# 인증이 적용된 라우터

- 경로에 상관없이 Layout은 그대로 유지되며 경로가 바뀌면 Outlet 부분만 바뀐 경로의 페이지를 출력한다

- index는 부모가 요청한 주소와 동일한 주소를 의미하며 첫화면에 사용한다

- 로그인 상태를 모든 페이지에서 표시할 것이므로 AuthProvider 컴포넌트로 감싼다

- Post 페이지에 접속할 때 인증이 필요하도록 만들기 위해 Post페이지를 input을 출력할 AuthRequired 컴포넌트로 감싼다 

function App() {
  return (
    <Router>
      <AuthProvider>
        <Routes>
          <Route path="/" element={<Layout />}>
            <Route index element={<Home />} />
            <Route path="posts" element={<Posts />} />
            <Route path="/post/:postId" element={
              <AuthRequired>
                <Post />
              </AuthRequired>
            } />
            <Route path="*" element={<NotFound />} />
          </Route>
        </Routes>
      </AuthProvider>
    </Router>
  )
}

 

* context

  - 일반적인 데이터 전달은 부모 자식간에 props를 통해 명시적으로 전달해야 한다. 따라서 정말 데이터가 필요한 자식에

    게 도달하기까지 모든 컴포넌트들이 필요없는 데이터를 공유하게 된다.

  - 그러나 현재 로그인한 유저, 테마, 언어 등과 같이 여러 부분에서 사용되는 데이터들은 context를 사용하여 데이터를 전

    역적(global) 변수로 사용할 수 있도록 한다. 중간에 있는 element들이 props를 넘겨받지 않아도 데이터전달이 가능하다.

  - 데이터를 받고자 하는 컨포넌트는 useContext Hook을 사용한다

const AuthContext = createContext();

function AuthProvider(props) {
  const [user, setUser] = useState(null);

  // 로그인
  function signIn(username) {
    setUser(username);
  }
  // 로그아웃
  function signOut() {
    setUser(null);
  }

  const value = {user, signIn, signOut};

  // Provider는 context를 구독하는 컴포넌트들에게 context의 변화를 알리는 역할을 한다
  // Provider 컴포넌트는 value prop을 받아서 이 값을 하위에 있는 컴포넌트에게 전달한다.
  return (
    <AuthContext.Provider value={value}>
      {props.children}
    </AuthContext.Provider>
  )
}

 

 

-  AuthProvider에서 넘겨준 value값을 사용하기 위해서는 useContext 훅을 사용한다.

- 삼항연산자로 auth의 user에 값이 있을때는 인삿말과 로그아웃 버튼을, 값이 없을 때는 Not logged in을 출력하도록 한다.

- Outlet으로 자식 컴포넌트들을 출력한다.

function Layout() {
  const auth = useContext(AuthContext);
  return (
    <>
      <nav>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/posts">Posts</Link>
          </li>
        </ul>
      </nav>

      {auth.user ? (
        <p>
          Hi, {auth.user} {" "}
          <button onClick={auth.signOut}>
            Logout
          </button>
        </p>
      ) : (
        <p>
          Not logged in
        </p>
      )}
      <hr/>
      <Outlet />
    </>
  )
}

 

 

- '/post'에 접근하기 전 로그인 창을 띄우기 위해 Post 컴포넌트를 감싸고 있던 AuthRequired에 AuthContext를 사용한다.

function Home() {
  return <h1>Home</h1>
}

function Posts() {
  return (
    <>
      <h1>Posts</h1>
      <ul>
        <li>
          <Link to="/post/p1">Post 1</Link>
        </li>
        <li>
          <Link to="/post/p2">Post 2</Link>
        </li>
      </ul>
    </>
  )
}

function AuthRequired(props) {
  const auth = useContext(AuthContext);
  console.log(auth);

  function handleSubmit(e) {
    e.preventDefault();

    const formData = new FormData(e.target);
    auth.signIn(formData.get('username'))
  }

  if(!auth.user) {
    return (
      <form onSubmit={handleSubmit}>
        <h1>Login</h1>
        <input type="text" name="username" autoComplete='off' required />
        <button type="submit">Login</button>
      </form>
    )
  }
  return props.children;
}

function Post() {
  const params = useParams();
  const postId = params.postId;

  return (
    <>
      <h1>Post</h1>
      <p>{postId}</p>
    </>
  )
}

function NotFound() {
  return <h1>404 Not Found</h1>
}

# 서버에서 데이터 가져오기 예시

 

* useEffect Hook

  - 비동기적으로 작동한다.

 

  1 useEffect(effect): effect는 컴포넌트가 실행될 때마다 실행됨

  2 useEffect(effect, []): effect는 컴포넌트의 최초 실행시에만 실행됨

  3 useEffect(effect, [dep]): effect는 컴포넌트의 최초 실행시, 혹은 dependency가 업데이트 될 때마다 실행됨

 

- useEffect 간단한 예시

function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log('lol')
  }, [])    // []가 있으므로 해당 effect는 최초 한번만 작동한다.

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

 

- 데이터 가져오기 예시

  서버에서 응답을 주는데 2초가 걸린다고 가정(setTimeout() 사용)

- promise.finally() : Promise가 성공적으로 수행 되었는지 거절되었는지에 관계없이 Promise가 처리 된 후에 코드가 무조건 한 번은 실행됨.

function fetchData() {
  const DATA ={
    username: 'bunny',
    image: 'https://#.com',
    bio: '안녕하세요 여러분!'
  }
  
  // 2초 뒤 DATA값을 반환하는 Promise객체
  const promise = new Promise((res, rej) => {
    setTimeout(() => {
      res(DATA)
    }, 2000)
  })

  return promise;
}

function App() {
  const [error, setError] = useState(null);
  const [isLoaded, setIsLoaded] = useState(false);
  const [profile, setProfile] = useState(null);

  useEffect(() => {
    fetchData()
    .then(data => {
      setProfile(data)
    })
    .catch(error => {
      setError(error)
    })
    // fetchData가 성공하든, 실패하든 2초 후 Isloaded값을 true로 변환한다
    .finally(() => setIsLoaded(true))
  }, [])

  if(error) {
    return <p>failde to fetch profile</p>
  }
  
  // 비동기함수인 fetchData()가 가장 늦게 시작되기 때문에 값을 변환하기 전까지 p태그를 출력한다
  if(!isLoaded) {
    return <p>fetching profile...</p>
  }

  return (
    <>
      <h1>Profile</h1>
      <img
        src={profile.image}
        alt={profile.username}
        style={{
          width:'100px',
          height: '100px',
          objectFit: 'cover',
          borderRadius: '50%'
        }}
      />
      <h3>{profile.username}</h3>
      <p>{profile.bio}</p>
    </>
  )
}

'JavaScript' 카테고리의 다른 글

패스트캠퍼스 JavaScript 코딩테스트 강의 1주차  (0) 2023.04.22
[React] tailwind 설치하기  (0) 2023.01.29
[React] 컴포넌트와 props  (0) 2023.01.20
React 1일  (0) 2023.01.17
JS 10일차  (0) 2023.01.03

댓글