ํผ์ค๋์ปฌ๋ฌ ์ง๋จ ์น์ฌ์ดํธ
์ ์๋๊ธฐ
์ต๊ทผ ํผ์ค๋ ์ปฌ๋ฌ์ ๋ํ ๋์ ๊ด์ฌ๋์ ํผ์ค๋ ์ปฌ๋ฌ ์ง๋จ ์๋น์ค ์์ฅ์ ํญ๋ฐ์ ์ธ ์ฑ์ฅ ์ฌ๋ ฅ์ ๋ฐํ์ผ๋ก ์ฃผ์ ๋ฅผ ์ ์ ํ์๊ณ , ํ๋ก์ ํธ๋ฅผ ์งํํ์์ต๋๋ค.
ํ๋ก ํธ์๋(React)๋ก ํ๋ก์ ํธ์ ์ฐธ์ฌํ์ผ๋ฉฐ ๋ก๊ทธ์ธ, ๋ก๊ทธ์์, ํ์๊ฐ์ , ๊ฒ์ํ ๊ตฌํ์ ๋งก์์ต๋๋ค.
๊ตฌํ๊ธฐ๋ฅ
- ๋ก๊ทธ์ธ, ๋ก๊ทธ์์, ํ์๊ฐ์ (jwt๋ฅผ ์ฌ์ฉํ ํ์์ธ์ฆ)
- ๊ฒ์ํ CRUD, ๋๊ธ๊ณผ ๋๋๊ธ CRUD
1. ๋ก๊ทธ์ธ, ๋ก๊ทธ์์, ํ์๊ฐ์ (jwt๋ฅผ ์ฌ์ฉํ ํ์์ธ์ฆ)
1-1. ํ์๊ฐ์
function SignUp() {
...
return (
...
<Form action="/" className="form-top"
onSubmit={function (e) {
e.preventDefault();
axios.post('/user/register/', {
username: e.target.username.value,
password1: e.target.password1.value,
password2: e.target.password2.value,
nickname: e.target.nickname.value,
gender: e.target.gender.value == 'femail' ? 'W' : 'M'
}).then((res) => {
if (res.status == 200) { // ๊ฐ์
์ฑ๊ณต
alert(res.data.message);
navigate('/signin')
} else { // ๊ฐ์
์คํจ
alert('๊ฐ์
์คํจ!!');
}
}).catch((err) => {
console.log(err);
})
}}>
...
</Form>
);
}
export default SignUp
<form>
ํ๊ทธ๋ฅผ ์ฌ์ฉํ์๊ณ , axios๋ฅผ ์ฌ์ฉํด์ ์๋ฒ๋ก postํ์๋ค.
์ด๋ e.preventDefault()
๋ form ์์ submit์ญํ ์ ํ๋ ๋ฒํผ์ ๋๋ ์ด๋ ํ๋ฉด์ ์๋ก๊ณ ์นจ ํ์ง์๊ธฐ ์ํด์ ์ฌ์ฉํ๋ค.(submit์ ์๋๋จ)
1-2. ๋ก๊ทธ์ธ
<form onSubmit={(e) => {
e.preventDefault();
axios.post('/user/login/', {
username: e.target.username.value,
password: e.target.password.value,
})
.then((res) => {
if (res.status == 200) {
alert(res.data.message)
localStorage.setItem('UserID', res.data.token.user);
setRefreshToken(res.data.token.refresh_token); //์ฟ ํค์ refreshToken์ ์ฅ
dispatch(setAuthToken(res.data.token.access_token));//redux์ accessToken์ ์ฅ
navigate('/');
}
})
.catch((err) => {
if (err.response.status == 400) {
alert('์์ด๋ ํนํ๋ ธ์ต๋๋ค')
}
setValue("password", "");
})
}>
์๋ฒ๋ก post์์ฒญ์ด ์ฑ๊ณตํ๋ฉด response๋ก accessToken๊ณผ refreshToken์ ๋ฐ๋๋ค.
accessToken : redux store์ ์ ์ฅ
refreshToken : ์ฟ ํค์ ์ ์ฅ
accessToken์ ๊ฒฝ์ฐ ํ์ทจ์ ์ํ์ด ์๊ธฐ ๋๋ฌธ์ ๋ธ๋ผ์ฐ์ ์ ์ฅ์(localStorage, cookie)๊ฐ ์๋ store์ ์ ์ฅํ๋ค.
์ด๋ store์ ์ ์ฅํ๋ฉด ๋ธ๋ผ์ฐ์ ๋ฅผ ์๊ณ ๋ก์นจ ํ ๋ redux๊ฐ์ด ์ด๊ธฐ๊ฐ์ผ๋ก ๋์๊ฐ๋ฉด์ ๋ก๊ทธ์์์ด ๋๋๋ฐ ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์๋ก๊ณ ์นจ ๋ ๋๋ง๋ค refreshToken์ ์ฌ์ฉํ์ฌ ๋ก๊ทธ์ธ์ ์ฐ์ฅ์์ผ ์คฌ๋ค.
์ด๋ฅผ ์ํด์ ์ฐ์ App.js์ <Route>
๋ค ์ ์ฒด๋ฅผ ๊ฐ์ธ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์๋ค.
(nested route๋ฅผ ์ฌ์ฉํด์ ์์ route์์ token์ ๊ฒ์ฌ ํน์ ์ฐ์ฅ์ ํ ํ <Outlet>
์ ํตํด ํ์ route๋ค์ ๋ณด์ฌ์ฃผ๊ธฐ ์ํด์)
์ ์ฒด๋ฅผ ๊ฐ์ธ๋ ์ปดํฌ๋ํธ๋ ๋ ๊ฐ์ง๋ก ๋๋ด๋ค.
<PublicRoute>
(accessToken์์ด ์ ๊ทผ์ด ๊ฐ๋ฅํ route๋ค์ ๊ฐ์)<PrivateRoute>
(accessToken์ด ์์ด์ผ๋ง ์ ๊ทผ์ด ๊ฐ๋ฅํ route๋ค ๊ฐ์)- <PublicRoute>, <PrivateRoute> ์์๋ ํ ํฐ ๊ฒ์ฌ ํจ์ CheckToken์ ์ํํ๋๋ฐ ์ด ํจ์๋ฅผ ํตํด์ ์๋ก๊ณ ์นจ ๋ง๋ค refreshToken์ ํตํด ๋ก๊ทธ์ธ์ ์ ์ง์์ผ ์ค๋ค.
<Routes>
<Route element={<PublicRoute></PublicRoute>}>
๋ก๊ทธ์ธ ์์ด ์ ๊ทผํ ์ ์๋ Route
</Route>
<Route element={<PrivateRoute></PrivateRoute>}>
๋ก๊ทธ์ธ์ ํด์ผ๋ง ์ ๊ทผํ ์ ์๋ Route
</Route>
<Route path='*' element={<div>์๋ชป๋ ๊ฒฝ๋ก์
๋๋ค:(</div>}></Route>
</Routes>
- PrivateRoute.js
//
import { CheckToken } from '../auth/CheckToken';
import { Outlet} from 'react-router';
export default function PrivateRoute() {
const { isAuth } = CheckToken(location.key); //CheckTokenํจ์ ์ํ
if (isAuth === 'Failed') {
๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ์ด๋
} else if (isAuth==='Loading') {
๋ก๋ฉ์ค ์ปดํฌ๋ํธ ๋ณด์ฌ์ค
}
return <Outlet /> // ์ฑ๊ณตํ๋ฉด ํ์ Route๋ฅผ ๋ณด์ฌ์ค
}
- CheckToken.js
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getRefreshToken, removeRefreshToken } from '../cookie/Cookie';
import { requestToken } from '../api/url';
import { deleteAuthToken, setAuthToken } from '../store';
export function CheckToken(key){
const [isAuth,setIsAuth] = useState('Loading');
const {authenticated,expireTime} = useSelector(state=>state.token);
const refreshToken = getRefreshToken();
const dispatch = useDispatch();
useEffect(()=>{
const checkAuthToken = async ()=>{
if(refreshToken==undefined){
dispatch(deleteAuthToken());
setIsAuth('Failed');
}else{
if (authenticated && new Date().getTime() < expireTime){
setIsAuth('Success');
}else{
const response = await requestToken(refreshToken); // accessToken ์ฌ๋ฐ๊ธ ์ํ ํจ์
if(response.status){
const token = response.access_token;
dispatch(setAuthToken(token));
setIsAuth('Success')
}else{
dispatch(deleteAuthToken());
removeRefreshToken();
setIsAuth('Failed');
}
}
}
}
checkAuthToken();
},[refreshToken,dispatch,key])
return{
isAuth
};
}
useEffect
๋ฅผ ์ฌ์ฉํด์ deps์์ ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์ฟ ํค์ ์ ์ฅ๋ refreshToken์ ์ ๋ฌด์ redux์ ์ ์ฅ๋ accessToken์ ์ ํจ์ฑ์ ๊ฒ์ฌํ๋ค.
refreshToken์ด ์๊ณ accessToken์ด ๋ง๋ฃ๋์๋ค๋ฉด requestToken
ํจ์๋ฅผ ์ฌ์ฉํ์ฌ accessToken์ ์ฌ๋ฐ๊ธ ๋ฐ๋๋ค.
์ต์ข
๊ฒฐ๊ณผ๋ฅผ isAuth
state์ ์ ์ฅํ๊ณ returnํ๋ค.
- useEffect์ deps
refreshToken
: cookie์ ์ ์ฅ๋์ด ์๊ธฐ ๋๋ฌธ์ ์๋ก๊ณ ์นจ์ ํ๋๋ผ๋ ์ฌ์ ํ ๊ฐ์ ๊ฐ์ ์ ์งํ๋ค. ๋ฐ๋ผ์ ํด๋น ํ์ด์ง์ ์ ๊ทผํ๋ ์ ์ ๊ฐ ์ธ์ฆ๋(๋ก๊ทธ์ธ ๋) ์ ์ ์ธ์ง๋ฅผ ํ๋ณํ๊ธฐ ์ํ ๊ฐ์ผ๋ก๋ก๊ทธ์ธ/๋ก๊ทธ์์
์ ์ํํ์ ๋๋ง useEffect๊ฐ ์คํ๋๋ค.dispatch
: accessToken์ redux์ ์ ์ฅํ๊ธฐ ์ํ usdDispatch hook์ด๋ค. ๋ฐ๋ผ์ dispatch๋ useDispatch hook์ ์ฌ์ฉํ๊ธฐ ์ํด ์ ์ธํ ๋ณ์๋ก ๊ณ์ ๊ฐ์ ๊ฐ์ ์ ์งํ๊ฒ ๋๊ณ , ๋ฐ๋ผ์์ฑ์ด ์ต์ด๋ก ๋๋๋ง(์๋ก๊ณ ์นจ ์ ์ธ)
๋์์ ๋์๋ง useEffect๊ฐ ์คํ๋๋ค. ์ด๊ธฐ์ checkTokenํจ์๋ฅผ ์ํํ๊ธฐ ์ํด ์ ์ธํ์๋ค๊ณ ์๊ฐํ๋ฉด ๋๋ค.key
: useLocation hook์ผ๋ก ๋ถํฐ return ๋ฐ์ location ๊ฐ์ฒด์ ์ผ๋ถ์ด๋ค. location ๊ฐ์ฒด์ ๊ณ ์ ์๋ณ์๋ก ํด๋ฆญ์ผ๋ก ์ธํ ๋งํฌ ์ด๋ ๋๋์๋ก๊ณ ์นจ
์ ๊ฐ์ด ๋ณํ๊ฒ ๋๋ค. ๋ฐ๋ผ์ ๋งํฌ์ด๋ (router์ด๋) ๋ฟ ์๋๋ผ ์น ๋ธ๋ผ์ฐ์ ์ ์๊ณ ๋ก์นจ ์์๋ checkTokenํจ์๋ฅผ ์ํํ๊ธฐ ์ํด ์ ์ธํ์๋ค.
- requestTokenํจ์
export const requestToken = async (refreshToken)=>{
const data = await axios.post('/refeshToken์ผ๋ก accessToken๋ฐ๊ธํด์ฃผ๋ api',{ refreshToken : refreshToken }).catch(()=>{
return statusError;
})
if (parseInt(Number(data.status))===200){
console.log(data)
const status = true;
const code = data.status;
const access_token = data.data.token.access_token;
return {
status,
code,
access_token
};
}else{
return statusError;
}
};
2. ๊ฒ์ํ
์นดํ
๊ณ ๋ฆฌ ๋ณ๋ก ๊ฒ์ํ ๋ชฉ๋ก์ ๋ณด์ฌ์ฃผ๊ณ ํด๋ฆญ์ ์์ธํ์ด์ง๋ก ์ด๋ํ ์ ์๋๋ก ๊ตฌํํ๋ค.
์ด๋ ์ข์ธก์ ์นดํ
๊ณ ๋ฆฌ ๋ฐ๋ ๊ทธ๋๋ก ๋๊ณ ๋ชฉ๋กํ์ด์ง์ ์์ธํ์ด์ง๋ง ๋ฐ๋๋๋ก nested route๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํํ๋ค.
๋ชฉ๋กํ์ด์ง
์์ธํ์ด์ง
<Route path="/board" element={<ArticleCategory category={category} setCategory={setCategory} setSearchToggle={setSearchToggle} />}> {/*nested route ์ฌ์ฉ*/}
<Route path="list" element={<Article category={category} setCategory={setCategory} searchToggle={searchToggle} setSearchToggle={setSearchToggle} />}></Route> {/*/board/list๋ก ์ ์ํ๋ฉด ์นดํ
๊ณ ๋ฆฌ์ ๊ฒ์ํ ๋ชฉ๋ก์ ๋ณด์ฌ์ค*/}
<Route path="detail/:id" element={<ArticleDetail setCategory={setCategory} recommentList={recommentList} setSearchToggle={setSearchToggle}/>}></Route> {/*/board/detail/:id๋ก ์ ์ํ๋ฉด ์นดํ
๊ณ ๋ฆฌ์ ์์ธ ๊ฒ์ํ์ ๋ณด์ฌ์ค*/}
</Route>
์ด๋ ์นดํ ๊ณ ๋ฆฌ ๋ฐ๋ฅผ ํผ์ณค๋ค๊ฐ ์ ์๋ค๊ฐ ํ ์ ์๋ boot-strap์ ์ฌ์ฉํ๋๋ฐ ๋ชฉ๋ก์์ ์์ธํ์ด์ง๋ก ์ด๋ํ๊ฑฐ๋, ์์ธํ์ด์ง์์ ๋ชฉ๋ก์ผ๋ก ์ด๋ ํ ๋๋ง๋ค ๊ณ์ ์ฌ๋๋๋ง์ด ์ผ์ด๋ฌ๋ค. (์ด๋ํ ๋ ๋ง๋ค ์ ํ๋ค ํด์ง)
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด localStorage์ ์นดํ ๊ณ ๋ฆฌ ๋ฐ์ ์ํ๋ฅผ ์ ์ฅํ๋ค.
๋ซ์์๋
์ด์์๋
3. ๋๊ธ ์์
๋๊ธ ์ ์์ ๋ฒํผ์ ๋๋ฅด๋ฉด ๋๊ธ์ฐฝ์ด ์ ๋ ฅ์ฐฝ์ผ๋ก ๋ฐ๋๊ณ ๋๊ธ์ ์์ ํ ์ ์๋ค.
์ธ๊ฐ์ state๋ฅผ ์ฌ์ฉํ์๋ค.
- ์์ ๋ฒํผ์ ๋๋ ๋์ง ๋ํ๋ด๋ state
- ์์ ๋ฒํผ์ ๋๋ฅธ ๋๊ธ์ id๋ฅผ ๊ธฐ์ตํ๋ state
- ๋๊ธ์ ๋ด์ฉ์ ๊ธฐ์ตํ๋ state
๐ ```js let [editClicked, setEditClicked] = useState(false); let [editCommentId, setEditCommentId] = useState(); let [edittingComment, setEdittingComment] = useState(''); ```
์์ ๋ฒํผ์ ๋๋ฅด๋ฉด editClicked
๊ฐ true๋ก ๋ฐ๋๊ณ , editCommentId๋ ์์ ๋ฒํผ์ ๋๋ฅธ ๋๊ธ์ id๋ก ๋ฐ๋๋ค. edittingComment๋ ์์ ์ค์ธ ๋ด์ฉ์ผ๋ก ๋ฐ๋๋ค.
editClicked์ editCommentId์ ์ฌ์ฉํ์ฌ ์์
๋ฒํผ์ ๋๋ฅด๋ฉด ์์ ์๋ฃ
๋ฒํผ์ด ๋์ค๊ฒ ๋๋ค.
{
editClicked == true && editCommentId == a.id
?
<div className="comment-edit" onClick={editComment}>์์ ์๋ฃ</div>
:
<div className="comment-edit" onClick={() => { setEditClicked(true); setEditCommentId(a.id); setEdittingComment(a.body) }}>์์ </div>
}
state์ ๋ฐ๋ผ์ ์์ ํ ์ ์๋ ์ ๋ ฅ์ฐฝ์ด ๋์ค๊ฑฐ๋, ๊ทธ๋ฅ ๋๊ธ์ ๋ณด์ฌ์ฃผ๊ฑฐ๋ ํ๋ค.
{
editClicked == true && editCommentId == a.id
?
<div className='comment-content'>
<textarea style={{ width: '100%', wordBreak: 'break-all' }} value={edittingComment} onChange={(e) => { setEdittingComment(e.target.value) }}></textarea>
</div>
:
<div className='comment-content'>
<div style={{ fontSize: 'large', width: '100%', wordBreak: 'break-all' }}>{a.body}</div>
</div>
}
์์ ๋ฒํผ ๋๋ฅด๊ธฐ ์
์์ ๋ฒํผ ๋๋ฅธ ํ
'๐ํ๋ก์ ํธ:Project' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
TAB(Take a Bus) (0) | 2023.08.31 |
---|---|
forURtravel (0) | 2023.06.20 |
SignLanguage (0) | 2023.05.03 |