React

 

코지 코더 Kossie Code

 

리액트js (Reactjs) 기초 익히기 기본 강좌

동영상 30개조회수 88,303회최종 업데이트: 2021. 4. 6.

 

https://www.youtube.com/playlist?list=PLB7CpjPWqHOuf62H44TMkMIsqfkIzcEcX

 

vscode  확장 패키지 추가

1. - Auto Import - ES6, TS, JSX, TSX

2. - Reactjs code snippets

3. - ESLint

4. - Prettier - Code formatter

Visual Studio Code 폴더/파일 아이콘 변경하기

 

 

리액트  프로젝트 생성

 npx create-react-app  경로

예) npx create-react-app E:\react-app2

 

 

 

 

클릭 =>  11 .컴포넌트와 props 데이터 보내기 복습

 

 

Movie.js

import React from 'react';

const Movie = ({ movie }) => {

    return (
        
{movie.title}
{movie.year}

); }; export default Movie;

 

 

App.js

import Movie from './components/Movie';


function App() {
  const movies = [
    { id: 1, title: 'kossie coder1', year: 2001 },
    { id: 2, title: 'kossie coder2', year: 2002 },
    { id: 3, title: 'kossie coder3', year: 2003 },
    { id: 4, title: 'kossie coder4', year: 2004 },
  ]

  const renderMovies = movies.map((movie) => {
    return 
  })

  return (

Movie list

{renderMovies}

); } export default App;

 

 

 

 

클릭 =>  12. 영화 리스트 추가 폼 만들기 1

 

App.js

import Movie from './components/Movie';
import { useState } from 'react';


function App() {

  const [movieTitle, setMovieTitle] = useState('');
  const [movieYear, setMovieYear] = useState('');
  const [maxId, setMaxId] = useState(5);


  const [movies, setMovies] = useState(
    [
      { id: 1, title: 'kossie coder1', year: 2001 },
      { id: 2, title: 'kossie coder2', year: 2002 },
      { id: 3, title: 'kossie coder3', year: 2003 },
      { id: 4, title: 'kossie coder4', year: 2004 },
    ]
  );


  const renderMovies = movies.map((movie) => {
    return <Movie key={movie.id} movie={movie} />
  })

  const addMovie = (event) => {
    setMaxId(maxId + 1);

    event.preventDefault();

    setMovies([...movies, {
      id: maxId,
      title: movieTitle,
      year: movieYear
    }])

  }

  return (
    <div className="App">
      <h1>Movie list</h1>
      <form onSubmit={addMovie}>
        <input
          type="text"
          value={movieTitle}
          placeholder='영화제목'
          onChange={e => setMovieTitle(e.target.value)}
        /><br />
        <input
          type="text"
          value={movieYear}
          placeholder='개봉년도'
          onChange={e => setMovieYear(e.target.value)}
        /><br /><br />

        <button>영화추가</button>
      </form><br /><br />
      {renderMovies}
    </div >
  );


}
export default App;

 

 

 

 

 

클릭 =>  13. 영화 리스트 추가 폼 만들기 2

1. 얕은 복사

얕은 복사란, 위의 코드에서 let temp = member;가 얕은 복사의 예시다.
이렇게 하면 temp도 member와 같이 동일한 객체(값){name:"홍길동", age:25, sex:"M"}을 가진다. 하지만 레퍼런스 또한 동일한 레퍼런스를 가진다.

변수가 선언되면 메모리에 적재되는데 얕은 복사를 사용하면 두 변수가 동일한 메모리 주소를 가리키게 된다.

따라서 temp와 member는 동일한 메모리 주소를 가리키므로 temp.name="고길동"을 하면 member.name도 고길동으로 바뀌는 것이다.
이렇게 되면 member라는 state값을 직접적으로 바꾸는 것과 다름이 없다.
그럼 어떻게 해야할까? 깊은 복사 가 답이다 ✔

2. 깊은 복사

동일한 값을 가지지만 다른 메모리 주소를 가리킨다

얕은 복사와는 다르게 다른 레퍼런스를 가진다. 따라서 위의 예로 설명해보면 member를 깊은 복사하여 나온 결과가 temp라면, temp와 member는 서로 전혀 다른 객체지만 같은 값을 가진 것이다.

temp.name="고길동"을 한다고해서 member.name의 값이 바뀌지 않는다

그렇다면 setMember(temp)가 우리가 원하는 대로 잘 동작 할 것이다.

 

 

 

깊은 복사 사용 방법

배열(map, filter, slice, concat, spread연산자)

 

 

1. spread operator 사용하기

아까 사용하였던 person 객체를 한번 더 사용해준다.
대신 이번에는 copiedPerson에 전개연산자를 이용하여 복사해준다.

let copiedPerson={...person};

후에 copiedPerson 의 age 를 할당해주면 원본( person )과 copiedPerson 의 age 가 연결되어있지 않고 서로 다른 값을 가짐을 확인 할 수 있다.

하지만 전개연산자를 활용하여도 두단계 이상의 depth부터는 깊은복사가 이루어지지 않는다..!

무슨 소리냐 하면은,
위에서 age property가 아닌 한단계 더 들어간 language property의 first property를 바꾸려는 시도를 한다면?


copiedPerson 의 language 의 first만 c++로 바꾸려고 하였을 뿐인데, 원본인 person까지 c++로 변경되었음을 확인 할 수 있다.
두단계 이상의 depth부터는 여전히 참조 값을 전달하는 얕은복사를 진행하고 있었다...ㅠㅠ

따라서 한단계까지의 깊은복사만을 이용하려면 전개연산자를 써도 좋을 것 같다.

2. Object.assign() 이용하기

let addPerson= {
  name: 'euneun',
  age: '2',
  language: {first: 'java', second: 'javscript'}
} 

let copiedPerson=Object.assign({}, persons, addPerson);  // 빈 Object에 originObj를 병합하여 반환.

copiedPerson.age=1;

 

실행결과는 아래와 같다.

역시, 원본(person)과 copiedPerson 의 age 가 연결되어있지 않고 서로 다른 값을 가짐을 확인 할 수 있다.

하지만 이 또한 전개연산자와 같이 두단계 이상의 depth부터는 얕은복사가 진행된다..ㅠㅠ

한단계 더 들어간 language 의 first 속성을 바꾸려는 시도를 한다면?

역시 원본인 person까지 c++로 변경되었음을 확인 할 수 있다.

그런데 그렇다고해서 아예 person과 copiedPerson의 주소값이 같아진 것은 아니다!
1단계까지의 깊은복사는 성공했기때문에, 둘의 값을 비교하면 false이다.

따라서 Object.assign() 또한 한단계까지의 깊은복사만을 이용하려면 사용할 수 있을 것 같다.

3. 라이브러리 이용하기 (lodash의 clone deep)

import cloneDeep from lodash/cloneDeep;

const original = { 
  ...
}
const copied = cloneDeep(original);
  
  
copied === original // false
copied.nested === original.nested // false
copied.nested.doubleNested === original.nested.doubleNested // false

4.JSON.stringify() 이용하기

JSON.stringfy()는 객체를 문자열로 변환시켜주는 함수이다.

객체를 json 문자열로 변환하는과정에서 원본 객체와의 참조가 모두 끊어진다.
문자열로 변환후 JSON.parse()를 이용해 다시 객체로 만들어주면 깊은 복사가 된다.

let person = {
  name: 'euneun',
  age: '2',
  language: {first: 'java', second: 'javscript'}
} 


let copiedPerson = JSON.parse(JSON.stringify(person));

copiedPerson.language.first = "c++"

person.language.first === copiedPerson.language.first 

실행결과는 false가 나온다.

하지만 이 방법은 메서드(함수)나 JSON으로 변경할 수 없는 프로퍼티들은 무시하기도하고, 성능면에서 리소스를 많이 잡아먹기때문에 지양하는편이 좋다고 한다...

결론적으로,
Object.assign()과 전개연산자는 객체의 한단계까지의 깊은복사만 가능하고,
그 이상의 depth부터는 여전히 얕은복사가 일어나므로
완벽한 깊은복사를 사용하기 위해서는 lodash 라이브러리를 사용하는 편이 좋을 것 같다!

참조

 

 

 

 

 

 

클릭 =>  14 .자식 컴포넌트에서 부모 컴포넌트 스테이트 변경하기

 

MovieForm.js

import React, { useState } from 'react';

const MovieForm = ({ addMovie }) => {

    const [movieTitle, setMovieTitle] = useState('');
    const [movieYear, setMovieYear] = useState('');
    const [maxId, setMaxId] = useState(5);

    const resetForm = () => {
        setMovieTitle("");
        setMovieYear("");
    }

    const onSubmit = (event) => {
        event.preventDefault();
        setMaxId(maxId + 1);
        addMovie(
            {
                id: maxId,
                title: movieTitle,
                year: movieYear
            }
        );
        resetForm();
    }


    return (
        <form onSubmit={onSubmit}>
            <input
                type="text"
                value={movieTitle}
                placeholder='영화제목'
                onChange={e => setMovieTitle(e.target.value)}
            /><br />
            <input
                type="text"
                value={movieYear}
                placeholder='개봉년도'
                onChange={e => setMovieYear(e.target.value)}
            /><br /><br />

            <button>영화추가</button>
        </form>
    );

};

export default MovieForm;

 

App.js

import Movie from './components/Movie';
import { useState } from 'react';
import MovieForm from './components/MovieForm';


function App() {

  const [movies, setMovies] = useState(
    [
      { id: 1, title: 'kossie coder1', year: 2001 },
      { id: 2, title: 'kossie coder2', year: 2002 },
      { id: 3, title: 'kossie coder3', year: 2003 },
      { id: 4, title: 'kossie coder4', year: 2004 },
    ]
  );


  const renderMovies = movies.map((movie) => {
    return <Movie key={movie.id} movie={movie} />
  })

  const addMovie = (movie) => {
    setMovies([...movies, movie]);
  }

  return (
    <div className="App">
      <h1>Movie list</h1>

      <MovieForm addMovie={addMovie} />

      <br /><br />
      {renderMovies}
    </div >
  );


}
export default App;

 

 

 

 

클릭 =>  15 .영화 삭제하기

 

 

 

Movie.js

import React from 'react';

const Movie = ({ movie, removeMovie }) => {

    return (
        <div className='movie' id={movie.id}>
            <div className='movie-title'>{movie.title}
                <span className='movie-year'>{movie.year}</span>
            </div>
            <div>
                <button onClick={() => removeMovie(movie.id)}>
                    삭제
                </button>
            </div>
        </div>
    );
};

export default Movie;

 

MovieForm.js

import React, { useState } from 'react';

const MovieForm = ({ addMovie }) => {

    const [movieTitle, setMovieTitle] = useState('');
    const [movieYear, setMovieYear] = useState('');


    const resetForm = () => {
        setMovieTitle("");
        setMovieYear("");
    }

    const onSubmit = (event) => {
        event.preventDefault();
        addMovie(
            {
                id: new Date(),
                title: movieTitle,
                year: movieYear
            }
        );
        resetForm();
    }


    return (
        <form onSubmit={onSubmit}>
            <input
                type="text"
                value={movieTitle}
                placeholder='영화제목'
                onChange={e => setMovieTitle(e.target.value)}
            /><br />
            <input
                type="text"
                value={movieYear}
                placeholder='개봉년도'
                onChange={e => setMovieYear(e.target.value)}
            /><br /><br />

            <button>영화추가</button>
        </form>
    );

};

export default MovieForm;

 

 

App.js

import Movie from './components/Movie';
import { useState } from 'react';
import MovieForm from './components/MovieForm';


function App() {

  const [movies, setMovies] = useState([]);

  const removeMovie = (id) => {
    setMovies(movies.filter((movie) => movie.id !== id));
  }

  const renderMovies = movies.length ? movies.map((movie) => {
    return <Movie key={movie.id} movie={movie} removeMovie={removeMovie} />
  }) : "추가된 영화가 없습니다. ";


  const addMovie = (movie) => {
    setMovies([...movies, movie]);
  }



  return (
    <div className="App">
      <h1>Movie list</h1>

      <MovieForm addMovie={addMovie} />

      <br /><br />
      {renderMovies}
    </div >
  );


}
export default App;

 

 

 

 

 

 

클릭 =>  16.영화 폼 validation

 

 

MovieForm.js

import React, { useState } from 'react';

const MovieForm = ({ addMovie }) => {

    const [movieTitle, setMovieTitle] = useState('');
    const [movieYear, setMovieYear] = useState('');
    const [titleError, setTitleError] = useState('');
    const [yearError, setYearError] = useState('');

    const resetForm = () => {
        setMovieTitle("");
        setMovieYear("");
    }

    const resetError = () => {
        setTitleError("");
        setYearError("");
    }

    const validationForm = () => {
        resetError();
        let validated = true;
        if (!movieTitle) {
            setTitleError("영화제목을 입력해 주세요.");
            validated = false;
        }

        if (!movieYear) {
            setYearError("개봉년도를 입력해 주세요.");
            validated = false;
        }

        return validated;
    }

    const onSubmit = (event) => {
        event.preventDefault();
        if (validationForm()) {
            addMovie(
                {
                    id: new Date(),
                    title: movieTitle,
                    year: movieYear
                }
            );
            resetError();
            resetForm();
        }
    }

    return (
        <form onSubmit={onSubmit}>
            <input
                type="text"
                value={movieTitle}
                placeholder='영화제목'
                onChange={e => setMovieTitle(e.target.value)}
            /><br />
            <div style={{ color: "red" }} >{titleError}</div>
            <input
                type="date"
                value={movieYear}
                placeholder='개봉년도'
                onChange={e => setMovieYear(e.target.value)}
            />
            <br /><div style={{ color: "red" }}> {yearError}</div >

            <br /><br />

            <button>영화추가</button>
        </form >
    );

};

export default MovieForm;

 

 

 

 

클릭 =>  17. InputField 컴포넌트로 빼내기 & Fragment

 

InputField.js

import React from 'react';

const InputField = ({
    type,
    value,
    placeholder,
    onChange,
    errorMessage

}) => {
    return (
        <>
            <input
                type={type}
                value={value}
                placeholder={placeholder}
                onChange={onChange}
            /><br />
            <div style={{ color: "red" }} >{errorMessage}</div>
        </>
    );
};

export default InputField;

 

 

MovieForm.js

import React, { useState } from 'react';
import InputField from './InputField';

const MovieForm = ({ addMovie }) => {

    const [movieTitle, setMovieTitle] = useState('');
    const [movieYear, setMovieYear] = useState('');
    const [titleError, setTitleError] = useState('');
    const [yearError, setYearError] = useState('');

    const resetForm = () => {
        setMovieTitle("");
        setMovieYear("");
    }

    const resetError = () => {
        setTitleError("");
        setYearError("");
    }

    const validationForm = () => {
        resetError();
        let validated = true;
        if (!movieTitle) {
            setTitleError("영화제목을 입력해 주세요.");
            validated = false;
        }

        if (!movieYear) {
            setYearError("개봉년도를 입력해 주세요.");
            validated = false;
        }

        return validated;
    }

    const onSubmit = (event) => {
        event.preventDefault();
        if (validationForm()) {
            addMovie(
                {
                    id: new Date(),
                    title: movieTitle,
                    year: movieYear
                }
            );
            resetError();
            resetForm();
        }
    }

    return (
        <form onSubmit={onSubmit}>

            <InputField
                type="text" value={movieTitle} placeholder='영화제목'
                onChange={e => setMovieTitle(e.target.value)}
                errorMessage={titleError}
            />

            <InputField
                type="date" value={movieYear} placeholder='개봉년도'
                onChange={e => setMovieYear(e.target.value)}
                errorMessage={yearError}
            />

            <br /><br />

            <button>영화추가</button>
        </form >
    );

};

export default MovieForm;

 

 

 

 

 

 

 

 

클릭 =>  18. React router 설치하기

 

bootstrap cdn   => https://www.bootstrapcdn.com/

 

react router => https://reactrouter.com/en/main/getting-started/overview

 

npm install react-router-dom@6

 

 

 

 

 

 

 

클릭 =>  19. 네비게이션 바 만들기

 

 

 

 

 

https://getbootstrap.com/docs/4.6/components/navbar/

 

 

Navbar.js

/* eslint-disable jsx-a11y/anchor-is-valid */
import React from 'react';

const Navbar = () => {
    return (
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
            <a class="navbar-brand" href="#">Home</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse"
                data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
                <div class="navbar-nav">
                    <a class="nav-link active" href="#">Movies <span class="sr-only">(current)</span></a>
                    <a class="nav-link" href="#">Users</a>
                </div>
            </div>
        </nav>
    );
};

export default Navbar;

 

 

 

 

 

 

 

 

클릭 =>  20.react router 적용하기

 

 

 

 

 

 

 

index.js 에서

<BrowserRouter>
      <App />
    </BrowserRouter>

 

PageMovies.js 생성

import React from 'react';
import MovieForm from '../components/MovieForm';

const PageMovies = ({ addMovie, renderMovies }) => {
    return (
        <div>
            <h1>Movie list</h1>
            <MovieForm addMovie={addMovie} />
            <br /><br />
            {renderMovies}
        </div>
    );
};

export default PageMovies;

 

App.js

import Movie from './components/Movie';
import { useState } from 'react';
import MovieForm from './components/MovieForm';
import Navbar from './components/Navbar';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import PageMovies from './pages/PageMovies';



function App() {

  const [movies, setMovies] = useState([]);

  const removeMovie = (id) => {
    setMovies(movies.filter((movie) => movie.id !== id));
  }

  const renderMovies = movies.length ? movies.map((movie) => {
    return <Movie key={movie.id} movie={movie} removeMovie={removeMovie} />
  }) : "추가된 영화가 없습니다. ";


  const addMovie = (movie) => {
    setMovies([...movies, movie]);
  }

  return (


    <div className="App">
      <Navbar />

      <Routes>
        <Route path='/' element={<h1>Home</h1>}></Route>
        <Route path='/movies' element={<PageMovies addMovie={addMovie} renderMovies={renderMovies} />}></Route>
        <Route path='/users' element={<h1>Users</h1>}></Route>
      </Routes>

    </div >


  );


}
export default App;

 

 

Navbar.js

/* eslint-disable jsx-a11y/anchor-is-valid */
import React from 'react';
import { Link, Routes, Route, Outlet } from "react-router-dom";

const Navbar = () => {
    return (

        <nav className="navbar navbar-expand-lg navbar-light bg-light">
            <Link className="navbar-brand" to="/">Home</Link>
            <button className="navbar-toggler" type="button" data-toggle="collapse"
                data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
                <span className="navbar-toggler-icon"></span>
            </button>
            <div className="collapse navbar-collapse" id="navbarNavAltMarkup">
                <div className="navbar-nav">
                    <Link className="nav-link active" to="/movies">Movies <span className="sr-only">(current)</span></Link>
                    <Link className="nav-link" to="/users">Users</Link>
                </div>
            </div>
        </nav>
    );
};

export default Navbar;

 

 

 

 

 

 

소스 :  https://github.com/braverokmc79/Rreact-KossieCoder

 

 

 

 

 

 

react

 

about author

PHRASE

Level 60  라이트

우리들 대부분은 초라한 옷차림과 엉터리 가구들을 부끄럽게 여기지만, 그보다는 초라한 생각과 엉터리 철학을 부끄럽게 여길 줄 알아야 한다. -아인슈타인

댓글 ( 4)

댓글 남기기

작성