Props
- 컴포넌트에 전달되는 단일 객체 (Children...)
- 순수 함수처럼 동작함
Props 를 수정하면 안됨
- 어떤 상태를 바꾸려면 state 활용
- 합성 : 여러 컴포넌트를 모아서 합성 가능
- 추출 : 여러 컴포넌트를 따로 빼놓고 재사용 가능
import React from 'react'
function formatDate(date) {
return date.toLocaleDateString();
}
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name} />
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
const comment = {
date: new Date(),
text: 'I hope you enjoy learning React!',
author: {
name: 'Hello Kitty',
avatarUrl: 'http://placekitten.com/g/64/64'
}
};
export default function Extraction() {
return (
<Comment
date={comment.date}
text={comment.text}
author={comment.author} />
)
}
State
- 컴포넌트 내의 상태, 자신의 출력값을 변경
- Class component : state LifeCycle
// rcc : 리액트 컴포넌트 자동완성
import React, { Component } from 'react'
export default class ClassComponent extends Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
// state 기본 값 설정
}
componentDidMount() { // 그려지자마자 호출되는 아이
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() { // 이 컴포넌트가 사라지기 직전에 호출될 아이
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() { // return 한 값 자체를 render
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
- function component : 훅으로 관리
import React, { useState, useEffect } from 'react';
export default function FunctionalComponent(){
const [date, setDate] = useState(new Date());
const tick = () => {
setDate(new Date());
}
useEffect(() => {
const interval = setInterval(() => tick(), 1000);
return() => {
clearInterval(interval);
}
}, []);
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {date.toLocaleTimeString()}.</h2>
</div>
)
}
- 직접 수정하면 비동기적일 수 있음
- setState에서 첫 인자로 오는 state 쓰기
컴포넌트 생명주기
- constructor: state 초기화, 메서드 바인딩
- componentDidMount: Dom 노드 초기화 및 데이터 fetch
- componentWillUnmonet: 타이머 제거 및 요청 취소 및 구독 해제
- functional Component: hook으로 대부분 구현 가능
이벤트
- 합성이벤트 : 인터페이스는 같지만 직접 대응되지 않음
- 버블링 : 어떤 element를 클릭했을 때 부모로 전파
- 캡쳐링 : 부모 요소가 자식 요소들 중 어떤 element에 클릭이 되었는지 보는 것
- 캡쳐링 후 버블링이 일어남
import React, { Component } from 'react'
export default class Event extends Component {
render() {
const handleClick = () => {
console.log('handleButtonClick')
}
const handleMouseLeave = () => {
console.log('handleMouseLeave')
}
const handleClickCapture = () => {
console.log("handleClickCapture")
}
const handleClickCapture2 = () => {
console.log("handleClickCapture2")
}
const handleClickBubble = () => {
console.log("handleClickBubble")
}
return (
<div onClickCapture={handleClickCapture}>
<div onClickCapture={handleClickCapture2} onClick={handleClickBubble}>
<button onClick={handleClick} onMouseLeave={handleMouseLeave}>button</button>
</div>
</div>
)
}
}
/*
!! console.log
handleClickCapture
handleClickCapture2
handleButtonClick
handleClickBubble
*/
조건부 렌더링
- 어떤 상황일 때 ~ 를 render한다
- if : if(condition){return A} eles {return B}
- && : condition && A // fasly 주의!
- 삼항연산자 : condition ? A : B
- return null : 아예 안그리고 싶은 경우
import React from 'react'
function UserGreeting(props){
return <h1>{props.name}, Welcome It's {props.count} times</h1>
}
function GuestGreeting(props){
return <h1>Please sign up.</h1>
}
function Greeting(props){
if(props.isLoggedIn){
return <UserGreeting name="jimy" count={0}/>
}
return <GuestGreeting />;
}
export default function Condition() {
const isLoggedIn = false;
return (
<div>
<Greeting isLoggedIn={isLoggedIn}/>
</div>
)
}
list
- (= array, 배열)
- props 로 key를 꼭 넣어야 한다
item 자체에 key를 주는 것이 아님
key는 형제 사이에서만 고유한 값이어야 하고 같은 컴포넌트 사이 부모가 다른 아이들이면 같은 id를 써도 무방하다
- map : 배열의 개별 요소를 순회함
- default key : 키를 안주면 index를 알아서 key로 쓰고 있음
(워닝은 나지만 동작은 함: 유효한 값을 줘야하는 이유, index는 수정될 수 있는 값)
- 고유성 : 형제 사이에서만 고유하면 됨
- key props : 키는 props 로 넘어가지 않는다
import React from 'react'
export default function List() {
// const numbers = [1, 2, 3, 4, 5]
// return (
// <ul>
// {numbers.map((item) => (<li>{item}</li>))}
// </ul>
// );
const todos = [
{id: 1, text: 'drink water'},
{id: 2, text: 'wash car'},
{id: 3, text: 'Listen Lecture'},
{id: 4, text: 'ge to bed'}
];
const Item = (props) => {
return (
<li>{props.id}
{props.text}</li>
);
};
const todoList = todos.map((todo) => <Item key={todo.id} id={todo.id} text={todo.text}/>);
return <>{todoList}</>;
}
- 제어 컴포넌트 : 컴포넌트 자체에 value 를 주는 것
- controlled component : input의 value를 state로 관리
import React, { useState } from 'react'
export default function ControlledComponent() {
const [name, setName] = useState('');
const [essay, setEssay] = useState("please ~")
function handleChange(event){
setName(event.target.value);
};
function handleEssayChange(event){
setEssay(event.target.value);
};
function handleSubmit(event) {
alert(`name: ${name}, essay: ${essay}`);
event.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<label>
Name:
<input type="text" value={name} onChange={handleChange}/>
</label>
<label>
Essay:
<textarea value={essay} onChange={handleEssayChange}/>
</label>
<input type="submit" value="submit"/>
</form>
)
}
- 다중 입력 : 하나의 핸들러를 가지고 target의 name으로 할 수 있음
- uncontrolled component : 폼 자체의 내부 상태를 활용
import React, {useRef} from 'react'
export default function UncontrolledComponent() {
const fileInputRef = useRef(null);
function handleSubmit(event){
event.preventDefault();
alert(`selected file = ${fileInputRef.current.files[0].name}`);
}
return (
<div>
<form onSubmit={handleSubmit}>
<label>
Upload file:
<input type="file" ref={fileInputRef}/>
</label>
<br/>
<button type="submit">submit</button>
</form>
</div>
)
}
- defaultValue, ref : 기본값, value 확인
Hook
- Class의 단점 보완, 재사용성 강화
- class를 사용하지 않고도 state와 다른 기능들을 사용할 수 있음
- 사용 규칙
- 최상위에서만 hook을 호출해야 한다
함수지만 반복문, 조건문, 중첩 함수에서 hook을 사용하면 에러가 난다
원하지 않는 side effect가 발생할 수 있음
- React 함수 컴포넌트 내에서만 hook 호출 해야 함
- effect가 업데이트 시마다 실행되는 이유?
- 표현을 하나로 합치고, 버그를 방지하기 위함
- 가독성이 좋아짐
- Reducer
import React , {useReducer} from 'react'
export default function Reducer() {
const initalState = {count: 0};
function reducer(state, action){
switch(action.type){
case 'reset':
return initalState;
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
const [state, dispatch] = useReducer(reducer, initalState);
return (
<div>
Count : {state.count}
<button onClick={() => dispatch({type: 'reset'})}>Reset</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</div>
)
}
import React , {useState} from 'react'
export default function State() {
const initialCount = 0;
const [count, setCount] = useState(initialCount);
return (
<div>
Count : {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prev => prev - 1)}>-</button>
<button onClick={() => setCount(prev => prev + 1)}>+</button>
</div>
)
}
Composition
- 컴포넌트 안에서 컴포넌트를 모아 출력한 것
- 상속 대신 합성을 사용하여 컴포넌트 간에 코드를 재사용함
- 담는 방법
- typeof 를 통해 확장 가능 (다양한 상황을 품을 수 있음)
HOC
- 컴포넌트 로직을 재사용하기 위한 고급기술
- React API의 일부가 아님
- 컴포넌트를 가져와 새 컴포넌트를 반환하는 함수
( 함수를 받아서 함수를 리턴 )
Memoization
- 동일한 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술
- React.memo : 동일한 props로 rendering을 한다면 react.memo를 사용하여 성능 향상을 할 수 있음
memo를 사용하면 컴포넌트를 랜더링 하지 않고 마지막으로 랜더링 한 것을 재사용한다.
import React, { Profiler, memo, useState, useMemo } from 'react'
import "./CommentItem.css";
function CommentItem({title, content, likes, onClick}) {
const [clickCount, setClickCount] = useState("");
function onRenderCallback(
id, // 방금 커밋된 Profiler 트리의 "id"
phase, // "mount" (트리가 방금 마운트가 된 경우) 혹은 "update"(트리가 리렌더링된 경우)
actualDuration, // 커밋된 업데이트를 렌더링하는데 걸린 시간
baseDuration, // 메모이제이션 없이 하위 트리 전체를 렌더링하는데 걸리는 예상시간
startTime, // React가 언제 해당 업데이트를 렌더링하기 시작했는지
commitTime, // React가 해당 업데이트를 언제 커밋했는지
interactions // 이 업데이트에 해당하는 상호작용들의 집합
) {
// 렌더링 타이밍을 집합하거나 로그...
console.log(`actualDuration(${title}: ${actualDuration})`)
}
const handleClick = () => {
onClick();
setClickCount(prev => prev + 1); // rate check를 다시 쓰게 됨, 이때 스는게 useMemp
alert(`${title} 눌림`);
}
const rate = useMemo(() => {
console.log("rate check");
return likes > 10 ? 'good' : "bad"; // useMemo 추가하면 rate check 안함
}, [likes]);
return (
<Profiler id="CommentItem" onRender={onRenderCallback}>
<div className="CommentItem" onClick={handleClick}>
<span>{title}</span>
<br/>
<span>{content}</span>
<br/>
<span>{likes}</span>
<br/>
<span>{rate}</span>
<br/>
<span>{clickCount}</span>
</div>
</Profiler>
)
}
export default memo(CommentItem);
/*
memo를 쓰지 않으면
console.log(`actualDuration(${title}: ${actualDuration})`) 에서 확인 시
새로 생기는 것들을 1부터 ~ 새로생긴 것만큼 계속 생김
memo를 쓰면
추가 된 것들만 console 에 찍힘
: 비효율을 줄일 수 있다
부모에 onClick 을 넣으면 memo를 써도 안쓴것처럼 됨..
comments 자체가 reRender 되기 때문
(설정한 함수가 계속 실행되기 때문)
>> useCallback 사용
*/
- Profiler API를 이용하여 리액트 성능 분석
- useCallback : 주입되거나 전달될 때 메모를 해놨음에도 계속 바뀜
(내가 받은 props가 바뀌기 때문)
import React, { useCallback } from 'react'
import CommentItem from './CommentItem'
export default function Comments({commentList}) {
const handleClick = useCallback(() => {
console.log("눌림");
}, []);
return (
<div>
{commentList.map(comment => <CommentItem
key={comment.title}
title={comment.title}
content={comment.content}
likes={comment.likes}
onClick={handleClick}
/>)}
</div>
)
}