본문 바로가기

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

[88일차]20210715 리액트 댓글 수정 삭제

반응형

 

어제 context 를 사용해서 만든  댓글 추가 기능 

어제까지 만든 리액트 

 

 

 

 

 

삭제하기

CommentList.jsx

    const Item = list.map((v, k) => {
        return (
            <>
                <CommentItem 
                    key={k} // props 가 아니야 
                    userid={v.userid}
                    content={v.content}
                    data={v.date}
                    index={k}  // kdy값 따로 보내기 
                />

CommentItem.jsx

import React from 'react'

const CommentItem = ({userid,content,date, index}) => {
    return (

이제 Context 사용하기

Form, List는 Context 사용한 상태 

이제 Item에도 사용하기 -> useContext가져오기 

import React, {useContext, useState} from 'react'
import Stroe from './store/context'

const CommentItem = ({userid,content,date, index}) => {
    const {state,dispatch} = useContext(Store)
    const [input,setInput] = useState('')


    return (
        <>
            <ul className="comment-row">
                <li className="comment-di">{userid}</li>
                <li className="comment-content">{content}</li>
                <li className="comment-date">{date}</li>
            </ul>
        </>
    )
}

export default CommentItem

삭제 버튼 만들고 click 이벤트 넣기 

import React, {useContext, useState} from 'react'
import Store from './store/context'

const CommentItem = ({userid,content,date, index}) => {
    const {state,dispatch} = useContext(Store)
    const [input,setInput] = useState('')

    const handleDelete =()=>{
        dispatch({type:'DELETE', payload:index,})
        setInput('')
    }

    return (
        <>
            <ul className="comment-row">
                <li className="comment-di">{userid}</li>
                <li className="comment-content">{content}</li>
                <span className="comment-delete-btn" onClick={handleDelete}> {/* 삭제 버튼 */}
                    X
                </span>
                <li className="comment-date">{date}</li>
            </ul>
        </>
    )
}

export default CommentItem

 reducer.jsx 에서 받은 DELETE 부분 수정

* 요기서 console.log 이것저것 쳐보기 ! 

// reducer가 바꿀 상태 state -> 이전 상태값 가져와야함 
// dispatch가 바꿀 정보를 줄꺼야 -> 두번째 인자값 action으로 받음
// 최종적으로 reducer는 수정된 state 값을 보내줌  
const reducer = (state, action) => {
    console.log('action',action)
    switch (action.type) {
        case 'CREATE':
            return {
                ...state, // 다른 값들 넣을 때 대비해서 일단 넣어두기 
                commentItem:[...state.commentItem, action.payload]
            }
        case 'UPDATE':
            return {
                ...state,
            }
        case 'DELETE':
            console.log(action, action.payload)
            console.log(state)
            console.log(state.commentItem)

            return {
                ...state,
                commentItem:[...state.commentItem.filter((v,k)=>action.payload !==k)]
            }
        default:
            return{
                ...state,
            }
    }
}

export default reducer

for문

        case 'DELETE':
            console.log(action, action.payload)
            console.log(state)
            console.log(state.commentItem)
            
            let new_arr = [];  //slice 도 써보기 // payload.index도 찍어보기 
            for(let i=0; i<state.commentItem.length; i++){
                console.log(i)
                if(i != action.payload)
                new_arr.push(state.commentItem[i])
            }
            
            //commentItem의 내용을 new_arr로 바꿔주기 
            
            return {
                ...state,
                commentItem:new_arr
            }
        default:
            return{
                ...state,
            }
    }

 

 

 

 

수정하기

commentItem.jsx 에서 {context}가 담긴 부분 클릭했을 때 input box 열리도록 만들 것! -> {content}를 클릭 이벤트 주기 편하게 span으로 감싸고  handleClick 이벤트 추가  + onChange 

CommentItem.jsx

import React, { useContext, useState } from 'react'
import Store from './store/context'

const CommentItem = ({ userid, content, date, index }) => {
    const { state, dispatch } = useContext(Store)
    const [input, setInput] = useState('')

    const handleDelete = () => {
        dispatch({ type: 'DELETE', payload: index, })
        setInput('')
    }

    const handleClick=()=>{
        setInput(content)
    }

    const handleChange=(e)=>{
        setInput(e.target.value)
    }

    return (
        <>
            <ul className="comment-row">
                <li className="comment-di">{userid}</li>
                <li className="comment-content">
                    <span onClick={handleClick}>
                        {/* {content} */}
                        {input ? <input type="text" value={input} onChange={handleChange} /> : content}
                    </span>
                </li>
                {/* 삭제 버튼 */}
                <span
                    className="comment-delete-btn"
                    onClick={handleDelete}
                >
                    X
                </span>
                <li className="comment-date">{date}</li>
            </ul>
        </>
    )
}

export default CommentItem

 

 

 

input 입력 다하고 엔터 키를 누를 때 dispatch 실행 

    const handleKeydown=(e)=>{
        console.log('e=',e)
        console.log('e.key=',e.key)
        if (e.key=='Enter'){
            dispatch({type:'UPDATE', payload:{index,content:input,}})
            setInput('')
        }

    }

    return (
        <>
            <ul className="comment-row">
                <li className="comment-di">{userid}</li>
                <li className="comment-content">
                    <span onClick={handleClick}>
                        {/* {content} */}
                        {input ? <input type="text" value={input} onChange={handleChange} onKeyDown={handleKeydown}/> : content}
                    </span>
                </li>

reducer.jsx 수정

여기서 console.log 찍어보니까 rerender 할 때 layout, form, 다 재 렌더된다. 

const reducer = (state, action) => {
    console.log('action',action)
    switch (action.type) {
        case 'CREATE':
            return {
                ...state, // 다른 값들 넣을 때 대비해서 일단 넣어두기 
                commentItem:[...state.commentItem, action.payload]
            }
        case 'UPDATE':
            console.log('UPDATE 도착햇ㄷㅏ.',action)
            let {content, index} = action.payload // == {...action.payload}
            console.log(content,index)
            
            let {commentItem} = state // {...state}
            commentItem[index].content = content
            return {
                ...state,
                conmmentItem:[...commentItem]  // == commentItem, 만 써도 됨 
            }

위의 코드를 똑같이 아래도 있,, 

근데 비구조 할당문으로 가져오는거 {...} [...] 요 부분이 헷갈린다. 

return 부분에 

state 만 쓰면 X

...state 만 써도 ok

...state 없이 commentItem, 만 써도 ok

...state, commentItem, 써도 ok

...state, commentItem:[...commentItem] 도 ok 

state, commentItem 을 쓰면 ㄴㄴ !

 

 

 

        case 'UPDATE':
            console.log('UPDATE 도착햇ㄷㅏ.',action)
            // let {content, index} = action.payload // == {...action.payload}
            // console.log(content,index)
            
            // let {commentItem} = state // {...state}
            // commentItem[index].content = content
            // return {
            //     ...state,
            //     conmmentItem:[...commentItem]
            // }

            let {content,index} = action.payload
            let commentItem = state.commentItem // 복사해서 가지고 오기 
            console.log(commentItem)
            console.log(commentItem[index])
            console.log(commentItem[index].content)
            commentItem[index].content = content 
            return{
                ...state,
                commentItem:[...commentItem]
            }

 

수정하기 , 삭제하기는 기본이니까 외워도도 좋다고 !!! 외우장.. 아니면 to do list를 만들어 봐도 좋을듯 

 

 

 

 



< 리액트 로그인하기 전 개념 설명 TIME=3 > 

웹팩의 역할 -> app.js를 만들어주기 

run dev -> app.js 파일에 모든 파일 담은 index.html (브라우저)를 출력 

ren dev -> 개발 서버 html을 브라우저에 띄울 수 있게끔 만듬 (하나의 서버이긴 함) 

 

fetch가 실행되면 브라우저가 node.js (백엔드)로 요청함 (개발 서버 run dev 는 관련이 없다! ) 

개발 서버는 index.html (with app.js, app.css) 만 브라우저에 던져줄뿐,,, 

 

순수히 브라우저에서 동작하는 js는 파일을 읽고 쓰고 보내고 기능이 전혀 없다.

유일하게(?) 가능했던 대표적인 사례는 공인인증서 ( ActiveX ) -> 보안을 위해 도입되었지만 결국 보안 문제가 야기됨

 

브라우저(8080)에서 백엔드- port:3000으로 요청이 될까 ? ---------> 안됨  (cors 문제) 

브라우저의 통신은 같은 도메인( port ) 이어야 가능하다.  - > 근데 친구가 되면 가능하다고(?) 

 

-> 우리의 폴더 작업은 webpack 설치된 리액트 폴더 + 원래 사용했던 express, node.js server + DB server 총 서버는 3개가 쓰인다. 

 

 

 

 


 

 

터미널 두개 만들 수 있다.  

이름 front, back으로 수정하고 

 

 

1. backend 폴더 만들기 (webpack front것 밖으로) 

terminal 위치 조정 -> backend 폴더로 들어가기 

npm init 

npm i express cors 

 

server.js 파일 만들고 기본 코드 작성

오랜만이닷..

const express = require('express')
const app = express()

app.get('/', (req,res)=>{
    res.send('hi')
})

app.listen(3000,()=>{
    console.log(`server port : 3000`)
})

 

리액트 하다가 요거 치니깐 친정 온 기분.. 

 

 

 

2. get('/api') 로 만들어서 json 정보 작성

const express = require('express')
const app = express()

app.get('/api', (req,res)=>{
    res.json({
        userid:'아이디',
        content:'댓글',
        date:'2021-07-15',
    })
})

app.listen(3000,()=>{
    console.log(`server port : 3000`)
})

 

 

 

3. 최초로 컴포넌트가 실행되었을 때 (render) console.log 찍기 - > useEffect 

useEffect는 Hooks API 의 생명 주기 (mount...이거) 

terminal front - npm run dev 해놓기 ! 

 

리액트 -  CommentLayout.jsx 

import React, { useContext, useReducer, useEffect } from 'react'
import Store from './store/context'
import reducer from './store/reducer'

const CommentLayout = ({ children }) => {

    const globalStore = useContext(Store) // context 사용할 때는 use
    //여기서 인자값 Store 들어오는 거 잘 분석하기 ! 
    // store 에서 export 하는거 내용 잘 봐 
    console.log(globalStore)

    const [state, dispatch] = useReducer(reducer, globalStore)
    console.log('state value = ', state)

    useEffect(()=>{
        console.log('최초 실행 render')
    },)

처음 실행되었을 때 server에 내용 요청할 예정 ! 

 

서버에 요청하는 코드 작성 

    useEffect(async()=>{
        console.log('최초 실행 render')
        //fetch 결과값 : Promise -> 비동기 
        const response = await fetch('http://localhost:3000/api')
        const data = await response.json()
        console.log(response)
        console.log(data)
    },)

 

도메인 다르다는 오류가 나옴 ! 친구인 걸 증명해야함 

 

 

 

 

4. 백엔드로 server.js 로 가기 

const express = require('express')
const app = express()
const cors = require('cors')

app.use(cors()) // 미들웨어에 실행시켜주기 

app.get('/api', (req, res) => {
    // 데이터는 DB에 접속해서 select -> 객체로 만들어서 응답 주기 
    res.json([{
        userid: '아이디',
        content: '댓글',
        date: '2021-07-15',
    }])
})

app.listen(3000, () => {
    console.log(`server port : 3000`)
})

backend server off -> on 

이제 둘이 친구가 됐다. 

 

 

5. context.jsx  의 객체를 지우기 

 

import React from 'react'

const initialState ={
    commentItem:[  ]
}

 

 

 

 

6. 이제 fetch로 받은 data로 내용 채우기 

    useEffect(async()=>{
        console.log('최초 실행 render')
        //fetch 결과값 : Promise -> 비동기 
        const response = await fetch('http://localhost:3000/api')
        const data = await response.json()
        console.log(response)
        console.log(data)
        dispatch({type:'INIT', payload:data})
    },)

reducer로 가서 INIT 생성, 코드 추가

 

 

 

 

7. reducer.jsx

 

1) 방법 1 

const reducer = (state, action) => {
    console.log('action',action)
    switch (action.type) {
        case 'INIT':
            return {
                ...state, 
                commentItem:action.payload
            }

2) 방법 2

        case 'INIT':
            state.commentItem = action.payload
            return {
                ...state, 
                // commentItem:action.payload
            }

 

 

 

 

 

8. comment > api 폴더 > api.jsx 파일 만들기 

비동기 코드를 다 빼볼 것 ! - > 함수만 쓸 예정 

 

 

코드 작성 (아까 CommentLayout.jsx에 쓴 fetch 코드를 따로 빼기) 

api.jsx

//비동기 함수들 모음 

export const getComment = async (dispatch) =>{
    dispatch({type:'GET_COMMENT'})
    try{
        const response = await fetch('http://localhost:3000/api')
        const data = await response.json()
        dispatch({type:'GET_COMMENT_SUCCESS', payload:data})
    }catch(e){
        dispatch({type:'GET_COMMENT_ERROR', payload:e})
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

9. Store > context.jsx 에 상태값 두 가지 추가 

import React from 'react'

const initialState ={
    commentItem:[  ],
    loadding: false,
    error:null,
}
const Store = React.createContext(initialState) // context 생성 
// 인자값으로 default값 

export default Store

 

 

 

 

 

10. reducer 에서 받을거니까 수정 ! 

reducer.jsx

const reducer = (state, action) => {
    console.log('action', action)
    switch (action.type) {
        case 'GET_COMMENT': //최초 실행 했을 때 
            return {
                ...state,
                loadding:true
            }
        case 'GET_COMMENT_SUCCESS':    // 로딩 끝났다 ~ 
            return {
                ...state,
                loadding:false,
                commentItem:action.payload
            }
        case 'GET_COMMENT_ERROR':   //에러 났다 ~ 
            return {
                ...state,
                error:action.payload
         }

로딩 중일 때 loadding:true 이고 get_comment_success가 실행되면 (data 잘 가져와서 뿌리면 ) 로딩 false로 종료 ! 

 

 

11. CommentLayout.jsx 

함수 파일 불러오기, 아래 코드들을 함수로 대체하기, 함수 매개변수 dispatch 넣기 !

import React, { useContext, useReducer, useEffect } from 'react'
import Store from './store/context'
import reducer from './store/reducer'
import {getComment} from './api/api'

const CommentLayout = ({ children }) => {

    const globalStore = useContext(Store) // context 사용할 때는 use
    //여기서 인자값 Store 들어오는 거 잘 분석하기 ! 
    // store 에서 export 하는거 내용 잘 봐 
    console.log(globalStore)

    const [state, dispatch] = useReducer(reducer, globalStore)
    console.log('state value = ', state)

    useEffect(async()=>{
        console.log('최초 실행 render')
        //fetch 결과값 : Promise -> 비동기 
        // const response = await fetch('http://localhost:3000/api')
        // const data = await response.json()
        // console.log(response)
        // console.log(data)
        // dispatch({type:'INIT', payload:data})
        getComment(dispatch)
        
    },)

 

 

 

 

 

12. CommentList.jsx

import React, { useContext, useState } from 'react'
import CommentItem from './CommentItem'
import Store from './store/context'

const CommentList = () => {

    const { state } = useContext(Store) // {state, dispatch}
    const list = state.commentItem
    const {loadding, commentItem, error} = state

    const Item = list.map((v, k) => {
        return (
            <>
                <CommentItem 
                    key={k} // props 가 아니야 내용 채울 때 속상값. 안쓰면 key값 오류남 
                    userid={v.userid}
                    content={v.content}
                    data={v.date}
                    index={k}  // kdy값 따로 보내기 
                />

            </>
        )
    })
    if(loadding) return <li>나 로딩 중 </li>;
    if(error) return <li>에러 났다능..</li>;
    return (
        <li>
            {Item}
        </li>
    )

}

export default CommentList

 

 

 

Network -> 저 위 부분 클릭하면 조금 느려짐 !! -> 그럼 새로고침하면 ' 나 로딩중..' 이 뜸 

 

fetch 주소 잘못 쓰면 에러가 뜸 ! 

 

 

 

 

반응형