๊ฐœ๋ฐœ ๊ณต๋ถ€/๋ฐ๋ธŒ์ฝ”์Šค TIL

[ํด๋ผ์šฐ๋”ฉ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์—”์ง€๋‹ˆ์–ด๋ง TIL] 240307, 240308 - ReactJS ๋น„๋””์˜ค ์—๋””ํ„ฐ ์ œ์ž‘ํ•˜๊ธฐ ํ”„๋กœ์ ํŠธ (2)

๊ฐ€์šค์ด 2024. 3. 9. 21:08

Intro


์ด๋ฒˆ์—” ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์ „๋ฐ˜์ ์ธ ์Šคํƒ€์ผ๋ง์„ ์ฃผ๋กœ ์ง„ํ–‰ํ–ˆ๋‹ค.

8์ผ์—” ํ”„๋กœ์ ํŠธ ์ค‘๊ฐ„ ์ ๊ฒ€์ด ์žˆ์–ด์„œ ์ค‘๊ฐ„ ๋ณด๊ณ ์„œ๋„ ์ž‘์„ฑ์„ ํ–ˆ๊ณ  ์ง€๊ธˆ๊นŒ์ง€ ์ง  ์ฝ”๋“œ๋“ค์„ ๋ฉ˜ํ† ๋‹˜๊ป˜ ์ฝ”๋“œ๋ฆฌ๋ทฐ๋„ ๋ฐ›์•˜๋‹ค.

 

 

 

 

ํ”„๋กœ์ ํŠธ ์ง„ํ–‰ ์‚ฌํ•ญ


๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ ์„ธํŒ…

๋ณธ ํ”„๋กœ์ ํŠธ์—์„  ํŽ˜์ด์ง€๊ฐ€ ํ•œ ํŽ˜์ด์ง€์ด๊ธด ํ•˜์ง€๋งŒ ์ถ”ํ›„ ํ™•์žฅ๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—.. ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ์ธ Header, Footer, CustomButton์„ ๊ตฌํ˜„ํ–ˆ๋‹ค.

 

  • App.css
/* HEADER */
.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid #ececec;
  margin-bottom: 24px;
  width: 100%;
  background-color: #fff;
  position: sticky;
  top: 0;
  z-index: 3;
}

 

Header๋Š” position: sticky๋ฅผ ์ด์šฉํ•ด ์Šคํฌ๋กค์„ ๋‚ด๋ ค๋„ ์ƒ๋‹จ์— ์œ ์ง€๋˜๋„๋ก ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

 

๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ๋Š” MUI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

 

React Button component - Material UI

Buttons allow users to take actions, and make choices, with a single tap.

mui.com

๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋น„ํ•ด ๊น”๋”ํ•œ ๋””์ž์ธ์ด ๋งˆ์Œ์— ๋“ค์—ˆ๋‹ค.

 

 

 

state context ์„ค์ •ํ•˜๊ธฐ

๋น„๋””์˜ค ํŒŒ์ผ๊ณผ ๋น„๋””์˜ค ํŒŒ์ผ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์‚ญ์ œํ•˜๋Š” ํ•จ์ˆ˜๋Š” ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉ๋  ๊ฑฐ๋ผ context๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ๋กœ ํ•˜์˜€๋‹ค.

  • App.js
import React, { useReducer } from 'react';

import './App.css';

import Header from './components/Header';
import Footer from './components/Footer';
import VideoEditorMain from './components/VideoEditorMain';

const reducer = (state, action) => {
  switch (action.type) {
    case 'ADD_FILE':
      return action.newItem;
    case 'DELETE_FILE':
      return null;
    default:
      return state;
  }
};

export const VideoFileContext = React.createContext();
export const VideoFileDispatchContext = React.createContext();

function App() {
  const [videoFile, dispatch] = useReducer(reducer, null);

  const addFile = file => {
    dispatch({ type: 'ADD_FILE', newItem: file });
  };

  const deleteFile = () => {
    dispatch({ type: 'DELETE_FILE' });
  };

  return (
    <VideoFileContext.Provider value={videoFile}>
      <VideoFileDispatchContext.Provider value={{ addFile, deleteFile }}>
        <div className="App">
          <Header></Header>
          <VideoEditorMain></VideoEditorMain>
          <Footer></Footer>
        </div>
      </VideoFileDispatchContext.Provider>
    </VideoFileContext.Provider>
  );
}

export default App;

 

๋ฆฌ์•กํŠธ Context๋กœ ๋น„๋””์˜ค ํŒŒ์ผ ๋ฐ์ดํ„ฐ์™€ dispatch ํ•จ์ˆ˜๋ฅผ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์— ์ œ๊ณตํ•˜์˜€๋‹ค.

 

 

๋น„๋””์˜ค ์—…๋กœ๋“œ UI ๊ตฌํ˜„

๋น„๋””์˜ค๋ฅผ ์—…๋กœ๋“œํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ input[type=file] ์š”์†Œ๋กœ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

 

์ฒ˜์Œ์—๋Š” ์ด๋ฏธ์ง€๋ฅผ ํด๋ฆญํ•˜๋ฉด useRef๋กœ input ์š”์†Œ๋ฅผ ํด๋ฆญํ•˜๋„๋ก ๊ตฌํ˜„ํ–ˆ๋Š”๋ฐ ๊ตณ์ด ๊ทธ๋Ÿด ํ•„์š”๊ฐ€ ์—†๋‹ค๋Š” ๊ฒƒ์„ ์ฝ”๋“œ๋ฆฌ๋ทฐ๋ฅผ ํ†ตํ•ด ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.

 

 

  • VideoPlaceholder.js
import React from 'react';
import video_placeholder from '../assets/video_placeholder.png';

const VideoPlaceholder = ({ onChange }) => {
  return (
    <section className={'upload-layout'}>
      <label>
        <img
          className={'video-upload-img'}
          src={video_placeholder}
          alt="๋น„๋””์˜ค๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”"
        />
        <input
          className={'video-upload-input'}
          type="file"
          accept="video/*"
          onChange={e => {
            onChange(e.target.files[0]);
          }}
        />
      </label>
    </section>
  );
};

export default VideoPlaceholder;

 

๋ผ๋ฒจ๋กœ img ํƒœ๊ทธ๋ฅผ ๊ฐ์‹ธ์„œ ์ด๋ฏธ์ง€๋ฅผ ํด๋ฆญํ–ˆ์„ ๋•Œ ํŒŒ์ผ ์„ ํƒ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ–ˆ๋‹ค.

 

 

 

 

 

 

๋งˆ๋ฌด๋ฆฌ


ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋‹ˆ๊นŒ ์‹œ๊ฐ„์ด ๋น ๋ฅด๊ฒŒ ํ˜๋Ÿฌ๊ฐ„๋‹ค.

์•„์ง๋„ ๊ตฌํ˜„ํ•ด์•ผ ํ•  ๊ฒŒ ๋งŽ์ด ๋‚จ์•˜๋Š”๋ฐ ์‹œ๊ฐ„ ๋‚ด์— ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์„๊นŒ..