import React, {useState} from 'react';
function App() {
const [count, setCount] = useState(0);
return (
<div>
react hooks
<div>
<p>
useState
</p>
<p>
{count}<br/>
<button onClick={()=> setCount(count+1)}>값 증가</button>
</p>
</div>
</div>
);
}
export default App;
function incrementCount() {
// count 상태값을 2번 증가시키려고 한다.
setCount(count+1);
setCount(count+1);
}
function incrementCount2() {
// incrementCount() 의 문제를 아래와 같이 해결이 가능
setCount(prevState => prevState + 1);
setCount(prevState => prevState + 1);
}
import React, {useEffect} from 'react';
export default function EffectComponent() {
// useEffect
useEffect(()=> {
console.log(`useEffect in EffectComponent`);
});
// useEffect
useEffect(()=> {
console.log(`useEffect in EffectComponent(first render)`);
},[]);
return (
<div>
EffectComponent
</div>
)
}
// useEffect
useEffect(()=> {
const onResize = () => console.log(`resize event`);
window.addEventListener('resize', onResize);
return () => {
window.removeEventListener('resize', onResize);
}
},[]);
import {useEffect, useState} from 'react';
export default function useMounted() {
const [mounted, setMounted] = useState(false);
useEffect(() => {
console.log('-- useEffect in useMounted --');
setMounted(true);
}, []);
return mounted;
}
function Profile() {
const [age, setAge] = useState(0); // 2
const [name, setName] = useState('홍길동'); // 3
useEffect(()=>{
setAge(10); // 1 age 상태값을 변경한다.
},[]);
//...
}
보통 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달할 때 props(속성값) 를 사용하는데 가까운 depth 에 있는 몇 개의 하위 컴포넌트로 전달할 때는 props 로 충분하다.
문제는 많은 수의 하위 컴포넌트로 또는 depth 가 많아질수록 props 로 내려주는 코드를 반복적으로 작성해야하는 문제가 발생한다. (이는 가독성 + 유지보수성도 좋지않다)
이런 경우에 context API 를 사용하면 컴포넌트의 중첩 구조가 복잡한 상황에서도 비교적 쉽게 데이터를 전달할 수 있다. (물론, redux / mobx 같은 상태관리라이브러리를 사용하여 해결해도 된다)
import React from 'react';
import Child from "./components/Child";
export default function App() {
return (
<div className="App">
<Child msg='hello Child Component'/>
</div>
);
}
import React from "react";
import Child2 from "./Child2";
export default function Child(props) {
return (
<div>
<p>{props.msg}</p>
<Child2 msg={props.msg}/>
</div>
)
}
import React from "react";
import Child3 from "./Child3";
export default function Child2(props) {
return (
<div>
<p>{props.msg}</p>
<Child3 msg={props.msg}/>
</div>
)
}
import React from "react";
export default function Child3(props) {
return (
<div>
<p>{props.msg}</p>
</div>
)
}
import React from 'react';
import Child from "./components/Child";
// context API
export const MyContext = React.createContext(''); // createContext() 호출하여 context 객체 생성
// {Provider, Consumer}
// 상위 컴포넌트에서 Provider 컴포넌트를 이용해서 데이터를 전달
// 하위 컴포넌트에서 Consumer 컴포넌트를 이용해서 데이터를 사용
export default function App() {
return (
<div className="App">
<MyContext.Provider value="hello Child3 Component">
<Child msg='hello Child Component'/>
</MyContext.Provider>
</div>
);
}
import React from "react";
// context API
import {MyContext} from '../App.js';
export default function Child3(props) {
return (
<MyContext.Consumer>
{msg => <p>{msg}</p>}
</MyContext.Consumer>
)
}
import React, {useState} from 'react';
import Child from "./components/Child";
// context API
export const MyContext = React.createContext(''); // createContext() 호출하여 context 객체 생성
// {Provider, Consumer}
// 상위 컴포넌트에서 Provider 컴포넌트를 이용해서 데이터를 전달
// 하위 컴포넌트에서 Consumer 컴포넌트를 이용해서 데이터를 사용
export default function App() {
const [msg, setMsg] = useState('hello Child3 Component');
return (
<div className="App">
<button onClick={()=> setMsg('안녕? 3번째 자식 컴포넌트!')}>메시지 변경</button>
<MyContext.Provider value={msg}>
<Child msg='hello Child Component'/>
</MyContext.Provider>
</div>
);
}
import React from "react";
import Child2 from "./Child2";
export default function Child(props) {
return (
<div>
<p>{props.msg}</p>
<Child2 msg={props.msg}/>
</div>
)
}
import React, {useState} from "react";
import Child3 from "./Child3";
const Child3Memo = React.memo(()=>{
return (<Child3/>)
});
export default function Child2(props) {
const [name, setName] = useState('zzzz');
return (
<div>
<p>{props.msg} / {name}</p>
<button onClick={() => setName('test')}>이름 변경</button>
<Child3Memo/>
</div>
)
}
import React from "react";
// context API
import {MyContext} from '../App.js';
export default function Child3() {
console.log(`Child3 Component rendered..`);
return (
<MyContext.Consumer>
{msg => <p>{msg}</p>}
</MyContext.Consumer>
)
}
이제 Consumer 컴포넌트 없이 context API 를 사용하자!(useContext)
import React, {useState} from 'react';
import Child from "./components/Child";
import Child4 from "./components/Child4";
// context API
export const MyContext = React.createContext(''); // createContext() 호출하여 context 객체 생성
// {Provider, Consumer}
// 상위 컴포넌트에서 Provider 컴포넌트를 이용해서 데이터를 전달
// 하위 컴포넌트에서 Consumer 컴포넌트를 이용해서 데이터를 사용
export const MyContext2 = React.createContext('');
const Child4Memo = React.memo(() => {
return (
<Child4/>
)
});
export default function App() {
const [msg, setMsg] = useState('hello Child3 Component');
return (
<div className="App">
<button onClick={() => setMsg('안녕? 3번째 자식 컴포넌트!')}>메시지 변경</button>
<MyContext.Provider value={{msg, setMsg}}>
<Child msg='hello Child Component'/>
<MyContext2.Provider value={msg}>
<Child4Memo/>
</MyContext2.Provider>
</MyContext.Provider>
</div>
)
}
import React, {useContext} from "react";
import {MyContext2} from '../App';
export default function Child4() {
const myContext = useContext(MyContext2); // useContext 훅을 사용
console.log('Child4 Component rendered..');
return (
<div>
<p style={{padding : '4px', border : '1px dotted gray'}}>
Child4 Component : {myContext}
</p>
</div>
)
}
import React, {useEffect, useState, useRef} from "react";
function TextInput({inputRef}) {
return (
<input type='text' ref={inputRef}/>
)
}
export default function RefComponent() {
const [inputText, setInputText] = useState('');
const inputRef = useRef();
const inputRef2 = useRef();
useEffect(()=>{
inputRef.current.focus();
},[]);
return (
<div>
<input type='text' ref={inputRef} onInput={(e) => setInputText(e.target.value)}/>
<p>{inputText}</p>
<TextInput inputRef={inputRef2}/>
<button onClick={() => inputRef2.current.focus()}>포커스 이동</button>
</div>
)
}
import React, {useRef} from "react";
const TextInput2 = React.forwardRef((props, ref) => {
return (
<input type='text' ref={ref}/>
)
});
export default function RefComponent() {
const inputRef3 = useRef();
return (
<div>
<TextInput2 ref={inputRef3}/>
<button onClick={() => inputRef3.current.focus()}>포커스 이동</button>
</div>
)
}
import React, {useRef, useState} from "react";
const INIT_TEXT = 'HI';
export default function RefComponent2() {
const [text, setText] = useState(INIT_TEXT);
const [showText, setShowText] = useState(true);
return (
<div>
{showText &&
<input type='text'
value={text}
onInput={(e) => setText(e.target.value)}
ref={(ref) => ref && setText(INIT_TEXT)}
/>
}
<button onClick={() => setShowText(!showText)}>보이기/가리기</button>
</div>
)
}
import React, {useState, useCallback, useRef, useEffect} from "react";
const INIT_TEXT = 'HI';
const INIT_TEXT2 = 'HI2';
export default function RefComponent2() {
const [text, setText] = useState(INIT_TEXT);
const [text2, setText2] = useState(INIT_TEXT2);
const [showText, setShowText] = useState(true);
const prevText2Ref = useRef();
useEffect(() => {
// text2 값이 변경될때마다..
prevText2Ref.current = text2;
},[text2]);
const setInitText = useCallback((ref) => ref && setText2(INIT_TEXT2), []); // 한번 생성된 함수를 계속 재사용 한다.
const prevText2Value = prevText2Ref.current;
return (
<div>
{showText && <div>
<input type='text'
value={text}
onInput={(e) => setText(e.target.value)}
ref={(ref) => ref && setText(INIT_TEXT)}
/>
<input type='text'
value={text2}
onChange={(e) => setText2(e.target.value)}
ref={setInitText}
/>
</div>
}
<button onClick={() => setShowText(!showText)}>보이기/가리기</button>
<p>
{`text2 이전값 : ${prevText2Value}`}
</p>
</div>
)
}
import React, {useState, useMemo} from 'react';
export default function App() {
const [number, setNumber] = useState(1);
const memoNumber = useMemo(()=> number * 100, [number]);
return (
<div>
<div style={{boarder : '1px dotted skyblue'}}>
<h2>useMemo</h2>
<p>
useMemo 훅은 계산량이 많은 함수의 반환값을 재활용하는 용도로 사용
</p>
<p>
{`memoNumber : ${memoNumber}`}
</p>
</div>
</div>
);
}
첫번째 인자로 함수를 받는다. useMemo 훅은 이 함수가 반환하는 값을 기억
두번째 인자로 의존성 배열을 받는다. 의존성 배열이 변경되지 않으면 이전에 반환된 값을 재사용(vue.js 에서 computed 를 사용하는 느낌)
useCallback : useCallback 훅은 리액트의 렌더링 성능을 위해 제공되는 훅이며, 컴포넌트가 렌더링될 때마다 새로운 함수를 생성해서 자식 컴포넌트의 속성값으로 입력하는 경우가 많다. 리액트팀에서는 브라우저에서 함수 생성이 성능에 미치는 영향이 적다고 주장한다. 그보다는 속성값이 매번 변경되기 때문에 React.memo 함수를 사용해도 불필요한 렌더링이 발생한다는 문제점이 있다.
import React, {useState, useMemo} from 'react';
import UserInfo from "./components/UserInfo";
const UserInfoMemo = React.memo((props) => {
return (
<UserInfo name={props.name} age={props.age} updateUserInfo={props.updateUserInfo}/>
)
});
export default function App() {
const [number, setNumber] = useState(1);
const [userInfo, setUserInfo] = useState({name: 'test', age: 20});
const memoNumber = useMemo(() => number * 100, [number]);
const updateUser = (targetName, targetAge) => {
setUserInfo({
name: targetName,
age: targetAge
});
};
return (
<div>
<div style={{boarder: '1px dotted skyblue', padding: '4px'}}>
<h2>useMemo</h2>
<p>
useMemo 훅은 계산량이 많은 함수의 반환값을 재활용하는 용도로 사용
</p>
<p>
{`memoNumber : ${memoNumber}`}
</p>
<button onClick={() => setNumber(number + 1)}>값 변경</button>
</div>
<div style={{boarder: '1px dotted skyblue', padding: '4px'}}>
<h2>useCallback</h2>
<p>
useCallback 훅은 리액트의 렌더링 성능을 위해 제공되는 훅
</p>
<p>
값 변경을 누르면, App 컴포넌트가 상태값이 변경되었기 때문에 re-rendering 된다.<br/>
UserInfo 컴포넌트를 React.memo 를 사용했는데, console 에 계속 렌더링 메세지가 출력이 된다. <br/>
이유는 App 컴포넌트가 re-rendering 되면서, updateUser 함수가 새로 생성이 되고 이를 속성값 변경이기 때문에 불필요한 렌더링이 발생된다.<br/>
</p>
<UserInfoMemo name={userInfo.name}
age={userInfo.age}
updateUserInfo={updateUser}/>
</div>
</div>
);
}
// UserInfo component
import React from "react";
export default function UserInfo({name, age, updateUserInfo}) {
console.log(`UserInfo Component rendered..`);
return (
<div>
<p>
{name} / {age}
</p>
<button onClick={() => updateUserInfo('변경이름', 30)}>고객정보변경</button>
</div>
)
}
import React, {useState, useMemo, useCallback} from 'react';
import UserInfo from "./components/UserInfo";
import UserInfo2 from "./components/UserInfo2";
const UserInfoMemo = React.memo((props) => {
return (
<UserInfo name={props.name} age={props.age} updateUserInfo={props.updateUserInfo}/>
)
});
const UserInfoMemo2 = React.memo((props) => {
return (
<UserInfo2 name={props.name} age={props.age} updateUserInfo={props.updateUserInfo}/>
)
});
export default function App() {
const [number, setNumber] = useState(1);
const [userInfo, setUserInfo] = useState({name: 'test', age: 20});
const memoNumber = useMemo(() => number * 100, [number]);
const updateUser = (targetName, targetAge) => {
setUserInfo({
name: targetName,
age: targetAge
});
};
const updateUserCallback = useCallback((targetName, targetAge) => updateUser(targetName, targetAge), [userInfo.name, userInfo.age]);
return (
<div>
<div style={{boarder: '1px dotted skyblue', padding: '4px'}}>
<h2>useMemo</h2>
<p>
useMemo 훅은 계산량이 많은 함수의 반환값을 재활용하는 용도로 사용
</p>
<p>
{`memoNumber : ${memoNumber}`}
</p>
<button onClick={() => setNumber(number + 1)}>값 변경</button>
</div>
<div style={{boarder: '1px dotted skyblue', padding: '4px'}}>
<h2>useCallback</h2>
<p>
useCallback 훅은 리액트의 렌더링 성능을 위해 제공되는 훅
</p>
<p>
값 변경을 누르면, App 컴포넌트가 상태값이 변경되었기 때문에 re-rendering 된다.<br/>
UserInfo 컴포넌트를 React.memo 를 사용했는데, console 에 계속 렌더링 메세지가 출력이 된다. <br/>
이유는 App 컴포넌트가 re-rendering 되면서, updateUser 함수가 새로 생성이 되고 이를 속성값 변경이기 때문에 불필요한 렌더링이 발생된다.<br/>
</p>
<UserInfoMemo name={userInfo.name}
age={userInfo.age}
updateUserInfo={updateUser}/>
<p>
useCallback() 훅을 이용하여 개선한 컴포넌트
</p>
<UserInfoMemo2 name={userInfo.name}
age={userInfo.age}
updateUserInfo={updateUserCallback}/>
</div>
</div>
);
}
// UserInfo2 component
import React from "react";
export default function UserInfo2({name, age, updateUserInfo}) {
console.log(`UserInfo2 Component rendered..`);
return (
<div>
<p>
{name} / {age}
</p>
<button onClick={() => updateUserInfo('변경이름22', 50)}>고객정보변경2</button>
</div>
)
}
useState 훅은 함수형 컴포넌트에서 상태값 관리를 위해 사용 / 상태값 변경 함수는 비동기로 동작 / 참조타입을 상태값으로 관리하는 경우 깊은비교, 얕은비교 주의 / 상태값 변경 시, 컴포넌트는 re-rendering(자식 컴포넌트 포함)
useEffect 훅은 함수형 컴포넌트에서 부수효과를 관리하기 위해 사용 / 의존성 배열에 따라 부수효과 함수 실행여부를 결정
useRef 훅은 실제 DOM 요소에 접근할때 사용하며, ref 객체를 통해 접근 / 조건부 렌더링을 사용하는 경우에 ref.current 속성이 없을수도 있기때문에 검사하는 로직이 필요 / useRef 훅을 사용하여 렌더링과 무관한 값을 저장가능 / ref 속성에 함수 지정 가능
useContext 훅은 Consumer 컴포넌트를 사용하지 않고 context API 로 부터 전달되는 데이터를 쉽게 사용 가능 / useContext 훅을 사용하는 경우 해당 컴포넌트는 context 에 의존적인 컴포넌트가 되어버리기 때문에 context 데이터가 변경되면 컴포넌트가 re-rendering
useCallback 훅은 컴포넌트가 렌더링될 때마다 새로운 함수를 생성하는 부분을 최적화 해줌 / useCallback 훅은 첫번째 인자로 함수를 받고, 두번째 인자로 의존성 배열을 받는다. -> 의존성 배열이 변경되지 않으면 이전에 생성한 함수를 재사용
useMemo 훅은 계산량이 많은 함수의 반환값을 재활용하는 용도로 사용 / useMemo 훅은 첫번째 인자로 받은 함수에서 처리한 반환값을 기억하며, 두번째 인자인 의존성 배열이 변경되지 않으면 이전에 반환된 값을 사용