두 자연수 A와 B가 있을 때, A = BC를 만족하는 자연수 C를 A의 약수라고 한다. 예를 들어, 2의 약수는 1, 2가 있고, 24의 약수는 1, 2, 3, 4, 6, 8, 12, 24가 있다. 자연수 A의 약수의 합은 A의 모든 약수를 더한 값이고, f(A)로 표현한다. x보다 작거나 같은 모든 자연수 y의 f(y)값을 더한 값은 g(x)로 표현한다.
자연수 N이 주어졌을 때, g(N)을 구해보자.
입력
첫째 줄에 자연수 N(1 ≤ N ≤ 1,000,000)이 주어진다.
출력
첫째 줄에 g(N)를 출력한다.
접근 방법 1
직관적으로 풀었더니 제한시간인 0.5초를 넘어서 시간초과가 떴다. f(1)부터 f(N)까지 하나씩 구해서 더하는 방식으로 짜면 안되는 것 같다.
#include <iostream>
using namespace std;
int main(){
int N, ans=0;
cin >> N;
for(int i = 1; i<=N; i++){
for(int j = 1; j<=i; j++){
if(i%j == 0) ans +=j;
}
}
cout << ans << "\n";
return 0;
}
접근방법 2
새로운 접근 방법으로 풀어보았다.
N = 10 일때를 보자.
f(1) = sum(1)
f(2) = sum(1, 2)
f(3) = sum(1, 3)
f(4) = sum(1, 2, 4)
f(5) = sum(1, 5)
f(6) = sum(1, 2, 3, 6)
f(7) = sum(1, 7)
f(8) = sum(1, 2, 4, 8)
f(9) = sum(1, 3, 9)
f(10) = sum(1, 2, 5, 10)
이고 약수의 숫자들을 카운팅해보면,
1 : 10개 (10을 1로 나눈 몫)
2 : 5개(10을 2로 나눈 몫)
3 : 3개(10을 3으로 나눈 몫)
4 : 2개(10을 4로 나눈 몫)
.
.
.
.
10: 1개(10을 1로 나눈 몫)
과 같이 규칙이 있음을 알 수 있다.
주의)
또한 처음에는 ans 변수를 int 형으로 선언했는데 오류가 났었다. 입출력 예시 자료를 보니 출력값이 82256014였고 이를 이진수로 변환하면 100111001110010000010001110(27비트)로 4바이트를 넘어가서 오류가 나는 거였다. 8바이트인 long long (int생략 가능) 으로 바꿔주니 정답처리가 되었다. 자료형에도 주의하자!!
#include <iostream>
using namespace std;
int main(){
int N;
long long ans=0;
cin >> N;
for(int i = 1; i<=N; i++){
ans += (N/i)* i;
}
cout << ans << "\n";
return 0;
}
import React, {useEffect} from 'react';
import axios from 'axios';
import {useDispatch} from 'react-redux'
import {auth} from '../_actions/user_action'
export default function (SpecificComponent, option, adminRoute = null){
function AuthenticationCheck(props){
const dispatch = useDispatch();
useEffect(() => {
dispatch(auth()).then(response => { //backend에서 처리한 정보들이 response안에 다 들어있음
console.log(response)
})
}, [])
return (
<SpecificComponent/>
)
}
return AuthenticationCheck
}
Step 2) 서버에서는 미들웨어로 이동
서버 쪽에 구현해놓은 API 를 보자.
index.js에 이미 구현해놓았다.
get request로 요청을 받으면 미들웨어 auth가
여기로 그 요청이 넘어온다. 여기서는 토큰을 사용해서 쿠키 안에 토큰을 집어 넣는다. 그 쿠키를 보고 로그인 한 유저인지 아닌지를 판단하고 그 결과를 리액트에 넘겨준다.
Step 1)-1 Action 구현
그리고 user_action.js에도 아래와 같이 인증 기능을 하는 함수를 추가해준다.
export function auth() {
const request = Axios.get('/api/users/auth') //endpoint로 get request , get이니까 login과 다르게 get 이니까 body 파라미터 필요 없음
.then(response => response.data)
return { //Action 했으니까 이제 Reducer로 보냄
type: AUTH_USER,
payload: request
}
}
Step 1)-2 Reducer 구현
action에 따라 결과를 리턴해주는 곳이다. AUTH_USER 케이스를 추가해준다.
import {
LOGIN_USER, REGISTER_USER, AUTH_USER
} from '../_actions/types';
export default function (state={}, action){ //state 는 이전 상태
switch(action.type){ //Action에는 여러 타입 존재함. 이 타입에 따라 다르게 반응하도록 작성
case LOGIN_USER:
return {...state, loginSuccess: action.payload} //... : spread operator은 파라미터 state를 그대로 가져온 것으로 빈 상태를 의미
break;
case REGISTER_USER:
return { ...state, register: action.payload}
break;
case AUTH_USER:
return { ...state, userData: action.payload}
break;
default:
return state;
}
}
Step 4) 컴포넌트를 Auth HOC에 넣기
각 컴포넌트를 Auth로 감싸주고 파라미터를 지정해서 넘겨준다. 파라미터 설명은 Step1) 에 기재했다.
import React from 'react'
import {
BrowserRouter as Router,
Switch,
Route,
// eslint-disable-next-line
Link
} from "react-router-dom";
import LandingPage from './components/views/LandingPage/LandingPage';
import LoginPage from './components/views/LoginPage/LoginPage';
import RegisterPage from './components/views/RegisterPage/RegisterPage';
import Auth from './hoc/auth'
function App() {
return (
<Router>
<div>
{/*
A <Switch> looks through all its children <Route>
elements and renders the first one whose path
matches the current URL. Use a <Switch> any time
you have multiple routes, but you want only one
of them to render at a time
*/}
{ /* 로그인한 유저는 로그인 페이지에 못들어감 */}
<Switch>
<Route exact path="/" component={Auth(LandingPage, null)} />
<Route exact path="/login" component={Auth(LoginPage, false)} />
<Route exact path="/register" component={Auth(RegisterPage, false)} />
</Switch>
</div>
</Router>
);
}
export default App;
Step 5) 기능 확인하기
이제 실행해서 결과를 확인해보자.
1) 로그인 전 LandingPage
2)로그인 페이지
isAuth 가 false이다.
3) 로그인 후 LandingPage
3) 로그아웃 후 로그인 페이지
Step 6) 페이지 접근 관리하기
위에 있는 payload.isAuth는 로그인 된 상태이고 아래 있는 payload.isAuth는 로그아웃된 상태이다. 각각 true, false 이므로 이에 맞게 코드를 작성하자.
auth.js를 다음과 같이 수정해서 로그인 한 사람은 로그인 페이지나 회원가입 페이지에 접근하지 못하도록 하는 등 페이지 접근을 관리한다.
import React, {useEffect} from 'react';
import axios from 'axios';
import {useDispatch} from 'react-redux'
import {auth} from '../_actions/user_action'
export default function (SpecificComponent, option, adminRoute = null){
function AuthenticationCheck(props){
const dispatch = useDispatch();
useEffect(() => {
//auth는 action이름
dispatch(auth()).then(response => { //backend에서 처리한 정보들이 response안에 다 들어있음
console.log(response)
//로그인 하지 않은 상태인데
if(!response.payload.isAuth) { //option이 true이면 즉 로그인 된 유저만 들어갈 수 있어야하면
if(option){
props.history.push('/login')
}
} else{ //로그인 된 상태인데
if(adminRoute && !response.payload.isAdmin){
props.history.push('/')
}
else{
if(option===false){ //option이 false면 로그인한 유저는 출입 불가능함. 로그인 페이지 or 회원가입 페이지
props.history.push('/')
}
}
}
})
}, [])
return (
<SpecificComponent/>
)
}
return AuthenticationCheck
}
참고)
Page 이동에 사용되는 history는 react-router-dom을 이용해서 쓰고 있는 것임.
withRouter 로
export default withRouter(LandingPage)
코드 맨 아래에 위와 같이 처리를 해줘야 오류가 안 난다.
여기까지 리엑트 노드를 이용한 로그인, 로그아웃, 회원가입 페이지 구현을 해 보았다.
직접 코드를 작성하면서 웹사이트 구현을 위한 기초를 다질 수 있었고, 이를 통해 서버 및 클라이언트의 전체적인 동작 원리 및 흐름과 MongoDB 사용법 또한 익힐 수 있었다.
import Axios from 'axios';
import {
LOGIN_USER, REGISTER_USER
} from './types';
export function loginUser(dataTosubmit) {
const request = Axios.post('/api/users/login', dataTosubmit) //서버에 리퀘스트 날리고
.then(response => response.data) //받은 데이터를 request에 저장
return { //Action 했으니까 이제 Reducer로 보냄
type: LOGIN_USER,
payload: request
}
}
export function registerUser(dataTosubmit) {
const request = Axios.post('/api/users/register', dataTosubmit) //서버에 리퀘스트 날리고
.then(response => response.data) //받은 데이터를 request에 저장
return { //Action 했으니까 이제 Reducer로 보냄
type: REGISTER_USER,
payload: request
}
}
Step 3) type 지정
//action의 타입들만 관리하는 파일
export const LOGIN_USER = "login_user";
export const REGISTER_USER = "register_user";
Step 4) Reducer 작성
user_reducer.js
import { LOGIN_USER, REGISTER_USER } from '../_actions/types';
export default function(state={}, action){ //state 는 이전 상태
switch(action.type){ //Action에는 여러 타입 존재함. 이 타입에 따라 다르게 반응하도록 작성
case LOGIN_USER:
return {...state, loginSuccess: action.payload} //... : spread operator은 파라미터 state를 그대로 가져온 것으로 빈 상태를 의미
break;
case REGISTER_USER:
return { ... state, register: action.payload}
break;
default:
return state;
}
}
import React, {useEffect} from 'react'
import axios from 'axios';
function LandingPage(){//Functional Component 만들기
useEffect(() => {
axios.get('/api/hello') //endpoint. getRequest를 server 즉 index.js로 보내질 것
.then(response => {console.log(response)}) //server 에서 돌아온 response를 콘솔창에 출력해봄
}, [])
return(
<div style={{
display: 'flex', justifyContent: 'center', alignItems: 'center',
width: '100%', height: '100vh'
}}>
<h2> 시작 페이지 </h2>
</div>
)
}
export default LandingPage
그럼 첫 시작 화면이 아래와 같이 뜬다.
Step 2) 로그인 페이지 작성
onChange에 함수를 지정해줌으로써 이메일이나 비밀번호를 입력할 때, 실시간으로 state가 변하게 해준다.
//client/src/components/views/LoginPage/LoginPage.js
import React, { useState } from 'react'
import Axios from 'axios'
import {useDispatch} from 'react-redux';
import {loginUser} from '../../../_actions/user_action'
function LoginPage(props) { //파라미터로 props 넣어줘야함! 로그인 완료된 후 처음 화면으로 돌아가게 하기 위함
const dispatch = useDispatch();
const [Email, setEmail] = React.useState(" ")
const [Password, setPassword] = React.useState(" ")
const onEmailHandler = (event) => {
setEmail(event.currentTarget.value)
}
const onPasswordHandler = (event) => {
setPassword(event.currentTarget.value)
}
const onSubmitHandler = (event) => {
event.preventDefault(); //리프레시 방지-> 방지해야 이 아래 라인의 코드들 실행 가능
// console.log('Email', Email);
// console.log('Password', Password);
let body={
email: Email,
password: Password
}
//디스패치로 액션 취하기
dispatch(loginUser(body))
.then(response => {
if(response.payload.loginSuccess) {
props.history.push('/') //리액트에서 페이지 이동하기 위해서는 props.history.push() 이용.
// 로그인 완료된 후 처음 화면(루트 페이지-landingpage로)으로 돌악가게 하기
} else{
alert(' Error')
}
})
}
return (
<div style={{
display: 'flex', justifyContent: 'center', alignItems: 'center',
width: '100%', height: '100vh'
}}>
<form style={{display: 'flex', flexDirection: 'column'}}
onSubmit={onSubmitHandler}
>
<label>Email</label>
<input type="email" value={Email} onChange={onEmailHandler}/>
<label>Password</label>
<input type="password" value={Password} onChange={onPasswordHandler}/>
<br />
<button>
Login
</button>
</form>
</div>
)
}
export default LoginPage
Step 3) Action 기능 구현
Login버튼을 누르면 디스패치가 action을 수행할 수 있도록 기능을 구현한다. 그 기능들은 _action/user_action.js 를 새로 생성해서 아래와 같이 코드를 작성하자.
- Server 에 보낼 때 Axios.post() 를 이용한다.
//client/src/_actions/user_action.js
import Axios from 'axios';
import {
LOGIN_USER
} from './types'
export function loginUser(dataTosubmit) {
const request = Axios.post('/api/users/login', dataTosubmit) //서버에 리퀘스트 날리고
.then(response => response.data) //받은 데이터를 request에 저장
return { //Action 했으니까 이제 Reducer로 보냄
type: LOGIN_USER,
payload: request
}
}
Step 4) Reducer 기능 구현
이 다음은 Reducer 차례로, (previousState, action) 을 받아서 nextState를 리턴해주는 역할을 할것이다.
Action의 여러가지 type들을 쉽게 관리하기 위해 client/src/_actions/types.js파일을 새로 만들어서 아래와 같이 코드를 작성한다.
//action의 타입들만 관리하는 파일
export const LOGIN_USER = "login_user";
그리고 client/src/_reducers/user_reducer.js 파일도 아래와 같이 작성해준다.
import { LOGIN_USER } from '../_actions/types';
export default function(state={}, action){ //state 는 이전 상태
switch(action.type){ //Action에는 여러 타입 존재함. 이 타입에 따라 다르게 반응하도록 작성
case LOGIN_USER:
return {...state, loginSuccess: action.payload} //... : spread operator은 파라미터 state를 그대로 가져온 것으로 빈 상태를 의미
break;
default:
return state;
}
}
그런데 Store에는 항상 객체 형식의 Action이 오는 것이 아니다. Promiese 또는 Function 형식으로 올 수도 있다. 이런 점을 해결하기 위해 Promise 형식으로 오는 것들은 reux-promise로, Function 형식으로 오는 것들은 redux-thunk로 처리하게 된다.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import * as serviceWorker from './serviceWorker';
import { Provider} from 'react-redux';
import 'antd/dist/antd.css';
import { applyMiddleware, createStore } from 'redux';
import promiseMiddleware from 'redux-promise';
import ReduxThunk from 'redux-thunk';
import Reducer from './reducer';
const createStoreWithMiddleware = applyMiddleware (promiseMiddleware, ReduxThunk)(createStore)
ReactDOM.render(
//App을 Provider로 감싸서 Redux랑 어플리케이션이랑 연결시키는 것
<Provider>
store={createStoreWithMiddleware(Reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && //extension
window.__REDUX_DEVTOOLS_EXTENSION__ ()
)}
<App />
</Provider>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
client/src/_reducers/index.js
import { combineReducers} from 'redux';
//import user from './user_reducer';
const rootReducer = combineReducers({
//user
})
export default rootReducer;
Store안에는 여러 State에 따른 다양한 Reducer들이 있다. 나눠진 Reducer들을 combineReducers를 이용해서 RootReducer에서 각 Reducer 들을 합쳐준다.
Class Component 와 Functional Component의 차이점
- Functional Component에서는 위 그림 중 어떠한 기능도 쓸 수 없다.
- 그래서 대부분 Class Component를 썼었다.
- 그러다 React 16.8 버전에서 React Hook을 발표를 했고, 이 이후로부터는 Functional Component에서도 위의 기능들을 쓸 수 있게 됐다.