[TIL] FE-07 - Props, Router, 인증과 인가, Conditional Rendering

W L·2026년 8월 5일

TIL

목록 보기
8/32

1. 오늘 학습한 주제

  • Props
  • pure function
  • react-bootstrap
  • react-router-dom
  • 인증 / 인가
  • Conditional Rendering

2. 배운 내용

Props

       Props
         ↓
 -----------------
| React Component |
 -----------------
         ↓
   React Element
개념비유
Component붕어빵 틀
Props팥, 슈크림
Element팥붕, 슈붕

같은 틀에 무엇을 넣느냐로 결과물이 갈린다.

Props는 Read-Only.
수정해야 하면 새로 전달해서 새 Element를 만들어내야 한다.

pure 함수

JavaScript 함수의 성질 중 하나.

  • input을 변경하지 않는다
  • 같은 입력값에 대해 항상 같은 출력값을 리턴한다

모든 React Component는 pure 해야 한다.
Props를 컴포넌트 안에서 고쳐 쓰면 이 규칙이 깨진다.

CapacityPage 마무리 — 상한/하한 처리

어제 만든 입장/퇴장 카운터에 비활성화 조건을 붙였다.

  • 입장 인원 10명
  • 입장 버튼을 누르면 증가, 정원이 차면 버튼 비활성화
  • 퇴장 버튼을 누르면 감소, 0이 되면 버튼 비활성화
const capacity = 10;

let [cnt, setCnt]     = useState(0);
let [full, setFull]   = useState(false);
let [empty, setEmpty] = useState(false);

useEffect( () => {
    console.log(`debug >>>> side effect render cnt ${cnt}`);
    setFull(cnt >= capacity);
    setEmpty(cnt <= 0);
}, [cnt]);
<Button title="입장" onClick={(e) => upCntHandler()} disabled={full} />
<Button title="퇴장" onClick={(e) => downCntHandler()} disabled={empty} />

cnt가 바뀔 때마다 useEffect가 돌면서 full, empty를 다시 계산한다.
버튼의 disabled는 이 state를 그대로 받는다.

HTTP와 인증

http의 성질

  • connectionless : 응답을 주면 연결을 끊는다
  • stateless : 이전 요청을 기억하지 않는다

서버가 나를 기억하지 못하므로, 로그인 상태는 매 요청마다 증명해야 한다.

인증과 인가는 별개

  • 인증(Authentication) : 신원 확인. 너 누구야
  • 인가(Authorization) : 특정 URL에 접근할 수 있는 권한. 너 여기 들어와도 돼

권한 != data. 로그인했다고 모든 데이터에 접근 가능한 게 아니다.

JWT

Json Web Token. 보통 헤더에 실려 다닌다.

// response.headers.get('Authorization');
// localStorage.setItem('token', 'token->xxxxxxxxx');

토큰은 보통 인메모리 DB에 저장한다. (Redis, h2 등)

로그인 폼 만들기 (EventPage)

npm install react-bootstrap bootstrap
import Button from 'react-bootstrap/Button';
import 'bootstrap/dist/css/bootstrap.min.css';

입력값을 state로 관리하는 이유

const data = { id : "medtate", password : "12345" };
let id = data.id;

이렇게 하면 state 관리가 안 된다. UI와 script 사이의 sync가 안 맞는다.

const [email, setEmail] = useState('');
const [pswd, setPswd]   = useState('');

const emailHandler = (e) => {
    setEmail(e.target.value);
}
<input  type="email"
        value={email}
        onChange={ (e) => emailHandler(e) }/>

value는 state에서 읽고, onChange로 state를 바꾼다.
입력창의 값과 script의 값이 항상 같은 곳을 본다.

인증 처리

await api.get(`/users?email=${email}&pswd=${pswd}`)
        .then( response => {
            const ary = response.data;
            if (ary.length > 0) {
                const user = ary[0];
                localStorage.setItem('userName', user.name);
                moveUrl('/success', { state : { user, from: '/signIn' } });
            } else {
                moveUrl('/error?category=react&sort=latest');
            }
        })

json-server는 쿼리스트링으로 필터링을 지원한다.
조회 결과 배열의 길이로 인증 성공 여부를 판단했다.

페이지 이동 (react-router-dom)

원하는 page를 원하는 경로로 이동시킬 수 있다.

<BrowserRouter>
    <Routes>
        <Route path="/"        element={<EventPage />} />
        <Route path="/success" element={<SuccessPage />} />
        <Route path="/error"   element={<ErrorPage />} />
        <Route path="/read/:id" element={<ViewPage />} />
    </Routes>
</BrowserRouter>

useNavigate — 코드로 이동시키기

const moveUrl = useNavigate();
moveUrl('/success');

<Link>가 사용자가 눌러서 가는 거라면, useNavigate는 로직 실행 후 보내는 것.

데이터를 넘기는 세 가지 방식

방식전달수신URL 노출
statemoveUrl('/success', { state : {...} })useLocation()X
query stringmoveUrl('/error?category=react')useSearchParams()O
path variable<Link to="/read/1">useParams()O

state — 브라우저 내에서 component 사이에 데이터를 주고받을 때의 개념.
URL 노출이 싫다면 state 방식.

// SuccessPage.jsx
const location = useLocation();
const {user, from} = location.state || {};

query string — 주소에 그대로 붙는다.

// ErrorPage.jsx
const [searchParams] = useSearchParams();
const category = searchParams.get('category');
const sort     = searchParams.get('sort');

path variable — 경로 자체가 데이터.

// ViewPage.jsx
const {id} = useParams();

POST처럼 몸통에 실어 보내는 게 state, GET처럼 주소에 드러내는 게 query string.

Conditional Rendering

truthy와 falsy

true, false는 아니지만 true(혹은 false)로 여겨지는 값.
조건문에 그대로 넣어도 동작한다.

inline condition

JSX 안에서 삼항 연산자로 분기한다.

// Greeting.jsx
const Greeting = (props) => {
    return props.flag ? <UserGreeting /> : <GuestGreeting />
}
// RenderingPage.jsx
const [flag, setFlag] = useState(false);

return (
    <div>
        <Greeting flag={flag} />
        {
            flag ? <LogoutButton isLogin={setFlag}/> : <LoginButton isLogin={setFlag}/>
        }
    </div>
)

state 변경 함수를 props로 내려보내기

// LogoutButton.jsx
const LogoutButton = (props) => {
    const logoutHandler = (setFlag) => {
        setFlag(false);
    }
    return <Button title={'로그아웃'} onClick={() => logoutHandler(props.isLogin)}/>
}

state는 부모(RenderingPage)가 갖고, 자식에게는 변경 함수를 통째로 내려준다.
자식이 그 함수를 호출하면 부모의 state가 바뀌고, 부모가 다시 렌더링된다.

drilling

RenderingPage → Greeting → UserGreeting 처럼
page에 전달된 걸 또 page로 전달하는 구조를 drilling이라고 한다.

3. 문제와 해결

Routes와 Route를 구분하지 못함

import { BrowserRouter, Routes } from "react-router-dom";

<BrowserRouter>
    <Routes>
        <Routes path="/"        element={<EventPage/>}/>
        <Routes path="/success" element={<SuccessPage/>}/>
        <Routes path="/error"   element={<ErrorPage/>}/>
    </Routes>
</BrowserRouter>

Route를 따로 import해야 하는 것도 몰랐고,
Routes 안에는 Route를 써야 한다는 것도 몰라서 전부 Routes로 채웠다. 에러 발생.

  • 수정
import { BrowserRouter, Routes, Route } from "react-router-dom";

<BrowserRouter>
    <Routes>
        <Route path="/"        element={<EventPage/>}/>
        <Route path="/success" element={<SuccessPage/>}/>
        <Route path="/error"   element={<ErrorPage/>}/>
    </Routes>
</BrowserRouter>
  • Routes : 여러 경로 중 현재 URL에 맞는 하나를 골라주는 역할. 컨테이너.
  • Route : 경로 하나와 그 경로에 그릴 컴포넌트를 짝지어주는 역할.

import 경로 복붙

import SuccessPage from "./pages/event/ErrorPage";
import ErrorPage   from "./pages/event/ErrorPage";

한 줄 복사해서 이름만 바꾸고 경로는 그대로 뒀다.

에러가 나지 않는다. 문법상 아무 문제가 없는 코드라서.
SuccessPage라는 이름표가 ErrorPage 모듈에 붙었을 뿐이다.
로그인에 성공해도 화면에는 ErrorPage가 떴다.

  • 수정
import SuccessPage from "./pages/event/SuccessPage";
import ErrorPage   from "./pages/event/ErrorPage";

항상 실제 경로가 맞는지를 확인하자.

4. 헷갈렸던 부분

  • side effect — 화면 그리기 외에 하는 일(서버 요청, localStorage, log). useEffect는 렌더링 이후에 실행되는 자리. 단, full/empty처럼 cnt로 바로 계산되는 값은 side effect가 아니라 그냥 const로 두면 된다.
  • state — 컴포넌트 함수는 매번 재실행되므로 지역 변수는 리셋된다. 함수 바깥(React 메모리)에 값을 맡겨두고, 변경 함수를 호출하면 재렌더링까지 같이 일어난다. 새로고침하면 사라진다.
  • setCnt(c => c + 1) — 핸들러 안의 cnt는 이번 렌더링 시점에 고정된 값이라, 값 대신 계산 함수를 넘겨야 최신 값 기준으로 갱신된다.
  • disabled — <button>에서는 브라우저가 처리하지만, 직접 만든 <Button>에서는 그냥 props 하나일 뿐이라 안쪽 태그까지 명시적으로 내려줘야 동작한다.
  • React — 값이 바뀌면 화면이 반응하는 구조. 화면 모양을 선언해두면 값만 바꿔도 갱신된다.
profile
한량

0개의 댓글