기억의 실마리
2022. 11. 27. 10:21

1. Node.js

자바스크립트를 개조해서 만들어진 런타임이다.

자바스크립트는 특정프로그램, 그중에서도

웹브라우저 안에서만 동작한다.

그런데, Node.js를 사용하면 터미널프로그램에서

node를 입력하여 브라우저없이 바로 실행이 가능하게 된다.

웹브라우저에서만 동작하는 자바스크립트의 한계를 극복하게 되고

이로써 서버를 만들 수 있게 되고 자바스크립트 단 하나의 언어로도

웹페이지를 만들 수 있게 해주는 런타임이다.

 

2. Node.js의 Single Thread

싱글스레드로 구성된 이벤트루프를 활용하여 요청을
받아들이고 스레드풀을 활용하여 병렬처리를 지원한다.

  • Node.js는 Non-Blocking 방식을 사용한다. 클라이언트로부터
    요청을 받으면 다른 스레드에게 요청을 전달한다. 그렇게 되면
    싱글스레드는 자유로워지고 바로 다음 요청을 받을 수 있게 된다.
    그렇게 두번째 요청을 받으면 또 다시 다른 스레드에게 전달하고
    두번째 요청을 전달받은 스레드는 다른서버나 DB에 쿼리를 날려
    요청을 처리한다.
  • asynchronous(비동기/async)방식을 사용하기 때문에
    클라이언트 요청을 처리한 스레드가 응답을 가져오면
    (스레드가 전달받은 요청을 서버 또는 DB에 쿼리를 날려 응답을 받아오면)
    콜백함수를 실행하게 된다.

 

3. 비동기 작업이 가능하게 된 이유

Node.js의 구조를 보면

v8 자바스크립트 엔진과 비동기 작업을 처리하는

libuv라는 라이브러리로 이루어져 있다.

v8에는 memory heap과 하나의 call stack이 있다.

(call stack은 싱글스레드와 같은 의미로 이해하자.)

memory heap은 메모리 할당이 일어나는 곳이며,

call stack은 코드 실행에 따라 호출스택이 차례대로 실행된다.

그렇기 때문에 v8에서는 비동기식 처리를 할 수가 없다.

 

즉, 비동기 작업을 가능하게 만들어 주는 것은 libuv라는 라이브러리가

Non-Blocking IO라는 기능을 가능하게 만들어주는 이벤트루프를

제공하기 때문이다. libuv는 c언어로 만들어졌고 시스템 커넬을 사용한다.

커넬은 멀티스레드를 사용하기 때문에 Node.js가 비동기처리를 할 수 있게 만든다.

 

4. eventLoop

  1. Node.js API로 요청이 들어오면, 들어온 요청은
    event queue(이하 큐)에 추가 된다.

  2. Node.js의 이벤트 루프는 큐를 리슨하여 들어온 요청이 있다면
    선착순으로 요청이 처리된다.

  3. 요청은 스레드풀로 보내진다. 스레드풀은 이벤트루프의 일부로서
    여러 요청을 수행할 수 있다. 이 후에는
    (이벤트루프는 큐에 요청이 있는지 리슨)  =>  (요청이있다면? 스레드풀로)
    이렇게 반복한다.

  4. 스레드풀은 DB또는 파일, 다른 서버 등에 보낸 요청을 수행한다.

  5. 요청 수행을 마쳤다면, 콜백함수를 실행시켜 이벤트 루프로 응답을 전달한다.
  6. 이벤트루프는 응답을 클라이언트로 보낸다.

 

5. 정리

Node.js는 자바스크립트 이벤트 기반 모델에서 영감을 받은

이벤트 루프가 있는 싱글 스레드이다. 자바스크립트와 비슷한 싱글 스레드 이지만

네트워크 호출, 파일시스템 작업 등과 같이 비동기적으로 수행되는 작업은

자바스크립트 코드가 아니라 thread pool에서 실행된다.

Node.js는 thread pool을 가지고 있고 멀티 스레드의 개념도 가지고 있다.

하지만 정의를 내리자면 자바스크립트 환경에서는

싱글 스레드로 사용된다 정의할 수 있다.

2022. 11. 26. 23:21

1. Blocking 통신 처리방식

클라이언트가 서버와 연결 됐을때 서버에서 스레드를 생성하고 클라이언트에게

스레드를 할당해준다. 클라이언트에 할당된 스레드는 요청을 체크하는 루프에 들어가게되어

스레드가 죽지않고 블로킹되어있는 상태가 된다. 이 후 클라이언트로부터 요청이 들어오면

블로킹 되어있던 스레드가 요청을 처리하고 처리가 끝나면 다시

요청을 체크하는 루프로 들어가게된다. 이렇게 한 클라이언트에 하나의 스레드를

전담으로 할당시켜주는 방식이다. 그렇기 때문에 동기식처리(직렬 처리)만 가능하다.

 

 

2. Non-Blocking 통신 처리방식

클라이언트가 서버와 연결되면 블로킹 방식이 아닌

요청을 리슨하는 eventLoop서버가 요청을 Event Queue에 넣는다.

Event Queue에 들어온 요청은 스레드풀에 담겨져 있던 스레드들이 할당받아

요청을 처리하고 클라이언트에 응답하게 된다.

요청을 처리하는 스레드는 하나의 클라이언트만을 전담하지 않고

어떤 요청이든 간에 Event Queue에 들어온 요청을 하나씩 하나씩 가져가

처리하기 때문에 비동기식처리(병렬 처리)가 가능하다.

 

 

Why Non-Blocking?

Blocking통신 처리방식은 각각 하나의 스레드를 전담마크식으로

클라이언트에게 할당시켜 주기 때문에 1000만명의 유저가 있다고

가정할 경우 1000만개의 스레드가 계속해서 대기를 하거나 요청을

처리하게 된다.

 

하지만 Non-Blocking의 경우 스레드 1000만개를 미리 만드는 것이 아니라

필요에 의해서 만들어진 만큼의 스레드만 스레드풀에 존재하고

1000만명의 유저가 동시 다발적으로 요청을 보낼 확률은 아주 낮기때문에

Blocking통신 처리방식에 비해 스레드의 총량이 획기적으로 차이가 난다.

그로 인해서 서버의 부담이 줄어들기 때문에 근래에는 Non-Blocking을

더 많이 채택하게 된 것이다.

 

'Computer Science > Network' 카테고리의 다른 글

[ TCP / UDP ] 특징과 차이점  (0) 2022.11.26
2022. 11. 26. 19:47

1. TCP ( Transmission Control Protocol )

  • 연결 지향 방식이다.
  • 3-way handshaking과정을 통해 연결을 설정하고 4-way handshaking을 통해 해제한다.
  • 흐름 제어 및 혼잡 제어.
  • 높은 신뢰성을 보장한다.
  • UDP보다 속도가 느리다.
  • 전이중(Full-Duplex), 점대점(Point to Point) 방식.

TPC는 연결지향 방식이며 이는 패킷을 전송하기 위해서 논리적 경로를 배정한다는 뜻이다.

연결형 서비스로 신뢰성을 보장하기에 3-way handshaking과정을 사용한다.

 TCP는 연속성보다 신뢰성있는 전송이 중요할 때에 사용하는 프로토콜이다.

예를 들면 파일 전송과 같은 경우에 사용된다.

 

TCP 서버의 특징

  • 서버소켓은 연결만을 담당한다.
  • 연결과정에서 반환된 클라이언트 소켓은 데이터의 송수신에 사용된다
  • 서버와 클라이언트는 1대1로 연결된다.
  • 스트림 전송으로 전송 데이터의 크기가 무제한이다.
  • 패킷에 대한 응답을 해야하기 때문에(시간 지연, CPU 소모) 성능이 낮다.
  • Streaming 서비스에 불리하다.(손실된 경우 재전송 요청을 하므로)

패킷(Packet)

인터넷 내에서 데이터를 보내기 위한 경로배정(라우팅)을 효율적으로 하기 위해

데이터를 여러 개의 조각들로 나누어 전송하는데 이 조각을 패킷이라고 한다.

 

TCP 패킷의 추적 및 관리

A,B,C 의 패킷이 있다고 가정하면 (A = 1), (B = 2), (C = 3) 이렇게 변수처럼
특정번호를 부여하여 패킷의 분실확인, 관리, 목적지 재조립을 한다.

 

 

2. UDP(User Datagram Protocol)

  • 비연결형 서비스로 데이터그램 방식을 제공한다
  • 정보를 주고 받을 때 정보를 보내거나 받는다는 신호절차를 거치지 않는다.
  • UDP헤더의 CheckSum 필드를 통해 최소한의 오류만 검출한다.
  • 신뢰성이 낮다
  • TCP보다 속도가 빠르다

UDP는 비연결형 서비스이며 연결을 설정하고 해제하는 과정이 존재하지 않는다.

서로 다른 경로로 독립적으로 처리하며 TCP와 반대로

순서부여->재조립, 흐름/혼잡제어 등을 하지 않아 속도가 빠르며

네트워크 부하가 적다는 장점이 있다.

신뢰성보다는 연속성이 중요한 서비스에 사용된다.

예를 들면 실시간 서비스(streaming)에 자주 사용된다.

 

UDP 서버의 특징

  • UDP에는 연결 자체가 없어서(connect 함수 불필요) 서버 소켓과 클라이언트 소켓의 구분이 없다.
  • 소켓 대신 IP를 기반으로 데이터를 전송한다.
  • 서버와 클라이언트는 1대1, 1대N, N대M 등으로 연결될 수 있다.
  • 데이터그램(메세지) 단위로 전송되며 그 크기는 65535바이트로, 크기가 초과하면 잘라서 보낸다.
  • 흐름제어(flow control)가 없어서 패킷이 제대로 전송되었는지, 오류가 없는지 확인할 수 없다.
  • 파일 전송과 같은 신뢰성이 필요한 서비스보다 성능이 중요시 되는 경우에 사용된다.

 

UDP의 흐름제어와 혼잡제어

흐름제어는 데이터를 송신하는 곳과 수신하는 곳의 데이터 처리 속도를 조절하여

수신자의 버퍼 오버플로우를 방지하는 것이다.

송신하는 곳에서 감당이 안되게 데이터를 빠르게, 많이 보내면 수신자에서 문제가 발생하기 때문이다.

 

혼잡제어는 네트워크 내의 패킷 수가 넘치게 증가하지 않도록 방지하는 것이다.

만약 정보의 소통량이 과다하면 패킷을 조금만 전송하여

혼잡 붕괴 현상이 일어나는 것을 막는다.

 

 

3. TCP와 UDP의 비교

프로토콜 종류 TCP UDP
연결 방식 연결형 서비스 비연결형 서비스
패킷 교환 방식 가상 회선 방식 데이터그램 방식
전송 순서 전송 순서 보장 전송 순서가 바뀔 수 있다
수신여부 확인 수신여부 확인 O 수신여부 확인 X
통신방식 1:1 통신 1:1  or  1:N  or  N:N 통신
신뢰성 높다 낮다
속도 느리다 빠르다

'Computer Science > Network' 카테고리의 다른 글

[ Blocking / Non-Blocking ] 통신 처리방식  (0) 2022.11.26
2022. 11. 19. 11:23

# 리덕스를 왜 쓰는가?

1. 리덕는 상태관리라이브러리이다. 즉 state를 관리한다는 것인데
state = '상태' 이며, 이 '상태'라는 것은 useState를 생각하면 쉽게 이해가능하다.
데이터를 변경시켜 주고 이를 setState를 통해 rerender시켜주는 것

이라고 이해하면 된다. 그리고 이 state들을 store에 넣어서

전역으로 관리하기 위함이다.

 

store = [State의 집합체] 이다.

 

 

2. 객체의 변화(데이터의 변화)를 감지하여 간단하게 비교(shallow equality 검사)하여

객체의 깊은 변화가 아닌 겉핥기식의 변화를 비교하기 때문에 좋은 성능을

유지할 수 있는 것이 가장 큰 장점이다.

props를 연속적으로 전달받는 방식을 대신하기 위함도 크다.





# shallow equality 검사와 불변성

리덕스에서 가장 큰 이점으로 앞서 겉핥기식의 변화를 언급했었다.

이는 불변성에 의한 검사가 이뤄지면 복잡한 변경을 감지하는 것보다

더 간단하고 빠르게 구현할 수 있기 때문에 비용 역시 줄어들게된다.

 

불변성이란?

절대 변경되지않는 데이터를 말한다.

 

불변성이 성능을 높여주는 가장 큰 이유는

[ 불변성데이터의 추론 ]  <<  [ 임의로 변경가능한 데이터의 추론 ]

 

이러하기 때문에 프로그래밍 및 디버깅이 간단하게 구현가능하다.

 

# 사용예시

 

< HTML 영역 >

<button id="add">Add</button>
<span>0</span>
<button id="minus">Minus</button>

 

< JAVA SCRIPT 영역 >

import { legacy_createStore } from "redux";

const add = document.getElementById("add");
const minus = document.getElementById("minus");
const number = document.querySelector("span");

const countModifier = (count = 0, action)=> {
/* countModifier는 리듀서이며 순수함수이고, 스토어데이터를 modify시킨다.
디폴트 = 0 이며 action은 전달받을 인자 or 파라미터(매개변수)다. */

    console.log(count, action);
    if (action.type === "ADD") {
        return count + 1;
    } else if (action.type === "MINUS") {
        return count - 1;
    } else {
        return count;
    }
    
};

const countStore = legacy_createStore(countModifier);
//store를 사용할 변수에 legacy_createStore를 할당시키고 리듀서를 매개변수로 넣는다.

const onChange = ()=> {
    number.innerText = countStore.getState();
};
// 스토어에 getState를 사용해서 변경된 데이터를 re-render시킨다.

countStore.subscribe(onChange);
/* subscribe는 스토어 내부 변화를 감지한다(리스너).
변화가 감지되면 매개변수에있는 함수를 실행시킨다. */

const handleAdd = ()=>{
    countStore.dispatch({ type: "ADD" })
};
 //dispatch = 스토어의 데이터를 변형시킨다.

const handleMinus = ()=>{
    countStore.dispatch({ type: "MINUS" })
};

add.addEventListener("click", (handleAdd));
minus.addEventListener("click", (handleMinus));

 간단한 예시를 위해 버튼을 클릭하면 가운데 숫자에서 마이너스, 플러스 되도록 구현했다.

'Frontend > Redux' 카테고리의 다른 글

[ Redux-Toolkit ] 더 간단해진 상태관리 라이브러리  (0) 2022.12.26
2022. 11. 17. 00:04

# App.js

index.js를 제외하면 최상위 컴포넌트 파일이다.

App.js에서부터 속성들을 가져와야 한다.

필요한 속성은 userObj이다.

 

userObj는 파이어베이스에서 getFirestore.currentUser.[ 필요한키 ]

이렇게 가져올 수 있는 데이터이지만 userObj라는 변수를 만들어 속성에 넣고

여기저기서 전달받아 사용하는 이유는 소스를 통합하여 확장성 있게

사용하고 싶기때문이다. userObj 하나만 변경해도 통합되어 변경되기 때문

더 직관적으로 변경,저장하기 쉬워진다.

function App() {
  const [init, setInit] = useState(false);  //false = 홈화면대기중...
  const [userObj, setUserObj] = useState(null);  //유저데이터를 위한 스테이트
  useEffect(()=>{
    authService.onAuthStateChanged((user)=>{
    //firestore에서 auth 변화리스너, 고로 user = usedata이다.
    
      if (user) {
        setIsloggedIn(true);
        setUserObj({
          displayName: user.displayName,
          uid: user.uid,
          updateProfile: { displayName: user.displayName },
      /* user에는 아주 많은 유저데이터가 있는데 그 중 필요한 key만 가져와서
        값을 적용시켜주고 있다. 방대한 데이터는 re-render에 불리하기때문이다. */
          
        });
      } else {
        setIsloggedIn(false);  //flase = 로그인실패.
      }
      setInit(true); //true = 홈화면이 된다.
    });
      },[]);
  const refreshUser = () => {
    const user = authService.currentUser;
    //refreshUser에는 user가 존재하지 않아, 새로 선언해준다.
    
    setUserObj({
      displayName: user.displayName,
      uid: user.uid,
      updateProfile: { displayName: user.displayName },
    });
  }
  //refreshUser함수를 만든 이유는 프로필 업데이트 기능을 위해서다.
  
  return (
      <>
        { init ? (
            <AppRouter
                isLoggedIn={isLoggedIn}
                userObj={userObj}
                refreshUser={refreshUser}
            />
        ) : (
            "Initializing...."
        ) }
      </>
  );
   //AppRouter에 속성들을 전달시켜준다.
}

 

# Router.js

두번째 컴포넌트파일이며, Profile 컴포넌트로 속성들을 전달해준다.

import {
    BrowserRouter as Router, Route, Routes,
} from "react-router-dom";
import {Auth} from "routes/Auth";
import {Home} from "routes/Home";
import {Profile} from "routes/Profile";
import {Navigation} from "components/Navigation";

export const AppRouter = ( {refreshUser, isLoggedIn, userObj} )=> {
    return (
        <Router>
            {isLoggedIn && <Navigation userObj={userObj}/>}
            <Routes>
                {isLoggedIn ? (
                    <>
                        <Route path="/" element={<Home userObj={userObj}/>}/>
                        <Route path="/Profile"
                               element={ <Profile userObj={userObj}
                                refreshUser={refreshUser} /> }
                        />
                    </>
                ): (
                    <>
                        <Route path="/" element={<Auth/>}/>
                    </>
                )}
            </Routes>
        </Router>
    );
};

AppRouter 인자로 오브젝트 리터럴로 속성이름을 넣어서 전달받고

컴포넌트요소 안에도 동일명속성으로 중괄호 안도 넣어준다.

 

# Navigation.js

고정 되어있는 네비게이션 컴포넌트파일이다.

import React from "react";
import {Link} from "react-router-dom";

export const Navigation = ({ userObj })=> {
    return (
    <nav>
        <ul>
            <li>
                <Link to="/">Home</Link>
            </li>
            <li>
                <Link to="/profile">{userObj.displayName}의 Profile</Link>
            </li>
        </ul>
    </nav>
    );
};

Home 메뉴와 userObj.displayName + "의 Profile" 메뉴가 있다.

userObj.displayName는 현재 접속되어있는 유저데이터에서 이름을 가져온다.

 

# Profile.js

import React, {useEffect, useState} from "react";
import {authService, dbService} from "Fbase";
import { useNavigate } from "react-router-dom";
import {collection, getDocs, query, where} from "@firebase/firestore";
import {updateProfile} from "@firebase/auth";

export const Profile = ( { refreshUser, userObj } )=> {
         //상위 컴포넌트에서 가져온 속성들

    const navigate = useNavigate();
    //로그아웃 처리 후 홈화면으로 이동시킬때 사용
    
    const [newDisplayName, setNewDisplayName] = useState(userObj.displayName);
    //가져온 유저데이터에서 displayName
    
    const onLogOutClick = () => {
        authService.signOut();
        navigate("/",{replace:true});
    };
    
    // eslint-disable-next-line react-hooks/exhaustive-deps
    const getMyZweets = async ()=> {
        try {
            const q = query(
                collection(dbService,"zweets"),
                where("creatorId", "==", userObj.uid),
            );
            const querySnapshot = await getDocs(q);
            querySnapshot.forEach((doc)=>{
                console.log(doc.id,"=>",doc.data());
            });
        } catch (e) {
            console.log(e);
        }
    }
    useEffect(()=> {
        getMyZweets();
    },[getMyZweets])
    const onChange = ( {target: {value} }) => {
        setNewDisplayName(value);
    }
    const onSubmit = async (e)=> {
        e.preventDefault();
        if(userObj.displayName !== newDisplayName){
            await updateProfile(authService.currentUser,{displayName: newDisplayName});
             /*여기서 userObj가 아닌 authService.currentUser를 사용한 이유는
             파이어베이스 v9부터는 메소드의 영역이 다르기 때문에 위와같이 해결했다. */
        }
        refreshUser();
        //서브밋되면 리프레쉬유저함수를 실행시켜 프로필도 re-render되게 만든다.
    }
    return (
        <>
            <form onSubmit={onSubmit}>
                <input
                    onChange={onChange}
                    type="text"
                    placeholder="Display name"
                    value={newDisplayName}
                />
                <input type="submit" value="Update Profile"/>
            </form>
            <button onClick={onLogOutClick}>Log Out</button>
        </>
    )
};

프로필 화면에서 텍스트인풋내에 글을 쓴 후 Update Profile 버튼이나

엔터를 눌러 서브밋하면 refreshUser함수가 실행되어 네비게이션의 프로필이름이 바뀐다.

 

# refreshUser 함수 다시보기

  const refreshUser = () => {
    const user = authService.currentUser;
    setUserObj({
      displayName: user.displayName,
      uid: user.uid,
      updateProfile: { displayName: user.displayName },
    });
  }

setUserObj로 인하여 userObj가 변경되고

변화를 감지한 react가 re-render시킨 것이다.

2022. 11. 16. 19:59

# useAxios의 기능

기능은 fetch와 유사하고 간단하게 사용할땐 fetch를,

확장성을 염두해봤을 땐 axios를 쓰는게 좋다.

fetch와 다르게 인스턴스를 사용할 수 있는 장점도 있다.

(두번째인자에 넣지 않을경우 기본값으로 설정된다.)

 

import defaultAxios from "axios";
//터미널에 npm i axios 하여 인스톨한 후 임포트해준다.

import { useEffect, useState } from "react";

const useAxios = (opts, axiosInstance = defaultAxios) => {
    //opts 는 api를 등록할 수 있다. fetch와 다르게 바로 json화 되는게 axios의 장점이다.
    //두번째 인자 axiosInstance는 인스턴스이고 아무것도 넣지않으면 기본값으로 적용

    const [state, setState] = useState({
        loading: true,
        error: null,
        data: null
    });
    //로딩, 에러, 데이터를 사용할 것이기 때문에 각각의 key와 value의 디폴트를 설정해준다.
    
    const [trigger, setTrigger] = useState(0);
    if (!opts.url) {  //유효성을 검사한다. 기본적으로 opts(API)는 url이기 때문
        return;
    }
    const refetch = () => {
        setState({
            ...state,
            loading: true
        });
        setTrigger(Date.now());
    };
    /* 리패치를 위한 함수이며 함수 실행시 setTrigger로 인해서 
    함수가 실행될 때마다 trigger는 계속해서 다른 값을 반환한다.
    이 후에 useEffect의 deps로 넣기 위한 트리거다.( 리패치 해야되니까 ) */


    useEffect(() => {
        axiosInstance(opts)       //fetch와 같은 기능을 해주고
            .then((data) => {     //로딩이되면(then) setState를 통해서 state값이 바뀐다.
                setState({
                    ...state,
                    loading: false,
                    data
                });
            })
            .catch((error) => {
                setState({ ...state, loading: false, error });
                //catch 는 에러를 캣치하며, reject상태일경우 내부 메소드를 실행한다.
                });
    }, [trigger]);
    //트리거가 deps에 들어가 있으니 trigger가 바뀌면 re-render한다.
    
    return { ...state, refetch };
    //반환값은 state의 모든 리터럴과 refetch함수이다.
};

 

# 사용예시

const { loading, data, refetch } = useAxios({
    url: "https://yts.mx/api/v2/list_movies.json"
  });

  return (
    <div className="App" style={{ height: "1000vh" }}>
      <h1>{data && data.status}</h1>
      <h2>{loading && "Loading"}</h2>
      <button onClick={refetch}>Refetch</button>
    </div>
  );

useAxios에서 opts를 { url: " 주소 " } 오브젝트로 전달해주었고

data가 있으면(API를 정상적으로 받아오면) data.status가 h1에 마운트되도록 만들었다.

로딩이 true일 경우에만 Loading이라는 문자열을 h2에 마운트하고

Refetch버튼을 누르면 refetch함수를 통해서 새로고침된다.

'Frontend > React-Hooks' 카테고리의 다른 글

[ React Hook ] 9. useNotification  (0) 2022.11.12
[ React Hook ] 8. useFullscreen  (0) 2022.11.09
[ React Hook ] 7. useFadeIn  (0) 2022.11.05
[ React Hook ] 6. useScroll  (0) 2022.11.04
[ React Hook ] 5. useNetwork  (0) 2022.11.03
2022. 11. 15. 23:22

3-1. 이미지업로드 게시글과 이어지는 내용이다.

3-1게시글에선 업로드가 가능할 수 있는 기능만 추가해준 것이고

 

3-2에선 확실하게 마운트 시켜주고 삭제시키는 기능을 구현했다.

 

# 메소드 영역

import {storageService} from "Fbase";
//Fbase.js에서 getStorage from "firebase/storage"를 가져온 것

import {ref, uploadString, getDownloadURL } from "@firebase/storage";
import { v4 as uuidv4 } from 'uuid';

export const Home = ({ userObj }) { 

//...

const [attachment, setAttachment] = useState("");
//업로드할 사진을 위한 스테이트

const onSubmit = async (event)=> {
    event.preventDefault();
    let attachmentUrl = "";    //서로 영역에서 사용하기 위해 let으로 선언하여 업데이트함.
    try{
        if(attachment !== "") {
            const attachmentRef = ref(storageService, `${userObj.uid}/${uuidv4()}`);
            //uuidv4는 랜덤으로 스트링을 만들어준다.
            
            const response = await uploadString(attachmentRef, attachment, "data_url");
            //"data_url"은 readAsDataURL에서 전달받는다.
            
            attachmentUrl = await getDownloadURL(response.ref);
        }
        const zweetObj = {
            text: zweet,
            createdAt: Date.now(),
            creatorId: userObj.uid,
            attachmentUrl               //zweetObj에서 attachmentUrl이 추가되었다.
        };
        await addDoc(collection(dbService, "zweets"),zweetObj);
    } catch (error) {
        console.error("Error adding document: ", error);
    }
    setZweet("");
    setAttachment("");   //서브밋 후 초기화
};

 

Home.js에서 onSubmit함수의 변경사항

 

const onDeleteClick = async () => {
    const ok = window.confirm("Are you sure you want to delete this zweet?");
    if(ok){
        await deleteDoc(zweetTextRef);
        
        await deleteObject(ref(storageService, zweetObj.attachmentUrl));
        //이미지가 있다면 이미지도 지워줘야 하기때문에 추가
    }
};

Zweet.js에서 onDeleteClick함수의 변경사항

 

# 컴포넌트 마운트 영역

<div>
    <h4>{zweetObj.text}</h4>
    {zweetObj.attachmentUrl && <img src={zweetObj.attachmentUrl} width="50px" height="50px"/>}
 //zweetObj에 attachmentUrl(이미지파일url)리터럴이 생겼기 때문에 zweetObj.attachmentUrl로 변경
    
  ...
   
</div>

지우거나 업로드 했을때 사진도 함께 업로드 되거나 삭제되어야 하기때문에 함수들에 변경사항이 생겼다.

2022. 11. 15. 21:25

2. 업로드된 컴포넌트 삭제/수정 기능과 이어지는 내용이다.

Home.js 파일내부에 내용이다.

 

Home.js는 Zwitter앱의 홈화면(메인화면)이 될 컴포넌트이다.

 

 

# 메소드 영역

const [attachment, setAttachment] = useState();
//파일을 위한 스테이트이다.

const onFileChange = ({target:{files}}) => {
    const theFile = files[0];
    //파일을 가져오고
    
    const reader = new FileReader();
    //reader를 만든 후
    
    reader.onloadend = ({currentTarget:{result}}) => {
        setAttachment(result);
    };
    //파일로드가 완료되면 파일을 스테이트에 넣어줌.
    
    reader.readAsDataURL(theFile);
    //readAsDataURL을 사용해서 파일을 읽는다.
}
const onClearAttachment = ()=> setAttachment(null);
//클리어 버튼을 누를경우 적용시켜줄 함수

 

# 컴포넌트 마운트 영역

{ attachment && (
    <div>
        <img src={attachment} width="50px" height="50px"/>
        <button onClick={onClearAttachment}>Clear</button>
    </div>
) }

이미지를 선택하여 업로드하면 attachment가 존재하게 되고 위와 같이 요소들이 마운트된다.

가로세로 50px의 미리보기 이미지가 나오게 되고 Clear버튼을 누르면

onClearAttachment함수에 의해서 이미지가 사라지게된다.

( setAttachment(null) => attachment = null )