리액트 기초 정리

milmil·2022년 6월 9일
0

리액트와 나

목록 보기
3/5
post-custom-banner

1. 리액트 시작

왜 리액트인가?

  • 주로 MVC, MVVM 아키텍처를 사용하는 기존 프레임워크들.
  • 데이터가 변할 때마다 기존 뷰를 날려 버리고 처음부터 새로 렌더링하는 방식

리액트 이해

  • 오직 VIEW만 신경 쓰는 라이브러리

초기 렌더링

render() {...}

컴포넌트를 정의하는 render 함수.
html 형식의 문자열을 반환하지 않고, 뷰가 어떻게 생겼고 어떻게 작동하는지에 대한 정보를 지닌 객체를 반환함. 컴포넌트 내부에는 또 다른 컴포넌트들이 들어갈 수 있으며 내부의 컴포넌트들을 재귀적으로 렌더링한다.

조화과정(reconciliation)

뷰가 변형되는 것이 아닌 새로운 요소로 갈아 치우는 것. 데이터를 업데이트 했을 때 render 함수를 호출해, 두 가지 뷰를 최소한의 연산으로 비교한 후 DOM 트리를 업데이트한다.

리액트의 특징

Virtual DOM

리액트는 Virtual DOM을 사용한다.

DOM이란?

  • Document Object Model
  • element의 수가 많아지면 큰 웹 애플리케이션에 성능 이슈가 발생
  • DOM 자체는 빠르지만 웹 브라우저 단에서 DOM 변화가 일어나면 처리 속도가 느려진다
  • DOM을 최소한으로 조작하는 방식으로 개선: Virtual DOM 방식으로 DOM 업데이트를 추상화

Virtual DOM

브라우저에 노출되는 element들에 저급하여 조작하는 대신 추상화한 자바스크립트 객체를 구성하여 사용

  1. 데이터를 업데이트하면 전체 UI를 Virtual DOM에 리렌더링
  2. 이전 Virtual DOM에 있던 내용과 현재 내용 비교
  3. 바뀐 부분만 실제 DOM에 적용

기타 특징

  • 오직 VIEW만 담당함
  • 라이브러리 (Not 프레임워크), 기능을 직접 구현해야 함
  • 라이브러리를 사용해 취향대로 스택 설정
  • 프레임워크와 혼용 가능

작업 환경 설정

Node.js와 npm

  • Node.js와 직접적인 연관은 없으나 개발 도구를 위해 설치
  • ES6을 호환시켜 주는 babel, 번들링 등의 기능을 지닌 webpack 포함
  • Node.js 설치 시 패키지 매니저 도구인 npm 설치
  • npm 대신 yarn을 설치해서 사용 가능

JSX

코드 이해하기

import React 'from';
  • 일반 자바스크립트에서 사용하지 못하는 import구문.
  • Node.js에서도 package.json을 수정하면 require/exports대신 사용할 수 있다.
{
   ...
   type: 'module',
   ...
}
  • 모듈 기능을 브라우저에서 사용하기 위해서는 bundler를 사용
  • 리액트 프로젝트에서는 주로 webpack 사용
  • babel이 ES6~문법을 ES5문법으로 변환

JSX란?

  • 공식적인 자바스크립트 문법이 아님
  • JS 문법으로 babel이 일반 JS 코드 형태로 변환
function App() {
    return (
        <div>
            Hello <b>React</b>
        </div>
    )
}

다음과 같은 방식으로 변환

function App() {
    return React.createElement("div", null, "Hello", React.createElement("b", null, "react));
}

JSX의 장점

  • 보기 쉽고 익숙하다
  • 활용도가 높다
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
  • ReactDom.render는 컴포넌트를 페이지에 렌더링하는 역할을 한다
  • React.SttrictMode는 리액트의 레거시 기능을 사용하면 경고를 출력한다
import React from 'react';

React17에서 생략 가능
https://ko.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html

JSX문법

감싸인 요소

반드시 부모 요소 하나로 감싸야 한다.

:collision:

import React from 'react';

function App() {
    return (
        <h1>리액트 안녕!</h1>
        <h2>잘 작동하니?</h2>
    )
}

export default App;

:+1:

import React from 'react';

function App() {
    return (
        <div>
            <h1>리액트 안녕!</h1>
            <h2>잘 작동하니?</h2>
        </div>

    )
}

export default App;

혹은...

import React, {Fragment} from 'react';

function App() {
    return (
        <Fragment>
            <h1>리액트 안녕!</h1>
            <h2>잘 작동하니?</h2>
        </Fragment>

    )
}

export default App;

또는...

import React from 'react';

function App() {
    return (
        <>
            <h1>리액트 안녕!</h1>
            <h2>잘 작동하니?</h2>
        </>

    )
}

export default App;

image

자바스크립트 표현

JSX 내부에서 {}로 감싸면 자바스크립트 표현식을 쓸 수 있다

import React from 'react';

const name = '리액트';
function App() {
  return (
    <>
      <h1>{name} 안녕!</h1>
      <h2>잘 작동하니?</h2>
    </>
  );
}

export default App;

if문 대신 조건부 연산자

  • JSX 내부에서 if문을 사용할 수 었음
  • 조건부 연산자 a.k.a.삼항연산자 사용
import React from 'react';

const name = '리액트';
function App() {
  return (
    <>
      {name === '리액트' ? (
          <h1>리액트입니다.</h1>
      ) : (
          <h2>리액트가 아닙니다.</h2>
      )}
    </>
  );
}

export default App;

image

import React from 'react';

const name = 'ㄹㅇㅌ';
function App() {
  return (
    <>
      {name === '리액트' ? (
          <h1>리액트입니다.</h1>
      ) : (
          <h2>리액트가 아닙니다.</h2>
      )}
    </>
  );
}

export default App;

image

AND (&&) 조건부 렌더링

import React from 'react';

const name = 'ㄹㅇㅌ';
function App() {
  return (
    <>
      {name === '리액트' ? (
          <h1>리액트입니다.</h1>
      ) : null}
    </>
  );
}

export default App;

아래와 같다

import React from 'react';

const name = 'ㄹㅇㅌ';
function App() {
  return (
    <>
      {name === '리액트' && (
          <h1>리액트입니다.</h1>
      )}
    </>
  );
}

export default App;

JSX가 여러 줄일 때 주로 괄호로 감싸지만 필수는 아님

undefined

  • undefined만 반환하여 렌더링하면 오류 발생
  • JSX 내부에서 undefined 렌더링은 ok

인라인 스타일링

  • DOM 요소에 스타일을 적용할 때는 객체 형태로
  • - 문자 -> 카멜 표기
import React from 'react';

function App() {
  const name = '리액트';
  const style = {
    backgroundColor: 'black',
    color: 'aqua',
    fontSize: '48px',
    fontWeight: 'bold',
    padding: 16, // 단위 생략, px
  };

  return <div style={style}>{name}</div>;
}

export default App;

또는...

import React from 'react';

function App() {
  const name = '리액트';

  return <div style={{
    backgroundColor: 'black',
    color: 'aqua',
    fontSize: '48px',
    fontWeight: 'bold',
    padding: 16, // 단위 생략, px
  }}>{name}</div>;
}

export default App;

image

class 대신 className

<div className="react"></div>
  • JS 예약어 Class, 대신 className
  • class로 CSS 클래스를 설정하면 v16 이상에서는 className으로 변환하고 경고 띄움

꼭 닫아야 하는 태그

<br />
<input></input>
  • 태그를 닫지 않으면 html과 달리 오류 발생
  • 태그 사이에 내용이 없는 self-closing 태그

주석

import React from 'react';

function App() {
  const name = '리액트';
  return (
    <>
      {/* 주석은 이렇게 작성합니다. */}
      <div
        className="react" // 시작 태그를 여러 줄로 작성하게 된다면 여기에 주석을 작성 할 수 있습니다.
      >
        {name}
      </div>
      // 하지만 이런 주석이나 /* 이런 주석은 페이지에 그대로 나타나게 됩니다. */
      <input />
    </>
  );
}

export default App;

22-05-06

3. 컴포넌트

클래스형 컴포넌트와 함수형 컴포넌트

  • 클래스형 컴포넌트와 함수형 컴포넌트 두 종류
  • Hooks의 도입으로 함수형 컴포넌트에서 state 사용 가능
  • 함수형 권장

컴포넌트 코드 작성하기

import React from 'react';

const MyComonent = () => {
    return <div>나의 새롭고 멋진 컴포넌트</div>;
};

export default MyComponent;

모듈 내보내기

export default MyComponent;

모듈 불러오기

import React from 'react';
import MyComponent from './MyComponent';

const MyComonent = () => {
    return <MyComponent>;
};

export default App;

Props

  • properties를 줄인 표현
  • 컴포넌트 속성을 설정
  • 부모 컴포넌트에서 설정

JSX 내부에서 렌더링

import React from 'react';

const MyComponent = (props) => {
  return <div>제 이름은 {props.name}입니다.</div>;
};

export default MyComponent;

props값 지정하기

import React from 'react';
import MyComponent from './MyComponent';

const App = () => {
    return <MyComponent name="React" />;
};

export default App;

image

props 기본값 설정: defaultProps

...
MyComponent.defaultProps = {
    name: '기본 이름'
}
...

image

태그 사이의 내용을 보여 주는 children

import React from 'react';
import MyComponent from './MyComponent';

const App = () => {
    return <MyComponent>리액트</MyComponent>;
};

export default App;
import React from 'react';

const MyComponent = (props) => {
  return (
    <div>
      제 이름은 {props.name}입니다. <br />
      children 값은 {props.children} 입니다.
    </div>
  );
};

MyComponent.defaultProps = {
  name: '기본 이름',
};

export default MyComponent;

image

비구조화 할당을 통한 props 내부 값 추출

...
const { name, children }  = props;
...
...
const MyComponent = ({name, children}) => {
...

propTypes를 통한 props 검증

  • 필수 props를 지정하거나 props의 타입을 지정
import PropTypes from 'prop-types'

import 구문으로 propTypes을 불러옴

MyComponent.propTypes = {
    name: PropTypes.string,
    favoriteNumber: PropTypes.number.isRequired
};
  • 타입이 일치하지 않을 시 Console에 에러 메세지 출력

PropTypes의 종류

  • array: 배열
  • bool: true or false
  • func: 함수
  • obejct: 객체
  • string: 문자열
  • instanceOf(클래스): 특정 클래스의 인스턴스
  • oneOf(['dog', 'cat']): 주어진 배열 요소 중 하나
  • oneOfType([React.PropTypes.string, Proptypes.number]): 배열 안의 종류 중 하나
  • any: 아무거나

state

  • 함수 내부에서 바뀔 수 있는 값

useState 사용하기

import React, { useState } from 'react';

const Say = () => {
  const [message, setMessage] = useState('');
  const onClickEnter = () => setMessage('안녕하세요!');
  const onClickLeave = () => setMessage('안녕히 가세요!');

  const [color, setColor] = useState('black');

  return (
    <div>
      <button onClick={onClickEnter}>입장</button>
      <button onClick={onClickLeave}>퇴장</button>
      <h1 style={{ color }}>{message}</h1>
      <button style={{ color: 'red' }} onClick={() => setColor('red')}>
        빨간색
      </button>
      <button style={{ color: 'green' }} onClick={() => setColor('green')}>
        초록색
      </button>
      <button style={{ color: 'blue' }} onClick={() => setColor('blue')}>
        파란색
      </button>
    </div>
  );
};

export default Say;

image

  • state값을 바꾸어야 할 때는 setState혹은 useState를 통해 전달받은 세터 함수를 사용해야 함
  • 배열이나 객체를 업데이트 해야 할 때는 객체 사본을 만들고 업데이트
//객체 다루기
const object = { a:1, b:2, c:3 };
const nextObject = { ...object, b: 2}; //사본을 만들어서 b값만 덮어쓰기

// 배열 다루기
const array = [
    { id: 1, value: true},
    { id: 2, value: true},
    { id: 3, value: false}
];
let nextArray = array.concat({id:4}) //새 항목 추가
nextArray.filter(item => item.id !== 2); // id가 2인 항목 제거
nextArray.map(item=> (item.id === 1 ? {...item, value: false} : item)); //id가 1인 항목의 value를 false로 설정
  • 함수형 컴포넌트를 만들 때는 useState를 사용하자!

이벤트 핸들링

리액트의 이벤트 시스템은 HTML 이벤트와 비슷하다

주의사항

  1. 카멜 표기법으로 작성
  2. 함수 형태의 값 전달
  3. DOM 요소에만 이벤트 설정 가능 (컴포넌트 자체적 이벤트 설정 X)

이벤트 연습

import React, { useState } from 'react';

const EventPractice = () => {
  const [form, setForm] = useState({
    username: '',
    message: ''
  });
  const { username, message } = form;
  const onChange = e => {
    setTimeout(() => console.log(e), 500);
    const nextForm = {
      ...form,
      [e.target.name]: e.target.value,
    };
    setForm(nextForm);
  };
  const onClick = () => {
    alert(username + ': ' + message);
    setForm({
      username: '',
      message: ''
    });
  };
  const onKeyPress = e => {
    if (e.key === 'Enter') {
      onClick();
    }
  };
  return (
    <div>
      <h1>이벤트 연습</h1>
      <input
        type="text"
        name="username"
        placeholder="유저명"
        value={username}
        onChange={onChange}
      />
      <input
        type="text"
        name="message"
        placeholder="아무거나 입력해보세요"
        value={message}
        onChange={onChange}
        onKeyPress={onKeyPress}
      />
      <button onClick={onClick}>확인</button>
    </div>
  );
};
export default EventPractice;

image

정리!!

리액트는 자바스크립트에 익숙하면 쉽게 사용할 수 있다!

5. ref: DOM에 이름 달기

ref는 어느 상황에서 써야 할까?

  • DOM을 직접적으로 건드려야 할 때
  • 리액트 컴포넌트 안에서 id를 사용하는 것은 권장하지 않음

꼭 사용해야 할 때

  • 특정 input에 포커스 주기
  • 스크롤 박스 조작하기
  • Canvas 요소에 그림 그리기 등

ref 사용

useRef를 사용한 예제

.success {
  background-color: lightgreen;
}
.failure {
  background-color: lightcoral;
}
import React, { useState, useRef } from 'react';
import './ValidationSample.css';
const ValidationSample = () => {
  const input = useRef();
  const [password, setPassword] = useState('');
  const [clicked, setClicked] = useState('');
  const [validated, setValidated] = useState('');

  const handleChange = (e) => {
    setPassword(e.target.value);
  };
  const handleButtonClick = () => {
    setClicked(true);
    setValidated(password === '0000');
  };
  return (
    <div>
      <input ref={input} type="password" value={password} onChange={handleChange} className={clicked ? (validated ? 'success' : 'failure') : ''} />
      <button onClick={handleButtonClick}>검증하기</button>
    </div>
  );
};
export default ValidationSample;

image

컴포넌트에 ref 달기

import ScrollBox from './ScrollBox';
import { useRef } from 'react';
const App = () => {
  const scrollBoxRef = useRef();
  return (
    <div>
      <ScrollBox ref={scrollBoxRef} />
      <button
        onClick={() => {
          const { scrollHeight, clientHeight } = scrollBoxRef.current;
          scrollBoxRef.current.scrollTop = scrollHeight - clientHeight;
        }}
      >
        맨 밑으로
      </button>
    </div>
  );
};

export default App;

scrollBox.js

import React, { forwardRef } from 'react';
const ScrollBox = forwardRef((props, ref) => {
  const style = {
    border: '1px solid black',
    height: '300px',
    width: '300px',
    overflow: 'auto',
    position: 'relative',
  };
  const innerStyle = {
    width: '100%',
    height: '650px',
    background: 'linear-gradient(white, black)',
  };
  return (
    <div style={style} ref={ref}>
      <div style={innerStyle} />
    </div>
  );
});
export default ScrollBox;

image

정리 :heart:

  • DOM에 직접 접근할 때는 ref를 쓰자

6. 컴포넌트 반복

자바스크립트 배열의 map()

arr.map(callback, [thisArg]);
  • callback: 새로운 배열의 요소를 생성하는 함수
    • currentValue: 현재 처리하고 있는 요소
    • index: 현재 처리하고 있는 요소의 index값
    • array: 현재 처리하고 있는 원본 배열
  • thisArg(선택항목): callback함수 내부에서 사용할 this 레퍼런스

데이터 배열을 컴포넌트 배열로 변환하기

import React from 'react';

const IterationSample = () => {
  const names = ['눈사람', '얼음', '눈', '바람'];
  const nameList = names.map(name => <li>{name}</li>);
  return <ul>{nameList}</ul>;
};

export default IterationSample;

image

key

  • key는 컴포넌트 배열을 렌더링했을 때 어떤 원소에 변동이 있었는지 알아내려고 사용
  • 값이 언제나 유일해야 함
  • 고유한 값이 없을 때 map 콜백 함수 인자인 index 값 사용 (배열이 변경될 때 효율적으로 리렌더링 하지 못함)

응용

import { useState } from 'react';
const App = () => {
  const [names, setNames] = useState([
    { id: 1, text: '눈사람' },
    { id: 2, text: '얼음' },
    { id: 3, text: '눈' },
    { id: 4, text: '바람' },
  ]);

  const [inputText, setInputText] = useState('');
  const [nextId, setNextId] = useState(5); //새로운 항목을 추가할 때 사용할 id

  const onChange = (e) => setInputText(e.target.value);
  const onClick = () => {
    const nextNames = names.concat({
      id: nextId,
      text: inputText,
    });
    setNextId(nextId + 1);
    setNames(nextNames);
    setInputText('');
  };
  const onRemove = (id) => {
    const nextNames = names.filter((name) => name.id !== id);
    setNames(nextNames);
  };

  const namesList = names.map((name) => (
    <li key={name.id} onDoubleClick={() => onRemove(name.id)}>
      {name.text}
    </li>
  ));

  return (
    <>
      <input value={inputText} onChange={onChange} />
      <button onClick={onClick}>추가</button>
      <ul>{namesList}</ul>
    </>
  );
};

export default App;

image

정리 :star:

  • 상태 안에서 배열을 변형할 때는 직접 접근해서 수정하지 않고 concat, filter등의 배열 내장 함수 사용
  • 기존 상태를 그대로 두면서 새로운 값을 상태로 설정하는 불변성 유지

8. Hooks

  • 리액트 v16.8에 도입
  • 함수형 컴포넌트에서도 상태 관리와 렌더링 직후 작업 설정 가능

useState

  • 가장 기본적인 Hook
  • 함수형 컴포넌트도 가변적인 상태를 지닐 수 있게 함
import React, { useState } from 'react';

const Counter = () => {
  const [value, setValue] = useState(0);

  return (
    <div>
      <p>
        현재 카운터 값은 <b>{value}</b> 입니다.
      </p>
      <button onClick={() => setValue(value + 1)}>+1</button>
      <button onClick={() => setValue(value - 1)}>-1</button>
    </div>
  );
};

export default Counter;

image

const [value, setValue] = useState(0);
  • value: 상태변수
  • setValue: 상태변수의 값을 갱신하기 위한 함수
  • useState(0): 상태변수의 초기값
return (
  <p>
    현재 카운터 값은 <b>{value}</b> 입니다.
  </p>
);
  • setValue()함수를 통해 갱신되면 자동으로 화면에 실시간 반영

useState여러번 사용

import React from 'react';
import useInputs from './useInputs';

const Info = () => {
  const [state, onChange] = useInputs({
    name: '',
    nickname: ''
  });
  const { name, nickname } = state;

  return (
    <div>
      <div>
        <input name="name" value={name} onChange={onChange} />
        <input name="nickname" value={nickname} onChange={onChange} />
      </div>
      <div>
        <div>
          <b>이름:</b> {name}
        </div>
        <div>
          <b>닉네임: </b> {nickname}
        </div>
      </div>
    </div>
  );
};

export default Info;

image

useEffect

  • useEffect는 리액트 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook
...
  useEffect(() => {
    console.log('렌더링이 완료되었습니다');
    console.log({
      name,
      nickname,
    });
  });
...

image

:question:StrictMode에서는 렌더링이 두 번 됨

마운트 될 때만 실행하고 싶을 때

함수 두 번째 파라미터로 비어 있는 배열을 넣음

특정 값이 업데이트 될 때만 실행하고 싶을 때

두 번째 파라미터 배열 안에 검사하고 싶은 값을 넣음

뒷정리하기

언마운트 되기 전이나 업데이트 하기 직전 작업을 수행하고 싶다면 뒷정리 함수를 반환한다

import Info from './Info';
import { useState } from 'react';
const App = () => {
  const [visible, setVisible] = useState(false);
  return (
    <>
      <button
        onClick={() => {
          setVisible(!visible);
        }}
      >
        {visible ? '숨기기' : '보이기'}
      </button>
      <hr />
      {visible && <Info />}
    </>
  );
};

export default App;
...
  useEffect(() => {
    console.log('effect');
    console.log(name);

    return () => {
      console.log('cleanup');
      console.log(name);
    };
  }, [name]);
...

image

언마운트 될 때만 뒷정리 함수를 호출하고 싶다면 두 번째 파라미터에 비어있는 배열을 넣음

useReducer

  • stateaction값을 전달받아 새로운 상태를 반환
  • 불변성을 지켜야 함

카운터 구현

import React, { useReducer } from 'react';

function reducer(state, action) {
  // action.type 에 따라 다른 작업 수행
  switch (action.type) {
    case 'INCREMENT':
      return { value: state.value + 1 };
    case 'DECREMENT':
      return { value: state.value - 1 };
    default:
      // 아무것도 해당되지 않을 때 기존 상태 반환
      return state;
  }
}

const Counter = () => {
  const [state, dispatch] = useReducer(reducer, { value: 0 });

  return (
    <div>
      <p>
        현재 카운터 값은 <b>{state.value}</b> 입니다.
      </p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+1</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-1</button>
    </div>
  );
};

export default Counter;
  • useReducer의 첫 번째 파라미터에는 리듀서 함수
  • 두 번째 파라미터는 리듀서의 기본값
  • state는 현재 가리키고 있는 상태
  • dispatch는 액션을 발생시키는 함수: 파라미터로 액션 값을 넣음
  • useReducer 사용 시 컴포넌트 업데이트 로직을 컴포넌트 바깥으로 빼낼 수 있다

image

인풋 상태 관리

import React, { useReducer } from 'react';

function reducer(state, action) {
  return {
    ...state,
    [action.name]: action.value,
  };
}

const Info = () => {
  const [state, dispatch] = useReducer(reducer, {
    name: '',
    nickname: '',
  });

  const { name, nickname } = state;
  const onChange = (e) => {
    dispatch(e.target);
  };

  return (
    <div>
      <div>
        <input name="name" value={name} onChange={onChange} />
        <input name="nickname" value={nickname} onChange={onChange} />
      </div>
      <div>
        <div>
          <b>이름:</b> {name}
        </div>
        <div>
          <b>닉네임: </b> {nickname}
        </div>
      </div>
    </div>
  );
};

export default Info;

e.target값 자체를 액션 값으로 사용

image

useMemo

useMemo로 함수형 컴포넌트 내부의 연산을 최적화 할 수 있다

useCallback

  • 첫 번째 파라미터에는 생성하고 싶은 함수
  • 두 번째 파라미터에는 배열로 어떤 값이 바뀔 때 함수를 새로 생성할지 명시
  • 비어있는 배열을 넣으면 컴포넌트가 렌더링될 때 만들었던 함수를 계속해서 재사용
  • 함수 내부에서 상태 값에 의존해야 할 때는 그 값을 반드시 두 번쨰 파라미터 안에 포함
import React, { useState, useMemo, useRef, useCallback } from 'react';

const getAverage = numbers => {
  console.log('평균값 계산중..');
  if (numbers.length === 0) return 0;
  const sum = numbers.reduce((a, b) => a + b);
  return sum / numbers.length;
};

const Average = () => {
  const [list, setList] = useState([]);
  const [number, setNumber] = useState('');
  const inputEl = useRef(null);

  const onChange = useCallback(e => {
    setNumber(e.target.value);
  }, []); // 컴포넌트가 처음 렌더링 될 때만 함수 생성
  const onInsert = useCallback(() => {
    const nextList = list.concat(parseInt(number));
    setList(nextList);
    setNumber('');
    inputEl.current.focus();
  }, [number, list]); // number 혹은 list 가 바뀌었을 때만 함수 생성

  const avg = useMemo(() => getAverage(list), [list]);

  return (
    <div>
      <input value={number} onChange={onChange} ref={inputEl} />
      <button onClick={onInsert}>등록</button>
      <ul>
        {list.map((value, index) => (
          <li key={index}>{value}</li>
        ))}
      </ul>
      <div>
        <b>평균값:</b> {avg}
      </div>
    </div>
  );
};

export default Average;

image

list 배열 내용이 바뀔 때만 getAverage 함수 호출

useRef

  • 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있게 하는 useRef
import {useRef} from 'react';

const RefSample () => {
    const id = useRef(1);
    const setId = (n) => {
        id.current = n;
    }
    const printId = () => {
        console.log(id.current);
    }

    return (
        <div>
            refsample
        </div>
    )
}

export default RefSample;

커스텀 Hooks 만들기

useInputs.js

import { useReducer } from 'react';

function reducer(state, action) {
  return {
    ...state,
    [action.name]: action.value
  };
}

export default function useInputs(initialForm) {
  const [state, dispatch] = useReducer(reducer, initialForm);
  const onChange = e => {
    dispatch(e.target);
  };
  return [state, onChange];
}

Info.js

import React from 'react';
import useInputs from './useInputs';

const Info = () => {
  const [state, onChange] = useInputs({
    name: '',
    nickname: ''
  });
  const { name, nickname } = state;

  return (
    <div>
      <div>
        <input name="name" value={name} onChange={onChange} />
        <input name="nickname" value={nickname} onChange={onChange} />
      </div>
      <div>
        <div>
          <b>이름:</b> {name}
        </div>
        <div>
          <b>닉네임: </b> {nickname}
        </div>
      </div>
    </div>
  );
};

export default Info;

정리 :angel:

  • Hooks 패턴을 사용하면 클래스형 컴포넌트를 작성하지 않고도 대부분의 기능을 구현할 수 있다

9. 컴포넌트 스타일링

  • 일반 CSS
  • Sass
  • CSS Module
  • styled-components

일반 CSS

  • 가장 흔한 방식
  • CSS파일을 import

Sass

  • .scss.sass를 지원
  • .sass: 중괄호와 세미콜론을 사용하지 않음

utils함수 분리하기

// 변수 사용하기
$red: #fa5252;
$orange: #fd7e14;
$yellow: #fcc419;
$green: #40c057;
$blue: #339af0;
$indigo: #5c7cfa;
$violet: #7950f2;

// 믹스인 만들기
@mixin square($size) {
  $calculated: 32px * $size;
  width: $calculated;
  height: $calculated;
}
@import './styles/utils';

CSS Module

  • [파일이름][클래스이름][해시값] 형태로 자동으로 만들어줌
  • .module.css 확장자로 파일을 저장하고 import
  • 서로 다른 CSS 파일에 같은 클래스 이름이 있어도 난독화 과정에서 서로 다른값으로 구분
:global .somthing {
    font-weight: 800;
}

:global: 난독화되지 않음

import styles from './CSSMOdule.module.css';
...
return (
    <div className={style.wrapper}>
    </div>
)
...
  • 클래스 이름은 카멜 표기로 지정
  • 스네이크일 경우 styles["클래스 이름"]
...
return (
    <div className={`${styles.wrapper} ${styles.inverted}`}>
    </div>
)
...
  • 클래스 이름을 두 개 이상 사용 할 때는 템플릿 리터럴 사용

또는

className={[styles.wrapper, styles.inverted].join(' ')}

classnames

  • CSS 클래스를 조건부로 설정할 때 유용한 라이브러리

    $ yarn add classnames

import classNames from 'classnames';

classNames('one', 'two'); //'one two'
classNames('one', {two: true}); //'one two'
classNames('one', {two: false}); //'one'
classNames('one', ['two', 'three']); //'one two three'

const myClass = 'hello';
classNames('one', myClass, {myCondition: true}); // 'one hello myCondition'

예제

const MyComponent = ({hightlighted, theme}) => (
    <div className={classNames('MyComponent', { hightlighted}, theme)}>Hello</div>
)
  • props 값이 존재할 때만 props에 저장된 값이 클래스 이름으로
  • classnames를 쓰지 않으면 삼항연산자를 사용

styled-components

  • 자바스크립트 파일 안에 스타일을 선언하는 방식

    $ yarn add styled-components

import styled from 'styled-components';

const Box = styled.div`
    /* props로 넣어 준 값을 직접 전달해 줄 수 있음 */
    background: ${props => props.color || 'blue'};

    &:hover{
        ...
    }

    ...

    /* inverted 값이 true일때 특정 스타일을 부여 */
    ${props =>
        props.inverted &&
        css `
            background: none;
            ...
        `
    }
`

    const styledComponent = () => {
        <Box color="black">
            <Button>안녕하세요</Button>
            <Button inverted={true}>테두리만</Button>
        </Box>
    }
...

Tagged 템플릿 리터럴

  • 템플릿 안에 자바스크립트 객체나 함수를 전달할 때 온전히 추출할 수 있다
function tagged(...args) {
    console.log(args);
}
tagged`hello ${{ foo: 'bar' }} ${()=> 'world'}!`

반응형 디자인

import React from 'react';
import styled, { css } from 'styled-components';

const sizes = {
  desktop: 1024,
  tablet: 768
};

// size 객체에 따라 자동으로 media 쿼리 함수를 만듦

const media = Object.keys(sizes).reduce((acc, label) => {
  acc[label] = (...args) => css`
    @media (max-width: ${sizes[label] / 16}em) {
      ${css(...args)};
    }
  `;

  return acc;
}, {});

const Box = styled.div`
  ${media.desktop`width: 768px;`}
  ${media.tablet`width: 100%;`};
`;
...

정리 :heart:

  • 어떻게 스타일링 할지는 취향대로 하자

13 리액트 라우터로 SPA 개발하기

SPA란?

  • Single Page Application
  • 한 개의 페이지
  • 필요한 부분만 자바스크립트로 업데이트
  • 라우팅: 다른 주소에 다른 화면을 보여줌

SPA의 단점

  • 실제로 방문하지 않을 수 있는 페이지의 스크립트로 불러옴
  • 검색 엔진 최적화의 문제
  • 서버사이드 렌더링으로 해결할 수 있음 (예: Next.js ...)

프로젝트에 라우터 적용

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { BrowserRouter } from 'react-router-dom';

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

특정 주소에 컴포넌트 연결하기

import { Routes, Route } from 'react-router-dom';

const App = () => {
  return (
    <>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about/:id" element={<About />} />
        <Route path="/*" element={<NotFount />} />
      </Routes>
    </>
  );
};

export default App;
  • exact 옵션 삭제, 여러 라우팅을 매칭하고 싶으면 * 사용
import { Link } from 'react-router-dom';
...
return (
    <>
        <li><Link to="/"></Link></li>
        <li><Link to="/about">소개</Link></li>
    </>
)
...

URL 파라미터와 쿼리

URL 파라미터

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

const About = () => {
    const { id } = useParams();

    ...
  • react-router-dom패키지의 useParams()로 URL 파라미터가 저장되어 있는 객체를 리턴받을 수 있다

URL 쿼리

import React from 'react';
import { useParams, useLocation } from 'react-router-dom';

function About() {
  const location = useLocation();
  const { search } = location;
  const query = new URLSearchParams(search);

  const showDetail = query.get('detail') === 'true';

  return (
    <>
      <h1>About</h1>
      <div>{showDetail && <p>detail값을 true로 설정하셨군요~~!</p>}</div>
    </>
  );
}

export default About;

쿼리의 파싱값은 문자열

image

서브 라우트

  • 라우트 내부에 또 라우트 정의
  • 라우트로 사용되고 있는 컴포넌트의 내부에 Route 컴포넌트를 또 사용
  • 또는: v6에서 추가된 Outlet

APP.js

<Route path="/users/:username/*" element={<UsersPage />}>
  <Route path="" element={<UserMain />} />
  <Route path="about" element={<About />} />
</Route>

UserPage.js

<Outlet />
  • Link대신 NavLink사용시 현재 경로와 Link에서 사용하는 경로가 일치하는 경우 스타일을 다르게 적용할 수 있음
<NavLink to="/about" style={({ isActive }) => ({ color: isActive ? 'green' : 'blue' })}>
  about
</NavLink>

또는

 <NavLink to="/about">about</NavLink>
  • 클래스 이름에 .active가 추가됨
profile
쓰고 싶은 글만 씀
post-custom-banner

0개의 댓글