심화반 - 2주차
1. Promise
: 비동기 연산이 종료된 후 결과를 알기 위해 사용하는 객체
1-1. Promise 생성
- Promise 생성자 함수(new 키워드 사용)를 통해 생성한다.
- 비동기 작업을 수행할 콜백함수를 전달받아 사용한다.
- resolve : 작업이 성공한 경우 호출할 콜백함수
- reject : 작업이 실패한 경우 호출할 콜백함수
const promise = new Promise((resolve, reject) => {
if(...){
...
resolve("성공!");
}else{
...
reject("실패!");
}
});
1-2. Promise 상태값
- pending: 비동기 처리 수행 전(resolve, reject이 아직 호출 전)
- fulfilled: 수행 성공(resolve가 호출된 상태)
- rejected: 수행 실패(reject가 호출된 상태)
- settled: 성공 또는 실패(resolve나 reject가 호출된 상태
1-3. Promise 후속 처리 메소드(.then)
- 후속 처리 메소드를 통해 비동기 처리 결과를 받아서 처리해야 한다.
- .then(): 첫 번째 인자는 성공 시 실행, 두번째 인자는 실패 시 실행한다. 첫 번째 인자만 넘겨도 된다.
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("완료!"), 1000);
});
promise.then((result) => {
console.log(result);
}, error => {
console.log(error);
});
1-4. Promise Chaining
: 후속 처리 메소드(then)을 쭉 연결해주는 것
- then 메소드를 체이닝해서 여러 개의 Promise를 연결해 콜백 헬 현상을 해결할 수 있다.
- Promise Chaining은 Promise를 여러 번 사용할 수 있다.
- 먼저 해야하는 비동기 처리를 Promise 객체로 만들어 주고 .then() 안에 차례대로 Promise로 만든 비동기 작업들을 불러준다.
promise.then((result) => {
console.log(result);
}).then((result) => {
console.log(result);
return "promise 2";
}).then((result) => {
console.log(result);
return "promise 3";
})
1-5. async & await
: Promise를 더 편하게 사용할 수 있는 키워드
async
- 함수 앞에 붙여서 사용하고, 항상 Promise를 반환한다.
async function myFunc() {
return "프라미스를 반환해요!";
}
myFunc().then((result) => {console.log(result)});
await
- async 없이 사용이 불가하고 async 함수 안에서만 동작한다.
- Promise가 처리될 때(fulfilled)까지 기다렸다가 결과를 반환한다.
- thread는 일단 비동기 작업을 위임하고 바로 다음 작업을 하는데 await는 Promise가 처리 될 때까지 기다리고 처리가 완료되면 다음 작업을 수행한다.
- .then()이 없어도 기다렸다가 처리된다.
async function myFunc(){
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("완료!"), 1000);
});
console.log(promise); // Promise 아직 안끝남
let result = await promise; // 여기서 기다린다는 신호를 준다.
console.log(promise); // Promise 끝남
console.log(result); // 끝난 Promise의 결과 출력
}
2. 토큰 기반 인증
: 인증 정보를 세션에 담지 않은 인증 방식
- 사용자가 로그인을 할 때 서버에서 유저 인증 토큰을 주는데 이 토큰으로 구분한다.
- 클라이언트에 토큰을 저장하고 서버에 토큰과 같이 요청을 보내면 서버에서 토큰으로 유저 확인 후 로그인 후에 사용가능한 기능들을 사용할 수 있다.
2-1. OAuth2.0(오어스 또는 오어터)
: 토큰을 발급하고 인증하는 방법을 관리하는 프레임워크
동작 방식
구글은 사용자의 정보를 가지고 있고 로그인 검증도 해주기 때문에 Resource Server(자원 서버) + Authorization server(권한 서버)이다.
① 구글이 로그인할 때 사용자가 입력한 정보를 보고 client에 접근 권한을 부여한다.
② client는 권한을 가지고 Authorization server(권한 서버)에 access_token을 요청한다.
③ Authorization server(권한 서버)는 client가 보낸 accss_token을 통해 권한을 확인하고 결과를 client에 보낸다.
Refresh token
- access_token이 만료되었을 때 구글에 다시 token을 요청할 수 있다.
- access_token보다 만료일이 더 길다.
2-2. JWT(Json Web Token)
: JSON 형태로 이루어진 토큰
- 전자서명이 포함되어 있어 보안이 좋다.
[header].[payload(내용)].[signature(서명)]
- header: 토큰의 타입, 암호화 방식 정보
- payload: name:value의 한 쌍으로 담긴 정보
- signature: secret key를 포함하고 header와 payload 정보가 암호화된 서명 정보
동작 방식
- 토큰 기반 동작 방식 그대로 움직인다.
① server에서 요청 확인 후 secret key를 가지고 access_token을 발급한다.
② client에 JWT를 전달한 후 다시 client는 Authorization header에 JWT를 담아서 보낸다.
③ server에서 JWT의 서명을 확인하고 payload에서 정보를 확인한 후 API 응답을 보낸다.
3. 웹 저장소
- access_token을 저장할 수 있는 클라이언트 저장소는 아래와 같다.
3-1. Cookies
- key:value 형태의 저장소
- 사이트에 접속한 후에 열어야 cookies의 사용이 가능하다.
- 4KB의 저장공간을 가지고 있다.
Cookies 사용법
document.cookie = "MY_COOKIE=here;"; // Cookies 만들기
console.log(document.cookie); // Cookies 가져오기
document.cookie = "MY_COOKIE=here; expires=new Date('2020-12-12').toUTCString()"; // Cookies 삭제
3-2. Session Storage
- key:value 형태의 저장소
- 브라우저를 닫으면 저장된 데이터는 삭제된다. 다음 브라우저를 열었을 때에도 유지해야 하는 데이터는 넣기 힘들다.
sessionStorage.setItem("MY_SESSION", "here"); // 추가하기
sessionStorage.getItem("MY_SESSION"); //가져오기
sessionStorage.removeItem("MY_SESSION"); //삭제하기
3-3. Local Storage
: key:value 형태의 저장소
- 따로 지우지 않는 한 계속 브라우저에 데이터가 남아있어 보안상 취약해지기 쉽다. 사용자의 중요한 정보는 저장하지 않는게 좋다.
- 5MB의 저장공간을 가지고 있다.
localStorage.setItem("MY_LOCAL", "here"); // 추가하기
localStorage.getItem("MY_LOCAL"); //가져오기
localStorage.removeItem("MY_LOCAL"); // 삭제하기
4. 쿠키 저장
- 로그인 페이지에서 id와 password를 입력하면 Cookies로 저장한다.
- Cookies 저장소 사용법을 이용해서 Cookies 값을 가져오고, 저장하고, 삭제하는 함수를 만든다.
- split(): 매개변수를 중심으로 문자열을 나누어 준다.
- pop(): 배열의 마지막 요소를 제거한다.
- shift(): 배열의 첫 번째 요소를 제거한다.
- toUTCString(): 표준 시간대를 사용한 날짜를 문자열로 변환한다.
// cookie 가져오기
const getCookie = (name) => {
let value = '; ' + document.cookie;
let parts = value.split(`; ${name}=`);
if(parts.length === 2) {
return parts.pop().split(';').shift();
}
}
// cookie 추가하기
const setCookie = (name, value, exp = 5) => {
let date = new Date();
date.setTime(date.getTime() + exp*24*60*60*1000);
document.cookie = `${name}=${value}; expires=${date.toUTCString()}`;
}
// cookie 삭제하기
const deleteCookie = (name) => {
let date = new Date('2021-01-01').toUTCString();
console.log(date);
document.cookie = name + '=; expires=' + date;
}
export { getCookie, setCookie, deleteCookie };
5. 로그인하기(1) - Header 분기, Redux
5-1. Header 컴포넌트 분기
- 로그인을 했을 때와 하지 않았을 때의 Header 컴포넌트가 바뀌어야 한다.
- useEffect Hook을 이용해 cookie가 있을 때와 없을 때의 Header 컴포넌트를 변경한다.
const [ is_login, setIsLogin ] = useState(false);
useEffect(() => {
let cookie = getCookie('user_id');
if(cookie){
setIsLogin(true);
} else {
setIsLogin(false);
}
})
if(is_login){
return (
<React.Fragment>
<Grid is_flex padding='4px 16px'>
<Grid>
<Text size='24px' bold margin='0px'>Hello</Text>
</Grid>
<Grid is_flex>
<Button text='내정보'></Button>
<Button text='알림'></Button>
<Button text='로그아웃' onClick={() => deleteCookie('user_id')}></Button>
</Grid>
</Grid>
</React.Fragment>
);
}
로그인 했을 때 Header
로그아웃 했을 때 Header
5-2. Redux 설치
- 페이지마다 쿠키를 체크하기가 어렵기 때문에 Redux를 사용해서 로그인 상태를 저장하고 어떤 컴포넌트에서든지 편하게 볼 수 있도록 한다.
- Redux, 경로 이동까지 해줄 History, Router와 History를 이어줄 모듈 설치
- 불변성을 관리하는 immer, 액션을 관리하는 Redux-actions 설치
6. 로그인하기(2) - 유저 모듈 만들기
6-1. import
- creatAction, handleActions는 action과 reducer를 편하게 사용할 수 있도록 도와준다.
import { createAction, handleActions } from 'redux-actions';
import { produce } from 'immer';
6-2. Action Type 만들기
const LOG_IN = 'LOG_IN';
const LOG_OUT = 'LOG_OUT';
const GET_USER = 'GET_USER';
6-3. Action 생성 함수 만들기
- Action 생성 함수가 있어야 Action 객체를 만들어 사용이 가능하다.
const logIn = createAction(LOG_IN, (user) => ({user}));
const logOut = createAction(LOG_OUT, (user) => ({user}));
const getUser = createAction(GET_USER, (user) => ({user}));
6-4. initialState
const initialState = {
user: null,
is_login: false,
}
6-5. immer를 사용한 Reducer
- immer로 원본 값(initialState)을 복사한 값을 draft를 통해 받아온다.
- draft가 불변성을 유지해서 바꿔준다.
- payload에 보낸 데이터가 담겨있다. payload.user를 해야 action을 통해 넘겨주었던 값을 가져올 수 있다.
export default handleActions({
[LOG_IN]: (state, action) => produce(state, (draft) => {
setCookie('is_login', 'succes');
draft.user = action.payload.user;
draft.is_login = true;
}),
[LOG_OUT]: (state, action) => produce(state, (draft) => {
deleteCookie('is_login');
draft.user = null;
draft.is_login = false;
}),
[GET_USER]: (state, action) => produce(state, (draft) => {}),
}, initialState);
6-6. actionCreator
const actionCreators = {
logIn,
getUser,
logOut,
};
export { actionCreators };
7. 로그인하기(3) - Redux Store 만들기
① combineReducers()를 사용해서 export한 reducer를 모아 root reducer를 만든다.
② 미들웨어를 적용해주고
③ createStore()를 사용해서 root reducer와 미들웨어를 엮어 스토어를 만든다.
7-1. import
import { createStore, combineReducers, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import { createBrowserHistory } from "history";
import { connectRouter } from "connected-react-router";
// reducer
import User from "./modules/user";
7-2. root reducer
- combineReducers()
const rootReducer = combineReducers({
user: User,
});
7-3. Middlewares
: 개발할 때 편하기 위해서 사용하기 때문에 개발환경일 때에만 사용한다.
- env: 어느 개발환경인지 알려준다. 배포를 한다면 배포환경을 알려준다.
- require: 패키지를 사용하려고 가져올 때 사용한다.
- logger: 콘솔에 이전 상태, 이후 상태가 찍힌다. 데이터 안에 담긴 값, 어떤 액션이 일어나서 변경된 값을 콘솔에 찍힌다.
const middlewares = [thunk];
const env = process.env.NODE_ENV;
// 개발환경에서는 로거라는 걸 하나만 더 써볼게요.
if (env === "development") {
const { logger } = require("redux-logger");
middlewares.push(logger);
}
7-4. Redux devTools
- 브라우저일 때에만 돌려주고 devTools가 설치되어 있으면 열어준다.
const composeEnhancers =
typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
})
: compose;
7-5. Middlewares 묶기
- applyMiddleware로 지금까지 있었던 모든 미들웨어들을 사용한다고 알려주고 composeEnhancers()를 사용해서 묶어준다.
const enhancer = composeEnhancers(applyMiddleware(...middlewares));
7-6. Store 만들기
- initialStore를 받아서 createStore로 미들웨어를 엮어서 Store를 만든다.
let store = (initialStore) => createStore(rootReducer, enhancer);
export default store();
7-7. history
- history 객체를 생성하고 생성한 history를 라우터와 연결되면 store에 저장된다.
- withExtraArgument: 다른 인수를 더 넘겨준다.
export const history = createBrowserHistory();
const rootReducer = combineReducers({
user: User,
router: connectRouter(history),
});
const middlewares = [thunk.withExtraArgument({history: history})];
8. 로그인하기(4) - Store 주입
8-1. Store 주입
index.js
- Provider를 이용해서 Store를 주입한다.
import { Provider } from 'react-redux';
import store from './redux/configureStore';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
App.js
- ConnectedRouter를 이용해서 Redux랑 같은 history를 사용하면서 공유한다.
import { ConnectedRouter } from 'connected-react-router';
import { history } from '../redux/configureStore';
return (
<ConnectedRouter history={history}>
<Route path="/" exact component={PostList} />
<Route path="/login" exact component={Login} />
<Route path="/signup" exact component={Signup} />
</ConnectedRouter>
);
8-2. Redux Hook
- useSelector: 상태를 조회한다.
- useDispatch: 생성한 dispatch를 발생한다.
Login.js
import {actionCreators as userActions} from "../redux/modules/user";
import { useDispatch } from "react-redux";
const login = () => {
dispatch(userActions.login({user_name: "perl"}));
}
user.js
const loginAction = (user) => {
return function (dispatch, getState, {history}) {
dispatch(logIn(user));
history.push('/');
}
}
...
const actionCreators = {
logIn,
getUser,
logOut,
loginAction,
};
header.js
const dispatch = useDispatch();
const is_login = useSelector((state) => state.user.is_login);
<Button text="로그아웃" _onClick={() => {dispatch(userActions.logOut({}));}}></Button>
9. 회원가입 구현
9-1. auth.createUserWithEmailAndPassword()
: 신규 사용자가 자신의 이메일 주와 비밀번호를 사용해 가입할 수 있도록 도와주는 firebase 함수
- 양식을 작성 후 이메일 주소와 비밀번호의 유효성 검사를 한 후 메소드에 전달한다.
9-2. auth.currentUser.updateProfile()
: 사용자의 기본 프로필 정보를 업데이트할 때 사용하는 메소드
- 이메일과 비밀번호 외에도 사용자의 닉네임을 업데이트하기 위해서 사용한다.
- 닉네임까지 변경하고 로그인 처리가 완료되면 Redux에 저장한 후 history.push를 통해 메인 페이지로 이동한다.
- firebase에 로그인이 되어있는 상태인데도 redux에 로그인 상태 정보가 없기 때문에 로그인 하기 전 Header로 변경되는 것이다.
auth.currentUser.updateProfile({
displayName: user_name,
}).then(()=>{
dispatch(setUser({user_name: user_name, id: id, user_profile: ''}));
history.push('/');
}).catch((error) => {
console.log(error);
});
10. 로그인 구현
10-1. auth.signInWithEmailAndPassword()
: 비밀번호를 통해 로그인을 처리하는 firebase 함수
- 사용자가 로그인을 하면 이메일 주소와 비밀번호를 메소드에 전달한다.
- 로그인 완료 후 메인 페이지로 이동한다.
10-2. type
- 현재 비밀번호를 입력하면 문자들이 보이는데 Input elements에서 type을 넘겨주어 * 로 보이도록 한다.
Input.defaultProps = {
label: "텍스트",
placeholder: "텍스트를 입력해주세요.",
type: "text",
_onChange: () => {},
};
11. 로그인 유지
- 페이지가 새로고침 되었을 때에도 로그인 유지를 해야한다.
11-1. auth.setPersistence()
: 메소드를 호출하면 기존의 지족성을 유지하거나 수정할 수 있는 함수
11-2. firebase.auth.Auth.Persistence.SESSION
: 현재의 session이나 탭에서만 상태가 기록하고 유지되고 인증된 탭이나 창을 닫으면 정보가 삭제된다.
firebase.auth().setPersistence(
firebase.auth.Auth.Persistence.SESSION
).then(() => {});
11-3. auth.onAuthStateChanged()
: 로그인이 되면 firebase에서 자체적으로 저장해 놓은 정보를 인증 함수를 통해 값을 가져와 Redux에 넣어주는 로그인 체크 함수
① 시작점은 App.js에서 Session Check를 먼저 한다.
② Session이 있으니 Redux로 로그인 상태를 보낸다.
③ 로그인 정보의 유무를 확인한 후 사용자의 정보를 넣어준다.
const _session_key = `firebase:authUser:${apiKey}:[DEFAULT]`;
const is_session = sessionStorage.getItem(_session_key)? true : false;
React.useEffect(() => {
if(is_session){
dispatch(userActions.loginCheckFB());
}
}, []);
12. 로그아웃 구현
12-1. auth.signOut()
: firebase에서 제공하는 로그아웃 함수
replace()
: 새로운 문자열을 반환하는 함수
- 로그아웃을 했을 때 뒤로가기를 한다고 해서 다시 들어가게 해주면 안된다.
- 현재 페이지와 replace() 안의 주소와 바꿔주면 뒤로가기 해도 원래 페이지가 나오지 않는다.
auth.signOut().then(() => {
dispatch(logOut());
history.replace("/");
});
13. 아이디 이메일 Check
13-1. 정규 표현식
: 문자를 조합해서 비교할 때 사용된다.
- 정규식 패턴
- ^[0-9a-zA-Z] : 첫 글자가 0~9거나 영문 대소문자이다.
- ([-_.0-9a-zA-Z])*: -_. 특수문자와 ()를 사용해 숫자나 영문자가 여러 개 들어올 수 있다.
- ([a-zA-Z])*: . 뒤에는 문자만 들어오도록 한다.
- _reg.test(email): 정규식 패턴에 맞는지 확인한다.
let _reg = /^[0-9a-zA-Z]([-_.0-9a-zA-Z])*@[0-9a-zA-Z]([-_.0-9a-zA-z])*.([a-zA-Z])*/;
_reg.test(email)
14. Post 상세 페이지
- 주소에 post id를 주면 좋다.
<Route path="/post/:id" exact component={PostDetail} />
Author And Source
이 문제에 관하여(심화반 - 2주차), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@rlatjdus0814/리액트-심화반-2주차저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)