본문 바로가기

블록체인 기반 핀테크 및 응용 SW개발자 양성과정 일기

[114일차] Next Redux - 동적라우팅, infinite스크롤이벤트

반응형

 

 

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 클릭 -> 해당 내용 보여주기 -> 돌아오기 -> 목록으로 돌아옴 

 

 

반응형