Propspure functionreact-bootstrapreact-router-dom인증 / 인가Conditional Rendering Props
↓
-----------------
| React Component |
-----------------
↓
React Element
| 개념 | 비유 |
|---|---|
| Component | 붕어빵 틀 |
| Props | 팥, 슈크림 |
| Element | 팥붕, 슈붕 |
같은 틀에 무엇을 넣느냐로 결과물이 갈린다.
Props는 Read-Only.
수정해야 하면 새로 전달해서 새 Element를 만들어내야 한다.
JavaScript 함수의 성질 중 하나.
모든 React Component는 pure 해야 한다.
Props를 컴포넌트 안에서 고쳐 쓰면 이 규칙이 깨진다.
어제 만든 입장/퇴장 카운터에 비활성화 조건을 붙였다.
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를 그대로 받는다.
connectionless : 응답을 주면 연결을 끊는다stateless : 이전 요청을 기억하지 않는다서버가 나를 기억하지 못하므로, 로그인 상태는 매 요청마다 증명해야 한다.
권한 != data. 로그인했다고 모든 데이터에 접근 가능한 게 아니다.
Json Web Token. 보통 헤더에 실려 다닌다.
// response.headers.get('Authorization');
// localStorage.setItem('token', 'token->xxxxxxxxx');
토큰은 보통 인메모리 DB에 저장한다. (Redis, h2 등)
npm install react-bootstrap bootstrap
import Button from 'react-bootstrap/Button';
import 'bootstrap/dist/css/bootstrap.min.css';
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는 쿼리스트링으로 필터링을 지원한다.
조회 결과 배열의 길이로 인증 성공 여부를 판단했다.
원하는 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>
const moveUrl = useNavigate();
moveUrl('/success');
<Link>가 사용자가 눌러서 가는 거라면, useNavigate는 로직 실행 후 보내는 것.
| 방식 | 전달 | 수신 | URL 노출 |
|---|---|---|---|
| state | moveUrl('/success', { state : {...} }) | useLocation() | X |
| query string | moveUrl('/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.
true, false는 아니지만 true(혹은 false)로 여겨지는 값.
조건문에 그대로 넣어도 동작한다.
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>
)
// LogoutButton.jsx
const LogoutButton = (props) => {
const logoutHandler = (setFlag) => {
setFlag(false);
}
return <Button title={'로그아웃'} onClick={() => logoutHandler(props.isLogin)}/>
}
state는 부모(RenderingPage)가 갖고, 자식에게는 변경 함수를 통째로 내려준다.
자식이 그 함수를 호출하면 부모의 state가 바뀌고, 부모가 다시 렌더링된다.
RenderingPage → Greeting → UserGreeting 처럼
page에 전달된 걸 또 page로 전달하는 구조를 drilling이라고 한다.
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 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";
항상 실제 경로가 맞는지를 확인하자.
useEffect는 렌더링 이후에 실행되는 자리. 단, full/empty처럼 cnt로 바로 계산되는 값은 side effect가 아니라 그냥 const로 두면 된다.setCnt(c => c + 1) — 핸들러 안의 cnt는 이번 렌더링 시점에 고정된 값이라, 값 대신 계산 함수를 넘겨야 최신 값 기준으로 갱신된다.disabled — <button>에서는 브라우저가 처리하지만, 직접 만든 <Button>에서는 그냥 props 하나일 뿐이라 안쪽 태그까지 명시적으로 내려줘야 동작한다.