[WebDevCurriculum] React 기반의 file system 구현 - txt/image 불러오기

1. 개요

기존 VanillaJS를 활용하여 filesystem을 구현하는 것에 비해, React 기반의 filesystem은 어떠한 장점이 있는지 살펴본다.

2. 핵심

  • file을 local system으로 부터 읽어오고, 이를 상태변수화하여 file load(반영)가 즉시 이루어지도록 한다.
  • react-router-dom이 version6로 업데이트되어 props 사용이 안되므로, localStorage를 활용하여 data 저장소를 구성한다.

3. 전체적인 logic

사용자가 로그인을 하면 로그인 정보에 맞춰 data를 유지 및 load 한다.

  • 사용자가 ID, PW를 입력하여 로그인한다.
  • 로그인을 하면 다른 페이지(Detail)로 넘어간다.
  • Detail 페이지에서는 사용자ID 등을 활용하여 데이터를 불러오고 유지한다.

4-1. App.js - routing 구조 구성

Routing 구조를 구성한다.

  • react-router-dom version 상향에 따른 준수사항에 맞추어 Routing을 구성한다.
  • BrowserRouter(최상위 Routing 계층), Routes, Route 으로 이루어져 있으며, 각 Route는 반드시 Routes component에 포함되어 있어야 한다.
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from '../routes/Home.js';
import Detail from '../routes/Detail.js';

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" exact={true} element={<Home/>} />
        <Route path="/:userID" element={<Detail/>}/>
      </Routes>
    </Router>
  );
}

export default App;

4-2. Home.js - 사용자 data 입력 및 localStorage을 활용한 data 전달

사용자가 입력한 data를 localStorage를 임시 저장소로 활용하여 전달한다.

  • 여기서의 핵심은 input data를 받고, 이를 상태관리하여 data 변화에 즉시적인 대응(이벤트 감지 등)을 할 수 있게 구성하는 것이다.
  • React에서는 VanillaJS와는 달리, component 내부적으로 상태값을 활용하여 함수 인자 등에 활용할 수 있다.
  • useState를 사용하여 상태관리선언 > 입력받은 data(value)를 상태변수에 저장
  • props를 통한 component 간 data 전달이 불가능하므로, localStorage를 임시적인 data 저장소로 활용한다.
const [userID, setUserID] = useState('');
const [userPW, setUserPW] = useState('');

setUserID(value);
setUserPW(value);

 <input type='text' placeholder="ID" value={userID} onChange={updateUserID}/>
 <input type='password' placeholder="PW" value={userPW} onChange={updateUserPW}/>
   
   
<Link to={{
                pathname: `/${userID}`
            }} onClick={() => saveStateValues(userID, userPW)}>로그인하기</Link>

Home.js의 전체 구성

import React, {useState} from 'react';
import {Link} from 'react-router-dom';
import {gql, useQuery} from '@apollo/client';
import styled from 'styled-components';

const dataContext = React.createContext();

const Container = styled.div`
`

const Title = styled.h1`
    padding: 1%;
    align-items: center;
`

const UserInformation = styled.div`
    display: flex;
`

function Home() {

    const [userID, setUserID] = useState('');
    const [userPW, setUserPW] = useState('');

    const updateUserID = e => {
        const {target : {value}} = e;
        //value 변수 자체는 문자 하나하나
        //각각 입력될때마다 반응

        setUserID(value);
        //이 입력되는 값들에 대해
        //상태관리화하면 (누적된) 상태관리가 가능해진다.
        console.log(userID);
    };

    const updateUserPW = e => {
        const {target : {value}} = e;
        //value 변수 자체는 문자 하나하나
        //각각 입력될때마다 반응

        setUserPW(value);
        //이 입력되는 값들에 대해
        //상태관리화하면 (누적된) 상태관리가 가능해진다.
        console.log(userPW);
    };
    
    const saveStateValues = (userID, userPW) => {
        localStorage.setItem(userID, userPW);
    };

    return(
        <Container>
            <Title>사용자 로그인</Title>
            <UserInformation>
                <input type='text' placeholder="ID" value={userID} onChange={updateUserID}/>
                <input type='password' placeholder="PW" value={userPW} onChange={updateUserPW}/>
            </UserInformation>
            <Link to={{
                pathname: `/${userID}`
            }} onClick={() => saveStateValues(userID, userPW)}>로그인하기</Link>
        </Container>
    )
};

export default Home;

이벤트를 구성하는 경우 작성형태에 유의한다.

  • onClick = {()=>function()} → event 인자와 같이 전달
  • onClick = {function()} → event 인자를 같이 전달하지 않는다.

4-3. Detail.js - 화면 전환 이후 data를 전달받고 data 유지 및 load

data 저장소 및 hooks(useParams 등)을 통해 이전 component에서 data를 전달받는다.

  • 사용자ID는 URL(useParams)를 통해 전달받는다.
  • 사용자PW는 localStorage를 통해 전달받는다.
  • 기본적으로 data를 local file system으로 입력받고 이에 대한 실시간 감지를 하기 위해 상태관리한다(useState, 최종적으로 받은 text/image data에 대한).
  • 상태변수를 return 값에 활용하면 즉시적인 data 반영이 가능해진다.
const {userID} = useParams();
const userPW = localStorage.getItem(userID);
//const userIDByLocal = localStorage.key(userPW);

const [finalText, setFinalText] = useState('');
const [finalImage, setFinalImage] = useState('');

let txtDATA = localStorage.getItem(`${userID}fortxt`);
let imageDATA = localStorage.getItem(`${userID}forimg`);

setFinalText(text);
setFinalImage(fileRead.result);

{finalText? (<pre className="textArea" contentEditable>{finalText}</pre>) : (<div>NO SAVED TEXT DATA</div>)}
{finalImage? (<div className="imageAreaCover"><img className="imageArea" src={finalImage}></img></div>) : (<div>NO SAVED IMAGE DATA</div>)}

※ file system(외부 입출력)을 통해 data를 전달받는 과정에서 상태관리를 바로 적용하지는 않는다.
※ 최종적으로 text/image file을 입력받은 data에 대해 상태관리변수에 담고, 이를 return(화면구현)에 사용한다.

Detail.js의 전체 구성

import React, {useState} from 'react';
import { useParams } from 'react-router';
import styled from 'styled-components'

//import {useRef} from 'react-router-dom';

let fileRead = new FileReader();

const Container = styled.div`
`

function Detail(){

    const {userID} = useParams();
    const userPW = localStorage.getItem(userID);
    //const userIDByLocal = localStorage.key(userPW);

    const [finalText, setFinalText] = useState('');
    const [finalImage, setFinalImage] = useState('');

    let txtDATA = localStorage.getItem(`${userID}fortxt`);
    let imageDATA = localStorage.getItem(`${userID}forimg`);

    let fileBuffer = null;

    async function fileOpenButton(){
        [fileBuffer] = await window.showOpenFilePicker();
        
        let fileData = await fileBuffer.getFile();
        console.log(fileData);
    
        if(fileData.type !== 'image/png'){
            //interface file data
            //await fileRead.readAsDataURL(fileData)
            let text = await fileData.text();
            localStorage.setItem(`${userID}fortxt`, text)

            setFinalText(text);
            return ;
    
        }else if(fileData.type == 'image/png' || 'image/jpeg'){
            //fileReader가 fileData를 먼저 읽어야 한다.
            await fileRead.readAsDataURL(fileData);
            fileRead.addEventListener('load', () => {
                localStorage.setItem(`${userID}forimg` , fileRead.result);
                setFinalImage(fileRead.result);
                //console.log(fileRead.result);
                //console.log(imageArea.src);
                })
            
            
            return;

            

            //fileRead - null, fileRead.result - image URL.
            //upload event를 부여할 경우에만 fileRead에서 data를 읽어올 수 있다.
            
        }else{
            return;
        }
    };

    return(
        <Container>
            <button className="fileOpen" type="file" onClick={fileOpenButton}>파일 불러오기</button>
            {finalText? (<pre className="textArea" contentEditable>{finalText}</pre>) : (
            <div>NO SAVED TEXT DATA</div>)}
            {finalImage? (<div className="imageAreaCover"><img className="imageArea" src={finalImage}></img></div>) : (
                <div>NO SAVED IMAGE DATA</div>)}
            <button className="fileSave">파일 저장하기</button>
            <button className="fileSaveAs">다른 이름으로 파일 저장하기</button>
        </Container>
    )
}

export default Detail;

4-4. 참고

별도의 화면 랜더링이 필요할 경우, 이에 대한 Component를 구성하고 분기처리를 통해 return(화면구현) 한다.

5. 참조링크

React event 처리하기
https://ko.reactjs.org/docs/handling-events.html

좋은 웹페이지 즐겨찾기