모든 코드는 git repository에서 확인 가능합니다.
2. 리액트 내장 훅 살펴보기
2.1 Consumer 컴포넌트 없이 콘텍스트 사용하기: useContext
-
코드 5-19 훅을 사용하지 않고 콘텍스트 API를 사용하기
import React from 'react'; const UserContetxt = React.createContext(); const user = { name: 'jyoon', age: 23 }; //POINT1 function ParentComponent() { return ( <UserContetxt.Provider value={user}> <ChildComponent /> </UserContetxt.Provider> ); } //POINT2 function ChildComponent() { //POINT3 return ( <div> <UserContetxt.Consumer> {(user) => ( <div> <div> <h1> 예제 </h1> <p>{`name is ${user.name}`}</p> <p>{`age is ${user.age}`}</p> </div> </div> )} </UserContetxt.Consumer> </div> ); } export default ParentComponent;
- ⭐️ "useContext" 훅을 이용하면 "Consumer 컴포넌트"를 사용하지 않고도 부모 컴포넌트로부터 전달된 코텍스트 데이터를 사용할 수 있다.
- POINT3. ⭐️ 기존에는 POINT3 영역에서 콘텍스트 데이터를 사용하기 위해 콘텍스트 데이터를 전달해 주는 고차 컴포넌트를 사용
-
코드 5-20에서 다룰 것!
- ⭐️ 'useContext' 훅을 사용해 POINT3영역에서도 콘텍스트 데이터에 접근 할 수 있다.
-
코드 5-20 useContext 훅 사용하기
import React, { useContext } from 'react'; const UserContetxt = React.createContext(); const user = { name: 'jyoon', age: 23 }; //POINT1 function ParentComponent() { return ( <UserContetxt.Provider value={user}> <ChildComponent /> </UserContetxt.Provider> ); } //POINT2 function ChildComponent() { //POINT3 const user = useContext(UserContetxt); console.log('### useContext를 통해서 return 영역이 아닌 function영역에서 conext에 접근가능!'); return ( <div> <UserContetxt.Consumer> {(user) => ( <div> <div> <h1> 설명 </h1> <p> 코드 5-20 useContext 훅 사용하기</p> <p> * ⭐️ useContext를 사용하면 코드5-19 POINT3 영역(useContext를 통해서 return 영역이 아닌 function영역에서 conext에 접근가능!)도 콘텍스트 데이터에 접근할 수 있다. </p> </div> <hr /> <div> <h1> 예제 </h1> <p>{`name is ${user.name}`}</p> <p>{`age is ${user.age}`}</p> </div> </div> )} </UserContetxt.Consumer> </div> ); } export default ParentComponent;
- ⭐️ useContext를 사용하면 코드5-19 POINT3 영역(useContext를 통해서 return 영역이 아닌 function영역에서 conext에 접근가능!)도 콘텍스트 데이터에 접근할 수 있다.
2.2 함수형 컴포넌트에서 돔 요소 접근하기: useRef
-
코드 5-21 훅 사용하기
import React, { useRef } from 'react'; export default function() { const inputEl = useRef(null); // const inputEl = useRef("최기화"); const onClick = () => { console.log('### inputEl value: ', inputEl); if (inputEl.current) { inputEl.current.focus(); } }; return ( <div> <div> <h1> 설명 </h1> <p> * 돔요소 접근 방법 </p> <p> - 클래스형 컴포넌트: createRef 함수 사용</p> <p> - ⭐️ 함수형 코포넌트: useRef 훅 사용</p> </div> <hr /> <div> <h1> 예제 </h1> <div> 버튼 클릭하면 input box에 focus가 갑니다.</div> <input ref={inputEl} type='text' /> <button onClick={onClick}>Focus the text</button> </div> </div> ); }
-
돔요소 접근 방법
- 클래스형 컴포넌트: createRef 함수 사용
- 함수형 코포넌트: useRef 훅 사용
-
- 함수형 컴포넌트에서 렌더링과 무관한 값 저장하기
-
코드 5-22 useRef 훅을 이용해서 이전 상탯값 저장하기
import React, { useRef, useState, useEffect } from "react"; export default function () { const [age, setAge] = useState(30); //POINT1 const prevAgeRef = useRef(30); //POINT2 useEffect(() => { prevAgeRef.current = age; }, [age]); //POINT3 const prevAge = prevAgeRef.current; //POINT3 const text = age === prevAge ? "same" : age > prevAge ? "older" : "yournger"; return ( <div> <h1> 예제 </h1> <p>{`age ${age} is ${text} than age ${prevAge}`}</p> <button onClick={() => { const age = Math.floor(Math.random() * 50 + 1); setAge(age); //POINT4 }} > 나이변경 </button> </div> </div> ); }
-
POINT1
- 이전 상태값 저장하기 위한 용도로 useRef 사용
-
POINT2
- age값 변경되면 prevAgeRef에 저장.
-
POINT3
- age의 이전 상탯값을 이용
-
POINT4
- age가 변경돼서 다시 렌더링 할때 POINT3의 prevAge는 age의 최신 상탯값으로 변경된다.
- ⭐️ button클릭으로 age 변경 -> POINT3 코드 수행 -> "render완료 되면" useEffect(POINT2수행)으로 useRef로 설정한 prevAgeRef 업데이트
- 이 외에도 setTimeout, setInterval 등의 함수가 반환하는 ID 값 처럼 클래스의 멤버 변수로 저장하던 값을 useRef 훅으로 저장가능
-
2.3 메모이제이션 훅: useMemo, useCallback
- useMemo
-
코드 5-23 useMemo 훅의 사용 예
import React, { useMemo, useState } from 'react'; export default function() { const [ value1, setValue1 ] = useState(''); const value = useMemo(() => value1, value1); console.log('### useMemo return Value: ', value); const onChangeInput = (e) => { setValue1(e.target.value); console.log(value1); }; return ( <div> <div> <h1> 예제 </h1> <input type='input' value={value1} onChange={onChangeInput} /> {/* <p>{`value is ${value}`}</p> */} </div> </div> ); }
- 계산량이 많은 함수의 반환값을 재활용하는 용도로 사용
- useMemo 훅은 로다시 같은 라이브러리에서 제공해주는 메모이제이션과 비슷하다
- useMemo 훅의 첫번째 매개변수로 함수를 입력
- useMemo 훅은 이 함수가 반환한 값을 기억
- useMemo 훅의 두번째 매개변수로 입력된 배열의 값이 변경되지 않으면 이전에 반환된 값을 재사용
- 만약 배열의 값이 변경됐으면 첫번째 배개 변수로 입력된 함수를 실행하고 그 반환값을 기억.
- useCallback
-
코드 5-24 useCallback 훅이 필요한 예
import React, { useState } from 'react'; function UserEdit({ setName, setAge }) { setTimeout(() => { setName('chageName'); setAge(100); }, 2000); return <div>THIS IS USEREDIT COMPONENT</div>; } function saveToServer({ name, age }) { console.log('### saveToServer function: ', { name, age }); } export default function() { const [ name, setName ] = useState('name'); const [ age, setAge ] = useState(10); return ( <div> <hr /> <div> <h1> 예제 </h1> <p>{`name is ${name}`}</p> <p>{`age is ${age}`}</p> {/* POINT1 */} <UserEdit onSave={() => saveToServer(name, age)} setName={setName} setAge={setAge} /> </div> </div> ); }
-
useCallback은 리액트의 렌더링 성능을 위해 제공되는 훅
- ⭐️ 컴포넌트가 렌더링될 때 마다 함수를 생성해서 자식 컴포넌트의 속성값으로 입력하는 경우가 많다.
- 리액트 팀에서는 최근의 브라우저에서 함수 생성이 성능에 미치는 영향은 작다고 주장
- ⭐️ 그 보다는 속성값이 매번 변경되기 때문에 자식 컴포넌트에서 PureComponent나 React.memo를 사용해도 불필요한 렌더링이 발생한다는 문제점이 있다.
- ⭐️ 리액트 에서는 이 문제를 해결하기 위해 useCallback 훅을 제공
-
-
코드 5-25 useCallback 훅 사용하기
import React, { useState, useCallback } from 'react'; function UserEdit({ setName, setAge }) { setTimeout(() => { setName('chageName'); setAge(100); }, 2000); return <div>THIS IS USEREDIT COMPONENT</div>; } function saveToServer({ name, age }) { console.log('### saveToServer function: ', { name, age }); } export default function() { const [ name, setName ] = useState('name'); const [ age, setAge ] = useState(10); //POINT1 const onSave = useCallback(() => saveToServer(name, age), [ name, age ]); return ( <div> <div> <h1> 예제 </h1> <p>{`name is ${name}`}</p> <p>{`age is ${age}`}</p> <UserEdit // onSave={() => saveToServer(name, age)} onSave={onSave} setName={setName} setAge={setAge} /> </div> </div> ); }
- 5-24에서 onSave 속성값으로 전달했던 것과 같은 함수를 useCallback 훅의 첫번째 매개변수로 입력한다.
- ⭐️ 두번째 매개변수로 전달한 배열의 값이 변경되지 않으면 이전에 생성한 함수가 재사용된다.
- ⭐️ 따라서 name과 age값이 변경되지 않으면 UserEdit 컴포넌트의 onSave 속성값으로 항상 같은 함수가 전달된다.
2.4 컴포넌트의 상탯값을 리덕스처럼 관리하기: useReducer
-
코드 5-26 useReducer 훅의 사용 예
import React, { useReducer } from 'react'; //POINT1 const INITIAL_STATE = { name: 'empty', age: 100 }; //POINT1 function reducer(state, action) { switch (action.type) { case 'setName': return { ...state, name: action.name }; case 'setAge': return { ...state, age: action.age }; default: return state; } } export default function() { //POINT2 const [ state, dispatch ] = useReducer(reducer, INITIAL_STATE); return ( <div> <div> <h1> 설명 </h1> </div> <hr /> <div> <h1> 예제 </h1> <p>{`name is ${state.name}`}</p> <p>{`age is ${state.age}`}</p> <input type='text' value={state.name} onChange={(e) => { //POINT3 dispatch({ type: 'setName', name: e.currentTarget.value }); }} /> <input type='number' value={state.age} onChange={(e) => { dispatch({ type: 'setAge', age: e.currentTarget.value }); }} /> </div> </div> ); }
-
POINT1
- 리덕스의 리듀서와 같은 방식으로 작성한 리듀서 함수
-
POINT2
- useReducer 훅의 매개변수로 앞에서 작성한 리듀서와 초기 상탯값을 입력
- useReducer 훅은 상탯값과 dispatch 함수를 차례대로 반환
-
POINT3
- 리덕스의 dispatch 함수와 같은 방식으로 사용
-
- 트리의 깊은 곳으로 이벤트 처리 함수 전달하기
-
코드 5-27 useReducer 훅과 콘텍스트 API를 이용해서 이벤트 처리 함수를 전달하기
import React, { useReducer } from 'react'; const INITIAL_STATE = { name: 'empty', age: 100 }; function reducer(state, action) { switch (action.type) { case 'setName': return { ...state, name: action.name }; case 'setAge': return { ...state, age: action.age }; default: return state; } } const ProfileDispatch = React.createContext(null); const ProfileState = React.createContext(null); function Profile() { const [ state, dispatch ] = useReducer(reducer, INITIAL_STATE); return ( <div> <h1> 예제 </h1> <p>{`name is ${state.name}`}</p> <p>{`age is ${state.age}`}</p> <ProfileState.Provider value={state}> <ProfileDispatch.Provider value={dispatch}> <SomeComponent profileInfo={state} /> </ProfileDispatch.Provider> </ProfileState.Provider> </div> ); } function SomeComponent({ profileInfo }) { console.log('### someComponent: ', profileInfo); return ( <div> "SOME COMPONENT" <Greeting profileInfo={profileInfo} /> </div> ); } function Greeting({ profileInfo }) { console.log('### Greeting component: ', profileInfo); return ( <ProfileState.Consumer> {(state) => ( <ProfileDispatch.Consumer> {(dispatch) => ( <div> <p>"GREETING COMPONENT"</p> <input type='text' value={state.name} onChange={(e) => { dispatch({ type: 'setName', name: e.currentTarget.value }); }} /> <input type='number' value={state.age} onChange={(e) => { dispatch({ type: 'setAge', age: e.currentTarget.value }); }} /> </div> )} </ProfileDispatch.Consumer> )} </ProfileState.Consumer> ); } export default Profile;
-
컴포넌트 구조
Profile ㄴ SomeComponent ㄴ Greeting
- Profile에서 reducer dispatch함수를 createContext를 통해서 바로 'Greeting' component로 전달
(props를 통해서 SomeComponent 보내지 않고 ) -
contextAPI를 사용해서 Profile 컴포넌트에서 SomeComponent를 건너뛰고 Greeting 컴포넌트로 데이터 바로 전달
- 사용한 contextAPI ProfileDispatch, ProfileState
-
2.5 부모 컴포넌트에서 접근 가능한 함수 구현하기: useImperativeHandle
- useImperativeHandle 훅으로 외부로 공개할 함수 정의하기
-
코드 5-28 부모 컴포넌트에서 접근 가능한 함수를 구현하기
import React, { forwardRef, useState, useImperativeHandle } from "react"; function Profile(props, ref) { const [name, setName] = useState(""); const [age, setAge] = useState(0); useImperativeHandle(ref, () => ({ addAge: (value) => setAge(age + value), getNameLength: () => name.length, })); return ( <div> <h1> Child Component </h1> <div> <h2> 예제 </h2> <p>{`name is ${name}`}</p> <p>{`age is ${age}`}</p> </div> </div> ); } export default forwardRef(Profile);
- 코드 5-29 부모 컴포넌트에서 호출될 자식 컴포넌트
-
부모 컴포넌트에서 접근 가능한 함수를 구현 방법
- useImperativeHandle 훅에 두개 매개변수 지정
- 첫번째 매개변수
: type - ref 객체
: useImperativeHandle 선언한 컴포넌트를 사용할 컴포넌트에서 useRef 훅으로 설정(5-29 참고)한 ref - 두번째 매개변수
: type - function
: seImperativeHandle 선언한 컴포넌트를 사용할 컴포넌트에서 호출할 함수
- useImperativeHandle 훅으로 정의한 함수를 외부에서 호출하기
-
코드 5-29 부모 컴포넌트에서 자식 컴포넌트 함수 호출하기
import React, { useRef } from 'react'; import Profile from './5-28'; function Parent() { const profileRef = useRef(); const onClick = () => { if (profileRef.current) { console.log('### useRef객체: ', profileRef); console.log('### useRef객체 current 속성 name length: ', profileRef.current.getNameLength()); profileRef.current.addAge(5); } }; return ( <div> <Profile ref={profileRef} /> <hr /> <hr /> <h1> Parent Component </h1> <div> <h2> 설명 </h2> <p> # 코드 5-29 부모 컴포넌트에서 자식 컴포넌트 함수 호출하기</p> <p> * useRef함수 return value</p> <p> - - useImperativeHandle 함수 두번째 변수 함수의 return value(함수가 등록된 dictionary object)</p> <p>* add Age 클릭시 ChildComponent의 age가 5씩 증가 합니다.</p> </div> <hr /> <div> <h2> 예제 </h2> <button onClick={onClick}> add Age </button> </div> </div> ); } export default Parent;
- ⭐️ Profile 컴포넌트의 속성 값으로 ref 객체를 전달한다.
-
useRef함수 return value
- useImperativeHandle 함수 두번째 변수 함수의 return value(함수가 등록된 dictionary object)
- add Age 클릭시 ChildComponent의 age가 5씩 증가 합니다.
2.6 기타 리액트 내장 훅: useLayoutEffect, useDebugValue
- useLayoutEffect
- useDebugValue
-
코드 5-30 useDebugValue 훅을 사용하는 코드
import { useState, useEffect, useDebugValue } from 'react'; function FriendStatus(props) { const [ isOnline, setIsOnline ] = useState(true); // Show a label in DevTools next to this Hook // e.g. "FriendStatus: Online" useDebugValue(isOnline ? 'Online' : 'Offline'); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } }); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
- 개발 편의를 위해 제공되는 훅
- 커스텀 훅의 내부 상태를 관찰 할 수 있따.
- 다음 예제는 useToggle 커스텀 훅에서 디버깅을 위해 sueDebuggValue훅을 사용하는 코드
- 리액트 개발자 도구에서 확인 할 수 있다.