React

React 개념 (230922)

Jinmidnight 2023. 9. 22. 02:42

React

- 유저 인터페이스(UI)를 관리하는 Javascript 라이브러리

 

NPM(node package manager)

- Javascript 라이브러리들을 설치하고 버전을 관리하는 도구

- node_modules 폴더에 있는 모든 라이브러리들을 주고받기에는 용량이 크기에, package.json만 주고받으며 npm을 통해 라이브러리들을 설치한다

 

Yarn

- NPM의 역할을 하는 동시에 NPM의 단점을 보완한 라이브러리 관리 도구 (feat. Meta)

# 빈 폴더를 Visual Studio Code에서 오픈

# 터미널에 입력하여 yarn 설치
npm -install -g yarn

# 어플리케이션 설치
yarn create react-app "앱 이름"

# 설치된 앱으로 이동
cd "앱 이름"

# 앱 실행
yarn start

 

JSX

- JS + XML

- Babel: 선언형(결과물을 선언) 문법인 JSX를 명령형(어떻게 만들지 명령) 문법인 JS로 변환해주는 도구

// React.Fragment의 축약형 <> </>: 불필요한 div를 사용하지 않고 여러 요소를 렌더링할 수 있게 해준다.
// 자바스크립트 표현식 사용 가능 ex. {name}
// 삼향연산자 사용 가능
function Hello() {
    const name = 'World';
    const num = 1;
    return (
        <>
            <div>Hello {name}!</div>
            {num === 1 ? <div>1입니다.</div> : <div>1이 아닙니다.</div>}
        </>
    );
};

 

Component

1. 클래스형 컴포넌트

- state 기능, 라이프 사이클 기능 제공

- 임의 메소드 정의 가능

- render 함수 필수: render 함수 내부에서 jsx 반환

- import를 통해 컴포넌트를 불러오고, export를 통해 컴포넌트를 내보냄

import React from 'react';
import { Component } from 'react';

class Hello extends Component {
    render() {
        return (<h1>Hello World!</h1>);
    }
};

export default Hello;

 

2. 함수형 컴포넌트: 권장

- state, 라이프 사이클 api 사용 불가능: 16.8 업데이트 이후 Hooks 도입으로 해결

- 배포 단계에서도 비교적 작은 파일 크기

- 편한 선언, 비교적 적은 메모리 자원 사용

- import를 통해 컴포넌트를 불러오고, export를 통해 컴포넌트를 내보냄

import React from 'react';

function Hello() {
    return (<h1>Hello World!</h1>);
};

export default Hello;

 

props

- 컴포넌트 속성 설정 시에 사용하는 요소

- props의 값은 부모 컴포넌트에서 설정

- 자식 컴포넌트를 수정하여 렌더링

// 부모 컴포넌트
import React from 'react';
import Child from './child';

const parent = () => {
    // 별도의 props 값이 없는 경우 보여주는 기본 값
    Child.defaultProps = {
        name: 'World'
    };

    return (
        <div>
            <Child name="React"/>
        </div>
    );
};

export default parent;
// 자식 컴포넌트
import React from 'react';

const child = (props) => {
    return (
        <div>
            Hello {props.name}!
        </div>
    );
};

export default child;

 

state

- 현재 상태에 따라 바뀌는 값

- useState를 활용하여 구현

// 배열의 첫 번째 원소에는 현재 상태 저장
// 배열의 두 번째 원소에는 상태를 바꿔주는 setter 함수
// 함수 인자에는 상태의 초기값

// name은 state, setName은 state를 변경하는 함수, "Tom"은 state의 초기값
const [name, setName] = useState("Tom")
props  state
부모 컴포넌트로부터
자녀 컴포넌트에 데이터 등을 전달
해당 컴포넌트 내부에서 데이터 전달
읽기 전용으로 자녀 컴포넌트 입장에서
변동 없음
변경 가능함(변경시 re-render)
둘 다 컴포넌트에서 사용하거나 렌더링 할 데이터를 담고 있음

 

 

map

- 반복되는 형태의 코드(컴포넌트 반복)를 효율적으로 관리

- 태그로 감싸준 코드를 map 할 때에는 key 값 설정 권장

const numbers = [1,2,3,4,5];
const result = numbers.map(num => num * num);
console.log(result); //[1,4,9,16,25]

 

MPA / SPA

1. MPA

- 사용자가 새로운 페이지를 요청할 때마다 서버에서 미리 준비한 화면을 보내줌

- 페이지를 이동하거나 새로고침할 때마다 전체 페이지를 렌더링하기 때문에 상태 유지가 어렵고, 불필요한 로딩이 생김

2. SPA: React

- 웹 에플리케이션에 필요한 모든 정적 리소스를 최초 접근 시 단 한번만 다운로드

- 라우팅(다른 주소에 다른 화면을 보여주는 기술)을 통해 페이지 전환

 

Router

# Router 설치
npm install react-router-dom@6
// index.js 코드를 아래와 같이 수정
import { BrowserRouter } from 'react-router-dom';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);
// App.js에서 라우팅할 페이지들을 불러오기
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import Home form "./pages/Home";
import Login form "./pages/Login";

function App() {
  return (
    <Routes>
      <Route path='/' element={<Home />} />
      <Route path='/login' element={<Login />} />
    </Routes>
  );
}

export default App;
// Home.js에서 Link를 통해 Loing.js로 이동할 수 있도록 구현
import React from 'react';
import { Link } from 'react-router-dom';

const Home = () => {   
    return (
        <div>
            <Link to='/login'>로그인</Link>
        </div>
    );
}

export default Home;

 

Router 중첩

// 상위 Route 안에 하위 Route들을 구현할 수 있음
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import Home form "./pages/Home";
import Login form "./pages/Login";
import Register form "./pages/Register";

function App() {
  return (
    <Routes>
      <Route path='/' element={<Home />}>
        <Route path='/login' element={<Login />} />
        <Route path='/register' element={<Register />} />
      </Route>
    </Routes>
  );
}

export default App;
// Outlet을 통해 login과 register 컴포넌트도 함께 렌더링할 수 있음
import React from 'react';
import { Link, Outlet } from 'react-router-dom';

const Home = () => {   
    return (
        <div>
            <Link to='/login'>로그인</Link>
            <Link to='/register'>회원가입</Link>
            <Outlet />
        </div>
    );
}

export default Home;

 

페이지 주소 정의

- URL 파라미터: 특정 아이디, 이름을 사용하여 조회

- 쿼리스트링: 키워드 검색, 페이지네이션, 옵션 전달

- NotFound 페이지: path에 *를 설정하여 구현

// :movieId는 URL 파라미터로 사용할 것임을 의미
// 존재하지 않는 URL을 요청했을 때 NotFound 컴포넌트를 렌더링
function App() {
  return (
    <Routes>
      <Route path='/' element={<Home />} />
      <Route path='/movies' element={<Movies />}>
        <Route path=':movieId' element={<Movie />} />
      </Route>
      <Route path='*' element={<NotFound />} />
    </Routes>
  );
}
// Movies.js에서 map을 통해 movie_data의 각 요소를 렌더링
const Movies = () => {   
    const movies = movie_data;

    return (
        <div>
            {movies.map(movie => () => (
                <Link to={`/movies/${movie.id}`}>
                    {movie.title}
                </Link>
            ))}
        </div>
    );
}
// Movie.js에서 특정 정보를 렌더링
const Movie = () => {   
    // URL 파라미터 사용하기
    const params = useParams();

    // movie_data에서 id가 일치하는 영화를 찾아서 movie에 저장
    const movie = movie_data.find(movie => movie.id === parseInt(params.movieId));

    // 쿼리스트링 사용 ex. /movies/1?detail=true
    const [searchParams, setSearchParams] = useSearchParams();
    const detail = searchParams.get('detail');
    const handleClick = () => {
        setSearchParams({ detail: detail === 'true' ? false : true });
    };

    return (
        <div>
            <div>{movie.title}</div>
            <div>감독: {movie.director}</div>
            <button onClick={handleClick}>상세보기</button>
            {detail && <div>상세정보: {movie.detail}</div>}
        </div>
    );
}

 

useNavigate

- Link처럼 페이지 전환 기능을 가지고 있는 Hook

// useNavigate를 사용하여 홈으로 이동
const Login = () => {
    const navigate = useNavigate();
    const goHome = () => {
        navigate('/');
    };
    
    return (
        <div>
            <div>Login</div>
            <button onClick={goHome}>홈으로</button>
        </div>
    );
}

 

NavLink

- Link의 특수한 버전으로 링크가 active되어 있을 때 특정 style 적용

// NavLink를 통해 클릭 시 style 적용
import React from 'react';
import { NavLink } from 'react-router-dom';

const Movies = () => {
    const movies = movie_data;
    const style = {
        fontWeight:'900',
        color:'red',
    }

    return (
        <div>
            {movies.map(movie => () => (
                <NavLink to={`/movies/${movie.id}`} activeStyle={style}>
                    {movie.title}
                </NavLink>
            ))}
        </div>
    );
}

export default Movies;

 

useEffect

- 리액트 컴포넌트가 렌더링 될때마다 특정 작업을 실행할 수 있도록 하는 Hook

- function / deps: 수행하고자 하는 작업 / 검사하고자 하는 값 또는 배열, 배열형태

// 가장 처음 렌더링 될 때 한 번만 실행: 빈 배열 넣기
useEffect(function, [])

// 특정 props나 state가 바뀔 때 실행: 특정 값 넣기
useEffect(function, [deps])

 

제어 컴포넌트

// 제어 컴포넌트를 통해 input 상태 관리
const WritePage = () => {
    // useState 만들어주기
    const [inputs, setInputs] = useState({
        title: '',
        content: '',
    });

    // 비구조화 할당을 통해 값 추출
    const { title, content } = inputs;

    // onChange 함수: input 상태 변동
    const onChange = (e) => {
        const { value, name } = e.target; // 우선 e.target 에서 name 과 value 를 추출
        setInputs({
            ...inputs, // 기존의 input 객체를 복사한 뒤
            [name]: value, // name 키를 가진 값을 value 로 설정
        });
    }

    return (
        <div>
            <input
                name="title"
                onChange={onChange}
                value={title}
            />
            <textarea
                name="content"
                onChange={onChange}
                value={content}
            />
        </div>
    );
}

 

React Virtual Dom

- 원래 브라우저는  DOM tree(HTML을 파싱하여 DOM 노드로 이뤄진 트리) + CSSOM(CSS을 파싱하여 생성) = Render tree를 생성하는 방식으로 작동

- 문제: 변화가 생길 때마다 tree를 전부 다시 생성해야하는 비효율성

- Virtual Dom: State Change > Compute Diff > Re-render 순으로 변화된 부분만을 재생성해 효율성 제고

 

useRef

- Virtual Dom을 효율적으로 관리할 수 있는 Hook

- 특정 컴포넌트에 focus를 줄 수 있도록 작동

function InputPost() {
  // useRef 설정
  const titleInput = useRef();
  const contentInput = useRef();

  // 첫 렌더링 후 title에 포커스
  useEffect(() => {
    titleInput.current.focus();
  }, []);

  // 엔터키 입력 시 content에 포커스
  const onKeyUp = (e) => {
    if (e.key === 'Enter') {
      contentInput.current.focus();
    }
  };

  return (
    <>
      <input
        name="title"
        ref={titleInput}
        onKeyUp={onKeyUp}
      />
      <textarea
        name="content"
        ref={contentInput}
      />
    </>
  );
}

 

useCallback

- 성능 최적화를 위해 함수를 memorize 해주는 Hook

- 컴포넌트가 re-render 될 때마다 컴포넌트의 함수가 재생성되는 문제 방지

- 연산이 오래 걸리고 복잡한 함수일수록 useCallback이 더욱 효율적

- function / deps: 수행하고자 하는 작업 / 검사하고자 하는 값 또는 배열, 배열형태

// deps가 바뀌면 함수를 재생성하고, deps가 바뀌지 않으면 변동 없음
useCallback(function, [deps])

 

useMemo

- 성능 최적화를 위해 연산된 값을 재사용하게 해주는 Hook

- function / deps: 수행하고자 하는 작업 / 검사하고자 하는 값 또는 배열, 배열형태

// deps가 바뀌면 함수를 호출하여 연산하고, deps가 바뀌지 않으면 변동 없음
useMemo(function, [deps])

 

React.memo

- 성능 최적화를 위해 컴포넌트를 memorize해놨다가 필요한 상황에만 re-render 해주는 Hook

- 상위 컴포넌트가 re-render되면 하위 컴포넌트도 무조건으로 re-render되는 문제 방지

- function / deps: 수행하고자 하는 작업 / 검사하고자 하는 값 또는 배열, 배열형태

// deps가 바뀌면 컴포넌트를 재생성하고, deps가 바뀌지 않으면 변동 없음
React.memo(function, [deps])


axios

- 외부와 데이터를 주고받을 때 대표적으로 사용하는 라이브러리

// 최초 렌더링 시에만 작동
useEffect(() => {
    // get 방식으로 데이터 조회
    axios.get(`${hostURL}/api/list`)
    .then((response) => {
        setItemList(response.data); // 받아온 데이터를 변수에 저장
        setLoading(false); // 로딩 종료
        window.location.reload(); // React는 SPA이므로 내부적으로 새로고침 기능이 없음
    })
}, []);

 

useQuery

- 데이터 조회 로직 단순화

- 에러처리, 캐싱 등을 간편하게 구현

function Todos() {
    // 간편하게 데이터 조회
    const { status, data, error } = useQuery("todos", fetchTodoList);

    // 로딩 중일 때
    if (status === "loading") {
        return <span>Loading...</span>;
    }

    // 에러가 발생했을 때
    if (status === "error") {
        return <span>Error: {error.message}</span>;
    }

    return (
        <ul>
            {data.map(todo => (
                <li key={todo.id}>{todo.title}</li>
            ))}
        </ul>
    );
}