본문 바로가기
Study/FrontEnd

React

by 왕방개 2024. 4. 26.

1.개요

1)SPA(Single Page Application)

=>html 파일 1개로 동작하는 웹 애플리케이션
=>여러 개의 html로 구성된 웹 애플리케이션(Multi Page Application)은 페이지 전환을 할 때 이전 페이지를 제거하고 새로운 페이지를 랜더링을 하게 되는데 이 과정에서 깜빡임이 발생
=>SPA를 이용하면 위 와 같은 깜빡임을 제거할 수 있습니다.
하나의 html로 구성되기 때문에 새로운 페이지를 랜더링하지 않음
=>이런 SPA 프레임워크로 angular, vue, react 가 있음

 

2)Template Engine

=>웹 서버 프레임워크는 대부분 HTML 템플릿 엔진을 제공
=>HTML 템플릿 엔진은 서버에서 제공하는 데이터 와 html 태그를 결합해서 웹 클라이언트에게 출력 내용을 전달
HTML 템플릿 엔진의 결과물은 HTML
=>react 와 SPA 프레임워크도 템플릿 엔진 과 유사하게 동작을 하는데 데이터를 HTML 과 결합해서 출력 내용을 만들어 주는데 결과물이 HTML이 아니고 DOM 객체

 

3)react
=>2013년 페이스북에서 만든 오픈 소스 자바스크립트 프레임워크
A Javascript library for building user interfaces
=>Virtual DOM 과 JSX 라는 문법을 결합시켜 동작하는 프레임워크
=>게임 엔진의 원리를 이용해서 출력
게임 엔진이 화면 출력을 하는 방식은 게임은 전체 화면이 전부 변경되는 경우가 드물기 때문에 전체 화면을 메모리에 만들어두고 출력한 후 변경이 발생하면 변경된 부분만 재출력
react도 출력할 내용을 메모리에 먼저 저장을 하는데 이를 Virtual DOM 이라고 하고 처음 출력할 때는 Virtual DOM 전체를 DOM으로 변환해서 출력을 하고 다음 출력부터는 전체 화면을 재출력하지 않고 변경된 부분만 출력
=>오직 View 만을 위한 라이브러리
View를 제외한 모든 기능은 react 가 처리할 수 없음
라우팅을 할 때는 react router를 데이터를 받아올 때는 ajax, fetch API 또는 axios 라이브러리를 이용
원하는 라이브러리를 마음대로 가져다 사용한다는 장점은 있지만 여러 라이브러리를 알아야하는 단점도 있음

 

4)작업 환경 설정
=>node를 설치: https://nodejs.org/ko/download/
=>작업을 편리하게 하고자 하는 경우 yarn 설치: npm install yarn
=>webpack
- 프로젝트에 사용된 파일을 분석해서 웹에서 사용가능한 문서 파일로 변환하는 도구
- JavaScript로 코드를 작성하는데 웹 화면은 HTML 과 CSS만 출력이 가능
스타일에 관련된 내용을 sass 확장자를 가진 파일로 만들면 webpack이 css로 변환을 해줍니다.
=>babel: ECMA2015 이후 버전의 문법을 사용해서 코드를 작성했을 때 모든 브라우저에서 실행이 가능하도록 ECMA2015 버전으로 변환해주는 Javascript 트랜스 컴파일러
=>타입스크립트 사용: npm install typescript ts-node

 

5)프로젝트 생성
npx create-react-app 앱이름

yarn create react-app 앱이름

=>타입스크립트를 사용하고자 하는 경우는 뒤에 --template typescript를 추가

 

6)프로젝트 실행
=>npm start
=>기본적으로 3000번 포트를 이용해서 실행

7)프로젝트 구조
=>node_modules
 - 노드 패키지가 설치되는 디렉토리
 - 코드 공유를 위해서 코드 레포지토리에 업로드는 하는 경우 이 디렉토리는 제외
 - npm install 이라는 명령을 사용하면 package.json에 설정된 의존성을 가지고 다시 설치
 - 프로젝트를 생성하면 git 디렉토리가 자동으로 생성되고 .gitignore가 만들어지는데 거기에 이미 node_modules는 포함되어 있으므로 git 관련 레포지토리에서는 별도의 작업을 하지 않아도 가능

=>public
 - 정적 파일이 저장되는 디렉토리
 - index.html 파일이 실제로 출력되는 파일
 - manifest.json: 앱에 대한 정보를 저장하고 있는 json 파일
 - robots.txt: 검색 엔진이 이 사이트를 조회해서 되는지 또는 조회하면 안되는지 여부를 설정하는 파일

=>src(source)
 - 프로그래밍의 대상이 되는 파일 이나 디렉토리를 배치되는 디렉토리
 - App.js: 첫 화면에 보여지는 컴포넌트
 - App.css: App.js에 대한 스타일이 저장된 파일
 - App.test.js: App을 테스트하기 위한 파일
 - index.css: Entry Point의 css
 - index.js: Entry Point
 - reportWebVitals.js: 성능 측정을 위한 파일로 console.log를 이용하면 개발 창을 통해서 앱의 퍼포먼스 시간을 분석해서 object 형태로 출력
 - setupTests.js: 테스트를 위한 준비 작업을 수행하는 파일

=> package.json: 노드 애플리케이션의 설정 파일
  - scripts 부분에 단축 명령어 설정을 합니다.
  - 프로젝트를 코드 레포지토리에 업로드 할 때는 src 디렉토리 와 package.json 만 업로드되면 됩니다.

 

8)npm run build - yarn build
=>배포 가능한 형태로 변환 - 실제 출력되는 HTML을 생성

 

2.React Framework 구성요소

=>Virtual DOM
 - Markup Language: 사람이 쉽게 작성하고 컴퓨터도 쉽게 의미를 파악할 수 있는 텍스트
 - DOM(Document Object Model): HTML에 출력되는 모든 객체, 태그
 - react가 HTML에 출력하기 전에 메모리에 출력하는 DOM
 - 작성을 JSX라는 마크업 언어를 이용할 수 있음

=>JavaScript XML
 - JavaScript에 XML을 추가한 확장 문법
 - XML 구문에 { }를 추가해서 Javascript 코드를 입력
 - 이 코드는 babel에 의해서 javascript로 변환됨

=>Component
 - 하나의 화면에 보여지는 재사용 가능한 객체
 - 자바스크립트 코드 만으로 만들 수 있고 JSX 문법을 이용해서 생성하는 것도 가능

=>react & react-dom 패키지
 - react 프로젝트는 항상 react 와 react-dom 패키지가 필요
 - react는 react 앱이 동작하는 환경과 무관하게 공통으로 사용하는 기능을 제공하는 패키지
 - react-dom/client, react-dom/server, react-dom/native 는 renderer 라는 패키지로 앱이 동작하는 환경에 종속적인 기능을 제공하는 패키지

 

3.JSX

1)장점
let name = 'adam';

//JSX를 사용하지 않고 컴포넌트를 생성
React.createElement('div', null, 'Hello ${name}');

//JSX를 사용
<div>Hello {name} </div>

=>코드가 간결해짐
=>가독성이 높아짐
=>JSX에 오류가 있으면 바벨이 코드를 변환하는 과정에서 이를 감지
=>Injection Attack 방어
 - Injection Attack: 입력 창에 문자 나 숫자 같은 일반적인 값이 아닌 소스 코드를 입력해서 해당 코드가 실행되도록 하는 것

 - SQL Injection: SQL 구문을 입력해서 공격하는 방법

 - react는 자바스크립트 코드를 직접 사용하지 않으므로 입력된 내용이 소스 코드라 하더라도 이를 일반 문자열로 변환하기 때문에 Injection Attack 이 불가능

 

2)규칙
- 모든 태그는 반드시 닫아야 합니다.
- 주석
 일반적인 주석은 {/* 주석 내용 */}
 닫는 태그가 없는 경우에는 // 나 /* */를 사용할 수 있는데 /> 이 부분이 다음 줄에 나와야 함
 <input //주석 /> 이렇게 작성하면 에러

<input //주석 
/>

- Component는 반드시 하나의 root를 가져야 함: 최상위 태그는 하나

- 자바스크립트 코드는 반드시 { }로 감싸야 합니다.
- undefined는 출력하지 않음

 

4.Hooks

1)개요
=>함수형 컴포넌트에서 클래스형 컴포넌트의 요소를 사용하기 위한 기술
=>Class 안에는 속성을 가질 수 있어서 속성을 이용해서 각 인스턴스의 특성을 유지할 수 있는데 함수는 호출되고 나면 소멸되어 버리기 때문에 데이터 유지가 안됩니다.
=>Class 형 컴포넌트에서는 수명주기를 메서드를 사용할 수 있는데 함수는 기본적으로 수명주기라는 표현을 사용하지 않습니다.
=>예전에는 클래스형 컴포넌트로 주로 작업을 했는데 Hook이 등장하면서 함수형 컴포넌트를 이용해서 주로 작업

 

2)종류
=>데이터 관리
useState
useMemo
useCallback
useReducer

=>수명주기 관리
useEffect
useLayoutEffect

=>컴포넌트 메서드
useRef
useImperativeHandle

=>정보 공유
useContext

 

3)useState
=>state는 react에서 Component가 소유한 변경 가능한 데이터
=>redering 이나 DataFlow에 사용되는 데이터만 state에 포함시켜야 함
state가 변경되면 랜더링을 다시 수행합니다.
=>사용 형식
const [변수명, setter 이름] = useState(초기값)
- 변수명으로 state가 생성되고 state를 수정할 수 있는 setter 함수가 생성되서 리턴됩니다.
- 변수를 직접 변경시키는 것은 의미가 없는데 react에서는 모든 state를 기본적으로 변경 불가능하도록 만들고 setter를 통해서만 변경가능하도록 해줍니다.

=>기본적인 사용법 - FunctionStateComponent.jsx

import {useState} from 'react';

//컴포넌트 이름은 반드시 대문자로 시작해야 합니다.
const FunctionStateComponent = (props) => {
    //함수 내에서 변경 가능한 데이터 만들기
    const [count, setCount] = useState(0);

    return (
        <>
            <p>데이터: {count}</p>

            <button onClick={(e)=>{
                //react에서 만들어지는 state는 불변 객체
                //count = count + 1;

                setCount(count + 1);
            }}>버튼</button>
        </>
    );
}

export default FunctionStateComponent;

=>입력 내용을 state에 계속 저장하기
- 입력을 받을 때는 받는 도중 state의 값을 계속해서 변경을 합니다.
- react를 사용하지 않는 web client application에서는 편집 중에 있는 값은 저장하지 않고 전송하거나 필요할 때 DOM 객체로부터 가져와서 사용합니다.
react에서는 일반적으로 input 객체에 state를 연결해서 값이 변경될 때 마다 저장합니다.
이렇게 하는 이유는 값을 변경할 때 유효성 검사같은 로직을 수행하기 위해서입니다.
비밀번호나 아이디를 생성하기 위해서 입력할 때 유효성 검사를 수행하는 경우가 최근에는 많음
입력 도구 들의 onChange 이벤트를 이용합니다.

 

=>2개의 입력 상자를 만들고 입력된 내용을 state에 계속 저장하고 출력하기: Info.jsx

import {useState} from "react";

const Info = (props) => {
    const [name, setName] = useState('');
    const [nickname, setNickname] = useState('');

    //이벤트 처리 함수
    const onChangeName = (e) => {
        //이벤트가 발생한 객체의 값을 name에 설정
        setName(e.target.value);
    }

    const onChangeNickname = (e) => {
        setNickname(e.target.value);
    }

    return(
        <>
            <div>
                <input name="name" type="text" value={name}
                onChange={onChangeName}/>
                <input name="nickname" type="text" value={nickname}
                onChange={onChangeNickname}/>
            </div>
            <div>
                <p>이름:{name.length>1 ? name : "이름이 너무 짧음"}</p>
                <p>닉네임:{nickname}</p>
            </div>
        </>
    );
}

export default Info;

 

=>서버로 부터 받은 데이터나 컴포넌트 내부에서 사용하는 데이터를 만들 때 useState를 사용

 

4)useEffect
=>state가 변경된 후 수행할 side effect를 설정하는 Hook
=>수명주기 관련 메서드
=>state가 변경된 후 호출되는 함수이므로 state를 사용하는 것이 가능
=>react의 class 수명주기 메서드 중에서 componentDidMount 와 componentDidUpdate 그리고 componentWillUnmount 메서드의 결합과 유사
=>사용 형식
useEffect(함수[, 의존성 배열])
 - 의존성 배열을 생략하면 모든 state가 변경될 때 호출
 - 의존성 배열에 빈 배열([ ])을 설정하면 맨 처음 한 번만 호출
 - 의존성 배열에 데이터를 설정하면 데이터가 변경된 경우에만 호출됩니다. 
=>useEffect도 여러 번 정의 가능
=>의존성 배열에 빈 배열을 사용하는 경우 함수를 리턴하면 이 컴포넌트가 제거될 때 호출되는 cleanup 함수를 만들 수 있습니다.

 

=>useEffect 사용 - FunctionEffect.jsx

import {useState, useEffect } from "react";


const FunctionEffect = (props) => {
    const [n1, setN1] = useState(0);
    const [n2, setN2] = useState(0);

    //의존성 배열을 설정하지 않음: state가 변경될 때 마다 무조건 호출
    useEffect(()=>{
        console.log("state가 변경되면 무조건 호출");
    })

    useEffect(()=>{
        console.log("처음 한 번 만 호출");
        return (() => {
            console.log("이 컴포넌트가 제거될 때 호출");
        })
    }, [])

    useEffect(()=>{
        console.log("n1이 변경된 경우에 만 호출");
    }, [n1])

    return (
        <>
            <button onClick={(e) => {
                setN1(n1 + 1);
            }}>버튼1</button>
            <button onClick={(e) => {
                setN2(n2 + 1);
            }}>버튼2</button>
        </>
    );
}

export default FunctionEffect;

 

=>마운트 할 때 수행할 작업
- props로 넘겨받은 데이터를 로컬에 저장
- 처음에 사용할 외부 데이터를 다운로드
- 타이머 생성
- 라이브러리 사용을 위한 인스턴스 생성

=>언마운트 할 때 수행할 작업
- 타이머 제거
- 라이브러리 인스턴스 제거

=>useLayoutEffect
- useEffect는 비동기적으로 동작하는데 useLayoutEffect는 동기적으로 동작
- useEffect가 동작하는 동안 다른 작업이 가능하지만 useLayoutEffect가 동작하는 동안에는 다른 작업을 수행할 수 없습니다.

5)useRef
=>일반 변수를 만드는 기능인데 이를 적절히 활용하면 다른 컴포넌트를 참조할 수 있습니다.
=>state는 수정이 되면 화면을 다시 출력하지만 ref는 화면을 다시 출력하지 않습니다.
=>생성
const refContainer = useRef(초기값);
- 생성한 후 current 라는 속성을 호출하면 값이 리턴됩니다.
- 참조하고자 하는 컴포넌트가 존재하면 그 컴포넌트의 ref 속성에 대입하면 생성된 변수가 컴포넌트를 가리키게 됩니다.

=>버튼을 누르면 특정 Input에 포커스를 설정: FunctionRef.jsx

import {useRef} from 'react';

const FunctionRef = (props) => {
    //일반 변수 생성
    //이 변수는 컴포넌트의 ref 속성에 할당하면 컴포넌트를 가리키게 됨
    const txt1 = useRef();    

    const onInit = (e) => {
        txt1.current.focus();
    }

    return (
        <>
            <input type="text" id="txt1" ref={txt1}/>
            <input type="text" id="txt2"/>
            <button id="btn" onClick={onInit}>버튼</button>
        </>
    );
}

export default FunctionRef;

 

6)state 와 ref 그리고 effect 사용
=>유저 목록 출력
- User 목록을 출력할 UserList 컴포넌트를 생성

//하나의 데이터를 출력할 컴포넌트
const User = ({user}) => {
    return (
        <div>
            <b>{user.username}</b> <span>({user.email})</span>
        </div>
    )
}

const UserList = ({users}) => {
    return(
        <div>
            {users.map(user => (
                <User user={user} key={user.id} />
            ))}
        </div>
    )
}

export default UserList;


- App.js를 수정

import FunctionStateComponent from "./components/FunctionStateComponent";
import Info from "./components/Info";
import FunctionEffect from "./components/FunctionEffect";
import FunctionRef from "./components/FunctionRef";


import UserList from "./components/UserList";

function App() {
  const users = [
    {id:1, username:"시모나", email:"simona@kakao.com"},
    {id:2, username:"카푸치노", email:"cappuccino@kakao.com"},
    {id:3, username:"핑크루비", email:"pinkruby@kakao.com"},
  ]

  return (
    <>
     <UserList users={users} />
    </>
  );
}

export default App;

 

=>데이터 추가를 구현
- CreateUser.jsx 파일을 만들어서 추가 컴포넌트를 생성

//이름 과 이메일 저장할 state 와 input의 내용이 변경될 때 호출될 함수
//그리고 버튼을 눌렀을 때 호출될 함수를 매개변수로 전달받습니다.
const CreateUser = ({username, email, onChange, onCreate}) => {
    return (
        <div>
            <input type="text" name="username" placeholder="이름"
            onChange={onChange} value={username}/>
            <input type="text" name="email" placeholder="이메일"
            onChange={onChange} value={email}/>
            <button onClick={onCreate}>등록</button>
        </div>
    );
}

export default CreateUser;

 

- App.js 수정

import React, {useState, useRef} from 'react';

import UserList from "./components/UserList";
import CreateUser from "./components/CreateUser";

function App() {
  /*
  const [username, setUsername] = useState('');
  const [email, setEmail] = useState('');
  */

  //state를 여러 개 생성하고 구조 분해 할당을 이용해서 하나씩 분할
  const [inputs, setInputs] = useState({
    username:'',
    email:''
  });

  const {username, email} = inputs;

  const onChange = (e) => {
    //name에는 e.target.name이 대입되고 value에는 e.target.value가 대입됨
    const {name, value} = e.target;
    //이름에 해당하는 속성에 값을 대입
    setInputs({
      ...inputs,
      [name]:value
    });
  }


  const [users, setUsers] = useState([
    {id:1, username:"시모나", email:"simona@kakao.com"},
    {id:2, username:"카푸치노", email:"cappuccino@kakao.com"},
    {id:3, username:"핑크루비", email:"pinkruby@kakao.com"},
  ]);

  //id를 설정하기 위한 변수
  const nextId = useRef(4);

  //데이터를 추가해주는 함수
  const onCreate = () => {
    const user = {
      id: nextId.current,
      username,
      email
    }
    //데이터를 추가할 때 기본 배열을 복사하고 새로운 데이터 와 결합
    //react에서는 배열에 직접 데이터를 추가하거나 배열에서 삭제하는 것은 안됨
    //react의 모든 데이터는 불변 객체이므로 복사해서 작업한 후 그 복사본을 다시
    //대입해서 이용
    setUsers([...users, user]);

    //id는 1 증가
    nextId.current += 1;
    //입력 란을 초기화
    setInputs({
      username:'',
      email:''
    })

  }

  return (
    <>
     <CreateUser username={username} email={email} onChange={onChange}
     onCreate={onCreate} />
     <UserList users={users} />
    </>
  );
}

export default App;

- react에서 배열이나 객체를 사용할 때 변경 불가능하다것을 기억해야 합니다.

=>유저를 더블 클릭해서 삭제 구현
- App.js 파일에 삭제를 위한 함수를 만들고 이 함수를 UserList에게 넘기기

import React, {useState, useRef} from 'react';

import UserList from "./components/UserList";
import CreateUser from "./components/CreateUser";

function App() {
  /*
  const [username, setUsername] = useState('');
  const [email, setEmail] = useState('');
  */

  //state를 여러 개 생성하고 구조 분해 할당을 이용해서 하나씩 분할
  const [inputs, setInputs] = useState({
    username:'',
    email:''
  });

  const {username, email} = inputs;

  const onChange = (e) => {
    //name에는 e.target.name이 대입되고 value에는 e.target.value가 대입됨
    const {name, value} = e.target;
    //이름에 해당하는 속성에 값을 대입
    setInputs({
      ...inputs,
      [name]:value
    });
  }


  const [users, setUsers] = useState([
    {id:1, username:"시모나", email:"simona@kakao.com"},
    {id:2, username:"카푸치노", email:"cappuccino@kakao.com"},
    {id:3, username:"핑크루비", email:"pinkruby@kakao.com"},
  ]);

  //id를 설정하기 위한 변수
  const nextId = useRef(4);

  //데이터를 추가해주는 함수
  const onCreate = () => {
    const user = {
      id: nextId.current,
      username,
      email
    }
    //데이터를 추가할 때 기본 배열을 복사하고 새로운 데이터 와 결합
    //react에서는 배열에 직접 데이터를 추가하거나 배열에서 삭제하는 것은 안됨
    //react의 모든 데이터는 불변 객체이므로 복사해서 작업한 후 그 복사본을 다시
    //대입해서 이용
    setUsers([...users, user]);

    //id는 1 증가
    nextId.current += 1;
    //입력 란을 초기화
    setInputs({
      username:'',
      email:''
    })

  }


  //데이터 삭제를 위한 함수
  const onRemove = (id) => {
    //넘겨받은 id 와 id가 일치하지 않은 데이터만 추출
    //filter 함수는 복제를 한 후 생성해서 리턴하기 때문에 기존 데이터를
    //수정하지 않음
    setUsers(users.filter(user => user.id != id));
  }


  return (
    <>
     <CreateUser username={username} email={email} onChange={onChange}
     onCreate={onCreate} />
     <UserList users={users} onRemove={onRemove}/>
    </>
  );
}

export default App;

 

=>UserList.jsx 파일을 수정

//하나의 데이터를 출력할 컴포넌트
const User = ({user, onRemove}) => {
    return (
        <div onDoubleClick={(e) => onRemove(user.id)}>
            <b>{user.username}</b> <span>({user.email})</span>
        </div>
    )
}

const UserList = ({users, onRemove}) => {
    return(
        <div>
            {users.map(user => (
                <User user={user} key={user.id} onRemove={onRemove} />
            ))}
        </div>
    )
}

export default UserList;

 

=>배열에 데이터 추가 와 삭제
- react는 state를 다룰 때 setter를 이용해서 편집을 하게 되는데 state의 데이터가 배열이나 객체이면 직접 핸들링 할 수 없고 복제를 해서 작업을 수행합니다.
배열이나 객체를 쉽게 복제하는 방법은 ...이름
배열의 경우 map 이나 filter 함수를 이용하면 복제를 해서 작업을 수행한 후 수행 결과를 가지고 새로운 만들어서 리턴합니다.

 

=>User의 계정 부분을 클릭하면 색상을 토글: 배열의 데이터 수정

- App.js 파일 수정

import React, {useState, useRef} from 'react';

import UserList from "./components/UserList";
import CreateUser from "./components/CreateUser";

function App() {
  /*
  const [username, setUsername] = useState('');
  const [email, setEmail] = useState('');
  */

  //state를 여러 개 생성하고 구조 분해 할당을 이용해서 하나씩 분할
  const [inputs, setInputs] = useState({
    username:'',
    email:''
  });

  const {username, email} = inputs;

  const onChange = (e) => {
    //name에는 e.target.name이 대입되고 value에는 e.target.value가 대입됨
    const {name, value} = e.target;
    //이름에 해당하는 속성에 값을 대입
    setInputs({
      ...inputs,
      [name]:value
    });
  }


  //데이터를 만드는 부분
  const [users, setUsers] = useState([
    {id:1, username:"시모나", email:"simona@kakao.com", active: true},
    {id:2, username:"카푸치노", email:"cappuccino@kakao.com", active: false},
    {id:3, username:"핑크루비", email:"pinkruby@kakao.com", active:false},
  ]);

  //데이터 수정을 위한 함수: id를 매개변수로 받아서 id에 해당하는 데이터의
  //active 값을 토글하는 함수
  //react에서 state는 setter로 수정해야 하고
  //배열이나 객체는 복제해서 작업을 수행한 후 다시 대입
  const onToggle = (id) => {
    setUsers(
      users.map((user) => user.id === id ? {...user, active:!user.active} : user
      )
    )
  }

  //id를 설정하기 위한 변수
  const nextId = useRef(4);

  //데이터를 추가해주는 함수
  const onCreate = () => {
    const user = {
      id: nextId.current,
      username,
      email
    }
    //데이터를 추가할 때 기본 배열을 복사하고 새로운 데이터 와 결합
    //react에서는 배열에 직접 데이터를 추가하거나 배열에서 삭제하는 것은 안됨
    //react의 모든 데이터는 불변 객체이므로 복사해서 작업한 후 그 복사본을 다시
    //대입해서 이용
    setUsers([...users, user]);

    //id는 1 증가
    nextId.current += 1;
    //입력 란을 초기화
    setInputs({
      username:'',
      email:''
    })

  }


  //데이터 삭제를 위한 함수
  const onRemove = (id) => {
    //넘겨받은 id 와 id가 일치하지 않은 데이터만 추출
    //filter 함수는 복제를 한 후 생성해서 리턴하기 때문에 기존 데이터를
    //수정하지 않음
    setUsers(users.filter(user => user.id != id));
  }


  return (
    <>
     <CreateUser username={username} email={email} onChange={onChange}
     onCreate={onCreate} />
     <UserList users={users} onRemove={onRemove} onToggle={onToggle}/>
    </>
  );
}

export default App;

 

- UserList.jsx 파일을 수정

//하나의 데이터를 출력할 컴포넌트
const User = ({user, onRemove, onToggle}) => {
    return (
        <div onDoubleClick={(e) => onRemove(user.id)}>
            <b style={{
                cursor: 'pointer',
                color: user.active ? 'green' : 'black'
            }}
            onClick = {(e) => onToggle(user.id)}
            >{user.username}</b> <span>({user.email})</span>
        </div>
    )
}

const UserList = ({users, onRemove, onToggle}) => {
    return(
        <div>
            {users.map(user => (
                <User user={user} key={user.id} onRemove={onRemove} 
                onToggle={onToggle}/>
            ))}
        </div>
    )
}

export default UserList;

 

=>state가 배열 일 때 세부 데이터를 수정할 때는 map 함수를 이용
map 함수를 이용해서 수정하고자 하는 데이터를 찾아서 그 데이터만 수정하면 됩니다.

=>User 컴포넌트가 화면에 보여지기 직전에 단 한번만 수행하는 로직 과 소멸될 때 수행되는 로직을 작성: useEffect를 이용하는데 의존성 배열을 빈 배열을 설정하면 됩니다.(의존성 배열을 생략하면 이 경우는 모든 state가 변경될 때 호출)

 

- UserList.jsx 파일 수정

import {useEffect} from 'react';

//하나의 데이터를 출력할 컴포넌트
const User = ({user, onRemove, onToggle}) => {
    useEffect(() =>{
        console.log("화면에 보여질 때 한번 만 호출");
        return(() => {
            console.log("소멸될 때 호출되는 코드");
        })
    }, []);

    return (
        <div onDoubleClick={(e) => onRemove(user.id)}>
            <b style={{
                cursor: 'pointer',
                color: user.active ? 'green' : 'black'
            }}
            onClick = {(e) => onToggle(user.id)}
            >{user.username}</b> <span>({user.email})</span>
        </div>
    )
}

const UserList = ({users, onRemove, onToggle}) => {
    return(
        <div>
            {users.map(user => (
                <User user={user} key={user.id} onRemove={onRemove} 
                onToggle={onToggle}/>
            ))}
        </div>
    )
}

export default UserList;

 

=>user 데이터가 변경될 때 수행할 작업을 설정: UserList.jsx 파일을 수정

import {useEffect} from 'react';

//하나의 데이터를 출력할 컴포넌트
const User = ({user, onRemove, onToggle}) => {
    useEffect(() =>{
        console.log("화면에 보여질 때 한번 만 호출");
        return(() => {
            console.log("소멸될 때 호출되는 코드");
        })
    }, []);

    useEffect(() => {
        console.log("user 데이터가 변경될 때 호출");
        return(() => {
            console.log("user 데이터가 소멸될 때 호출되는 코드");
        })
    }, [user])

    return (
        <div onDoubleClick={(e) => onRemove(user.id)}>
            <b style={{
                cursor: 'pointer',
                color: user.active ? 'green' : 'black'
            }}
            onClick = {(e) => onToggle(user.id)}
            >{user.username}</b> <span>({user.email})</span>
        </div>
    )
}

const UserList = ({users, onRemove, onToggle}) => {
    return(
        <div>
            {users.map(user => (
                <User user={user} key={user.id} onRemove={onRemove} 
                onToggle={onToggle}/>
            ))}
        </div>
    )
}

export default UserList;

 

7)node 기반 프로젝트에서의 git
=>node 기반의 프로젝트의 패키지의 의존성을 package.json 파일에 기록하고 다운로드 받은 패키지를 node_modules 라는 파일에 저장합니다.
package-lock.json 파일이 생성되는 경우가 있는 이 파일은 세부 의존성까지 표현하는 파일입니다.
node 기반의 프로젝트들은 소스 레포지토리에 업로드 할 때 node_modules 와 package-lock.json은 제외한 채로 업로드를 합니다.
다운로드 받아서 사용을 할 때는 npm install 명령을 수행하면 package.json 파일의 내용에 기록된 의존성을 기반으로 패키지를 다시 설치합니다.
node 기반의 프로젝트는 기본적으로 .gitignore를 제공
node 프로젝트를 소스 레포지토리에 업로드할 때 node_module 디렉토리를 업로드하면 파일 개수 초과로 업로드 실패합니다.

 

8)useMemo
=>개요
- 연산된 값을 재사용하는 성능 최적화를 위해서 사용하는 Hook
- useMemo를 사용하지 않는다고 해서 기능이 동작하지는 앞지만 효율이 높아집니다.

=>형식
useMemo(함수[, 의존성 배열])
- 의존성 배열의 값이 변경되면 함수를 호출해서 결과를 다시 계산하고 그 이외의 경우는 이전에 연산한 값을 재사용
- 함수를 호출하는 것보다는 이전에 연산한 값을 사용하는 것이 효율이 높기 때문입니다.

 

9)useCallback
=>useCallback은 사용하는 방식은 useMemo 와 동일
=>함수를 다시 호출하는 것이 아니고 함수를 다시 만들지 않음

10)Memoization
=>최적화를 위해서 사용하는 개념
=>비용이 높은 함수의 호출 결과를 저장해 두었다가 동일한 입력값으로 함수를 호출하면 새로 함수를 호출하지 않고 이전에 저장해놨던 호출 결과를 바로 반환하는 것으로 재귀함수에 사용하면 메모리 효율을 높일 수 있고 빠른 실행 속도를 만들어 낼 수 있습니다.
=>useMemo 와 useCallback은 Memoization의 개념
=>python에서는 dict를 이용하고 java에서는 Map을 이용해서 Memoization을 구현하는 경우가 많음

 

11)React.memo
=>컴포넌트의 props가 변경되지 않는다면 rerendering을 방지해서 성능 최적화를 해줄 수 있는 함수
=>컴포넌트를 React.memo 함수로 감싸주기만 하면 됩니다.
=>react는 props 와 state가 변경되면 컴포넌트를 rerendering
랜더링에 포함되지 않는 데이터는 ref로 생성하고 불필요하게 함수 호출을 할 필요가 없는 경우에는 useMemo 그리고 불필요하게 함수를 다시 생성할 필요가 없는 경우 useCallback 그리고 불필요한 rerendering을 방지하기 위한 React,memo를 사용

 

12)useReducer
=>개요
- useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트하고자 할 때 사용하는 Hook
- useState로 만든 상태는 함수 내부에서만 사용이 가능하고 다른 컴포넌트에서 사용하고자 하는 경우는 넘겨주어야 합니다.
- 컴포넌트 와 상태를 분리하고자 할 때 사용하는 것이 Reducer
- 현재 상태 그리고 업데이트를 위해 필요한 정보를 담은 action 값을 전달받아서 새로운 상태를 반환하는 함수

 

=>생성 및 사용 방식

function 함수(state, action){
action type에 따라 새로운 state를 리턴
}

호출하는 곳에서는 action.type에 해당하는 type 과 데이터를 넘겨주면 됩니다.

=>useReducer를 확장한 라이브러리가 redux 입니다.
react를 전문적으로 하고자 할 때는 redux를 사용하는 것을 권장

=>useReducer를 사용하지 않는 state 변경 - 버튼을 2개 만들어서 각 버튼을 누르면 숫자의 값을 1증가시키거나 1감소시키는 컴포넌트

 

- Counter 컴포넌트를 생성해서 구현

import {useState} from 'react';

const Counter = (props) => {
    const [num, setNum] = useState(0);

    const onIncrement = (e) => {
        setNum((prevNum) => prevNum + 1);
    }

    return(
        <div>
            <div>{num}</div>
            <p>
                <button onClick = {onIncrement}>1 증가</button>
                <button onClick = {
                    (e) => {
                        setNum((prevNum) => prevNum - 1);
                    }
                }>1 감소</button>
            </p>
        </div>
    );
}

export default Counter;

 

- Counter 컴포넌트 수정: useReducer를 이용

import {useReducer} from 'react';
//외부에서 함수 내부에 만들어진 변수를 수정할 수 있습니다.
//이 함수가 다른 파일에 있어도 가능
function reducer(state, action){
    switch(action.type){
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
    }
}

const Counter = (props) => {
    //num은 하나의 state 처럼 동작하고 dispatch는 reducer 함수의 역할을 수행
    const [num, dispatch] = useReducer(reducer, 0);

    const onIncrement = (e) => {
        dispatch({type:'INCREMENT'});
    }

    return(
        <div>
            <div>{num}</div>
            <p>
                <button onClick = {onIncrement}>1 증가</button>
                <button onClick = {
                    (e) => {
                        dispatch({type:'DECREMENT'});;
                    }
                }>1 감소</button>
            </p>
        </div>
    );
}

export default Counter;

=>여러 곳에서 하나의 변수를 수정하고자 하는 경우 state를 만들어서 계속 넘겨주는 것 보다는 useReducer를 하는 것이 효율적일 수 있습니다.

5.Context API

1)개요
=>전역 상태 관리를 위한 API
=>useReducer를 이용해서도 전역은 아니지만 여러 컴포넌트에서 하나의 상태를 관리할 수 있지만 공유할 수는 없습니다.
=>전역 상태(global 상태)를 만들어서 사용하고자 하는 경우 react에서는 Context API를 제공하고 redux 는 react의 것이 아닌 외부 라이브러리이고 react가 아닌 곳에서도 사용 가능

2)Context 생성 및 사용
=>전역 데이터를 위한 contexts 디렉토리를 생성

=>색상 값을 전역으로 저장할 color.js 파일을 contexts 디렉토리에 생성하고 작성

import {createContext} from 'react';

//전역 변수 생성
const ColorContext = createContext({color:'black'});

export default ColorContext;

=>ColorContext를 사용하기 위한 컴포넌트 파일을 생성하고 작성 - ColorBox.jsx  
import ColorContext from "../contexts/color";

const ColorBox = (props) => {
    return (
        //ColorContext에서 만들어서 리턴한 데이터를 value로 사용 가능
        <ColorContext.Consumer>
            {
                value => (
                    <div style={{width:'64px', height:'64px', background:value.color}} />
                )
            }
        </ColorContext.Consumer>
    );
}

export default ColorBox;

=>App.js 파일에서 ColorBox 출력

6.React Router

1)개요

=>SPA 는 하나의 페이지를 만들어서 ㅏㅅ용

=>Routing은 뷰를 보여줄 때 다른 페이지를 보여주는데 다른 URL을 사용할 수 있도록 해주는 기능

=>react-router-dom 패키지를 이용해서 Routing 기능을 구현할 수 있습니다. 다른 패키지들에서도 제공을 합니다.

 

2)Rouning 구현
=>패키지 설치: yarn add react-router-dom

=>index.js 파일을 수정해서 App을 BrowserRouter 가 감싸도록 함

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

import {BrowserRouter} from 'react-router-dom';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

reportWebVitals(console.log);

 

=>메인 페이지의 역할을 수행할 Home.jsx 파일을 생성하고 작성

const Home = (props) => {
    return (
        <div>
            <h1>홈</h1>
            <p>메인 페이지</p>
        </div>
    )
}

export default Home;

 

=>페이지의 역할을 수행할 About.jsx 파일을 생성하고 작성

const About = (props) => {
    return (
        <div>
            <h1>페이지 소개</h1>
            <p>React Router를 실습하기 위한 페이지</p>
        </div>
    )
}

export default About;

 

=>URL에 페이지를 연결: <Route path="URL" element={컴포넌트}>
=>App.js 파일을 수정해서 URL에 페이지를 연결

import React from 'react';
import {Route, Routes} from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
    </Routes>
  );
}

export default App;

 

3)링크 설정
=>링크를 설정할 때는 <Link to="URL">내용</Link>
=>Home.jsx 파일에 about 링크를 추가

import {Link} from 'react-router-dom';

const Home = (props) => {
    return (
        <div>
            <h1>홈</h1>
            <p>메인 페이지</p>
            <Link to="/about">소개</Link>
        </div>
    )
}

export default Home;

 

4)Route 안에 Route를 만드는 것이 가능
=>공통 레이아웃 적용이 가능
공통으로 만들어져야 하는 컴포넌트를 하나만 만들어서 한꺼번에 적용이 가능
Outlet 태그를 이용하는데 이 자리는 컴포넌트의 내용이 출력될 위치가 됩니다.

 

=>공통 레이아웃 작성
- 공통으로 적용할 레이아웃 컴포넌트를 생성: Layout.jsx

 

import {Outlet} from 'react-router-dom';
const Layout = (props) => {
    return (
        <div>
            <header style={{background: 'lightgray', padding:16,
        fontSize:24}}>
                Header
            </header>
            <main>
                <Outlet />
            </main>
        </div>
    );
}

export default Layout;

 

- App.js 파일을 공통 레이아웃이 적용되도록 수정

import React from 'react';
import {Route, Routes} from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';

import Layout from './pages/Layout';

function App() {
  return (
    <Routes>
      <Route element={<Layout />}>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Route>
    </Routes>
  );
}

export default App;

 

5)useNavigate
=>링크가 아닌 코드를 이용해서 이동을 하고자 할 때 사용하는 Hook
const 이름 = useNavigate();

이름(숫자 나 문자열로 URL 대입)
숫자는 음수를 이용하면 뒤로의 기능을 수행

6)NavLink
=>현재 경로 와 Link에서 사용하는 경로가 일치하는 경우 특정 스타일을 적용할 수 있는 컴포넌트

7)Navigate
=>컴포넌트를 하면에 보여주는 순간 다른 페이지로 이동하고자 할 때 사용하는 컴포넌트
리다이렉트 할 때 사용
=>반드시 조건문 과 함께
단독으로 사용되면 현재 페이지가 존재할 필요가 없음
이 때 사용되는 것은 URL
=>실습

- 리다이렉트될 컴포넌트를 생성: Login.jsx
const Login = () => {
    return <div>로그인 페이지</div>
}

export default Login;

- 리다이렉트가 수행될 컴포넌트를 생성: MyPage.jsx
import { Navigate } from "react-router-dom";

const MyPage = (props) => {
    return <Navigate to="/login" replace={true}/>
}

export default MyPage;



- App.js 파일에 링크를 생성

import React from 'react';
import {Route, Routes} from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';

import Layout from './pages/Layout';

import Login from './pages/Login';
import MyPage from './pages/MyPage';

function App() {
  return (
    <Routes>
      <Route element={<Layout />}>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/login" element={<Login />} />
        <Route path="/mypage" element={<MyPage />} />
      </Route>
    </Routes>
  );
}

export default App;


8)에러 페이지 출력
=>Route 태그를 가장 아래에 작성하고 path를 *로 설정하게 되면 위에 일치하는 URL이 없을 때 보여지는 Component 생성 가능

'Study > FrontEnd' 카테고리의 다른 글

React 서버 연동  (0) 2024.04.29
JavaScript(5)  (2) 2024.04.23
JavaScript(4)  (0) 2024.04.23
JavaScript(3)  (1) 2024.04.18
React(3)  (0) 2024.01.31