1. 기본 npm
npm init
npm i react react-dom next
2. pages 폴더 - index.jsx
import Raact from 'react'
const Idex =()=>{
return (
<>
NEXT !
</>
)
}
export default Index
3. packages json
{
"name": "next0827",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev":"next dev",
"build":"next build",
"start":"next start"
},
"author": "",
"license": "ISC",
"dependencies": {
"next": "^11.1.0",
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}
4. 실행시켜보기
npm run dev
localhost:3000들어가면
작업하기 직전까지 만들어놓고 NEXT 할 때 그 코드를 clone해서 쓰는게 좋을 것 !
Redux 까지하는 환경이면 Redux까지 !
5. pages > _app.jsx 파일 생성
index 파일 시작 전 app.jsx 파일이 시작이됨
pageProps
- SRR 떄문에 사용
컴포에 하위로 프롭스를 전달해주는
const MyApp = ({Component, pageProps}) => {
return (
<Component {...pageProps}/>
)
}
export default MyApp
6. pages > _document.jsx 생성
_document.jsx - 프젝할 때 한 번만 쓰는 거라고 함 !
styled component 할 때 srr 이 안되어서 css를 서버사이드 랜더링을 처리하려고 코드를 복붙
import Document, {
Html,
Head,
Main,
NextScript,
} from 'next/document';
import { ServerStyleSheet } from "styled-components";
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
// sheet을 사용해 정의된 모든 스타일을 수집
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
});
// Documents의 initial props
const initialProps = await Document.getInitialProps(ctx);
// props와 styles를 반환
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
render(){
return(
<Html>
<Head>
<link
rel="preconnect"
href="https://fonts.googleapis.com"
/>
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin="true"
/>
<link
href="https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap"
rel="stylesheet"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
7. .babelrc 폴더 생성 = style component 에 관한 코드 넣기
.babelrc = 객체 로 시작
{
"presets":["next/babel"],
"plugins:":[
["babel-plugin-styled-components",{
"ssr":true,
"displayName":true
}]
]
}
npm install
npm install babel-plugin-styled-components
------여기까지가 next 사용하겠다 ~ css 사용하겠다 ~
8. 이제 디렉토리 만들기- 파일 트리
1. pages : 페이지 화면의 대표화면에 대한 컴포넌트
리덕스 할거라
2. saga : 리덕스 Redux middleware인 saga 내용 넣는 디렉토리
3. reduces : actions 내용과 reducer 내용을 넣는 디렉토리
4. Providers : Store 내용을 담아주는 / Theme (사이트 구성할 때 대표적인 Layout들 넣는 곳 )
5. Components : 화면 구성하는 버튼, 작은 요소 단위들 (조금더 작은 단위의 - 폴더 세분화)
6. Layout : header, footer, navigation 컴포넌트 - 중복적으로 많이 사용하는 pages의 요소들
7. utils : 커스텀 hook or 데이터 정리할 수 있는 코드들
9. Providers - rootProvider.js 생성 & 아래 코드 입력 : 페이지 컴포넌트에서 감싸줄 애들 ! (데이터 중심 ! )
import React from 'react'
// App compo 안에 있는 user, ..등등 컴포를 감싸는.. 미리 빼놓기
const RootProvider =({children})=>{
return(
<ThemeLayout>
{children}
</ThemeLayout>
)
}
export default RootProvider
10. Layouts > ThemeLayout.jsx 생성 & 코드작성 : 페이지컴포넌트를 감싸줄건데 UI적인거 !
import React from 'react'
const ThemeLayout =({children})=>{
return(
<>
{children}
</>
)
}
export default ThemeLayout
11. 리덕스 세팅하기
최소 기능 아래 4가지
npm i redux react-redux next-redux-wrapper redux-saga
추가 기능 :
npm i redux-devtools-extension //크롬에서 사용할 리덕스 devtool 을 사용 위함
npm i axios // saga때 요청 보내기위함
12. 리덕스 맨 처음 할 때 생각할것 : Store !
Providers > createCtx.js 파일 생성
ctx = context
쓸 것 가져오기
import { createWrapper } from "next-redux-wrapper";
import {applyMiddleware, compose, createStore} from 'redux'
// applyMiddleware 미들웨어 정착할 때 쓰는 함수
// compose applyMiddleware와 세트
// createStore 스토어 생성 함수
import { composeWithDevTools } from "redux-devtools-extension";
import {createSaga} from 'redux-saga'
// saga에 미들웨어 사용해줘야해서 saga도 가져오기
리덕스 스토어를 생성
import { createWrapper } from "next-redux-wrapper";
import {applyMiddleware, compose, createStore} from 'redux'
// applyMiddleware 미들웨어 정착할 때 쓰는 함수
// compose applyMiddleware와 세트
// createStore 스토어 생성 함수
import { composeWithDevTools } from "redux-devtools-extension";
import {createSaga} from 'redux-saga'
// saga에 미들웨어 사용해줘야해서 saga도 가져오기
import reduce from '../reducers/' // index가져올것
import rootSaga from '../saga'
const configureStore=()=>{
// 미들웨어를 생성해주는
const sagaMiddlewares = createSaga();
// 특정 배열에 사가를 넣기 지금은 saga밖에 없어서 하나만
const Middlewares = [sagaMiddlewares]
// 우리는dev중이라 : 이후의 것이 실행됨
const enhancer = process.env.NODE_ENV === 'production'
? compose(applyMiddleware(...Middlewares)) // 함수 안에 미들웨어 내용의 변수 명 - 배열에 넣었기 때문에 비구조할당문으로 뺴가지고 각각 배열에 있는 내용을 인자값으로 넣기 위해
: composeWithDevTools(applyMiddleware(...Middlewares))
// createStore 두 가지 인자값 : reducer, 미들웨어 장착한 변수 (?) -enhancer
// 일단 reducer 있따고 생각하고 넣어보기 -> 이후에 만들것!
const Store = createStore(reducer, enhancer );
// rootSaga = 미들웨어를 만들었던 index
// serverside rendering 할 때 쓸 것
Store.sagaTask = sagaMiddlewares.run(rootSaga)
return Store
}
이제 묶어주는 것 만들기
import { createWrapper } from "next-redux-wrapper";
import {applyMiddleware, compose, createStore} from 'redux'
// applyMiddleware 미들웨어 정착할 때 쓰는 함수
// compose applyMiddleware와 세트
// createStore 스토어 생성 함수
import { composeWithDevTools } from "redux-devtools-extension";
import {createSaga} from 'redux-saga'
// saga에 미들웨어 사용해줘야해서 saga도 가져오기
import reduce from '../reducers/' // index가져올것
import rootSaga from '../saga'
const configureStore=()=>{
// 미들웨어를 생성해주는
const sagaMiddlewares = createSaga();
// 특정 배열에 사가를 넣기 지금은 saga밖에 없어서 하나만
const Middlewares = [sagaMiddlewares]
// 우리는dev중이라 : 이후의 것이 실행됨
const enhancer = process.env.NODE_ENV === 'production'
? compose(applyMiddleware(...Middlewares)) // 함수 안에 미들웨어 내용의 변수 명 - 배열에 넣었기 때문에 비구조할당문으로 뺴가지고 각각 배열에 있는 내용을 인자값으로 넣기 위해
: composeWithDevTools(applyMiddleware(...Middlewares))
// createStore 두 가지 인자값 : reducer, 미들웨어 장착한 변수 (?) -enhancer
// 일단 reducer 있따고 생각하고 넣어보기 -> 이후에 만들것!
const Store = createStore(reducer, enhancer );
// rootSaga = 미들웨어를 만들었던 index
// serverside rendering 할 때 쓸 것
Store.sagaTask = sagaMiddlewares.run(rootSaga)
return Store
}
// 첫번째 인자값 : store, 두번째 : 설정값
const wrapper = createWrapper(configureStore, {
// 개발 모드일 떄만 debug = true
debug:process.env.NODE_ENV === "development"
});
export default Wrapper
13. createCtx.js 만들었으니 -> _app.jsx에서 사용
import React from 'react'
import wrapper from '../Providers/createCtx'
const MyApp = ({Component, pageProps}) => {
return (
<Component {...pageProps}/>
)
}
// withRedux 함수로 MyApp을 감싸주면 끝남
export default wrapper.withRedux(MyApp)
14. reducers > index.js / saga > index.js, postSaga.js 파일 생성 (createCtx.js에서 import했던)
saga > index.js (역할 : 다른 미들웨어들을 묶어주는...)
import {all, fork} from 'redux-saga/effects'
import postSaga from './postSaga'
export default function* rootSaga(){
yield all([
// 가져올 또 saga들을 넣기
fork(postSaga)
])
}
saga > postSaga.js
import {all, fork, takeLatest, call} from 'redux-saga/effects'
import axios from 'axios'
let BaseURL = process.env.NODE_ENV.backurl || 'http://localhost:3001'
async function getPostAPI(data){
// 비동기 통신
const response = axios.get(BaseURL)
}
function* getPosts(){
// api통신 이루어짐 web server랑 통신 fetch , axios 우리는 axios사용
// try catch로 예외처리
// call - fork와 같은 원리 - 인자값안에있는 함수 실행 다만 *** 비동기로 작업할 때!!
// 비동기면 async await 를 generate 함수에서 쓰는걸 XX 대신 yield
try{
yield call(getPostAPI)
}catch(e){
}
}
// takeLatest = 특정 액션값이 떨어지면 맞으면 실행
// takeLatest - 가장많이 쓰는 디폴트 함수
function* watchPosts(){
// yield 뒤에 sagaeffect
// takeLatest 첫번째인자값 : 어떤 action (string)
// 두번째인자값 : 실행할 함수
yield takeLatest('GET_POST_REQUEST', getPosts)
}
export default function* postSaga(){
yield all([
/// fork 안에 있는 인자값 함수를 실행주는 역할 - 함수는 우리가 만듬
fork(watchPosts)
])
}
다른사람이 만들어 놓은 가라 json data
url 뒤에 /6 을 쓰면 id=6인 자료만 나옴
https://jsonplaceholder.typicode.com/posts/
위의 url로 대체 ↓↓
import {all, fork, takeLatest, call} from 'redux-saga/effects'
import axios from 'axios'
let BaseURL = process.env.NODE_ENV.backurl || 'https://jsonplaceholder.typicode.com'
async function getPostAPI(data){
// 비동기 통신
const response = await axios.get(`${BaseURL}/posts/${data}`)
return response
}
import { all, fork, takeLatest, call, put } from 'redux-saga/effects'
import axios from 'axios'
let BaseURL = process.env.NODE_ENV.backurl || 'https://jsonplaceholder.typicode.com'
async function getPostAPI(data) {
// 비동기 통신
let response = await axios.get(`${BaseURL}/posts/${data}`)
return response
}
function* getPosts() {
// api통신 이루어짐 web server랑 통신 fetch , axios 우리는 axios사용
// try catch로 예외처리
// call - fork와 같은 원리 - 인자값안에있는 함수 실행 다만 *** 비동기로 작업할 때!!
// 비동기면 async await 를 generate 함수에서 쓰는걸 XX 대신 yield
try {
let { data } = yield call(getPostAPI)
// put - sideEffect 이므로 yield 붙여주기
yield put({
type: 'GET_POSTS_SUCCESS',
data
})
} catch (e) {
yield put({
type: 'GET_POST_FAIL',
data:'ERROR',
})
}
}
// takeLatest = 특정 액션값이 떨어지면 맞으면 실행
// takeLatest - 가장많이 쓰는 디폴트 함수
function* watchPosts() {
// yield 뒤에 sagaeffect
// takeLatest 첫번째인자값 : 어떤 action (string)
// 두번째인자값 : 실행할 함수
yield takeLatest('GET_POST_REQUEST', getPosts)
}
export default function* postSaga() {
yield all([
/// fork 안에 있는 인자값 함수를 실행주는 역할 - 함수는 우리가 만듬
fork(watchPosts)
])
}
reducers > index.js
import React from 'react'
import {HYDRATE} from 'next-redux-wrapper'
import { combineReducers, combineReducers } from 'redux'
const rootReducer =(state,action)=>{
switch(action.type){
case HYDRATE:
return action.payload
default:{
const combineReducer = combineReducers({
post // 곧 만들 것
})
return combineReducer(state,action)
}
}
}
export default rootReducer
reducers > post.js 생성
export const initialState ={
posts:[],
postDetaill:null,
// 로딩 상태 변수를 만듬
loadding:false,
}
// 액션에 따라 코드 변경
const reducer=(state = initialState, action)=>{
switch(action.type){
case "GET_POSTS_REQUEST":
return {
...state
}
case "GET_POSTS_SUCCESS":
return {
...state
}
case "GET_POSTS_FAIL":
return {
...state
}
default:
return state
}
}
export default reducer
-> 변수를 따로 뺴기
export const initialState ={
posts:[],
postDetaill:null,
// 로딩 상태 변수를 만듬
loadding:false,
}
/* REDUX ACTIONS */
export const GET_POSTS_REQUEST = "GET_POSTS_REQUEST"
export const GET_POSTS_SUCCESS = "GET_POSTS_SUCCESS"
export const GET_POSTS_FAIL = "GET_POSTS_FAIL"
export const GET_POST =()=>{
return{
type:GET_POSTS_REQUEST
}
}
// 액션에 따라 코드 변경
const reducer=(state = initialState, action)=>{
switch(action.type){
case GET_POSTS_REQUEST:
return {
...state
}
case GET_POSTS_SUCCESS :
return {
...state
}
case GET_POSTS_FAIL:
return {
...state
}
default:
return state
}
}
export default reducer
export const initialState ={
posts:[],
postDetaill:null,
// 로딩 상태 변수를 만듬
loadding:false,
}
/* REDUX ACTIONS */
export const GET_POSTS_REQUEST = "GET_POSTS_REQUEST"
export const GET_POSTS_SUCCESS = "GET_POSTS_SUCCESS"
export const GET_POSTS_FAIL = "GET_POSTS_FAIL"
export const GET_POST =()=>{
return{
type:GET_POSTS_REQUEST
}
}
// 액션에 따라 코드 변경
const reducer=(state = initialState, action)=>{
switch(action.type){
case GET_POSTS_REQUEST:
return {
...state,
posts:[...state.posts, ...action.data],
loadding:true
}
case GET_POSTS_SUCCESS :
return {
...state,
loadding:false,
}
case GET_POSTS_FAIL:
return {
...state,
loadding:false,
}
default:
return state
}
}
export default reducer
* 오류
npm i styled-componen
==============여기까기 리덕스 세팅 끝 아직 사용해서 데이터 뿌리고 등등은 안함 !
위의 코드 교수님 github :
https://github.com/ingoo-code/Next_Redux_Setting
git clone 해옴
git clone
cd [해당파일]
npm install
npm run dev
pages > index.jsx 수정
import React from 'react'
import RootProvider from '../Providers/rootProvider'
const index = () => {
return (
<RootProvider>
Hello Next !
</RootProvider>
)
}
export default index
Providers > rootProvider.js ThemeLayout import 해오지않은 부분 수정
import React from 'react'
import ThemeLayout from '../Layouts/ThemeLayout'
const RootProvider = ({children}) => {
return (
<ThemeLayout>
{children}
</ThemeLayout>
)
}
export default RootProvider
Layouts > ThemeLayouts - navigation쓸꺼야
Layouts > Navigation.jsx 생성 & 아래 코드 작성
import Link from 'next/link'
import Styled from 'styled-components'
const Navigation = ()=>{
return (
<ul>
<li>HOME</li>
<li>POST</li>
</ul>
)
}
export default Navigation
ul components css 포함해서 바꾸기
import Link from 'next/link'
import Styled from 'styled-components'
const Gnb = Styled.ul`
display:flex;
flex-direction:row;
& > li {
margin-left:20px;
}
`
const Navigation = ()=>{
return (
<Gnb>
<li>HOME</li>
<li>POST</li>
</Gnb>
)
}
export default Navigation
LINK 걸어주기
link 안에 a tag 있어야함
const Navigation = ()=>{
return (
<Gnb>
<li><Link href="/"><a>HOME</a></Link></li>
<li><Link href="/posts"><a>POST</a></Link></li>
</Gnb>
)
}
Layouts > ThemeLayout.jsx 에 가져와서 쓰기
import React from 'react'
import Navigation from './Navigation'
const ThemeLayout = ({ children }) => {
return (
<>
<Navigation />
{children}
</>
)
}
export default ThemeLayout
아직 posts를 안만들었음 ! 어디에 만들기 ? -> pages에 똑같은 이름으로 만들기
pages > posts.jsx
const posts =()=>{
return(
<>
<h1>Posts (10)</h1>
<ul>
<li>
<h2>게시물 제목</h2>
<span>게시물 내용</span>
</li>
</ul>
</>
)
}
export default posts
이제 브라우저에서 POST 를 누르면 아래처럼 나옴 !
posts.jsx
import wrapper from '../Providers/createCtx'
const posts =()=>{
return(
<>
<h1>Posts (10)</h1>
<ul>
<li>
<h2>게시물 제목</h2>
<span>게시물 내용</span>
</li>
</ul>
</>
)
}
// serverside rendering
// 실행순서가 꼽사리가 꼈다고 생각 !
// 익명함수 안에 익명함수 넣어주기
// 첫번째 인자값 = Store라는 변수 (아무거나) 넣기 : redux로 감싸고 있던 상태들을 담아 놓은 변수명
export const getServerSideProps = wrapper.getServerSideProps((Store)=>(req,res)=>{
// 첫번째는 dispatch를 써서 API 요청 보내기
// 그리고 상태 변경 시키기
console.log(Store)
})
export default posts
console.log vscode에 찍힐려면 POST 눌러야함 -> Store안에 dispatch가 있는걸 알게됨
import wrapper from '../Providers/createCtx'
import {GET_POST} from '../reducers/post'
const posts =()=>{
return(
<>
<h1>Posts (10)</h1>
<ul>
<li>
<h2>게시물 제목</h2>
<span>게시물 내용</span>
</li>
</ul>
</>
)
}
// serverside rendering
// 실행순서가 꼽사리가 꼈다고 생각 !
// 익명함수 안에 익명함수 넣어주기
// 첫번째 인자값 = Store라는 변수 (아무거나) 넣기 : redux로 감싸고 있던 상태들을 담아 놓은 변수명
export const getServerSideProps = wrapper.getServerSideProps((Store)=>(req,res)=>{
// 첫번째는 dispatch를 써서 API 요청 보내기
// 그리고 상태 변경 시키기
console.log(Store)
Store.dispatch(GET_POST())
})
export default posts
오류 :
reducers > post.js
SUCDESS 쪽으로 posts:[] 옮기기 !
const reducer = (state = initalState,action) => {
switch(action.type){
case GET_POSTS_REQUEST:
return {
...state,
loadding:true
}
case GET_POSTS_SUCCESS:
return {
...state,
posts:[...state.posts,...action.data],
loadding:false,
}
case GET_POSTS_FAIL:
saga > postSaga.js 수정
import {all,fork,takeLatest,call,put} from 'redux-saga/effects'
import axios from 'axios'
import {
GET_POSTS_REQUEST, GET_POSTS_SUCCESS, GET_POSTS_FAIL
} from '../reducers/post'
let BaseURL = process.env.NODE_ENV.backurl || 'https://jsonplaceholder.typicode.com'
async function getPostAPI(data = "") {
const response = await axios.get(`${BaseURL}/posts/${data}`)
return response
}
function* getPosts(){
// API 통신 web server랑 통신을 하게될겁니다. fetch axios
try{
const {data} = yield call(getPostAPI)
console.log(data)
yield put({
type:GET_POSTS_SUCCESS,
data
})
} catch (e) {
yield put({
type:GET_POSTS_FAIL,
data:'ERROR'
})
}
}
function* watchPosts(){
yield takeLatest(GET_POSTS_REQUEST,getPosts)
}
export default function* postSaga(){
yield all([
fork(watchPosts),
])
}
Providers > createCtx.js 의 오타
sageTask -> sagaTask 수정
const configureStore = () => {
const sagaMiddlewares = createSaga()
const Middlewares = [sagaMiddlewares]
const enhancer = process.env.NODE_ENV === 'production'
? compose(applyMiddleware(...Middlewares))
: composeWithDevTools(applyMiddleware(...Middlewares))
const Store = createStore(reducer,enhancer)
Store.sagaTask = sagaMiddlewares.run(rootSaga) // server side rendering
return Store
}
console.log 에 100개 불러와지고 + 아래처럼 떠야함
postsaga
yield put({}) 이 잘 되나 확인
function* getPosts(){
// API 통신 web server랑 통신을 하게될겁니다. fetch axios
try{
const {data} = yield call(getPostAPI)
console.log(data)
// put은 dispatch와 같음
yield put({ // 요 부분
type:GET_POSTS_SUCCESS,
data
})
} catch (e) {
yield put({
type:GET_POSTS_FAIL,
data:'ERROR'
})
}
reducers > post.js : console.log로 확인
const reducer = (state = initalState,action) => {
switch(action.type){
case GET_POSTS_REQUEST:
return {
...state,
loadding:true
}
case GET_POSTS_SUCCESS:
console.log('도착햇니?', action.data)
return {
...state,
posts:[...state.posts,...action.data],
loadding:false,
}
case GET_POSTS_FAIL:
return {
...state,
loadding:false,
}
pages > posts.jsx
import {useSelector}
pages > posts.jsx 에서 이제 data 뿌리기
import wrapper from '../Providers/createCtx'
import {GET_POST} from '../reducers/post'
import {END} from 'redux-saga'
import { useSelector } from 'react-redux'
const posts =()=>{
// 아래 둘 중 하나 쓰기
// const posts = useSelector(state=>state.post.posts)
const {posts} = useSelector(state=>state.post)
console.log('posts다~', posts) //가져온 그 json 가라 data
const postLink = posts.map((v,k)=>{
return(
<li key={k}>
<h2>{v.title}</h2>
<span>{v.body}</span>
</li>
)
})
const len = posts.length;
return(
<>
<h1>Posts ({len})</h1>
<ul>
{postLink}
</ul>
</>
)
}
// url들어가면 psots 함수보다 getServerSideProps 가 먼저 실행됨->
// dispatch 실행 -> GET_POSTS_REQUEST 로 type 날아감
// 중간에 가로챈 saga는 같은 action값인지 쳌 watchPosts()에서
// 동일하면 -> 실행 (API 통신하는 아이) axios 요청 -> 응답 담아서
// 지금 postSaga.js console.log(data) 쭉 나옴
//
// serverside rendering
// 실행순서가 꼽사리가 꼈다고 생각 !
// 익명함수 안에 익명함수 넣어주기
// 첫번째 인자값 = Store라는 변수 (아무거나) 넣기 : redux로 감싸고 있던 상태들을 담아 놓은 변수명
export const getServerSideProps = wrapper.getServerSideProps((Store)=> async(req,res)=>{
// 첫번째는 dispatch를 써서 API 요청 보내기
// 그리고 상태 변경 시키기
console.log(Store)
Store.dispatch(GET_POST())
// next에서 알려준 serversiderending 을 완성시키는 코드 END는 redux-saga에 있음
Store.dispatch(END)
await Store.sagaTask.toPromise()
})
export default posts
요건 SSR = render 전 데이터 가져와서 한 번만 그림
!=
useEffect는 render 후 한 번 더 그린거!
클릭했을 때 동적라우팅을 통해 가져오기
백서버가 잘라서 보내는 기능이 없
스크롤 위치에 따라 이벤트 만들기 100개 다 보면 -> 또 100개 보이기 -> 반복
스크롤 이벤트 infinite scroll
pages > posts.jsx
window 객체 addEventListener , useEffect 사용
import wrapper from '../Providers/createCtx'
import {GET_POST} from '../reducers/post'
import {END} from 'redux-saga'
import { useSelector } from 'react-redux'
import { useEffect } from 'react'
const posts =()=>{
// 아래 둘 중 하나 쓰기
// const posts = useSelector(state=>state.post.posts)
const {posts} = useSelector(state=>state.post)
console.log('posts다~', posts) //가져온 그 json 가라 data
const postLink = posts.map((v,k)=>{
return(
<li key={k}>
<h2>{v.title}</h2>
<span>{v.body}</span>
</li>
)
})
const len = posts.length;
// 요기서 scroll event 만들기
// = componentDidMount
// page load될때 등록만됨 -> 끊어줘야해 ! remove
useEffect(()=>{
function scrollFn(){
console.log('hellele')
}
// 쓸 때만 사용하고 안 쓸때는 버리기
window.addEventListener('scroll',scrollFn)
return()=>{
window.removeEventListener('scroll',scrollFn)
}
},[])
scroll 내릴 때 cosole.log 찍히는지 확인
3가지 요소 확인
// 요기서 scroll event 만들기
// = componentDidMount
// page load될때 등록만됨 -> 끊어줘야해 ! remove
useEffect(()=>{
function scrollFn(){
// scroll과 관련한 세가지의 값을 출력
//
console.log('scrollY=', window.scrollY)
console.log('clientHeight=',document.documentElement.clientHeight)
console.log('scroll=', document.documentElement.scrollHeight)
}
// 쓸 때만 사용하고 안 쓸때는 버리기
window.addEventListener('scroll',scrollFn)
return()=>{
window.removeEventListener('scroll',scrollFn)
}
},[])
알 수 있는 것 :
scrollY만 움직임 => 현재 scroll의 위치를 말함
clientHeight => 브라우저의 높이
scroll (ScrollHeight) => scroll의 총 높이
scroll = scrollY + clientHeight 같다는 건 scroll이 끝까지 내려졌다는 의미
// 요기서 scroll event 만들기
// = componentDidMount
// page load될때 등록만됨 -> 끊어줘야해 ! remove
useEffect(()=>{
function scrollFn(){
// scroll과 관련한 세가지의 값을 출력
//
// console.log('scrollY=', window.scrollY)
// console.log('clientHeight=',document.documentElement.clientHeight)
// console.log('scroll=', document.documentElement.scrollHeight)
if((window.scrollY + document.documentElement.clientHeight ) +(document.documentElement.scrollHeight)){
console.log('scroll 끝입니까 휴먼 ')
}
}
// 쓸 때만 사용하고 안 쓸때는 버리기
window.addEventListener('scroll',scrollFn)
return()=>{
window.removeEventListener('scroll',scrollFn)
}
},[])
끝에 scroll 이 도착해야 console.log 찍힘
useDispatch 추가
import wrapper from '../Providers/createCtx'
import {GET_POST} from '../reducers/post'
import {END} from 'redux-saga'
import { useSelector, useDispatch } from 'react-redux'
import { useEffect } from 'react'
const posts =()=>{
const dispatch = useDispatch()
useEffect 안에 스크롤 끝까지 도착했을 때 dispatch 사용
useEffect(()=>{
function scrollFn(){
// scroll과 관련한 세가지의 값을 출력
//
// console.log('scrollY=', window.scrollY)
// console.log('clientHeight=',document.documentElement.clientHeight)
// console.log('scroll=', document.documentElement.scrollHeight)
if((window.scrollY + document.documentElement.clientHeight ) ===(document.documentElement.scrollHeight)){
console.log('scroll 끝입니까 휴먼 ')
dispatch(GET_POST())
}
}
// 쓸 때만 사용하고 안 쓸때는 버리기
window.addEventListener('scroll',scrollFn)
return()=>{
window.removeEventListener('scroll',scrollFn)
}
},[])
= > 스크롤이 바닥을 찍으면 100개씩 계속 render가된다 !!!
동적 라우팅
SSR
데이터가 떨어지기 전 undefined가 나옴
동적 라우팅은 웬만해서는 SSR를 해야함
pages > posts 폴더 생성 > [id].jsx 파일 생성
const Post = ()=>{
return(
<>
hello
</>
)
}
export default Post
posts/1 ~100 까지 어떤 숫자를 넣어도 아래처럼 나옴
posts = 폴더명 자리
file 자리는 배열로 함
이제 id 값 가져오기
import {useRouter} from 'next/router'
const Post = ()=>{
const router = useRouter()
const {id} = router.query
return(
<>
hello {id}
</>
)
}
export default Post
동적라우팅 : url값에 따라 특정 내용이 바뀌는... => view , list etc..
위 추가한 코드 지우고 아래처럼 사용 SSR
import wrapper from '../../Providers/createCtx'
const Post = () => {
return (
<>
hello
</>
)
}
export const getServerSideProps = wrapper.getServerSideProps((Store)=> async(req,res)=>{
})
export default Post
import wrapper from '../../Providers/createCtx'
const Post = () => {
return (
<>
hello
</>
)
}
export const getServerSideProps = wrapper.getServerSideProps((Store)=> async(req,res)=>{
const {id} = req.params // 현재 내가 보고싶은 id값을 가져올 수 있음
console.log(id)
})
export default Post
SSR 코드는 프론트까지 안가 터미널에서 뜸 (console.log가)
reducers > post.js
export const initalState = {
posts: [],
postDetaill: null,
loadding: false,
}
/* REDUX ACTIONS */
export const GET_POSTS_REQUEST = "GET_POSTS_REQUEST"
export const GET_POSTS_SUCCESS = "GET_POSTS_SUCCESS"
export const GET_POSTS_FAIL = "GET_POSTS_FAIL"
export const GET_POST_DETAIL_REQUEST = "GET_POSTS_DETAIL_REQUEST"
export const GET_POST_DETAIL_SUCCESS = "GET_POSTS_DETAIL_SUCCESS"
export const GET_POST_DETAIL_FAIL = "GET_POSTS_DETAIL_FAIL"
export const GET_POST = () => {
return {
type: GET_POSTS_REQUEST
}
}
export const GET_POST_DETAIL = (data) => {
return {
type: GET_POST_DETAIL_REQUEST,
data, // id값 넣는 곳
}
}
const reducer = (state = initalState, action) => {
switch (action.type) {
case GET_POSTS_REQUEST:
return {
...state,
loadding: true
}
case GET_POSTS_SUCCESS:
console.log('도착햇니?')
return {
...state,
posts: [...state.posts, ...action.data],
loadding: false,
}
case GET_POSTS_FAIL:
return {
...state,
loadding: false,
}
case GET_POST_DETAIL_REQUEST:
return {
...state,
loadding: true,
}
case GET_POST_DETAIL_SUCCESS:
return {
...state,
loadding: false,
postDetaill:action.data
}
case GET_POST_DETAIL_FAIL:
return {
...state,
loadding: false,
}
default:
return state
}
}
export default reducer
이제 saga쪽 내용 추가
import {all,fork,takeLatest,call,put} from 'redux-saga/effects'
import axios from 'axios'
import {
GET_POSTS_REQUEST,
GET_POSTS_SUCCESS,
GET_POSTS_FAIL,
GET_POST_DETAIL_REQUEST,
GET_POST_DETAIL_SUCCESS,
GET_POST_DETAIL_FAIL,
} from '../reducers/post'
let BaseURL = process.env.NODE_ENV.backurl || 'https://jsonplaceholder.typicode.com'
async function getPostAPI(data = "") {
const response = await axios.get(`${BaseURL}/posts/${data}`)
return response
}
function* getPosts(){
// API 통신 web server랑 통신을 하게될겁니다. fetch axios
try{
const {data} = yield call(getPostAPI)
console.log(data)
// put은 dispatch와 같음
yield put({
type:GET_POSTS_SUCCESS,
data
})
} catch (e) {
yield put({
type:GET_POSTS_FAIL,
data:'ERROR'
})
}
}
function* watchPosts(){
yield takeLatest(GET_POSTS_REQUEST,getPosts)
}
function* getPostDetail(action){
try{
const {data} = yield call(getPostAPI, action.data)
console.log(data)
yield put({
type:GET_POST_DETAIL_SUCCESS,
data
})
} catch (e) {
yield put({
type:GET_POST_DETAIL_FAIL,
data:'ERROR'
})
}
}
function* watchPostDetail(){
yield takeLatest(GET_POST_DETAIL_REQUEST, getPostDetail)
}
export default function* postSaga(){
yield all([
fork(watchPosts),
fork(watchPostDetail)
])
}
pages > posts > [id].jsx
import wrapper from '../../Providers/createCtx'
import {GET_POST_DETAIL} from '../../reducers/post'
import {END} from 'redux-saga'
const Post = () => {
return (
<>
hello
</>
)
}
export const getServerSideProps = wrapper.getServerSideProps((Store)=> async(req,res)=>{
const {id} = req.params // 현재 내가 보고싶은 id값을 가져올 수 있음
console.log(id)
Store.dispatch(GET_POST_DETAIL(id))
Store.dispatch(END)
await Store.sagaTask.toPromise()
})
export default Post
상태에 있는 내용을 가져와서 뿌리기 + 목록가기 Link import
[id].jsx
import wrapper from '../../Providers/createCtx'
import { GET_POST_DETAIL } from '../../reducers/post'
import { END } from 'redux-saga'
import { useSelector } from 'react-redux'
import Link from 'next/link'
const Post = () => {
const post = useSelector(state=>state.post.postDetaill)
return (
<>
<h3>{post.title}</h3>
<dl>
<dt>{post.userId}</dt>
<dd>{post.body}</dd>
</dl>
<Link href="/posts/"><a>목록가기</a></Link>
</>
)
}
export const getServerSideProps = wrapper.getServerSideProps((Store) => async (req, res) => {
const { id } = req.params // 현재 내가 보고싶은 id값을 가져올 수 있음
console.log(id)
Store.dispatch(GET_POST_DETAIL(id))
Store.dispatch(END)
await Store.sagaTask.toPromise()
})
export default Post
pagse > posts.jsx LINK 걸기
import wrapper from '../Providers/createCtx'
import {GET_POST} from '../reducers/post'
import {END} from 'redux-saga'
import { useSelector, useDispatch } from 'react-redux'
import { useEffect } from 'react'
import Link from 'next/link'
const posts =()=>{
const dispatch = useDispatch()
// 아래 둘 중 하나 쓰기
// const posts = useSelector(state=>state.post.posts)
const {posts} = useSelector(state=>state.post)
console.log('posts다~', posts) //가져온 그 json 가라 data
const postLink = posts.map((v,k)=>{
return(
<li key={k}>
{/* <h2><Link href="/posts/[id]" as={`/posts/${v.id}`}><a>{v.title}</a></Link></h2> */}
<h2><Link href={`/posts/${v.id}`}><a>{v.title}</a></Link></h2>
<span>{v.body}</span>
</li>
)
})
아래 둘 중에 하나 사용 가능
<h2><Link href="/posts/[id]" as={`/posts/${v.id}`}><a>{v.title}</a></Link></h2>
=
<h2><Link href={`/posts/${v.id}`}><a>{v.title}</a></Link></h2>
이제 list 클릭 -> 해당 내용 보여주기 -> 돌아오기 -> 목록으로 돌아옴
'블록체인 기반 핀테크 및 응용 SW개발자 양성과정 일기' 카테고리의 다른 글
[115일차 복습] 리눅스 기초 Ubuntu 입/출력 및 기타 기본 명령어 공부 (0) | 2021.08.30 |
---|---|
[115일차] 리눅스 ubuntu 입/출력 (0) | 2021.08.30 |
[112-3일차 복습] 운영체제, 커널, 리눅스, 쉘 (Shell)이란? (0) | 2021.08.26 |
[113일차] Linux 리눅스/ 커널/ Shell Script 쉘 스크립트 커스터마이징 (0) | 2021.08.26 |
[112일차] 리눅스 명령어 mv(파일명 변경), cat, |, htop, netstat, 방화벽 풀어 Server 열기 - 웹서버 (0) | 2021.08.25 |