React

2023. 2. 1. 10:58개발/실무용 메모

1. React 소개

npm install -g create-react-app // 리액트 설치
create-react-app . // 디렉토리 생성
  • Vue와 마찬가지로 VirtualDOM 을 사용하는 라이브러리.
  • 기본적으로 CSR (Client Side Rendering) 로 동작.
  • 리액트 핵심 모듈 2가지.
//1. 리액트 컴포넌트 => HTMLElement 연결하기
import ReactDOM from 'react-dom';

//2. 리액트 컴포넌트 만들기
import React from 'react';
  • JS/JSX 를 이용한 리액트 컴포넌트 사용.
//main
ReactDOM.render(
    <Vhows name = "GreenLabs"/>
);

//component
class Vhows extends React.Component {
    render() {
        return(
            <div>
                Hello {this.props.name}
            </div>
        )
    }
}

Vue와 차이점

  • 간단한 To-do list 앱으로 비교해보는 차이점
    • 출처 : 링크
    • 파일 구조 차이

좌측 - Vue / 우측 - React

  • 컴포넌트 내부 구조 차이
//ToDoItem.vue => vue
<template>
    <div class="ToDoItem">
        <p class="ToDoItem-Text">{{todo.text}}</p>
        <div class="ToDoItem-Delete" 
                @click="deleteItem(todo)">
        </div>
    </div>
</template>

<script>
    export default {
        name: "to-do-item",
        props: ['todo'],
        methods: {
            deleteItem(todo) {
                this.$emit('delete', todo)
            }
        }
    }
</script>

<style>
    .ToDoItem {
        display: flex;
        justify-content: center;
        align-items: center;
    }

    .ToDoItem-Text {
        width: 90%;
        background-color: white;
        border: 1px solid lightgrey;
        box-shadow: 1px 1px 1px lightgrey;
        padding: 12px;
        margin-right: 10px;
    }
</style>
//ToDoItem.js => react
//css 별도 파일 작성
import React, {Component} from 'react';
import './ToDoItem.css';

class ToDoItem extends Component {
    render() {
        return (
            <div className="ToDoItem">
                <p className="ToDoItem-Text">{this.props.item}</p>
                <div className="ToDoItem-Delete"
                            onClick={this.props.deleteItem}>
                </div>
            </div>
        );
    }
}

export default ToDoItem;
  • Vue 앱은 하나의 .vue 파일에 template , script , style 3가지 영역이 나뉘어 있고, 각 영역은 HTML , 자바스크립트 , CSS를 작성하는 패턴이다.
  • React 앱은 .js 파일과 .css 파일이 나뉘어 있고, js 파일에서 jsx 형태로 코드를 작성한다.

2. React Component

  • 컴포넌트란 기능 단위의 덩어리로, State 관리 및 Props 교환 등을 통해 DOM 변경을 JSX 문법으로 정의함 (CSS 정의는 포함되지 않음)
  • 컴포넌트는 화면 렌더링이 마운트 / 업데이트 / 제거되는 LifeCycle을 지니기 때문에, 이를 알맞게 정의해줘야함

Component 생성

  • 컴포넌트 정의 방법의 변천사 : 클래스형 → 함수형
    • 클래스형 컴포넌트와 함수형 컴포넌트의 차이
      • 클래스형
        • class 키워드로 선언
        • State 사용가능 / this.setState() 를 통해 변경
        • 자식 컴포넌트로 this.props 를 통해 값 전달
        • LiftCycle API를 통한 생명주기 관리
        • 화면 렌더링 시, render()만 실행
        // Class형
        class Vhows extends React.Component {
            render() {
                return <div>Hello</div>;
            }
        }
      • 함수형
        • function() 혹은 화살표함수 형태로 선언
        • State 사용불가 ⇒ 리액트훅 (useState)을 사용하여 관리 및 변경
        • 매개변수를 통해 값 전달
        • LifeCycle API 사용불가 ⇒ 리액트훅(useEffect)을 사용하여 생명주기 관리
        • 화면 렌더링 시, 컴포넌트 함수 전체 실행
        // Function형
        function Vhows() {
            return <div>Hello</div>;
        }
        
        // Arrow Function형
        const Vhows = () => {
            return <div>Hello</div>;
        }
    • React Hook이 나오기 전에는 클래스형과 함수형 구분하여 사용 하였으나, (상태 및 라이프사이클 관리 등) hook 개념이 나온 이후 최신 React는 함수형이 승리 ← 최신 ES의 지향점
      • 버전마다 방향성이 왔다갔다해서 뒤늦게 합류하니 지랄맞음
  • 컴포넌트에서 DOM 렌더링을 정의하는 방식의 변천사
    • React.createElement() 사용하는 방식에서 render() JSX 및 Fragment 단축 문법 등으로 훨씬 간단하게 표현이 가능해짐
      • React.createElement() 예제
        • 이렇게 사용하면 태그안에 ...등 Depth가 늘어날 때 매우 복잡해짐 → 따라서 JSX 문법 등장 
          • render() JSX 및 Fragment 단축 문법 예제
    class CustomComponent extends React.Component {
        render() {
          return (
            <>
              <p>Vhows</p>
            </>
          );
        }
      }
    
      // 또는 함수형
      const CustomComponent = () => {
        return (
          <>
            <p>Vhows</p>
          </>
        );
      };

State

  • State = { 상태값 지정 }
    • 화면에 동적으로 변경되는 상태 변수를 지정
  • setState()
    • 상태값을 변경할 때 반드시 setState() 사용
      • setState 를 사용하지 않고 this.state.aaa = 'bbb'; 이런식으로 직접 명시해서 수정할 경우 값은 변경되어도 화면이 render 되지 않음
      • 함수형 프로그래밍이 추구하는 불변성을 바탕으로 함
    • 일반적으론 객체형으로 선언하고, 이전 State를 사용하는 경우 함수형 return 값으로 선언함
    • setSate()가 실행될 때 마다 render()가 실행 → 자원 문제 고려해야 함
    • 비동기로 동작함
    • 사용 예시 (출처:https://velopert.com/3629)
import React, { Component } from 'react';

class Counter extends Component {
  state = {
    number: 0,
  }

  handleIncrease = () => {
    // 일반적인 setState() 객체형 형태
    this.setState({
      number: this.state.number + 1
    });
  }

  handleDecrease = () => {
    // 이전 스테이트를 가져온 setState() 함수형 형태
    this.setState((prevState) => {
      return {
        number: prevState.number - 1
      };
    });
  }

  render() {
    return (
      <div>
        <h1>카운터</h1>
        <div>값: {this.state.number}</div>
        <button onClick={this.handleIncrease}>+</button>
        <button onClick={this.handleDecrease}>-</button>
      </div>
    );
  }
}

export default Counter;

Props

  • Props - 부모 컴퍼넌트가 자식 컴퍼넌트에게 전달해주는 값

JSX

  • JSX 문법으로 작성된 코드는 순수한 자바스크립트로 컴파일해서 사용 ⇒ Babel (참조:https://babeljs.io/)
  • 문법
    • 최상위 요소가 하나여야 함.
    • 자식요소를 바로 렌더하고 싶을땐 <></> 사용 ⇒ Fragment (v16.2 에서 도입)
    • 표현식은 중괄호 {} 사용.
    • if 문 사용 불가. ⇒ 삼항 연산자 또는 && 사용. (return()내에서)
    • style 을 이용해 인라인 스타일링 가능.
    • class 대신 className 을 사용해 class 적용 가능

LifeCycle

  • LiftCycle API 는 컴포넌트가 브라우저에서 나타날때, 사라질때, 업데이트 될때 호출 되는 API
  • constructor
    • 컴포넌트 생성자 함수. 컴포넌트가 새로 만들어 질때 최초로 이 함수가 호출됨.
  • componentWillMount
    • 컴포넌트가 화면에 그려지기 바로 직전에 호출되는 함수 → 현재는 사용되지 않음.
  • componentDidMount
    • 컴포넌트가 화면에 그려진후에 한번 호출되는 함수. → 해당 컴포넌트에서 필요로 하는 데이터를 요청하기 위해 fetch 등, ajax 요청을 하거나 속성값들을 변경하는 작업.
  • componentDidUpdate
    • 컴포넌트의 props 나 state 값이 변경 되면, 호출되는 함수.
  • shouldComponentUpdate
    • true인 경우 화면 렌더링 업데이트를 함.
    • 기본적으로 true를 반환하며, 따로 작성한 조건에 따라 false 를 반환하게 되면 해당 조건에서는 render 함수를 호출하지 않음.
  • componentWillUnmount
    • 해당 컴포넌트가 더이상 사용되지 않을때 호출됨, 즉 Dom 에서 제거될때 → 예를 들면 라우터 이동등..

Component 기타

  • Ref
    • DOM에 직접 접근할 때 사용 (보통 input 포커스 줄 때 사용)

3. React Hooks

  • 함수형 컴포넌트를 온전하게 사용할 수 있도록, State 관리나 LifeCycle 관리 등을 함수 형태로 사용하는 방법
    • 상태 관리를 할 수 있게 해주는 useState , 렌더링 직후 작업을 설정하는 useEffect 등
    • 기타 다른 내장된 Hooks : 링크 참조
  • 보통 useXxx() 시작됨
  • React는 함수형 컴포넌트 및 Hooks 지향 = 함수형 프로그래밍을 지향

useState

const [state, setState] = useState("");
  • 개요
    • State를 관리하는 Hook
    • 하나의 상태를 관리하는 기본적인 방법
    • useState 예제 1
    import React, {useState} from 'react';
    
    const ExampleCounter = () => {
        // 배열 첫번째 count 상태 값을 담는 변수
        // 배열 두번째 setCount 상태 값을 변경해주는 함수
        // 인자 0 은 count의 초기값
        const [count, setCount] = useState(0);
        return (
            <div>
                <p>{count}</p>
                <button onClick={()=>{
                        setCount(count + 1)
                    }}>
                    button
                </button>
            </div>
        )
    }
    • useState 예제 2 - buttonClick 함수를 중간에 추가 
import React, {useState} from 'react';

const ExampleCounter = () => {
	const [count, setCount] = useState(0);

	const buttonClick = () => {
		setCount(count + 1);
	}

	return (
		<div>
			<p>{count}</p>
			<button onClick={buttonClick}>
				button
			</button>
		</div>
	)
}
  • 여러 개의 상태를 관리 할 때는 용도에 따라 2가지 방법이 있음
    1. 각 상태 별로 useState 각각 정의
    2. 상태를 객체 등으로 구조화하고, 모든 상태 값에 대한 변경을 하나의 함수에서 정의

useEffect

useEffect(() => {
    console.log('컴포넌트가 화면에 나타남');
    return () => {
      console.log('컴포넌트가 화면에서 사라짐');
    };
  }, []);
  • 개요
    • 클래스형 lifeCycle의 componentDidMount() 와 componentDidUpdate() 그리고 componentWillUnmount() 세가지가 합쳐진 형태로 보면 된다
    • useEffect 의 매개변수는 ()⇒{} 익명함수와, [] 배열 두가지가 들어감
    • 두번째 인자는 의존값이 들어있는 배열 (deps 배열)
      • 빈 배열을 넣을경우 화면에 처음 렌더링 될 때 한번만 실행.
        • return 을 이용하여 cleanup 함수 반환
          • 컴포넌트가 사라질때(unmount 될때) 호출
      • 특정 값을 넣으면 처음 렌더링 될 때와 해당 값이 업데이트 될때 실행.
        • cleanup 함수가 컴포넌트가 사라질때(unmount 될때) 뿐만 아니라, 값이 변경되기 직전에도 실행
      • 생략할경우 렌더링이 다시 일어날때마다 실행.
  • 참고 사항

useRef

const nameInput = useRef(null);

<input
    **ref={nameInput}**
  name="name"
  placeholder="이름"
  onChange={onChange}
  value={name}
/>
  • 개요
    • DOM 접근을 위한 Hook
      • 자바스크립트에서 특정 DOM을 선택하고자 할 때, getElementById, querySelector 를 사용하는 것 처럼 리액트에서는 ref 를 사용
    • useRef() 를 사용하여 Ref 객체를 만들고, 이 객체를 선택할 DOM 에 ref값으로 설정하면, Ref 객체의 .current값은 해당 DOM 을 가르키게 됨.
  • 참고 사항
    • state의 값과 ref가 가르키는 값의 렌더링시 차이
      • state는 컴포넌트가 렌더링이 되는, 혹은 ‘렌더링 된 시점’의 상태값을 가리키고, ref는 컴포넌트의 ‘현재’ 상태값을 가리킨다.

useMemo

// 사용 전
const count = countActiveUsers(users)

// 사용 후
const count = useMemo(() => countActiveUsers(users), [users]);
  • 개요
    • 성능 최적화를 위해 연산된 값을 재사용 할 수 있도록 해주는 Hook
    • 첫번째 파라미터는 어떻게 연산할지 정의하는 함수, 즉 실행할 함수를 담음.
    • 두번째 파라미터는 deps 배열을 넣어준다.
      import React, { useRef, useState, useMemo } from 'react';
      import UserList from './UserList';
      import CreateUser from './CreateUser';
      
      function countActiveUsers(users) {
        console.log('활성 사용자 수를 세는중...');
        return users.filter(user => user.active).length;
      }
      
      const UseMemo = () => {
        const [inputs, setInputs] = useState({
          username: '',
          email: ''
        });
        const { username, email } = inputs;
        const onChange = e => {
          const { name, value } = e.target;
          setInputs({
            ...inputs,
            [name]: value
          });
        };
        const [users, setUsers] = useState([
          {
            id: 1,
            username: 'velopert',
            email: 'public.velopert@gmail.com',
            active: true
          },
          {
            id: 2,
            username: 'tester',
            email: 'tester@example.com',
            active: false
          },
          {
            id: 3,
            username: 'liz',
            email: 'liz@example.com',
            active: false
          }
        ]);
      
        const nextId = useRef(4);
        const onCreate = () => {
          const user = {
            id: nextId.current,
            username,
            email
          };
          setUsers(users.concat(user));
      
          setInputs({
            username: '',
            email: ''
          });
          nextId.current += 1;
        };
      
        const onRemove = id => {
          // user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
          // = user.id 가 id 인 것을 제거함
          setUsers(users.filter(user => user.id !== id));
        };
        const onToggle = id => {
          setUsers(
            users.map(user =>
              user.id === id ? { ...user, active: !user.active } : user
            )
          );
        };
        // const count = countActiveUsers(users);
        const count = useMemo(() => countActiveUsers(users), [users]);
        return (
          <>
            <CreateUser
              username={username}
              email={email}
              onChange={onChange}
              onCreate={onCreate}
            />
            <UserList users={users} onRemove={onRemove} onToggle={onToggle} />
            <div>활성사용자 수 : {count}</div>
          </>
        );
      }
      
      export default UseMemo;

useCallback

// 사용 전
const increament = () => {
  setCount({ num: count.num + 1 });
};

// 사용 후
const increament = useCallback(() => {
  setCount({ num: count.num + 1 });
},[count]);
  • 개요
    • useMemo 기반으로 만들어진, 함수를 위해 사용성을 증가시킨 Hook
    • useMemo는 특정 결과값 (연산된 값)을 재사용 할 때 사용하는 반면, useCallback 은 특정 함수를 재사용 하고자 할 때 사용.
  • useMemo 와의 차이점
    • useMemo
      • 함수의 값만 메모이제이션해서 반환
      • 자식 컴포넌트에서 많은 계산량이나 복잡한 계산식의 값등, 특정 props값들을 최적화 하고자 할 때 사용
    • useCallback
      • 함수 자체를 메모이제이션해서 반환
      • 부모 컴포넌트에서 많은 계산량이나 복잡한 계산식의 함수를 자식 컴포넌트에 props로 전달해줄 때 사용
  • React.memo 와 함께 사용하여 최적화
    • React.memo 란?
      • 컴포넌트의 props가 바뀌지 않았다면, 리렌더링을 방지해서 성능 최적화를 해줄 수 있는 함수
      • React.memo() 로 감싸서 사용.
    • useCallback 을 사용하여, 자식 컴포넌트로 전달된 함수의 의존성 때문에 React.memo로 감싸진 자식 컴포넌트가 여러번 렌더링 되는 현상이 발생할 수 있음.
      • 비동기적인 setState를 함수형 업데이트로 변경하여, 각각의 콜백함수에서 최신의 상태값을 바라볼수 있도록 해줌으로써 최적화&해결 가능.
        import React, { useRef, useState, useMemo, useCallback } from 'react';
        import UserList from './UserList';
        import CreateUser from './CreateUser';
        
        function countActiveUsers(users) {
          console.log('활성 사용자 수를 세는중...');
          return users.filter(user => user.active).length;
        }
        
        const UseCallback = () => {
          const [inputs, setInputs] = useState({
            username: '',
            email: ''
          });
          const { username, email } = inputs;
          const onChange = useCallback(
            e => {
              const { name, value } = e.target;
              setInputs({
                ...inputs,
                [name]: value
              });
            },
            [inputs]
          );
          const [users, setUsers] = useState([
            {
              id: 1,
              username: 'velopert',
              email: 'public.velopert@gmail.com',
              active: true
            },
            {
              id: 2,
              username: 'tester',
              email: 'tester@example.com',
              active: false
            },
            {
              id: 3,
              username: 'liz',
              email: 'liz@example.com',
              active: false
            }
          ]);
        
          const nextId = useRef(4);
        
          const onCreate = useCallback(() => {
            const user = {
              id: nextId.current,
              username,
              email
            };
            setUsers(users => users.concat(user));
        
            setInputs({
              username: '',
              email: ''
            });
            nextId.current += 1;
          }, [username, email]);
        
          const onRemove = useCallback(id => {
            // user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
            // = user.id 가 id 인 것을 제거함
            setUsers(users => users.filter(user => user.id !== id));
          }, []);
        
          const onToggle = useCallback(id => {
              setUsers(
                users.map(user =>
                  user.id === id ? { ...user, active: !user.active } : user
                )
              );
            },
            [users]
          );
        
          // const onToggle = id => {
          //   setUsers(
          //     users.map(user =>
          //       user.id === id ? { ...user, active: !user.active } : user
          //     )
          //   );
          // };
        
          const count = useMemo(() => countActiveUsers(users), [users]);
        
          return (
            <>
              <CreateUser
                username={username}
                email={email}
                onChange={onChange}
                onCreate={onCreate}
              />
              <UserList users={users} onRemove={onRemove} onToggle={onToggle} />
              <div> 활성사용자 수 : {count} </div>
            </>
          );
        }
        
        export default UseCallback;

useReducer

const [state, dispatch] = useReducer(reducer, initialState);
  • 개요
    • useState의 확장 개념
      • 리덕스 (참고 : Redux) 를 알고있다면 쉽게 사용 가능
    • 이 Hook 함수를 사용하면 컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있음.
      • 상태 업데이트 로직을 컴포넌트 바깥쪽, 혹은 다른 파일에 작성 후 불러와서 사용 가능.
    • 다수의 하위값을 포함하는 복잡한 정적 로직을 만드는 경우,
    • 다음 state 가 (변경될 state) 이전 state (변경되기전의 state) 에 의존적인 경우에 사용
  • 키워드 & 개념
    • reducer
      • state를 변경하는 로직이 담겨 있는 함수
    • dispatch
      • action 객체를 넣어서 실행
    • action
      • 객체이고 필수 프로퍼티로 type을 가짐

Custom Hooks

  • 자신만의 커스텀 hook을 만들면 컴포넌트 로직을 재사용 가능한 함수로 추출 할 수 있게 된다.
  • 규칙 (필수는 아니나 권장) : 다른 Hook을 호출하며, 함수 이름이 'use'로 시작해야 한다.

4. 기타

'개발 > 실무용 메모' 카테고리의 다른 글

ReScript  (0) 2023.02.01
Redux  (0) 2023.02.01
Rendering  (0) 2023.02.01
SSR - Next.j  (0) 2023.02.01
Formatting & Linting  (0) 2023.01.31