React/Electron

[React]Electron에 React 적용 #2. 파일 업로드

DevStory 2022. 1. 31.

이번 포스팅은 Electron에 React를 적용하여 파일 업로드하는 방법을 소개합니다. 다음은 예제에 사용될 폴더 구조입니다.

폴더 구조

FileUpload 폴더가 존재하며, FileUpload에는 A, B 폴더 그리고 MainImage.jpeg 파일이 존재합니다.

A 폴더에는 AA 폴더 그리고 AImage.jpeg 파일이 존재합니다.

AA 폴더는 빈 폴더입니다.

B 폴더에는 BImage.jpge 파일이 존재합니다.

 


기존 JavaScript 파일 업로드의 문제점

기존 JavaScript의 파일 업로드는 input 태그에 type='file'로 설정하여 파일 업로드를 구현할 수 있습니다. 하지만, 이러한 방식은 몇 가지 단점이 존재합니다.

  • 폴더를 선택하면 선택한 폴더와 하위 폴더 정보를 알 수 없으며, 파일 정보만 알 수 있습니다.
  • 해당 폴더의 파일뿐만 아니라 하위 폴더의 파일도 읽습니다.(파일을 읽는 시간이 오래 걸림)

즉, 폴더는 고려하지 않고 파일만 고려합니다.

 

좀 더 확실한 설명을 위해 예제 코드를 통해 알아보도록 합니다.

import React, { useState, Fragment } from 'react'

function App() {
  return (
    <div>
      <input type='file' webkitdirectory='true'
             onChange={e => {console.log(e.target.files)}}
      />
    </div>
  )
}

export default App;

input 태그가 파일 업로드 전용이므로 type을 type='file'로 설정하고 폴더만 선택할 수 있도록 webkitdirectory='true'로 설정합니다. 그리고 파일을 선택하면 onChange 이벤트가 동작하여 선택한 파일을 콘솔에 출력하도록 합니다.

 

애플리케이션을 실행 후 파일 선택기 창에서 FileUpload 폴더를 선택합니다.

파일 선택기 창

그리고 콘솔 창에서 e.target.files의 내용을 보면 다음과 같습니다.

Console

FolderUpload에는 3개의 폴더와 3개의 파일이 존재하는데, 콘솔에서는 3개의 파일 정보만 확인되었습니다. 심지어 FileUpload 하위 폴더인 A와 B 폴더 아래의 파일도 읽는 단점도 있습니다.

 

이러한 문제는 JavaScript 개발 당시 의도한 것으로 보이는데, 정확한 이유는 모르겠네요.

 

다음은 Electron의 dialog 모듈을 사용하여 파일 업로드하는 방법을 소개합니다.


Electron 폴더 업로드

Electron에는 dialog라는 모듈이 존재합니다. dialog에는 여러 기능이 존재하지만, 이번 포스팅은 파일 업로드에 대해서만 다루기 때문에 dialog.showOpenDialogdialog.showOpenDialogSync에 대해 간략하게 설명합니다.

 

showOpenDialogSync는 동기식 다이어로그로 파일을 선택하거나 취소될 때까지 다른 이벤트 핸들러 동작을 차단합니다.

showOpenDialog는 비동기식 다이어로그로 다른 이벤트 핸들러 동작을 차단하지 않습니다. 그리고 비동기식으로 동작하므로 Promise 객체를 반환합니다.

 

dialog 모듈을 사용하기 위해 상단에 require() 함수를 사용하여 모듈을 추가합니다.

const electron = window.require('electron');
const remote = electron.remote
const {dialog} = remote

그리고 button 태그를 생성하고 onClick 이벤트가 발생하면 dialog를 호출하도록 합니다. dialog.showOpenDialog의 옵션에서 properties속성을 'openDirecdtory'로 설정하여 폴더만 선택할 수 있도록 합니다. properties 속성은 여러 값을 가질 수 있으므로 배열로 할당합니다. 

function App() {
  // 선택한 폴더의 경로를 가지는 state
  const [folderPath, setFolderPath] = useState('');
  
  const handleSelectFolder = (e) => {
    try{
      dialog.showOpenDialog({ properties: ['openDirectory'] })
      .then(result => {
        console.log(result);
      })
    } catch(error) {
      console.error(error);
    }
  }

  return (
    <div>
      <input type='text' value={folderPath} readOnly/>
      <button onClick={e => handleSelectFolder(e)}>
        FileUpload
      </button>
    </div>
  )
}

export default App

 

showOpenDialog의 결과는 Promise 객체이므로 then() 함수로 전달받은 값을 확인합니다.

 

애플리케이션을 실행 후 파일 선택기 창에서 FileUpload 폴더를 선택합니다. 그리고 콘솔 창에서 result를 확인합니다.

선택한 폴더 또는 파일의 경로는 filePaths라는 속성으로 확인할 수 있으며, JavaScript의 e.target.files와 다르게 경로만 확인됩니다.

 

여러 개의 폴더 또는 파일을 선택하고 싶은 경우 'multiSelections' 값을 추가합니다.

dialog.showOpenDialog({ properties: ['openDirectory', 'multiSelections'] })

다음 사진처럼 여러 개의 폴더를 선택할 수 있습니다.

2개의 폴더를 선택했으므로 filePaths에는 2개의 값이 존재합니다.

 

다시 원래대로 돌아와서 dialog의 properties에서 'multiSelections' 값을 제거합니다. 그리고 폴더를 선택하면 folderPath라는 state를 변경합니다.

const handleSelectFolder = (e) => {
  try{
    dialog.showOpenDialog({ properties: ['openDirectory'] })
    .then(result => {
      setFolderPath(result.filePaths[0]);
    })
  } catch(error) {
    console.error(error);
  }
}

폴더를 선택하면 type이 text인 input 태그에 선택한 폴더의 경로가 표시됩니다.


선택한 폴더의 하위 파일 또는 폴더

electron의 dialog 모듈을 사용하면, 선택한 폴더의 경로를 알 수 있지만, 하위 파일과 폴더를 알 수 없다는 문제에 직면합니다.

 

위 문제를 해결하기 위해서는 Node.js의 fs(file system) 모듈을 사용해야 합니다. require() 함수를 사용하여 fs 모듈을 추가합니다.

const fs = window.require('fs')

 

handleSelectFolder() 함수를 다음과 같이 수정합니다.

const handleSelectFolder = (e) => {
  try{
    // dialog 오픈
    dialog.showOpenDialog({ properties: ['openDirectory'] })
    .then(result => {
      // 선택한 폴더의 경로
      const selectFolderPath = result.filePaths[0];
      
      // 선택한 폴더 정보
      const selectFolderInfo = fs.statSync(selectFolderPath);
      
      // 하위 파일 및 폴더
      let childPath = [];
          
      // 파일 탐색기에서 선택한게 폴더인 경우
      if(selectFolderInfo.isDirectory()) {
        // 폴더 경로를 전달하여 폴더의 내용을 읽음
        fs.readdirSync(selectFolderPath)
        .map(file => { // 하위 파일 또는 폴더가 여러개인 경우 순회하여 접근
          // 파일 이름을 childPath 배열에 추가
          childPath.push(file);
        })
      }
      console.log(childPath);
      setFolderPath(result.filePaths[0]);
    })
  } catch(error) {
    console.error(error);
  }
}

애플리케이션을 실행하여 FileUpload 폴더를 선택 후 콘솔 창을 확인하면 다음과 같이 하위 파일과 폴더 이름을 알 수 있습니다.

FileUpload 폴더에는 A, B 폴더 그리고 FileUploadImage.png 파일이 있다는 것을 확인할 수 있습니다. ".DS_Store"는 Mac OS 내부적으로 생성되는 파일이므로 무시하셔도 됩니다.

 

다음은 예제에 사용된 전체 소스 코드입니다.

import React, { useState, Fragment } from 'react'

const fs = window.require('fs')
const electron = window.require('electron');
const remote = electron.remote
const {dialog} = remote

function App() {
    const [folderPath, setFolderPath] = useState('');
    const handleSelectFolder = (e) => {
      try{
        dialog.showOpenDialog({ properties: ['openDirectory',] })
        .then(result => {
          // 선택한 폴더의 경로
          const selectFolderPath = result.filePaths[0];
          // 선택한 폴더 정보
          const selectFolderInfo = fs.statSync(selectFolderPath);
          // 하위 파일 및 폴더
          let childPath = [];
          
          // 폴더인 경우
          if(selectFolderInfo.isDirectory()) {
            // 폴더 경로를 전달하여 폴더의 내용을 읽음
            fs.readdirSync(selectFolderPath)
            .map(file => {
                childPath.push(file);
            })
          }
          console.log(childPath);
          setFolderPath(result.filePaths[0]);
        })
      } catch(error) {
        console.error(error);
      }
    }
  
    return (
      <div>
        <input type='text' value={folderPath} readOnly/>
        <button onClick={e => handleSelectFolder(e)}>
          FileUpload
        </button>
      </div>
    )
  }
  
  export default App

참고자료

Electron의 dialog API 문서

https://www.electronjs.org/docs/latest/api/dialog

 

dialog | Electron

Display native system dialogs for opening and saving files, alerting, etc.

www.electronjs.org

 

Node.js의 fs API 문서

https://nodejs.org/api/fs.html

 

File system | Node.js v17.4.0 Documentation

 

nodejs.org

 

반응형

'React > Electron' 카테고리의 다른 글

[React]Electron에 React 적용 #1. 환경설정 및 배포  (2) 2022.01.28

댓글