리액트의 특징
- state 값이 변경되면 DOM을 업데이트하지 않고 아예 새로 만듦
- JSX라는 문법을 사용
- Webpack, babel 등
리액트의 Virtual DOM
- 브라우저에 실제로 보여지는 DOM이 아니라 메모리에 가상으로 존재하는 DOM
- Javascript 객체
=> 실제 DOM보다 속도가 굉장히 빠름
JSX
- babel을 통해 JSX 문법이 Javascript로 변환
- 닫힌 태그가 꼭 있어야 함(혹은 self closing tag)
- 하나의 최상단 태그로 감싸야 함
=> 리액트의 Fragment 사용 가능(<></>)
- JSX 내부에 자바스크립트를 사용해야 할 경우 {}로 감싸서 사용
Babel
- 자바스크립트 문법 확장 도구
- 아직 지원되지 않는 최신 문법 혹은 실험적인 문법을 정식 자바스크립트 형태로 변환하여 구형 브라우저같은 환경에서도 실행이 가능하도록 해주는 도구
JSX vs HTML 차이점
- 컴포넌트에 style 속성을 전달할 경우에는 객체 형태로 전달해야 함 + camel case
- class가 아닌 className 사용
props
- JSX 컴포넌트에 전달되는 속성
- 값 전달을 생략할 경우에는 true가 전달됨
- 컴포넌트명.defaultProps = { ... } 으로 props의 기본값 설정 가능
Hello.defaultProps = {
name: 'babo',
}
Q. 컴포넌트 태그 안에 다른 컴포넌트를 넣었는데 안보여요!
A. 자식 컴포넌트들이 props.children으로 전달되어 그렇다. 부모 컴포넌트에서 children props를 받아서 JSX 컴포넌트 안에 넣어주자.
/* App.js */
const App = () => {
return (
<Wrapper>
<Hello name="배대시"></Hello> // Wrapper 안에 있는 Hello 컴포넌트가 페이지에 보이지 않음
<Hello></Hello>
</Wrapper>
)
}
/* Wrapper.js */
const Wrapper = ({ children }) => {
const style = {
border: '2px solid black',
padding: '16px',
};
return (
<div style={style}>
{children}
</div>
)
}
propTypes
- 부모로부터 전달받은 props의 type을 검사하는 모듈
- 자식 컴포넌트에서 명시한 props의 데이터 타입과 부모로부터 넘겨받은 props의 데이터 타입이 일치하지 않으면 콘솔 에러 출력
- 사용법
import PropTypes from 'prop-types'
/* Hello.js */
Hello.propTypes = {
name: PropTypes.string // name이 string 타입이 아니면 에러 발생
}
cf) 리액트 코드 자동완성
: vscode에서 Reactjs code snippets 플러그인 설치
종류 | 설명 |
rcc | 클래스 컴포넌트 생성 |
rrc | 클래스 컴포넌트와 react-redux를 연결하여 생성 |
rcjc | import와 export 없이 클래스 컴포넌트 생성 |
rwwd | import 없이 클래스 컴포넌트 생성 |
rsc | 화살표 함수형 컴포넌트 생성 |
rsf | function 키워드의 함수형 컴포넌트 생성 |
조건부 렌더링
- 삼항 연산자 혹은 &&을 이용하여 조건부 렌더링 가능
Hook API
1. useState
- 아래 형태로 state와 state를 변경하는 함수를 선언하여 사용
const [number, setNumber] = useState(0)
- setState 함수는 이전 state 값을 파라미터로 받음
const [number, setNumber] = useState(0)
const plus = () => setNumber(prev => prev + 1)
const minus = () => setNumber(prev => prev - 1)
return (
<div className="App">
{number}
<button type="button" onClick={minus}>-</button>
<button type="button" onClick={plus}>+</button>
</div>
)
- state 객체 하나로 여러 개의 state를 한 번에 관리할 수도 있음
: 객체 형태로 state 선언, 스프레드 연산자(...)를 이용하여 setState에 값 전달
const App = () => {
const [input, setInput] = useState({
name: '',
nickname: '',
})
const { name, nickname } = input
const change = ({ target }) => {
setInput({
...input,
[name]: target.value,
})
}
const reset = () => setInput({
name: '',
nickname: '',
})
return (
<div className="App">
{name}({nickname})
<input name="name" type="text" placeholder="이름" onInput={change} value={name}></input>
<input name="nickname" type="text" placeholder="닉네임" onInput={change} value={nickname}></input>
<button type="button" onClick={reset}></button>
</div>
)
}
- 배열 state에 새로운 값을 추가할 때는 기존 state의 값을 변경하면 안됨
: 기존 배열을 복사하여 새로운 배열을 만든 후, 새로운 배열에 수정 사항을 반영해야 함
=> 스프레드 연산자(...) 혹은 concat을 사용
const [lst, setLst] = useState([1, 2, 3])
// 1. 스프레드 연산자 사용
setLst([ ...lst, 4 ])
// 2. concat 사용
setLst(lst.concat(4))
- 배열 state에서의 값 삭제는 filter() 고차함수를 활용
- 배열 state에서의 값 수정은 map() 고차함수를 활용
2. useRef
- DOM에 직접 접근해야 할 경우에 사용
- useRef()를 사용하여 ref 객체를 만들고, 접근하려는 컴포넌트의 ref 값으로 전달
- current 객체를 통해 지정한 DOM에 접근 가능
const App = () => {
const nameRef = useRef()
const reset = () => nameRef.current.focus()
return (
<div className="App">
{name}({nickname})
<input ref={nameRef} name="name" type="text" placeholder="이름"></input>
<input name="nickname" type="text" placeholder="닉네임"></input>
<button type="button" onClick={reset}></button>
</div>
)
}
- 컴포넌트 내의 다음과 같은 변수들을 useRef()를 사용하여 관리할 수 있음
- setInterval, setTimeout으로 생성된 ID
- 외부 라이브러리를 통해 생성된 인스턴스
- 스크롤 위치
const nextId = useRef(4)
const onCreate = () => {
nextId.current += 1 // 변수명.current로 해당 값에 접근할 수 있음
}
=> useRef()를 이용하여 변수를 관리하면 리렌더링 없이 변수 값을 변경할 수 있음!
3. useEffect
- 컴포넌트 마운트/언마운트시 혹은 props의 업데이트시에 특정 작업을 실행되도록 함
- 첫 번째 파라미터는 함수, 두 번째 파라미터는 해당 함수의 의존값 배열을 전달
- return되는 함수는 cleanup의 역할
- 빈 배열 전달
: 해당 컴포넌트가 렌더링/사라질 때 실행(마운트/언마운트 시)
useEffect(() => {
console.log('컴포넌트가 처음 렌더링될 때 실행') // componentDidmount
return () => {
console.log('컴포넌트가 사라질 때 실행') // componentWillUnmount
}
}, [])
=> 주로 다음과 같은 상황에 사용됨
- 마운트 사용 예시
- props 값을 컴포넌트의 state로 설정
- 외부 API 요청
- 라이브러리 사용(D3.js, Video.js 등)
- setInterval 혹은 setTimeout을 이용한 반복 작업
- 언마운트 사용 예시
- clearInterval, clearTimeout
- 라이브러리 인스턴스 제거
- 배열에 특정 값(props)를 전달하는 경우
: 배열 내의 값들이 변경될 때마다 실행됨
useEffect(() => {
console.log('값이 바뀐 후 혹은 처음 설정될 때 실행') // componentDidmount + componentDidUpdate
return () => {
console.log('값이 바뀌기 직전에 실행') // componentWillUnmount
}
}, [name, email])
- 배열 자체를 생략하는 경우(파라미터를 전달하지 않는 경우)
: 컴포넌트가 리렌더링될 때마다 실행됨 (자식 컴포넌트의 경우에는 부모 컴포넌트가 리렌더링될 때에도 실행됨)
useEffect(() => {
console.log('리렌더링될 때 실행')
})
4. useMemo
- 메모이제이션, 자주 연산되는 값을 저장하여 재사용
- 첫 번째 파라미터는 함수, 두 번째 파라미터는 해당 함수의 의존값 배열을 전달
- 두 번째 파라미터 배열의 값이 변경되지 않는다면 이전 값을 재사용하여 성능 최적화
const count = countFunc() // 컴포넌트가 렌더링될 때마다 실행되므로 비효율적
const count = useMemo(() => { // 값을 저장하여 사용하므로 효율적
countFunc(users)
}, [count])
cf) React.memo
- props가 바뀌지 않았다면 컴포넌트의 리렌더링을 방지하는 성능 최적화 함수
- export할 때 컴포넌트 자체를 React.memo()로 감싸서 사용하면 됨
import React from 'react'
const UserList = ({ users, onRemove, onToggle }) => {
return (
<div>
{users.map(user => (
<User
user={user}
key={user.id}
onRemove={onRemove}
onToggle={onToggle}
/>
))}
</div>
)
}
export default React.memo(UserList) // 이렇게 감싸주면 됨
- React.memo는 기본적으로 얕은 비교로 동작
: 두 번째 파라미터로 비교함수를 전달하여 해당 컴포넌트의 prevProps와 curProps를 비교하여 true를 반환하면 컴포넌트 재사용
const areEqual = (prev, next) => {
return (
prev.title === next.title &&
prev.content === next.content
);
}
export default React.memo(Component, areEqual);
- 무한 스크롤이나 더보기 버튼을 클릭하여 새로운 요소를 생성하는 경우처럼 기존 렌더링된 요소들이 계속 렌더링되는 것을 방지하기 위해 주로 사용
5. useCallback
- useMemo 기반으로 만들어짐
- useMemo는 값을 재사용하는 반면, useCallback은 함수를 재사용하는 데 사용
=> 컴포넌트가 리렌더링될 때마다 함수가 재생성되는 걸 방지
- 첫 번째 파라미터는 함수, 두 번째 파라미터는 해당 함수의 의존값 배열을 전달
=> 의존값 배열에는 함수 내에서 사용되는 state 혹은 props를 포함(ref는 포함 X)
// before
const onRemove = id => setUsers(users.filter(user => user.id !== id))
// after
const onRemove = useCallback(
id => setUsers(users.filter(user => user.id !== id)), [users]
)
6. useReducer
- 상태관리의 방식 중 하나
- 컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리할 수 있음
=> 상태 업데이트 로직을 컴포넌트 바깥(다른 파일)에 작성하여 import할 수 있음
- reducer란?
: 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해 주는 함수
action은 업데이트를 위한 데이터를 가지며, 주로 type 값을 지닌 객체 형태로 사용
// 리듀서 예시
const stateReducer = (state, action) => {
switch(action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
// action 객체 예시
{ type: 'INCREMENT' }
{ type: 'DECREMENT' }
{
type: 'CHANGE_INPUT',
key: 'email',
value: 'wastfg6972@naver.com',
}
- dispatch 함수를 사용하여 액션을 발생시키고 리듀서를 실행
: const [state명, dispatch] = useReducer(reducer명, 초기값) 형태로 선언하여 사용
function Counter() {
const [number, dispatch] = useReducer(stateReducer, 0)
return (
<div>
<h1>{number}</h1>
<button onClick={() => dispatch(number, { type: 'INCREMENT'})}>+1</button>
<button onClick={() => dispatch(number, { type: 'DECREMENT'})}>-1</button>
</div>
);
}
export default Counter;
cf) useReducer vs useState
: 상태가 단순한 값이거나(ex boolean) 하나인 경우에는 useState로 관리하는 게 편함
하지만 상태값을 여러 개 관리해야 한다거나 구조가 복잡해지는 경우에는 useReducer가 적절
7. Context API(React.CreateContext, useContext)
- 부모 컴포넌트에서 하위 자식 컴포넌트로 props를 전달해줄 때 여러 개의 컴포넌트를 거쳐서 전달해야 하는 기존 방식의 불편함을 해결하기 위한 방식
- 프로젝트 안에서 전역적으로 사용할 수 있는 값을 선언하여 사용할 수 있음
=> state가 될 수도 있고 라이브러리의 인스턴스가 될 수도 있음, 혹은 DOM도 가능
- React.createContext() 함수를 사용하여 Context를 정의할 수 있으며, Context 안에 Provider라는 컴포넌트를 통해 Context의 값을 정할 수 있음
: Provider의 value에 값 또는 함수를 전달하여 전역적으로 사용 가능
// Context 선언 및 export
export const UserDispatch = React.createContext(null)
// 위 방식처럼 선언한 후, 아래 return부에서 선언한 Context의 Provider 컴포넌트로 감싸야 함
return (
<UserDispatch.Provider value={dispatch}>
<CreateUser
username={username}
email={email}
onChange={onChange}
onCreate={onCreate}
/>
<UserList users={users} />
<div>활성사용자 수 : {count}</div>
</UserDispatch.Provider>
)
// import하여 사용 가능
import { UserDispatch } from './App'
- useContext를 사용하여 정의된 Context를 조회하여 사용할 수 있음
import { UserDispatch } from './App'
const dispatch = useContext(UserDispatch)
// ...
function User({ user, onRemove, onToggle }) {
return (
<div>
<b
onClick={() => dispatch({ type: 'TOGGLE_USER', id: user.id})} // 해당 방식처럼 사용 가능
>
{user.username}
</b>
<span>({user.email})</span>
<button onClick={() => dispatch({ type: 'REMOVE_USER', id: user.id})}>삭제</button>
</div>
);
}
- React.createContext
const Store = React.createContext(defaultValue)
: context 객체를 만드는 함수로, Provider와 Consumer 컴포넌트를 반환함
만들어진 context를 사용하려면 해당 context의 Provider 컴포넌트로
defaultValue는 Provider와 Consumer에서 적절한 value값을 찾지 못한 경우의 기본값으로 사용됨
- Context.Provider
: 정의한 context를 하위 컴포넌트에게 전달하는 역할
Provider 내부에 하위 Provider를 배치할 수 있으나, 그럴 경우에는 하위 Provider 값이 우선시됨
Provider 하위에 context를 가진 컴포넌트들은 Provider의 value인 state의 값이 변할 때마다 전부 리렌더링됨
- Context.Consumer
: context 변화를 구독하는 컴포넌트
context의 자식으로는 함수 또는 함수 컴포넌트를 가짐
함수의 매개변수로는 가장 가까운 Provider의 value가 전달됨
상위 Provider가 존재하지 않는 경우에는 createContext()에서 정의한 defaultValue가 전달됨
커스텀 Hooks 만들기
- 컴포넌트를 만들면서 반복되는 로직을 분리하여 커스텀 Hooks로 만들어 재사용이 가능하다.
- use + 키워드 형태의 이름을 갖는 함수를 만들어서 export하여 커스텀 Hooks로 사용하면 된다.
// useInputs.js
import { useState, useCallback } from 'react';
function useInputs(initialForm) {
const [form, setForm] = useState(initialForm);
// change
const onChange = useCallback(e => {
const { name, value } = e.target;
setForm(form => ({ ...form, [name]: value }));
}, []);
const reset = useCallback(() => setForm(initialForm), [initialForm]);
return [form, onChange, reset];
}
export default useInputs;
// App.js
const initInputs = {
username: '',
email: ''
}
const [{ username, email }, onChange, reset] = useInputs(initInputs)
Immer를 사용한 불변성 관리
- 리액트에서의 불변성을 편리하게 관리할 수 있는 라이브러리
- 기존 스프레드 연산자(혹은 map, concat, filter)를 이용하여 불변성을 관리하던 방식 대체
=> immer를 사용하여 코드량을 단축시킬 수는 있으나 오히려 길어기는 경우도 있으니 주의해서 사용하자!
- 기존 방식보다 성능은 평균적으로 느림
- 사용법
1. immer 라이브러리 설치
# npm i immer
// 혹은
# yarn add immer
2. import하여 사용
: 보통 produce라는 이름으로 불러음
첫 번째 파라미터는 prevState, 두 번째 파라미터는 업데이트 함수를 넣어줌
import produce from 'immer'
const state = {
number: 1,
dontChangeMe: 2
}
const nextState = produce(state, draft => draft.number += 1)
cf) React Developer Tools
: 리액트 디버깅용 크롬 확장 프로그램
클래스형 컴포넌트
- render() 메소드가 꼭 있어야 함
- props와 state에 접근하기 위해서는 this.props 형태로 앞에 this를 붙여서 접근해야 함
- state 변경은 this.setState()를 이용하면 됨
: setState() 함수에 콜백 함수를 넣을 수도 있음!
this.setState({ counter: this.state.counter - 1 })
// 혹은
this.setState(state => { counter: state.counter - 1 }) // 연속적으로 setState를 호출할 때 사용하면 좋음
- defaultProps는 분리해서 선언해도 되고, 아래처럼 static 키워드로 선언해도 됨
- 컴포넌트 내에 메소드 선언시, this를 bind 해줘야 함
: 메소드를 화살표 함수로 선언하면 bind를 하지 않아도 됨 but 정식 자바스크립트 문법은 아니므로 CRA 프로젝트 내에서만 사용 가능
import React, { Component } from 'react'
class Hello extends Component {
constructor(props) {
super(props)
this.handleIncrease = this.handleIncrease.bind(this)
//this.handleDecrease = this.handleDecrease.bind(this) // 안해도 됨
this.state = {
counter: 0
}
}
handleIncrease() {
console.log('increase')
}
handleDecrease = () => {
console.log('decrease')
}
static defaultProps = {
name: '이름없음'
}
render() {
const { color, name, isSpecial } = this.props
return (
<div>
<h1>{this.state.counter}</h1>
<button onClick={this.handleIncrease}>+1</button>
<button onClick={this.handleDecrease}>-1</button>
</div>
)
}
}
export default Hello
편리한 개발 도구 모음
Prettier
- 코드 포매터, 스타일러 도구
- 사용 방법
1. .prettierrc 파일을 만들어서 내용을 입력
{
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": true
}
2. prettier 익스텐션 다운로드
3. ctrl + , 키를 눌러서 VSCode 환경설정을 연 뒤, Format On Save 검색 후 체크 + Default Formatter에서 Prettier 선택
ESLint
- 자바스크립트 문법 검사 도구
- CRA 프로젝트에서는 기본 적용이 되어 있음
- 기본 설정 규칙 자체를 라이브러리로 제공함
- eslint-config-airbnb
- eslint-config-google
- eslint-config-standard
=> 적용 방법
1. 해당 config 라이브러리 설치
# npm i eslint-config-standard
2. package.json 파일을 열어서 eslintConfig 부분을 아래와 같이 수정
"eslintConfig": {
"extends": [ "react-app", "standard" ] // 설치한 config파일의 이름을 끝에 추가
},
3. (prettier와 함께 사용하는 경우에만 설정) eslint-config-prettier 라이브러리 설치 후 2번처럼 eslintConfig 부분 뒤에 prettier 추가
# npm i eslint-config-perttier
"eslintConfig": {
"extends": [ "react-app", "standard", "prettier" ] // 뒤에 prettier 추가
},
- 문법 규칙 비활성화 방법
: package.json의 "rules"에서 비활성화하고자 하는 규칙에 0 값으로 설정하면 됨
"eslintConfig": {
"extends": [ "react-app", "standard", "prettier" ], // 뒤에 prettier 추가
"rules" : {
"react/jsc-filename-extension" : 0, // rules에서 비활성화하고자 하는 규칙에 0을 주면 비활성화
"no-unused-vars": 1
}
},
Snippet
- 자주 사용되는 코드에 대한 단축어를 만들어서 사용할 수 있는 IDE 자체 기능
- react snippet 등의 확장 플러그인을 마켓에서 다운받아 사용하거나 직접 스니펫을 만들어서 사용할 수 있음
'Frontend > React' 카테고리의 다른 글
forwardRef - 함수 컴포넌트에서의 ref 속성 사용 (0) | 2022.03.16 |
---|---|
React - 2. 리액트 컴포넌트 스타일링하기 (2) | 2022.01.16 |
React.js - JSX에서 문자열(String) 타입을 HTML으로 렌더링 (0) | 2021.06.17 |
React.js - 함수형 컴포넌트의 생명주기(Life Cycle) (0) | 2021.06.09 |
React.js - 클래스형 컴포넌트의 생명주기(Life Cycle) (0) | 2021.06.09 |