본문 바로가기
블로그 이미지

방문해 주셔서 감사합니다! 항상 행복하세요!

  
   - 문의사항은 메일 또는 댓글로 언제든 연락주세요.
   - "해줘","답 내놔" 같은 질문은 답변드리지 않습니다.
   - 메일주소 : lts06069@naver.com


토이 프로젝트(TOY PROJECT)

리액트 익스프레스 게시판 (React, Express typescript, file drag and drop)

야근없는 행복한 삶을 위해 ~
by 마샤와 곰 2022. 6. 18.

 

리엑트(React)와 Node.js의 익스프레스(Express)를 활용하여 만든 프로젝트 입니다.

전반적인 기능은 아래와 같습니다.

1. 파일 드래그앤 드롭
2. 리엑트 hook-form을 활용한 데이터 바인딩
3. 데이터 등록은 FormData를 활용한 axios 라이브러리 사용
4. 리엑트 레덕스(redux)를 활용하여 상태 관리
5. 익스프레스 라이브러리를 활용한 서버 구성
6. multer를 활용한 파일 업로드 기능 구현
7. sqlite를 활용한 데이터베이스 구성

 

게시판(Notice Board) 작업을 한번 하고 나면 라이브러리, 프레임워크에 대한 기능이 좀 더 눈에 잘 들어옵니다.

게시판이라 불리는 기능에는 등록(Create), 읽기(Read), 수정(Update), 삭제(Delete) 의 기능이(CRUD) 전부 들어있기 때문 입니다.

 

#1. 익스프레스 서버(Express, Typescript)

익스프레스 라이브러리를 활용하여 웹 어플리케이션 서버를 구축하여 보았습니다.

타입제한을 두기 위해서 Typescript 기반으로 구현되어 있습니다.

 

이번 기능에서 "파일업로드" 기능을 구현 할 예정이였으므로 multer 라이브러리를 추가하여 주었습니다.

//생략....
const app: express.Application = express();

//생략....

const FILE_PATH: string = "uploads/";
const upload: multer.Multer = multer({ dest: FILE_PATH });

app.post("/data/insert", upload.any(), (req: express.Request, res: express.Response) => {
    let { name, desc} = req.body; //1) 일반 파라미터
    let files = req.files;  //2) 파일목록
});

 

multer 라이브러리를 활용하면 위 코드 샘플처럼 간단하게 기능을 붙일 수 있습니다.

upload 객체는 multer를 활용하여 생성된 파일저장과 관련된 객체이며, any, array, field 등 함수를 지원 합니다.

여기서 사용된 any라는 함수는 들어온 파일을 모두 받는 기능을 의미 합니다.

물론 보내는 브라우저 코드는 http프로토콜 기반 multipart 형식과 post 방식을 지켜주어여 합니다.

 

업로드한 파일을 다운받기 위해서는 응답객체에 download 함수를 사용하여 주면 됩니다.

download 함수 첫번째 값은 파일경로+이름 값 이며, 두번째 값은 사용자가 받을 파일 이름을 의미 합니다.

app.all(
  "/data/download/",
  (req: express.Request, res: express.Response) => {
    const file = `파일경로와이름`;
    res.download(file, `파일이름`);
  }
);

 

데이터베이스는 sqlite를 사용 하였습니다.

몽고, 오라클, mysql 데이터베이스는 따로 설치를 해 주어야 하지만, sqlite는 그렇게 하지 않아도 되기 때문 입니다.

간단하게 사용하는 데이터베이스 이므로 대량의 데이터를 관리하는 경우에는 다른 데이터베이스를 고려 해야 합니다.

 

로그인, 인증 및 기타 작업이 없는 어플리케이션 서버이므로 코드가 그리 어렵지는 않습니다.

데이터베이스도 따로 라이브러리(프레임워크) 등을 쓰지 않았기에 향후 Sequelize나 mybatis 같은 라이브러리를 사용하는 것이 좋을 것 같습니다.

 

#2. 리엑트(React)

리엑트나 앵귤러, 뷰 등 이러한 프레임워크(라이브러리)에서는 기본 틀 구조에 라우팅을 반드시 적용 합니다.

1개 파일에서의 모든 화면과 관련된 기능을 작성하는 것은 어렵기 때문 입니다.

마찬가지로 여기서도 라우팅을 적용하여 주었습니다. 서버에서 새로고침 문제를 따로 하지 않을 예정이므로 해쉬라우터(Hash)를 사용하였습니다.

import { HashRouter, Routes, Route } from 'react-router-dom'
import DashBoard from './component/DashBoard'
import NotFound from './component/NotFound'

//라우팅을 담당하는 App 함수 입니다.
function App(props) {
  return (
    <HashRouter>
      <Routes>
        <Route path="/" element={<DashBoard {...props} />} ></Route>
        <Route path="*" element={<NotFound {...props} />} ></Route>
      </Routes>
    </HashRouter>
  );
}

export default App;

화면을 담당하는 컴포넌트역할의 파일을 DashBoard와 Notfound 라는 2개만 만들어 보았습니다.

Notfound는 없는 주소로 접근하는 경우 동작하는 컴포넌트 입니다.

DashBoard는 게시판 역할을 담당하는 기능 입니다.

 

게시판에서 파일 저장을 위해 드래그앤드롭 기능을 추가하여 보았습니다.

<div className='dropArea'
    onDragOver={(event) => { return dragFunction(event, 'over'); }}
    onDrop={(event) => dragFunction(event, 'drop')}
    onDragEnter={(event) => dragFunction(event, 'enter')}
    onDragLeave={(event) => dragFunction(event, 'leave')}>
    <div>File Drop zone</div>
</div>

 

드래그한 대상이 움직여서 드랍되는 영역에 오게되면 아래 4종류의 이벤트가 감지 됩니다.

 * onDragEnter : 드래그한 대상이 드랍영역에 다다르면 이벤트가 발생 합니다.

 * onDragLeave : 드래그한 대상이 드랍하지 않고 떠나는 경우 이벤트가 발생 합니다.

 * onDrop : 대상이 드랍되면 이벤트가 발생 합니다.

 * onDragOver : 드래그 대상이 드랍영역에 오버(over)하는 경우 발생 합니다.

 

여기서 주의해야되는 점은 onDrop 이벤트와 onDragOver이벤트는 반드시 같이 써 주어야 합니다.(2022.06.20 기준)

그렇지 않는다면 onDrop 이벤트는 발생하지 않습니다...ㅠ

* 리액트 드래그 앤 드롭 기능 구현시 주의해야될 점

https://lts0606.tistory.com/602

 

React Drag and Drop 기능구현 시 주의 할 점(드래그 앤 드롭)

리엑트에서 파일 드래그 앤 드롭 기능을 구현하기 위해서 아래 속성을 부여하여 보았습니다.  * onDrop : 대상이 드랍되면 이벤트가 발생 합니다.  * onDragEnter : 드래그한 대상이 드랍영역에 다다

lts0606.tistory.com

 

이렇게 추가된 파일은 배열값 상태에 넣어주고, 넣어준 상태를 서버로 보내는 경우에는 FormData 객체를 사용하면 쉽게 구현 할 수 있습니다.

  const { register, getValues, handleSubmit} = useForm();  //일반 값 상태 입니다(hook-form)
  const [fileData, setFileData] = useState([]);  //드랍된 파일 상태 입니다.
  //생략..

  //#3. 파일 드래그 앤 드롭 기능 입니다.
  const dragFunction = (event, type) => {
    event.preventDefault();
    event.stopPropagation();
    if (type === 'drop') {  //드랍 이벤트라면
      const files = event?.dataTransfer?.files; //dataTransfer 값에서 파일속성을 가져옵니다
      if (files) {
        for (let i = 0; i < files.length; i++) {  //파일 상태를 추가합니다
          setFileData(oldData => oldData.concat(files[i]));
        }
      }
    }
  }
  
  
  //생략..
  
  //#5. 데이터를 저장합니다.
  const saveResult = () => {
    let param = getValues();
    let bodyFormData = new FormData();  //FormData를 활용하여 요청합니다.
    bodyFormData.append('name', param.name);  //일반 파라미터 입니다. 
    bodyFormData.append('desc', param.desc);  //일반 파라미터 입니다.

    fileData.forEach((value, idx) => {  //FormData에 파일을 추가 합니다
      bodyFormData.append(`file${idx}`, value);
    })

    axios({
      method: "post",
      url: "/data/insert",
      data: bodyFormData,
      headers: { "Content-Type": "multipart/form-data" },  //타입지정 반드시 멀티파트로!
    }).then(arg => {
      let { result } = arg.data;
      console.log(result);
    });
  }

 

나머지 기능인 값을 가져오기, 수정하기 및 삭제하기는 일반적인 기능만 추가하였습니다.

axios 라이브러리와 useEffect 함수를 활용하면 어렵지 않게 구현 가능 합니다.

 

저장된 파일 다운로드 기능은 마찬가지로 axios 라이브러리를 사용하였습니다.

아래 코드처럼 응답타입(responseType)을 blob 형식으로 해 주는 것이 핵심이라 할 수 있습니다.

axios({
    url: `파일을 받을주소`, 
    method: 'GET',
    responseType: 'blob',
}).then((response) => {
    const url = window.URL.createObjectURL(new Blob([response.data]));
    const link = document.createElement('a');
    link.href = url;
    link.setAttribute('download', '파일이름');
    document.body.appendChild(link);
    link.click();
});

 

실제 동작하는 모습입니다.

완성된 모습 입니다.

 

스타일이나 노드구조를 과도하게 줄 경우에는 코드를 확인하고 어렵기 때 문에 간단하게만 틀을 구성하였습니다.

스타일을 잘 못다루는 개발자에겐 역시 부트스트랩만한게 없는 것 같습니다..^^;

 

위 내용에 사용된 모든 소스코드는 아래 제 깃허브에서 받아볼 수 있습니다.

https://github.com/TaeSeungRyu/ReactWithExpress_DashBoard

 

GitHub - TaeSeungRyu/ReactWithExpress_DashBoard: 리엑트와 익스프레스 서버로 만든 구조 연습용 게시판 리파

리엑트와 익스프레스 서버로 만든 구조 연습용 게시판 리파지토리 입니다. Contribute to TaeSeungRyu/ReactWithExpress_DashBoard development by creating an account on GitHub.

github.com

 

리엑트를 사용하면 서 가장 신경써야되는 부분은 상태(state)관리에 따른 화면적용 및 렌더링이 아닐 까 합니다.

상태로 관리되지 않는 데이터는 어떠한 행위를 하더라도 화면에 변경된 내용이 표출되지 않기 때문 입니다.

앵귤러(Angular)나 뷰(Vue) 같은 경우에는 양방향바인딩(2 way data binding) 기능이 제공되기 때 문에 이러한 부분을 신경쓰지 않아도 참 좋았었는데...

 

여기까지, 리엑트와 익스프레스를 활용한 간단한 게시판 작업 내용 이였습니다.

궁금한점 또는 틀린 부분은 언제든 연락주세요!.👻

반응형
* 위 에니메이션은 Html의 캔버스(canvas)기반으로 동작하는 기능 입니다. Html 캔버스 튜토리얼 도 한번 살펴보세요~ :)
* 직접 만든 Html 캔버스 애니메이션 도 한번 살펴보세요~ :)

댓글