React - 기초 문법

김영준·2023년 8월 27일
0

TIL

목록 보기
84/91
post-thumbnail

리액트 프로젝트 시작하기

npx create-react-app 프로젝트명

component에 props 전달하기

<Logo size={300} />
import logo from "./logo.svg";
import PropTypes from "prop-types";

function Logo(props) {
  return (
    <img
      src={logo}
      className="App-logo"
      alt="logo"
      style={{ width: props.size, height: props.size }}
    />
  );
}

Logo.defaultProps = {
  size: 200,
}; // props의 기본값 지정

Logo.propTypes = {
  size: PropTypes.number,
}; // props의 타입 지정

export default Logo;

객체 비구조화 할당을 사용하면 좀 더 간단하게 작성할 수 있다.
defaultProps를 정의 할 필요가 없다.

import logo from "./logo.svg";
import PropTypes from "prop-types";

function Logo({ size = 200 }) {
  return ( 
  		<img 
  		  src={logo}
  		  className="App-logo"
  		  alt="logo"
  		  style={{ width: size, height: size }}
		/>;
  );
}

Logo.propTypes = {
  size: PropTypes.number,
}; // props의 타입 지정

export default Logo;

컴포넌트 내부에 작성한 요소를 전달하려면 children 속성을 이용할 수 있다.
children의 타입은 node(JSX 및 각종 Element 요소를 받을 수 있는 타입)이다.
isRequired: 필수 요소

<Paragraph>
  Edit <code>src/App.js</code> and save to reload.
</Paragraph>

<Paragraph size={14} color="blue">
  I'm blue
</Paragraph>
import PropTypes from "prop-types";

function Paragraph({ children, size = 16, color = "white" }) {
  return <p style={{ fontSize: size, color }}>{children}</p>;
}

Paragraph.propTypes = {
  children: PropTypes.node.isRequired, // children은 node 타입
  size: PropTypes.number,
  color: PropTypes.string,
};

export default Paragraph;

조건부 렌더링

논리곱 연산자(&&)를 사용해서 조건부 렌더링을 할 수 있다.

import { useState } from "react";

function App() {
  const [visible, setVisible] = useState(false);
  return (
    <div>
      <button onClick={() => setVisible(!visible)}>Toggle</button>
      {visible && (
      <h1>논리곱 연산자를 통해 쉽게 JSX 렌더링 여부를 결정할 수 있습니다.</h1>
      )}
    </div>
  );
}

export default App;

삼항 연산자도 사용 가능하다.

import { useState } from "react";

function App() {
  const [visible, setVisible] = useState(false);
  return (
    <div>
      <button onClick={() => setVisible(!visible)}>Toggle</button>
      {visible ? (
      <h1>논리곱 연산자를 통해 쉽게 JSX 렌더링 여부를 결정할 수 있습니다.</h1>
      ) : null}
    </div>
  );
}

export default App;

반복

map 함수를 사용하여 특정 요소를 반복 출력할 수 있다.
이 때 최상위 요소에 key 속성을 반드시 명시해야 한다. -> 내부 최적화

import { useState } from "react";
import Board from "./components/Board";

function App() {
  const articles = [
    {
      id: 1,
      title: "리스트1 :",
      author: "김영준",
    },
    {
      id: 2,
      title: "리스트2 :",
      author: "이영준",
    },
    {
      id: 3,
      title: "리스트3 :",
      author: "박영준",
    },
  ];
  
  return (
    <div>
      <Board articles={articles} />
    </div>
  );
}

export default App;
import PropTypes from "prop-types";

const Board = ({ articles }) => {
  return (
    <div>
      <h1>Board</h1>

      <ul>
        {articles.map((article) => (
          <li key={article.id}>
            {article.id} | {article.title} | {article.author}
          </li>
        ))}
      </ul>
    </div>
  );
};

Board.propTypes = {
  articles: PropTypes.array,
};

export default Board;

지역 상태 관리

useState 훅을 사용해서 지역 변수를 생성할 수 있다.

const [state명, state를 변경할 함수] = useState(초깃값)

이벤트 바인딩

const handleIncrease = () => {
    setCount(count + 1);
    if (onIncrease) {
      onIncrease(count + 1);
    }
  };

const handleDecrease = () => {
    setCount(count - 1);
    if (onDecrease) {
      onDecrease(count - 1);
    }
  };
  return (
    <div>
      <span style={{ fontSize: 40 }}>{count}</span>
      <br />
      <button onClick={handleIncrease}>+</button>
      <button onClick={handleDecrease}>-</button>
    </div>
  );

부모 컴포넌트에 state 전달

// App.js

import { useState } from "react";
import Counter from "./components/Counter";

function App() {
  const [totalCount, setTotalCount] = useState(0);
  return (
    <div>
      TotalCount: {totalCount}
      <Counter
        onChange={(count) => {
          setTotalCount(count);
        }}
      />
    </div>
  );
}

export default App;
// Counter.js

import { useState } from "react";
import PropTypes from "prop-types";

function Counter({ onChange }) {
  const [count, setCount] = useState(0);

  const handleIncrease = () => {
    setCount(count + 1);
    if (onChange) {
      onChange(count + 1);
    }
  };

  const handleDecrease = () => {
    setCount(count - 1);
    if (onChange) {
      onChange(count - 1);
    }
  };
  return (
    <div>
      <span style={{ fontSize: 40 }}>{count}</span>
      <br />
      <button onClick={handleIncrease}>+</button>
      <button onClick={handleDecrease}>-</button>
    </div>
  );
}

Counter.propTypes = {
  onChange: PropTypes.func,
};

export default Counter;

useEffect

state의 변화를 감지한다.
첫 번째 인수는 변화가 감지될 때 동작할 함수, 두 번째 인수는 변화를 감지 할 state를 지정

import { useEffect, useState } from "react";

function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log(`Clicked ${count} times.`);
  }, [count]); // count의 변화를 감지한다.

  useEffect(() => {
    console.log("Component Loaded");
    const handleScroll = () => {
      console.log(window.scrollY);
    };

    document.addEventListener("scroll", handleScroll); // 전역적인 이벤트를 사용할 때 쓸 수 있다.
    return () => document.removeEventListener("scroll", handleScroll); // return으로 변환한 함수는 컴포넌트가 제거될 때 실행된다.
  }, []); // []은 컴포넌트가 처음 로드될 때 실행된다.

  return (
    <div>
      <div>You clicked {count} times.</div>
      <button onClick={() => setCount(count + 1)}>+</button>

      <div style={{ height: 10000 }}></div>
    </div>
  );
}

export default App;

useRef

1. Dom 요소를 참조할 때 사용한다.

선언한 ref의 current 속성으로 해당 Element를 참조할 수 있다.
ref를 컴포넌트에 전달하려면 React.forwardRef() 로 함수를 감싸주어야 한다.

// App.js

import { useRef } from "react";
import Input from "./components/Input";

function App() {
  const inputRef = useRef();
  return (
    <div>
      <Input ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
    </div>
  );
}

export default App;
// Input.js

import React from "react";

const Input = React.forwardRef((_, ref) => {
  return (
    <>
      Input: <input ref={ref} />
    </>
  );
});

export default Input;

2. 지역 변수로 사용할 때 사용한다.

useState는 값이 변경될 때 다시 렌더링을 한다.
useRef는 값이 변경되더라도 다시 렌더링을 하지 않는다.

따라서 값이 변경되어도 다시 렌더링 하지 않을 지역 변수를 사용할 때 ref를 사용한다.

ref를 지역변수로 사용하는 예제

import { useRef, useState } from "react";

const AutoCounter = () => {
  const [count, setCount] = useState(0);
  const intervalId = useRef();

  const handleStart = () => {
    intervalId.current = setInterval(() => {
      console.log(intervalId);
      setCount((count) => count + 1);
    }, 1000);
  };

  const handleStop = () => {
    clearInterval(intervalId.current);
  };

  return (
    <>
      <div>{count}</div>

      <button onClick={handleStart}>Start</button>
      <button onClick={handleStop}>Stop</button>
    </>
  );
};

export default AutoCounter;

interval id가 변경되어도 다시 렌더링 하지 않는다.


페이지네이션 구현하기

// App.js

import { useState } from "react";
import Board from "./components/Board";
import Pagination from "./components/Pagination";

function App() {
  const [page, setPage] = useState(0);
  const articles = new Array(100).fill().map((_, i) => ({
    id: i,
    title: `${i}번 게시물`,
  }));

  const limit = 10;
  const offset = page * limit;
  return (
    <div>
      <Pagination defaultPage={0} limit={limit} total={articles.length} onChange={setPage} />
      <Board articles={articles.slice(offset, offset + limit)} />
    </div>
  );
}

export default App;
// Board.js

import PropTypes from "prop-types";

const Board = ({ articles }) => {
  return (
    <ul>
      {articles.map((article) => (
        <li key={article.id}>
          {article.id} | {article.title}
        </li>
      ))}
    </ul>
  );
};

Board.propTypes = {
  articles: PropTypes.array,
};

export default Board;
// Pagination.js

import { useState } from "react";

const Pagination = ({ defaultPage, limit, total, onChange }) => {
  const [page, setPage] = useState(defaultPage);
  const totalPage = Math.ceil(total / limit);

  const handleChangePage = (newPage) => {
    onChange(newPage);
    setPage(newPage);
  };

  return (
    <div>
      <button onClick={() => page !== 0 && handleChangePage(page - 1)}>이전</button>
      {Array.from(new Array(totalPage), (_, i) => i)
        .filter((i) => {
          if (page < 2) {
            return i < 5;
          } else if (page > totalPage - 3) {
            return i >= totalPage - 5;
          }
          return i >= page - 2 && i <= page + 2;
        })
        .map((i) => (
          <button
            key={i}
            onClick={() => handleChangePage(i)}
            style={{ backgroundColor: page === i ? "red" : undefined }}
          >
            {i + 1}
          </button>
        ))}
      <button onClick={() => page + 1 !== totalPage && handleChangePage(page + 1)}>다음</button>
    </div>
  );
};

export default Pagination;
profile
꾸준히 성장하는 개발자 블로그

0개의 댓글