프로젝트에서 사용할 수 있는 유용한 React Hooks
게시 됨: 2022-03-10후크는 단순히 React 기능에 연결 하거나 사용할 수 있는 기능입니다. 그들은 React Conf 2018에서 클래스 구성 요소의 세 가지 주요 문제인 래퍼 지옥, 거대한 구성 요소 및 혼란스러운 클래스를 해결하기 위해 도입되었습니다. Hooks는 React 기능 구성 요소에 힘을 주어 전체 애플리케이션을 개발할 수 있도록 합니다.
앞서 언급한 클래스 구성 요소의 문제는 연결되어 있고 다른 하나 없이 해결하면 더 많은 문제가 발생할 수 있습니다. 고맙게도 후크는 모든 문제를 간단하고 효율적으로 해결하면서 React에서 더 흥미로운 기능을 위한 공간을 만들었습니다. Hook은 이미 존재하는 React 개념과 클래스를 대체하지 않으며, 직접 액세스할 수 있는 API를 제공할 뿐입니다.
React 팀은 React 16.8에서 몇 가지 후크를 도입했습니다. 그러나 응용 프로그램에서 타사 공급자의 후크를 사용하거나 사용자 지정 후크를 만들 수도 있습니다. 이 튜토리얼에서는 React의 유용한 후크와 사용 방법을 살펴보겠습니다. 각 후크의 여러 코드 예제를 살펴보고 사용자 지정 후크를 만드는 방법도 살펴보겠습니다.
참고: 이 튜토리얼은 자바스크립트(ES6+)와 React에 대한 기본적인 이해가 필요합니다.
후크 뒤에 동기 부여
앞서 언급했듯이 후크는 래퍼 지옥, 거대한 구성 요소 및 혼란스러운 클래스의 세 가지 문제를 해결하기 위해 만들어졌습니다. 각각에 대해 더 자세히 살펴보겠습니다.
래퍼 지옥
클래스 구성 요소로 구축된 복잡한 응용 프로그램은 래퍼 지옥에 쉽게 빠지게 됩니다. React Dev Tools에서 애플리케이션을 살펴보면 깊게 중첩된 구성 요소를 발견할 수 있습니다. 이로 인해 구성 요소로 작업하거나 디버그하기가 매우 어렵습니다. 이러한 문제는 고차원 구성 요소 와 렌더링 소품 으로 해결할 수 있지만 코드를 약간 수정해야 합니다. 이것은 복잡한 응용 프로그램에서 혼란을 초래할 수 있습니다.
후크는 공유하기 쉽고 논리를 재사용하기 전에 구성 요소를 수정할 필요가 없습니다.
이것의 좋은 예는 Redux 저장소를 구독하기 위해 Redux connect
HOC(Higher Order Component)를 사용하는 것입니다. 모든 HOC와 마찬가지로 연결 HOC를 사용하려면 정의된 고차 함수와 함께 구성 요소를 내보내야 합니다. connect
의 경우 이런 형식을 갖게 됩니다.
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
여기서 mapStateToProps
및 mapDispatchToProps
는 정의할 함수입니다.
Hooks 시대에는 Redux useSelector
및 useDispatch
hooks를 사용하여 간단하고 간결하게 동일한 결과를 쉽게 얻을 수 있습니다.
거대한 구성 요소
클래스 구성 요소에는 일반적으로 부작용과 상태 저장 논리가 포함됩니다. 응용 프로그램이 복잡해짐에 따라 구성 요소가 지저분해지고 혼란스러워지는 것이 일반적입니다. 부작용이 기능보다는 라이프사이클 방식 으로 정리될 것으로 예상되기 때문이다. 구성 요소를 분할하여 더 단순하게 만드는 것이 가능하지만 이는 종종 더 높은 수준의 추상화를 도입합니다.
Hooks는 기능별로 부작용을 정리하고 기능에 따라 구성 요소를 조각으로 나눌 수 있습니다.
혼란스러운 수업
클래스는 일반적으로 함수보다 더 어려운 개념입니다. React 클래스 기반 구성 요소는 장황하고 초보자에게는 약간 어렵습니다. Javascript를 처음 사용하는 경우 클래스에 비해 가벼운 구문으로 인해 시작하기 쉬운 함수를 찾을 수 있습니다. 구문이 혼동될 수 있습니다. 때로는 코드를 깨뜨릴 수 있는 이벤트 핸들러 바인딩을 잊어버릴 수 있습니다.
React는 기능적 구성 요소와 후크를 사용하여 이 문제를 해결하여 개발자가 코드 구문보다 프로젝트에 집중할 수 있도록 합니다.
예를 들어 다음 두 개의 React 구성 요소는 정확히 동일한 결과를 생성합니다.
import React, { Component } from "react"; export default class App extends Component { constructor(props) { super(props); this.state = { num: 0 }; this.incrementNumber = this.incrementNumber.bind(this); } incrementNumber() { this.setState({ num: this.state.num + 1 }); } render() { return ( <div> <h1>{this.state.num}</h1> <button onClick={this.incrementNumber}>Increment</button> </div> ); } }
import React, { useState } from "react"; export default function App() { const [num, setNum] = useState(0); function incrementNumber() { setNum(num + 1); } return ( <div> <h1>{num}</h1> <button onClick={incrementNumber}>Increment</button> </div> ); }
첫 번째 예제는 클래스 기반 구성 요소이고 두 번째 예제는 기능 구성 요소입니다. 이것은 간단한 예이지만 첫 번째 예가 두 번째 예와 비교하여 얼마나 엉터리인지 주목하십시오.
Hooks 규칙 및 규칙
다양한 후크에 대해 알아보기 전에 해당 후크에 적용되는 규칙과 규칙을 살펴보는 것이 도움이 될 수 있습니다. 다음은 후크에 적용되는 몇 가지 규칙입니다.
- 후크의 명명 규칙은 접두사
use
로 시작해야 합니다. 따라서useState
,useEffect
등을 가질 수 있습니다. Atom 및 VSCode와 같은 최신 코드 편집기를 사용하는 경우 ESLint 플러그인은 React 후크에 매우 유용한 기능이 될 수 있습니다. 플러그인은 모범 사례에 대한 유용한 경고와 힌트를 제공합니다. - 후크는 return 문 전에 구성 요소의 최상위 수준에서 호출되어야 합니다. 조건문, 루프 또는 중첩 함수 내에서 호출할 수 없습니다.
- Hook은 React 함수(React 구성 요소 또는 다른 hook 내부)에서 호출해야 합니다. Vanilla JS 함수에서 호출하면 안 됩니다.
useState
후크
useState
훅은 가장 기본적이고 유용한 React 훅입니다. 다른 내장 후크와 마찬가지로 이 후크는 애플리케이션에서 사용하기 위해 react
에서 가져와야 합니다.
import {useState} from 'react'
상태를 초기화하려면 상태와 해당 업데이터 함수를 모두 선언하고 초기 값을 전달해야 합니다.
const [state, updaterFn] = useState('')
우리는 우리가 원하는 대로 state와 updater 함수를 자유롭게 호출할 수 있지만 관례에 따라 배열의 첫 번째 요소는 state가 되고 두 번째 요소는 updater 함수가 됩니다. 업데이터 함수에 접두사 세트 를 붙이고 그 뒤에 낙타 형태의 상태 이름을 붙이는 것이 일반적인 관행입니다.
예를 들어 카운트 값을 보유하도록 상태를 설정해 보겠습니다.
const [count, setCount] = useState(0)
count
상태의 초기 값은 빈 문자열이 아니라 0
으로 설정되어 있습니다. 즉, 숫자, 문자열, 부울, 배열, 개체 및 BigInt와 같은 모든 종류의 JavaScript 변수로 상태를 초기화할 수 있습니다. useState
후크를 사용하여 상태를 설정하는 것과 클래스 기반 구성 요소 상태 사이에는 분명한 차이가 있습니다. useState
후크가 상태 변수라고도 하는 배열을 반환하고 위의 예에서 배열을 state
및 updater
함수로 구조화 해제했다는 점은 주목할 만합니다.
컴포넌트 렌더링
useState
후크로 상태를 설정하면 해당 구성 요소가 다시 렌더링됩니다. 그러나 이것은 React가 이전 또는 이전 상태와 새 상태 간의 차이를 감지한 경우에만 발생합니다. React는 Javascript Object.is
알고리즘을 사용하여 상태 비교를 수행합니다.
useState
로 상태 설정하기
다음과 같이 setCount(newValue)
와 같이 setCount
업데이트 함수에 새 값을 전달하기만 하면 count
상태를 새 상태 값으로 설정할 수 있습니다.
이 방법은 이전 상태 값을 참조하고 싶지 않을 때 작동합니다. 그렇게 하려면 setCount
함수에 함수를 전달해야 합니다.
버튼을 클릭할 때마다 count
변수에 5를 추가한다고 가정하면 다음을 수행할 수 있습니다.
import {useState} from 'react' const CountExample = () => { // initialize our count state const [count, setCount] = useState(0) // add 5 to to the count previous state const handleClick = () =>{ setCount(prevCount => prevCount + 5) } return( <div> <h1>{count} </h1> <button onClick={handleClick}>Add Five</button> </div> ) } export default CountExample
위의 코드에서는 먼저 react
에서 useState
후크를 가져온 다음 기본값 0으로 count
상태를 초기화했습니다. 버튼을 클릭할 때마다 count
값을 5씩 증가시키는 onClick
핸들러를 만들었습니다. 그런 다음 결과를 h1
태그에 표시했습니다.
배열 및 객체 상태 설정
배열과 객체의 상태는 다른 데이터 유형과 거의 동일한 방식으로 설정할 수 있습니다. 그러나 이미 존재하는 값을 유지하려면 상태를 설정할 때 ES6 스프레드 연산자를 사용해야 합니다.
Javascript의 스프레드 연산자는 이미 존재하는 개체에서 새 개체를 만드는 데 사용됩니다. 이것은 React
가 상태를 Object.is
작업과 비교한 다음 그에 따라 다시 렌더링하기 때문에 여기에서 유용합니다.
버튼 클릭 시 상태를 설정하는 아래 코드를 살펴보겠습니다.
import {useState} from 'react' const StateExample = () => { //initialize our array and object states const [arr, setArr] = useState([2, 4]) const [obj, setObj] = useState({num: 1, name: 'Desmond'}) // set arr to the new array values const handleArrClick = () =>{ const newArr = [1, 5, 7] setArr([...arr, ...newArr]) } // set obj to the new object values const handleObjClick = () =>{ const newObj = {name: 'Ifeanyi', age: 25} setObj({...obj, ...newObj}) } return( <div> <button onClick ={handleArrClick}>Set Array State</button> <button onClick ={handleObjClick}>Set Object State</button> </div> ) } export default StateExample
위의 코드에서 우리는 2개의 arr
과 obj
를 생성했고 그것들을 각각 어떤 배열과 객체 값으로 초기화했습니다. 그런 다음 배열과 객체의 상태를 각각 설정하기 위해 handleArrClick
및 handleObjClick
이라는 onClick
핸들러를 생성했습니다. setArr
이 실행되면 handleArrClick
을 호출하고 ES6 확산 연산자를 사용하여 이미 존재하는 배열 값을 확산하고 여기에 newArr
을 추가합니다.
handleObjClick
핸들러에 대해서도 동일한 작업을 수행했습니다. 여기서 setObj
를 호출하고 ES6 확산 연산자를 사용하여 기존 객체 값을 확산하고 name
및 age
값을 업데이트했습니다.
useState
의 비동기 특성
이미 보았듯이 업데이트 함수에 새 값을 전달하여 useState
로 상태를 설정합니다. 업데이터가 여러 번 호출되면 새 값이 대기열에 추가되고 JavaScript Object.is
비교를 사용하여 그에 따라 다시 렌더링이 수행됩니다.
상태는 비동기식으로 업데이트됩니다. 이는 새 상태가 먼저 보류 상태에 추가된 후 상태가 업데이트됨을 의미합니다. 따라서 설정된 상태에 즉시 액세스하면 여전히 이전 상태 값을 얻을 수 있습니다.
이 동작을 관찰하기 위해 다음 예를 살펴보겠습니다.
위의 코드에서 useState
후크를 사용하여 count
상태를 만들었습니다. 그런 다음 버튼을 클릭할 때마다 count
상태를 증가시키는 onClick
핸들러를 만들었습니다. count
상태가 증가했지만 h2
태그에 표시되는 것처럼 이전 상태는 여전히 콘솔에 기록됩니다. 이것은 후크의 비동기 특성 때문입니다.
새 상태를 얻으려면 비동기 함수를 처리하는 것과 비슷한 방식으로 처리할 수 있습니다. 여기에 한 가지 방법이 있습니다.
여기에 생성된 newCountValue
를 저장하여 업데이트된 카운트 값을 저장하고 업데이트된 값으로 count
상태를 설정합니다. 그런 다음 콘솔에 업데이트된 카운트 값을 기록했습니다.
useEffect
후크
useEffect
는 대부분의 프로젝트에서 사용되는 또 다른 중요한 React hook입니다. 클래스 기반 구성 요소의 componentDidMount
, componentWillUnmount
및 componentDidUpdate
수명 주기 메서드와 유사한 작업을 수행합니다. useEffect
는 애플리케이션에 부작용이 있을 수 있는 명령형 코드를 작성할 수 있는 기회를 제공합니다. 이러한 효과의 예로는 로깅, 구독, 돌연변이 등이 있습니다.
useEffect
가 실행될 시기를 사용자가 결정할 수 있지만 설정하지 않으면 모든 렌더링 또는 다시 렌더링할 때 부작용이 실행됩니다.
아래의 예를 고려하십시오.
import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ console.log(count) }) return( <div> ... </div> ) }
위의 코드에서 우리는 단순히 count
를 useEffect
에 기록했습니다. 이것은 구성 요소를 렌더링할 때마다 실행됩니다.
때로는 구성 요소에서 (마운트에서) 후크를 한 번 실행하고 싶을 수 있습니다. useEffect
후크에 두 번째 매개변수를 제공하여 이를 달성할 수 있습니다.
import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ setCount(count + 1) }, []) return( <div> <h1>{count}</h1> ... </div> ) }
useEffect
후크에는 두 개의 매개변수가 있습니다. 첫 번째 매개변수는 실행하려는 함수이고 두 번째 매개변수는 종속성 배열입니다. 두 번째 매개변수가 제공되지 않으면 후크가 계속 실행됩니다.
후크의 두 번째 매개변수에 빈 대괄호를 전달하여 마운트에서 useEffect
후크를 한 번만 실행하도록 React에 지시합니다. 구성 요소가 마운트될 때 카운트가 0에서 1로 한 번 업데이트되기 때문에 이렇게 하면 h1
태그에 값 1
이 표시됩니다.
또한 일부 종속 값이 변경될 때마다 부작용을 실행할 수도 있습니다. 이것은 종속성 목록에서 이러한 값을 전달하여 수행할 수 있습니다.
예를 들어 다음과 같이 count
가 변경될 때마다 useEffect
가 실행되도록 할 수 있습니다.
import { useState, useEffect } from "react"; const App = () => { const [count, setCount] = useState(0); useEffect(() => { console.log(count); }, [count]); return ( <div> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }; export default App;
위의 useEffect
는 이 두 조건 중 하나가 충족될 때 실행됩니다.
- 마운트 시 — 구성 요소가 렌더링된 후
-
count
값이 변경될 때.
마운트 시 console.log
표현식이 실행되고 count
를 0으로 기록합니다. count
가 업데이트되면 두 번째 조건이 충족되므로 useEffect
가 다시 실행되고 버튼을 클릭할 때마다 계속됩니다.
useEffect
에 두 번째 인수를 제공하면 모든 종속성을 여기에 전달할 것으로 예상됩니다. ESLINT
가 설치된 경우 매개변수 목록에 종속성이 전달되지 않으면 린트 오류가 표시됩니다. 또한 특히 전달되지 않은 매개변수에 의존하는 경우 부작용이 예기치 않게 작동할 수 있습니다.
효과 정리
useEffect
를 사용하면 구성 요소가 마운트 해제되기 전에 리소스를 정리할 수도 있습니다. 이는 메모리 누수를 방지하고 응용 프로그램을 보다 효율적으로 만들기 위해 필요할 수 있습니다. 이렇게 하려면 후크 끝에서 정리 함수를 반환합니다.
useEffect(() => { console.log('mounted') return () => console.log('unmounting... clean up here') })
위의 useEffect
후크는 구성 요소가 마운트될 때 mounted
것으로 기록됩니다. 마운트 해제 중... 여기 정리 는 구성 요소가 마운트 해제될 때 기록됩니다. 이는 구성 요소가 UI에서 제거될 때 발생할 수 있습니다.
정리 프로세스는 일반적으로 아래 형식을 따릅니다.
useEffect(() => { //The effect we intend to make effect //We then return the clean up return () => the cleanup/unsubscription })
useEffect
구독에 대한 사용 사례가 많지 않을 수 있지만 구독 및 타이머를 처리할 때 유용합니다. 특히 웹 소켓을 처리할 때 구성 요소가 마운트 해제될 때 리소스를 절약하고 성능을 향상시키기 위해 네트워크에서 구독을 취소해야 할 수 있습니다.
useEffect
로 데이터 가져오기 및 다시 가져오기
useEffect
후크의 가장 일반적인 사용 사례 중 하나는 API에서 데이터를 가져오고 미리 가져오는 것입니다.
이를 설명하기 위해 JSONPlaceholder
에서 만든 가짜 사용자 데이터를 사용하여 useEffect
후크로 데이터를 가져옵니다.
import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [users, setUsers] = useState([]); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/users"; useEffect(() => { const fetchUsers = async () => { const { data } = await axios.get(endPoint); setUsers(data); }; fetchUsers(); }, []); return ( <div className="App"> {users.map((user) => ( <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> ))} </div> ); }
위의 코드에서 useState
후크를 사용하여 users
상태를 만들었습니다. 그런 다음 Axios를 사용하여 API에서 데이터를 가져왔습니다. 이것은 비동기식 프로세스이므로 async/await 함수를 사용했으며 점을 사용한 다음 구문을 사용할 수도 있습니다. 사용자 목록을 가져왔으므로 단순히 매핑하여 데이터를 표시했습니다.
후크에 빈 매개변수를 전달했습니다. 이렇게 하면 구성 요소가 마운트될 때 한 번만 호출됩니다.
일부 조건이 변경되면 데이터를 다시 가져올 수도 있습니다. 아래 코드에서 이를 보여드리겠습니다.
import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [userIDs, setUserIDs] = useState([]); const [user, setUser] = useState({}); const [currentID, setCurrentID] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/userdata/users"; useEffect(() => { axios.get(endPoint).then(({ data }) => setUserIDs(data)); }, []); useEffect(() => { const fetchUserIDs = async () => { const { data } = await axios.get(`${endPoint}/${currentID}`}); setUser(data); }; fetchUserIDs(); }, [currentID]); const moveToNextUser = () => { setCurrentID((prevId) => (prevId < userIDs.length ? prevId + 1 : prevId)); }; const moveToPrevUser = () => { setCurrentID((prevId) => (prevId === 1 ? prevId : prevId - 1)); }; return ( <div className="App"> <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> <button onClick={moveToPrevUser}>Prev</button> <button onClick={moveToNextUser}>Next</button> </div> ); }
여기에서 두 개의 useEffect
후크를 만들었습니다. 첫 번째 방법에서는 dot then 구문을 사용하여 API에서 모든 사용자를 가져왔습니다. 이것은 사용자 수를 결정하는 데 필요합니다.
그런 다음 id
를 기반으로 사용자를 가져오기 위해 또 다른 useEffect
후크를 만들었습니다. 이 useEffect
는 ID가 변경될 때마다 데이터를 다시 가져옵니다. 이를 보장하기 위해 종속성 목록에 id
를 전달했습니다.
다음으로 버튼을 클릭할 때마다 id
값을 업데이트하는 함수를 만들었습니다. id
값이 변경되면 useEffect
가 다시 실행되고 데이터를 다시 가져옵니다.
원한다면 Axios에서 약속 기반 토큰을 정리하거나 취소할 수도 있습니다. 위에서 논의한 정리 방법으로 그렇게 할 수 있습니다.
useEffect(() => { const source = axios.CancelToken.source(); const fetchUsers = async () => { const { data } = await axios.get(`${endPoint}/${num}`, { cancelToken: source.token }); setUser(data); }; fetchUsers(); return () => source.cancel(); }, [num]);
여기에서 Axios의 토큰을 axios.get
에 두 번째 매개변수로 전달했습니다. 구성 요소가 마운트 해제되면 원본 개체의 취소 메서드를 호출하여 구독을 취소했습니다.
useReducer
후크
useReducer
후크는 useState
후크와 유사한 작업을 수행하는 매우 유용한 React 후크입니다. React 문서에 따르면 이 후크는 useState
후크보다 더 복잡한 논리를 처리하는 데 사용해야 합니다. useState
후크를 사용하여 내부적으로 구현된다는 점은 주목할 가치가 있습니다.
후크는 리듀서를 인수로 사용하고 선택적으로 초기 상태와 초기화 함수를 인수로 사용할 수 있습니다.
const [state, dispatch] = useReducer(reducer, initialState, init)
여기서 init
는 함수로 초기 상태를 느리게 생성하고 싶을 때 사용합니다.
아래 샌드박스와 같이 간단한 할일 앱을 생성하여 useReducer
후크를 구현하는 방법을 살펴보겠습니다.
먼저 상태를 유지하기 위해 감속기를 만들어야 합니다.
export const ADD_TODO = "ADD_TODO"; export const REMOVE_TODO = "REMOVE_TODO"; export const COMPLETE_TODO = "COMPLETE_TODO"; const reducer = (state, action) => { switch (action.type) { case ADD_TODO: const newTodo = { id: action.id, text: action.text, completed: false }; return [...state, newTodo]; case REMOVE_TODO: return state.filter((todo) => todo.id !== action.id); case COMPLETE_TODO: const completeTodo = state.map((todo) => { if (todo.id === action.id) { return { ...todo, completed: !todo.completed }; } else { return todo; } }); return completeTodo; default: return state; } }; export default reducer;
작업 유형에 해당하는 세 가지 상수를 만들었습니다. 문자열을 직접 사용할 수도 있지만 오타를 피하기 위해 이 방법을 사용하는 것이 좋습니다.
그런 다음 감속기 기능을 만들었습니다. Redux
에서처럼 리듀서는 상태와 액션 객체를 취해야 합니다. 그러나 Redux와 달리 여기에서 감속기를 초기화할 필요가 없습니다.
또한 많은 상태 관리 사용 사례의 경우 컨텍스트를 통해 노출된 dispatch
와 함께 useReducer
를 사용하면 더 큰 애플리케이션에서 작업을 실행하고 state
를 업데이트하고 이를 수신할 수 있습니다.
그런 다음 switch
문을 사용하여 사용자가 전달한 작업 유형을 확인했습니다. 작업 유형이 ADD_TODO
이면 새 할일을 전달하고 REMOVE_TODO
이면 할일을 필터링하고 사용자가 전달한 id
에 해당하는 작업을 제거하려고 합니다. COMPLETE_TODO
인 경우 할일을 매핑하고 사용자가 전달한 id
로 전환하고 싶습니다.
다음은 reducer
를 구현한 App.js
파일입니다.
import { useReducer, useState } from "react"; import "./styles.css"; import reducer, { ADD_TODO, REMOVE_TODO, COMPLETE_TODO } from "./reducer"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = [ { id: id, text: "First Item", completed: false } ]; //We could also pass an empty array as the initial state //const initialState = [] const [state, dispatch] = useReducer(reducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); dispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="App"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit">+</button> </form> <div className="todos"> {state.map((todo) => ( <div key={todo.id} className="todoItem"> <p className={todo.completed && "strikethrough"}>{todo.text}</p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ))} </div> </div> ); }
여기에서 사용자 입력을 수집하기 위한 입력 요소와 작업을 트리거하는 버튼을 포함하는 양식을 만들었습니다. 양식이 제출되면 ADD_TODO
유형의 작업을 전달하고 새 ID와 할 일 텍스트를 전달합니다. 이전 id 값을 1씩 증가시켜 새 id를 만들었습니다. 그런 다음 입력 텍스트 상자를 지웠습니다. 할 일을 삭제하고 완료하기 위해 적절한 조치를 전달하기만 하면 됩니다. 이것들은 위에 표시된 것처럼 감속기에서 이미 구현되었습니다.
그러나 useReducer
후크를 사용하기 때문에 마술이 일어납니다. 이 후크는 리듀서와 초기 상태를 받아들이고 상태와 디스패치 함수를 반환합니다. 여기에서 dispatch 함수는 useState
후크에 대한 setter 함수와 동일한 목적을 수행하며 dispatch
대신 원하는 모든 것을 호출할 수 있습니다.
할 일 항목을 표시하기 위해 위의 코드와 같이 상태 개체에 반환된 할 일 목록을 통해 간단히 매핑했습니다.
이것은 useReducer
후크의 위력을 보여줍니다. useState
후크를 사용하여 이 기능을 달성할 수도 있지만 위의 예에서 볼 수 있듯이 useReducer
후크는 작업을 더 깔끔하게 유지하는 데 도움이 되었습니다. useReducer
는 상태 개체가 복잡한 구조이고 단순한 값 바꾸기와 다른 방식으로 업데이트될 때 유용합니다. 또한 이러한 업데이트 기능이 더 복잡해 useReducer
를 사용하면 모든 복잡성을 감속기 기능(순수한 JS 기능)에 쉽게 보관할 수 있으므로 감속기 기능에 대한 테스트를 매우 쉽게 작성할 수 있습니다.
초기 상태를 느리게 생성하기 위해 세 번째 인수를 useReducer
후크에 전달할 수도 있습니다. 이것은 init
함수에서 초기 상태를 계산할 수 있음을 의미합니다.
예를 들어 다음과 같이 init
함수를 만들 수 있습니다.
const initFunc = () => [ { id: id, text: "First Item", completed: false } ]
그런 다음 useReducer
후크에 전달합니다.
const [state, dispatch] = useReducer(reducer, initialState, initFunc)
이렇게 하면 initFunc
가 우리가 제공한 initialState
를 재정의하고 초기 상태가 느리게 계산됩니다.
useContext
후크
React Context API는 React 구성 요소 트리 전체에서 상태 또는 데이터를 공유하는 방법을 제공합니다. API는 React에서 실험적인 기능으로 잠시 동안 사용할 수 있었지만 React 16.3.0에서 사용하기에 안전해졌습니다. API를 사용하면 소품 드릴을 제거하면서 구성 요소 간의 데이터 공유를 쉽게 할 수 있습니다.
React Context를 전체 애플리케이션에 적용할 수 있지만 애플리케이션의 일부에 적용하는 것도 가능합니다.
후크를 사용하려면 먼저 React.createContext
를 사용하여 컨텍스트를 생성해야 하며 이 컨텍스트를 후크에 전달할 수 있습니다.
useContext
후크의 사용을 보여주기 위해 애플리케이션 전체에서 글꼴 크기를 늘리는 간단한 앱을 만들어 보겠습니다.
context.js
파일에 컨텍스트를 생성해 보겠습니다.
import { createContext } from "react"; //Here, we set the initial fontSize as 16. const fontSizeContext = createContext(16); export default fontSizeContext;
여기에서 컨텍스트를 만들고 초기 값 16
을 전달한 다음 컨텍스트를 내보냈습니다. 다음으로 컨텍스트를 애플리케이션에 연결해 보겠습니다.
import FontSizeContext from "./context"; import { useState } from "react"; import PageOne from "./PageOne"; import PageTwo from "./PageTwo"; const App = () => { const [size, setSize] = useState(16); return ( <FontSizeContext.Provider value={size}> <PageOne /> <PageTwo /> <button onClick={() => setSize(size + 5)}>Increase font</button> <button onClick={() => setSize((prevSize) => Math.min(11, prevSize - 5)) } > Decrease font </button> </FontSizeContext.Provider> ); }; export default App;
위의 코드에서 전체 구성 요소 트리를 FontSizeContext.Provider
로 래핑하고 size
를 값 소품에 전달했습니다. 여기서 size
는 useState
훅으로 생성된 상태입니다. 이를 통해 size
상태가 변경될 때마다 value prop을 변경할 수 있습니다. 전체 구성 요소를 Provider
로 래핑하여 애플리케이션의 모든 위치에서 컨텍스트에 액세스할 수 있습니다.
예를 들어, <PageOne />
및 <PageTwo />
> 컨텍스트에 액세스했습니다. 결과적으로 App.js
파일에서 글꼴 크기를 늘리면 이 두 구성 요소에서 글꼴 크기가 증가합니다. 위와 같이 버튼에서 글꼴 크기를 늘리거나 줄일 수 있으며 일단 수행하면 응용 프로그램 전체에서 글꼴 크기가 변경됩니다.
import { useContext } from "react"; import context from "./context"; const PageOne = () => { const size = useContext(context); return <p style={{ fontSize: `${size}px` }}>Content from the first page</p>; }; export default PageOne;
여기에서는 PageOne
구성 요소의 useContext
후크를 사용하여 컨텍스트에 액세스했습니다. 그런 다음 이 컨텍스트를 사용하여 font-size 속성을 설정했습니다. 유사한 절차가 PageTwo.js
파일에 적용됩니다.
테마 또는 기타 고차 앱 수준 구성은 컨텍스트에 적합한 후보입니다.
useContext
및 useReducer
사용
useReducer
후크와 함께 사용하면 useContext
를 사용하여 자체 상태 관리 시스템을 만들 수 있습니다. 전역 상태를 만들고 애플리케이션에서 쉽게 관리할 수 있습니다.
컨텍스트 API를 사용하여 할 일 애플리케이션을 개선해 보겠습니다.
평소와 같이 todoContext.js
파일에 todoContext
를 생성해야 합니다.
import { createContext } from "react"; const initialState = []; export default createContext(initialState);
여기에서 빈 배열의 초기 값을 전달하여 컨텍스트를 만들었습니다. 그런 다음 컨텍스트를 내보냈습니다.
할 일 목록과 항목을 분리하여 App.js
파일을 리팩토링해 보겠습니다.
import { useReducer, useState } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import TodoList from "./TodoList"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <div className="app"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </TodoContext.Provider> ); }
여기에서 App.js
파일을 TodoContext.Provider
로 래핑한 다음 todoReducer
의 반환 값을 전달했습니다. 이렇게 하면 리듀서의 상태 및 dispatch
기능이 애플리케이션 전체에서 액세스할 수 있습니다.
그런 다음 할 일 표시를 구성 요소 TodoList
로 분리했습니다. Context API 덕분에 소품 드릴 없이 이 작업을 수행했습니다. TodoList.js
파일을 살펴보겠습니다.
import React, { useContext } from "react"; import TodoContext from "./todoContext"; import Todo from "./Todo"; const TodoList = () => { const [state] = useContext(TodoContext); return ( <div className="todos"> {state.map((todo) => ( <Todo key={todo.id} todo={todo} /> ))} </div> ); }; export default TodoList;
배열 구조화를 사용하여 useContext
후크를 사용하여 컨텍스트에서 상태에 액세스할 수 있습니다(디스패치 함수를 떠나서). 그런 다음 상태를 매핑하고 할 일 항목을 표시할 수 있습니다. 우리는 여전히 이것을 Todo
컴포넌트에서 추출했습니다. ES6+ 맵 기능을 사용하려면 고유 키를 전달해야 하며 특정 할일이 필요하므로 함께 전달합니다.
Todo
구성 요소를 살펴보겠습니다.
import React, { useContext } from "react"; import TodoContext from "./todoContext"; import { REMOVE_TODO, COMPLETE_TODO } from "./todoReducer"; const Todo = ({ todo }) => { const [, dispatch] = useContext(TodoContext); const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="todoItem"> <p className={todo.completed ? "strikethrough" : "nostrikes"}> {todo.text} </p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ); }; export default Todo;
다시 배열 구조화를 사용하여 컨텍스트에서 디스패치 함수에 액세스했습니다. 이를 통해 useReducer
섹션에서 이미 논의한 대로 completeTodo
및 removeTodo
함수를 정의할 수 있습니다. todoList.js
에서 전달된 todo
소품을 사용하여 할 일 항목을 표시할 수 있습니다. 또한 완료로 표시하고 적절하다고 판단되면 할 일을 제거할 수 있습니다.
애플리케이션의 루트에 둘 이상의 컨텍스트 공급자를 중첩할 수도 있습니다. 이것은 우리가 애플리케이션에서 다른 기능을 수행하기 위해 하나 이상의 컨텍스트를 사용할 수 있음을 의미합니다.
이를 시연하기 위해 할 일 예제에 테마를 추가해 보겠습니다.
다음은 우리가 구축할 사항입니다.
다시, 우리는 themeContext
를 생성해야 합니다. 이렇게 하려면 themeContext.js
파일을 만들고 다음 코드를 추가합니다.
import { createContext } from "react"; import colors from "./colors"; export default createContext(colors.light);
여기에서 컨텍스트를 만들고 colors.light
를 초기 값으로 전달했습니다. colors.js
파일에서 이 속성을 사용하여 색상을 정의해 보겠습니다.
const colors = { light: { backgroundColor: "#fff", color: "#000" }, dark: { backgroundColor: "#000", color: "#fff" } }; export default colors;
위의 코드에서 우리는 light와 dark 속성을 포함하는 colors
객체를 만들었습니다. 각 속성에는 backgroundColor
및 color
개체가 있습니다.
다음으로 테마 상태를 처리하기 위해 themeReducer
를 만듭니다.
import Colors from "./colors"; export const LIGHT = "LIGHT"; export const DARK = "DARK"; const themeReducer = (state, action) => { switch (action.type) { case LIGHT: return { ...Colors.light }; case DARK: return { ...Colors.dark }; default: return state; } }; export default themeReducer;
모든 감속기와 마찬가지로 themeReducer
는 상태와 동작을 취합니다. 그런 다음 switch
문을 사용하여 현재 작업을 결정합니다. LIGHT
유형이면 Colors.light
props를 할당하고 DARK
유형이면 Colors.dark
props를 표시합니다. useState
후크를 사용하여 이 작업을 쉽게 수행할 수 있지만 포인트를 홈으로 이동하기 위해 useReducer
를 선택합니다.
themeReducer
를 설정 App.js
파일에 통합할 수 있습니다.
import { useReducer, useState, useCallback } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import ThemeContext from "./themeContext"; import TodoList from "./TodoList"; import themeReducer, { DARK, LIGHT } from "./themeReducer"; import Colors from "./colors"; import ThemeToggler from "./ThemeToggler"; const themeSetter = useCallback( theme => themeDispatch({type: theme}, [themeDispatch]); export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const [themeState, themeDispatch] = useReducer(themeReducer, Colors.light); const themeSetter = useCallback( (theme) => { themeDispatch({ type: theme }); }, [themeDispatch] ); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <ThemeContext.Provider value={[ themeState, themeSetter ]} > <div className="app" style={{ ...themeState }}> <ThemeToggler /> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </ThemeContext.Provider> </TodoContext.Provider> ); }
위의 코드에서 우리는 이미 존재하는 할 일 애플리케이션에 몇 가지를 추가했습니다. ThemeContext
, themeReducer
, ThemeToggler
및 Colors
를 가져오는 것으로 시작했습니다. We created a reducer using the useReducer
hook, passing the themeReducer
and an initial value of Colors.light
to it. This returned the themeState
and themeDispatch
to us.
We then nested our component with the provider function from the ThemeContext
, passing the themeState
and the dispatch
functions to it. We also added theme styles to it by spreading out the themeStates
. This works because the colors
object already defined properties similar to what the JSX styles will accept.
However, the actual theme toggling happens in the ThemeToggler
component. 살펴보겠습니다.
import ThemeContext from "./themeContext"; import { useContext, useState } from "react"; import { DARK, LIGHT } from "./themeReducer"; const ThemeToggler = () => { const [showLight, setShowLight] = useState(true); const [themeState, themeSetter] = useContext(ThemeContext); const dispatchDarkTheme = () => themeSetter(DARK); const dispatchLightTheme = () => themeSetter(LIGHT); const toggleTheme = () => { showLight ? dispatchDarkTheme() : dispatchLightTheme(); setShowLight(!showLight); }; console.log(themeState); return ( <div> <button onClick={toggleTheme}> {showLight ? "Change to Dark Theme" : "Change to Light Theme"} </button> </div> ); }; export default ThemeToggler;
In this component, we used the useContext
hook to retrieve the values we passed to the ThemeContext.Provider
from our App.js
file. As shown above, these values include the ThemeState
, dispatch function for the light theme, and dispatch function for the dark theme. Thereafter, we simply called the dispatch functions to toggle the themes. We also created a state showLight
to determine the current theme. This allows us to easily change the button text depending on the current theme.
The useMemo
Hook
The useMemo
hook is designed to memoize expensive computations. Memoization simply means caching. It caches the computation result with respect to the dependency values so that when the same values are passed, useMemo
will just spit out the already computed value without recomputing it again. This can significantly improve performance when done correctly.
The hook can be used as follows:
const memoizedResult = useMemo(() => expensiveComputation(a, b), [a, b])
Let's consider three cases of the useMemo
hook.
- When the dependency values, a and b remain the same.
TheuseMemo
hook will return the already computed memoized value without recomputation. - When the dependency values, a and b change.
The hook will recompute the value. - When no dependency value is passed.
The hook will recompute the value.
Let's take a look at an example to demonstrate this concept.
In the example below, we'll be computing the PAYE and Income after PAYE of a company's employees with fake data from JSONPlaceholder.
The calculation will be based on the personal income tax calculation procedure for Nigeria providers by PricewaterhouseCoopers available here.
This is shown in the sandbox below.
First, we queried the API to get the employees' data. We also get data for each employee (with respect to their employee id).
const [employee, setEmployee] = useState({}); const [employees, setEmployees] = useState([]); const [num, setNum] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; useEffect(() => { const getEmployee = async () => { const { data } = await axios.get(`${endPoint}/${num}`); setEmployee(data); }; getEmployee(); }, [num]); useEffect(() => { axios.get(endPoint).then(({ data }) => setEmployees(data)); }, [num]);
첫 번째 useEffect에서 axios
와 async/await
메서드를 사용하고 두 번째 useEffect
에서 dot then 구문을 사용했습니다. 이 두 가지 접근 방식은 같은 방식으로 작동합니다.
다음으로 위에서 얻은 직원 데이터를 사용하여 구호 변수를 계산해 보겠습니다.
const taxVariablesCompute = useMemo(() => { const { income, noOfChildren, noOfDependentRelatives } = employee; //supposedly complex calculation //tax relief computations for relief Allowance, children relief, // relatives relief and pension relief const reliefs = reliefAllowance1 + reliefAllowance2 + childrenRelief + relativesRelief + pensionRelief; return reliefs; }, [employee]);
이것은 상당히 복잡한 계산이므로 메모화하거나 최적화하기 위해 useMemo
훅으로 래핑해야 했습니다. 이런 식으로 메모하면 같은 직원에 다시 액세스하려고 할 때 계산이 다시 계산되지 않습니다.
또한, 위에서 구한 세금 감면 값을 이용하여 PAYE와 PAYE 이후의 소득을 계산하고자 합니다.
const taxCalculation = useMemo(() => { const { income } = employee; let taxableIncome = income - taxVariablesCompute; let PAYE = 0; //supposedly complex calculation //computation to compute the PAYE based on the taxable income and tax endpoints const netIncome = income - PAYE; return { PAYE, netIncome }; }, [employee, taxVariablesCompute]);
위에서 계산한 세금 변수를 사용하여 세금 계산(상당히 복잡한 계산)을 수행한 다음 useMemo
훅으로 메모했습니다.
전체 코드는 여기에서 볼 수 있습니다.
이것은 여기에 제공된 세금 계산 절차를 따릅니다. 먼저 소득, 자녀 수, 부양 친족 수를 고려하여 세금 감면을 계산했습니다. 그런 다음 과세 소득에 PIT 세율을 단계적으로 곱했습니다. 문제의 계산이 이 튜토리얼에서 완전히 필요한 것은 아니지만 useMemo
가 필요한 이유를 보여주기 위해 제공됩니다. 이것은 또한 상당히 복잡한 계산이므로 위와 같이 useMemo
를 사용하여 기억해야 할 수도 있습니다.
값을 계산한 후 결과를 간단히 표시했습니다.
useMemo
후크에 대해 다음을 참고하세요.
-
useMemo
는 계산을 최적화해야 하는 경우에만 사용해야 합니다. 즉, 재계산 비용이 많이 드는 경우입니다. - 먼저 암기하지 않고 계산을 작성하고 성능 문제를 일으키는 경우에만 암기하는 것이 좋습니다.
-
useMemo
후크의 불필요하고 관련 없는 사용은 성능 문제를 악화시킬 수도 있습니다. - 때로는 너무 많은 메모이제이션도 성능 문제를 일으킬 수 있습니다.
useCallback
훅
useCallback
은 useMemo
와 같은 용도로 사용되지만 메모화된 값 대신 메모화된 콜백을 반환합니다. 즉, useCallback
은 함수 호출 없이 useMemo
를 전달하는 것과 같습니다.
예를 들어 아래의 다음 코드를 고려하십시오.
import React, {useCallback, useMemo} from 'react' const MemoizationExample = () => { const a = 5 const b = 7 const memoResult = useMemo(() => a + b, [a, b]) const callbackResult = useCallback(a + b, [a, b]) console.log(memoResult) console.log(callbackResult) return( <div> ... </div> ) } export default MemoizationExample
위의 예에서 memoResult
와 callbackResult
는 모두 동일한 값 12
를 제공합니다. 여기에서 useCallback
은 메모된 값을 반환합니다. 그러나 함수로 전달하여 메모화된 콜백을 반환하도록 할 수도 있습니다.
아래의 useCallback
은 메모화된 콜백을 반환합니다.
... const callbackResult = useCallback(() => a + b, [a, b]) ...
그런 다음 작업이 수행되거나 useEffect
후크에서 콜백을 트리거할 수 있습니다.
import {useCallback, useEffect} from 'react' const memoizationExample = () => { const a = 5 const b = 7 const callbackResult = useCallback(() => a + b, [a, b]) useEffect(() => { const callback = callbackResult() console.log(callback) }) return ( <div> <button onClick= {() => console.log(callbackResult())}> Trigger Callback </button> </div> ) } export default memoizationExample
위의 코드에서는 useCallback
hook을 사용하여 콜백 함수를 정의했습니다. 그런 다음 컴포넌트가 마운트될 때와 버튼을 클릭할 때 useEffect
후크에서 콜백을 호출했습니다.
useEffect
와 버튼 클릭 모두 동일한 결과를 산출합니다.
useCallback
후크에 적용되는 개념, 해야 할 사항 및 하지 말아야 할 사항은 useMemo
후크에도 적용됩니다. useCallback
으로 useMemo
예제를 다시 만들 수 있습니다.
useRef
후크
useRef
는 애플리케이션에서 지속할 수 있는 객체를 반환합니다. 후크에는 current
하나의 속성만 있으며 인수를 쉽게 전달할 수 있습니다.
클래스 기반 구성 요소에서 사용되는 createRef
와 동일한 용도로 사용됩니다. 다음과 같이 이 후크로 참조를 생성할 수 있습니다.
const newRef = useRef('')
여기에서 newRef
라는 새 ref를 만들고 빈 문자열을 전달했습니다.
이 후크는 주로 두 가지 목적으로 사용됩니다.
- DOM 액세스 또는 조작
- 변경 가능한 상태 저장 — 값이 변경될 때 구성 요소가 다시 렌더링되는 것을 원하지 않을 때 유용합니다.
DOM 조작
DOM 요소에 전달되면 ref 개체는 해당 요소를 가리키고 DOM 속성 및 속성에 액세스하는 데 사용할 수 있습니다.
다음은 이 개념을 보여주는 매우 간단한 예입니다.
import React, {useRef, useEffect} from 'react' const RefExample = () => { const headingRef = useRef('') console.log(headingRef) return( <div> <h1 className='topheading' ref={headingRef}>This is a h1 element</h1> </div> ) } export default RefExample
위의 예에서 우리는 빈 문자열을 전달하는 headingRef
후크를 사용하여 useRef
를 정의했습니다. 그런 다음 ref = {headingRef}
를 전달하여 h1
태그에 ref를 설정합니다. 이 ref를 설정하여 h1
요소를 가리키도록 headingRef
에 요청했습니다. 이것은 ref에서 h1
요소의 속성에 액세스할 수 있음을 의미합니다.
이를 확인하기 위해 console.log(headingRef)
의 값을 확인하면 {current: HTMLHeadingElement}
또는 {current: h1}
이 표시되고 요소의 모든 속성이나 속성을 평가할 수 있습니다. 비슷한 것이 다른 HTML 요소에도 적용됩니다.
예를 들어, 컴포넌트가 마운트될 때 텍스트를 기울임꼴로 만들 수 있습니다.
useEffect(() => { headingRef.current.style.font; }, []);
텍스트를 다른 것으로 변경할 수도 있습니다.
... headingRef.current.innerHTML = "A Changed H1 Element"; ...
부모 컨테이너의 배경색도 변경할 수 있습니다.
... headingRef.current.parentNode.style.backgroundColor = "red"; ...
모든 종류의 DOM 조작이 여기에서 수행될 수 있습니다. headerRef.current 는 document.querySelector('.topheading')
headingRef.current
같은 방식으로 읽을 수 있습니다.
DOM 요소를 조작할 때 useRef
후크의 흥미로운 사용 사례 중 하나는 커서를 입력 요소에 초점을 맞추는 것입니다. 빠르게 실행해 보겠습니다.
import {useRef, useEffect} from 'react' const inputRefExample = () => { const inputRef = useRef(null) useEffect(() => { inputRef.current.focus() }, []) return( <div> <input ref={inputRef} /> <button onClick = {() => inputRef.current.focus()}>Focus on Input </button> </div> ) } export default inputRefExample
위의 코드에서 useRef
후크를 사용하여 inputRef
를 만든 다음 입력 요소를 가리키도록 요청했습니다. 그런 다음 구성 요소가 로드될 때와 inputRef.current.focus()
를 사용하여 버튼을 클릭할 때 커서가 입력 참조에 초점을 맞추도록 했습니다. 이는 focus()
가 입력 요소의 속성이고 ref가 메소드를 평가할 수 있기 때문에 가능합니다.
부모 구성 요소에서 생성된 참조는 React.forwardRef()
를 사용하여 전달하여 자식 구성 요소에서 평가할 수 있습니다. 살펴보겠습니다.
먼저 다른 구성 요소 NewInput.js
를 만들고 여기에 다음 코드를 추가해 보겠습니다.
import { useRef, forwardRef } from "react"; const NewInput = forwardRef((props, ref) => { return <input placeholder={props.val} ref={ref} />; }); export default NewInput;
이 구성 요소는 props
및 ref
를 허용합니다. ref를 ref prop에 전달하고 props.val
을 placeholder prop에 전달했습니다. 일반 React 구성 요소는 ref
속성을 사용하지 않습니다. 이 속성은 위와 같이 React.forwardRef
로 래핑할 때만 사용할 수 있습니다.
그러면 상위 구성 요소에서 이것을 쉽게 호출할 수 있습니다.
... <NewInput val="Just an example" ref={inputRef} /> ...
변경 가능한 상태 저장
참조는 DOM 요소를 조작하는 데만 사용되는 것이 아니라 전체 구성 요소를 다시 렌더링하지 않고 변경 가능한 값을 저장하는 데 사용할 수도 있습니다.
다음 예제에서는 구성 요소를 다시 렌더링하지 않고 버튼을 클릭한 횟수를 감지합니다.
import { useRef } from "react"; export default function App() { const countRef = useRef(0); const increment = () => { countRef.current++; console.log(countRef); }; return ( <div className="App"> <button onClick={increment}>Increment </button> </div> ); }
위의 코드에서 버튼을 클릭할 때 countRef
를 증가시킨 다음 콘솔에 기록했습니다. 콘솔에 표시된 대로 값이 증가하지만 구성 요소에서 직접 평가하려고 하면 변경 사항을 볼 수 없습니다. 다시 렌더링할 때만 구성 요소에서 업데이트됩니다.
useState
는 비동기식이지만 useRef
는 동기식입니다. 즉, 값이 업데이트된 직후에 사용할 수 있습니다.
useLayoutEffect
후크
useEffect
후크와 마찬가지로 useLayoutEffect
는 구성 요소가 마운트되고 렌더링된 후에 호출됩니다. 이 후크는 DOM 변형 후에 실행되며 동기식으로 실행됩니다. DOM 변형 후 동기적으로 호출되는 것 외에 useLayoutEffect
는 useEffect
와 동일한 작업을 수행합니다.
useLayoutEffect
는 DOM 변형 또는 DOM 관련 측정을 수행할 때만 사용해야 하며, 그렇지 않으면 useEffect
후크를 사용해야 합니다. DOM 돌연변이 함수에 useEffect
후크를 사용하면 깜박임과 같은 일부 성능 문제가 발생할 수 있지만 useLayoutEffect
는 돌연변이가 발생한 후 실행될 때 이를 완벽하게 처리합니다.
이 개념을 설명하기 위해 몇 가지 예를 살펴보겠습니다.
- 크기 조정 시 창의 너비와 높이를 가져옵니다.
import {useState, useLayoutEffect} from 'react' const ResizeExample = () =>{ const [windowSize, setWindowSize] = useState({width: 0, height: 0}) useLayoutEffect(() => { const resizeWindow = () => setWindowSize({ width: window.innerWidth, height: window.innerHeight }) window.addEventListener('resize', resizeWindow) return () => window.removeEventListener('resize', resizeWindow) }, []) return ( <div> <p>width: {windowSize.width}</p> <p>height: {windowSize.height}</p> </div> ) } export default ResizeExample
위의 코드에서 너비와 높이 속성을 가진 상태 windowSize
를 만들었습니다. 그런 다음 창 크기를 조정할 때 상태를 현재 창의 너비와 높이로 각각 설정합니다. 또한 마운트 해제될 때 코드를 정리했습니다. 정리 프로세스는 DOM 조작을 정리하고 효율성을 개선하기 위해 useLayoutEffect
에서 필수적입니다.
-
useLayoutEffect
를 사용하여 텍스트를 흐리게 처리해 보겠습니다.
import { useRef, useState, useLayoutEffect } from "react"; export default function App() { const paragraphRef = useRef(""); useLayoutEffect(() => { const { current } = paragraphRef; const blurredEffect = () => { current.style.color = "transparent"; current.style.textShadow = "0 0 5px rgba(0,0,0,0.5)"; }; current.addEventListener("click", blurredEffect); return () => current.removeEventListener("click", blurredEffect); }, []); return ( <div className="App"> <p ref={paragraphRef}>This is the text to blur</p> </div> ); }
위의 코드에서 useRef
와 useLayoutEffect
를 함께 사용했습니다. 우리는 먼저 단락을 가리키는 ref, paragraphRef
를 만들었습니다. 그런 다음 단락이 클릭될 때 모니터링할 온 클릭 이벤트 리스너를 만든 다음 정의한 스타일 속성을 사용하여 단락을 흐리게 처리했습니다. 마지막으로 removeEventListener
를 사용하여 이벤트 리스너를 정리했습니다.
useDispatch
및 useSelector
후크
useDispatch
는 애플리케이션에서 작업을 디스패치(트리거)하기 위한 Redux 후크입니다. 액션 객체를 인수로 취하고 액션을 호출합니다. useDispatch
는 mapDispatchToProps
에 대한 후크의 동등물입니다.
반면에 useSelector
는 Redux 상태를 평가하기 위한 Redux 후크입니다. 스토어에서 정확한 Redux 감속기를 선택한 다음 해당 상태를 반환하는 함수가 필요합니다.
Redux 저장소가 Redux 공급자를 통해 React 애플리케이션에 연결되면 useSelector
로 작업을 호출하고 useDispatch
로 상태에 액세스할 수 있습니다. 모든 Redux 작업과 상태는 이 두 개의 후크로 평가될 수 있습니다.
이러한 상태는 React Redux(React 애플리케이션에서 Redux 저장소를 쉽게 평가할 수 있게 해주는 패키지)와 함께 제공됩니다. 핵심 Redux 라이브러리에서는 사용할 수 없습니다.
이 후크는 사용이 매우 간단합니다. 먼저 디스패치 함수를 선언한 다음 트리거해야 합니다.
import {useDispatch, useSelector} from 'react-redux' import {useEffect} from 'react' const myaction from '...' const ReduxHooksExample = () =>{ const dispatch = useDispatch() useEffect(() => { dispatch(myaction()); //alternatively, we can do this dispatch({type: 'MY_ACTION_TYPE'}) }, []) const mystate = useSelector(state => state.myReducerstate) return( ... ) } export default ReduxHooksExample
위의 코드에서는 react-redux
에서 useDispatch
및 useSelector
를 가져왔습니다. 그런 다음 useEffect
후크에서 액션을 전달했습니다. 다른 파일에서 작업을 정의한 다음 여기에서 호출하거나 useEffect
호출에 표시된 대로 직접 정의할 수 있습니다.
작업을 전달하면 상태를 사용할 수 있습니다. 그런 다음 그림과 같이 useSelector
후크를 사용하여 상태를 검색할 수 있습니다. 상태는 useState
후크에서 상태를 사용하는 것과 같은 방식으로 사용할 수 있습니다.
이 두 개의 후크를 보여주는 예를 살펴보겠습니다.
이 개념을 시연하려면 Redux 저장소, 감속기 및 작업을 만들어야 합니다. 여기에서 일을 단순화하기 위해 JSONPlaceholder의 가짜 데이터베이스와 함께 Redux Toolkit 라이브러리를 사용할 것입니다.
시작하려면 다음 패키지를 설치해야 합니다. 다음 bash 명령을 실행합니다.
npm i redux @reduxjs/toolkit react-redux axios
먼저 직원의 API에 대한 감속기와 작업을 처리하기 위해 employeesSlice.js
를 생성해 보겠습니다.
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import axios from "axios"; const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; export const fetchEmployees = createAsyncThunk("employees/fetchAll", async () => { const { data } = await axios.get(endPoint); return data; }); const employeesSlice = createSlice({ name: "employees", initialState: { employees: [], loading: false, error: "" }, reducers: {}, extraReducers: { [fetchEmployees.pending]: (state, action) => { state.status = "loading"; }, [fetchEmployees.fulfilled]: (state, action) => { state.status = "success"; state.employees = action.payload; }, [fetchEmployees.rejected]: (state, action) => { state.status = "error"; state.error = action.error.message; } } }); export default employeesSlice.reducer;
이것은 Redux 툴킷의 표준 설정입니다. Thunk
를 사용하여 createAsyncThunk
미들웨어에 액세스하여 비동기 작업을 수행했습니다. 이를 통해 API에서 직원 목록을 가져올 수 있었습니다. 그런 다음 employeesSlice
를 생성하고 작업 유형에 따라 "loading", "error" 및 직원 데이터를 반환했습니다.
Redux 툴킷을 사용하면 저장소를 쉽게 설정할 수도 있습니다. 여기 가게가 있습니다.
import { configureStore } from "@reduxjs/toolkit"; import { combineReducers } from "redux"; import employeesReducer from "./employeesSlice"; const reducer = combineReducers({ employees: employeesReducer }); export default configureStore({ reducer });;
여기에서는 리듀서를 묶기 위해 combineReducers
를 사용했고 저장소를 설정하기 위해 Redux 툴킷에서 제공하는 configureStore
기능을 사용했습니다.
우리의 응용 프로그램에서 이것을 계속 사용합시다.
먼저 Redux를 React 애플리케이션에 연결해야 합니다. 이상적으로는 애플리케이션의 루트에서 이 작업을 수행해야 합니다. index.js
파일에서 하는 것을 좋아합니다.
import React, { StrictMode } from "react"; import ReactDOM from "react-dom"; import store from "./redux/store"; import { Provider } from "react-redux"; import App from "./App"; const rootElement = document.getElementById("root"); ReactDOM.render( <Provider store={store}> <StrictMode> <App /> </StrictMode> </Provider>, rootElement );
여기에서는 위에서 만든 스토어와 react-redux
에서 Provider
를 가져왔습니다.
그런 다음 Provider
함수로 전체 응용 프로그램을 래핑하여 저장소를 전달했습니다. 이렇게 하면 응용 프로그램 전체에서 저장소에 액세스할 수 있습니다.
그런 다음 useDispatch
및 useSelector
후크를 사용하여 데이터를 가져올 수 있습니다.
App.js
파일에서 이 작업을 수행해 보겠습니다.
import { useDispatch, useSelector } from "react-redux"; import { fetchEmployees } from "./redux/employeesSlice"; import { useEffect } from "react"; export default function App() { const dispatch = useDispatch(); useEffect(() => { dispatch(fetchEmployees()); }, [dispatch]); const employeesState = useSelector((state) => state.employees); const { employees, loading, error } = employeesState; return ( <div className="App"> {loading ? ( "Loading..." ) : error ? ( <div>{error}</div> ) : ( <> <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <h3>{`${employee.firstName} ${employee.lastName}`}</h3> </div> ))} </> )} </div> ); }
위의 코드에서 useDispatch
후크를 사용하여 employeesSlice.js
파일에서 생성된 fetchEmployees
작업을 호출했습니다. 이렇게 하면 직원 상태가 애플리케이션에서 사용 가능하게 됩니다. 그런 다음 useSelector
후크를 사용하여 상태를 가져왔습니다. 이후 employees
을 통해 매핑하여 결과를 표시했습니다.
useHistory
후크
탐색은 React 애플리케이션에서 매우 중요합니다. 몇 가지 방법으로 이를 달성할 수 있지만 React Router는 React 애플리케이션에서 동적 라우팅을 달성하는 간단하고 효율적이며 널리 사용되는 방법을 제공합니다. 또한 React Router는 라우터의 상태를 평가하고 브라우저에서 탐색을 수행하기 위한 몇 가지 후크를 제공하지만 이를 사용하려면 먼저 애플리케이션을 올바르게 설정해야 합니다.
React Router 후크를 사용하려면 먼저 BrowserRouter
로 애플리케이션을 래핑해야 합니다. 그런 다음 Switch
및 Route
를 사용하여 경로를 중첩할 수 있습니다.
하지만 먼저 다음 명령을 실행하여 패키지를 설치해야 합니다.
npm install react-router-dom
그런 다음 응용 프로그램을 다음과 같이 설정해야 합니다. 내 App.js
파일에서 이 작업을 수행하는 것을 좋아합니다.
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import Employees from "./components/Employees"; export default function App() { return ( <div className="App"> <Router> <Switch> <Route path='/'> <Employees /> </Route> ... </Switch> </Router> </div> ); }
렌더링하려는 구성 요소의 수에 따라 가능한 한 많은 경로를 가질 수 있습니다. 여기에서는 Employees
구성 요소만 렌더링했습니다. path
속성은 React Router DOM에 구성 요소의 경로를 알려주며 쿼리 문자열 또는 기타 다양한 방법으로 평가할 수 있습니다.
여기서 순서가 중요합니다. 루트 경로는 하위 경로 등 아래에 배치되어야 합니다. 이 순서를 무시하려면 루트 경로에 exact
키워드를 포함해야 합니다.
<Route path='/' exact > <Employees /> </Route>
이제 라우터를 설정했으므로 애플리케이션에서 useHistory
후크 및 기타 React Router 후크를 사용할 수 있습니다.
useHistory
후크를 사용하려면 먼저 다음과 같이 선언해야 합니다.
import {useHistory} from 'history' import {useHistory} from 'react-router-dom' const Employees = () =>{ const history = useHistory() ... }
콘솔에 기록을 기록하면 연결된 여러 속성이 표시됩니다. 여기에는 block
, createHref
, go
, goBack
, goForward
, length
, listen
, location
, push
, replace
포함됩니다. 이러한 모든 속성이 유용하지만 다른 속성보다 history.push
및 history.replace
를 더 자주 사용할 것입니다.
이 속성을 사용하여 한 페이지에서 다른 페이지로 이동해 보겠습니다.
특정 직원의 이름을 클릭할 때 특정 직원에 대한 데이터를 가져오려고 한다고 가정합니다. useHistory
후크를 사용하여 직원의 정보가 표시될 새 페이지로 이동할 수 있습니다.
function moveToPage = (id) =>{ history.push(`/employees/${id}`) }
다음을 추가하여 Employee.js
파일에서 이를 구현할 수 있습니다.
import { useEffect } from "react"; import { Link, useHistory, useLocation } from "react-router-dom"; export default function Employees() { const history = useHistory(); function pushToPage = (id) => { history.push(`/employees/${id}`) } ... return ( <div> ... <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <span>{`${employee.firstName} ${employee.lastName} `}</span> <button onClick={pushToPage(employee.id)}> » </button> </div> ))} </div> ); }
pushToPage
함수에서 useHistory
후크의 history
을 사용하여 직원 페이지로 이동하고 직원 ID를 함께 전달했습니다.
useLocation
후크
이 후크는 또한 React Router DOM과 함께 제공됩니다. 쿼리 문자열 매개변수로 작업하는 데 사용되는 매우 인기 있는 후크입니다. 이 후크는 브라우저의 window.location
과 유사합니다.
import {useLocation} from 'react' const LocationExample = () =>{ const location = useLocation() return ( ... ) } export default LocationExample
useLocation
후크는 pathname
, search
매개변수, hash
및 state
를 반환합니다. 가장 일반적으로 사용되는 매개변수에는 pathname
과 search
이 포함되지만 hash
를 동등하게 사용할 수 있으며 애플리케이션에서 많은 것을 state
할 수 있습니다.
위치 pathname
속성은 Route
설정에서 설정한 경로를 반환합니다. search
은 쿼리 검색 매개변수가 있는 경우 반환합니다. 예를 들어 쿼리에 'http://mywebsite.com/employee/?id=1'
을 전달하면 pathname
은 /employee
이고 search
은 ?id=1
이 됩니다.
그런 다음 쿼리 문자열과 같은 패키지를 사용하거나 코딩하여 다양한 검색 매개변수를 검색할 수 있습니다.
useParams
후크
경로 속성에 URL 매개변수가 있는 경로를 설정하면 useParams
후크를 사용하여 해당 매개변수를 키/값 쌍으로 평가할 수 있습니다.
예를 들어 다음과 같은 경로가 있다고 가정해 보겠습니다.
<Route path='/employees/:id' > <Employees /> </Route>
경로는 :id
대신에 동적 id를 기대합니다.
useParams
후크를 사용하여 사용자가 전달한 ID(있는 경우)를 평가할 수 있습니다.
예를 들어, 사용자가 history.push
를 사용하여 함수에서 다음을 전달한다고 가정합니다.
function goToPage = () => { history.push(`/employee/3`) }
다음과 같이 useParams
후크를 사용하여 이 URL 매개변수에 액세스할 수 있습니다.
import {useParams} from 'react-router-dom' const ParamsExample = () =>{ const params = useParams() console.log(params) return( <div> ... </div> ) } export default ParamsExample
콘솔에 params
를 기록하면 다음 객체 {id: "3"}
를 얻습니다.
useRouteMatch
후크
이 후크는 일치 개체에 대한 액세스를 제공합니다. 인수가 제공되지 않은 경우 구성 요소에 가장 가까운 일치 항목을 반환합니다.
일치 객체는 path
(Route에 지정된 경로와 동일), URL
, params
객체 및 isExact
를 포함한 여러 매개변수를 반환합니다.
예를 들어 useRouteMatch
를 사용하여 경로를 기반으로 구성 요소를 반환할 수 있습니다.
import { useRouteMatch } from "react-router-dom"; import Employees from "..."; import Admin from "..." const CustomRoute = () => { const match = useRouteMatch("/employees/:id"); return match ? ( <Employee /> ) : ( <Admin /> ); }; export default CustomRoute;
위의 코드에서 우리는 useRouteMatch
로 라우트의 경로를 설정하고 사용자가 선택한 라우트에 따라 <Employee />
또는 <Admin />
컴포넌트를 렌더링했습니다.
이것이 작동하려면 App.js
파일에 경로를 추가해야 합니다.
... <Route> <CustomRoute /> </Route> ...
커스텀 훅 만들기
React 문서에 따르면 사용자 정의 후크를 빌드하면 로직을 재사용 가능한 함수로 추출할 수 있습니다. 그러나 React 후크에 적용되는 모든 규칙이 사용자 정의 후크에 적용되는지 확인해야 합니다. 이 튜토리얼 상단에 있는 React hook의 규칙을 확인하고 사용자 정의 hook이 각각의 규칙을 준수하는지 확인하세요.
사용자 정의 후크를 사용하면 함수를 한 번 작성하고 필요할 때마다 재사용할 수 있으므로 DRY 원칙을 준수합니다.
예를 들어 다음과 같이 페이지에서 스크롤 위치를 가져오기 위해 사용자 지정 후크를 만들 수 있습니다.
import { useLayoutEffect, useState } from "react"; export const useScrollPos = () => { const [scrollPos, setScrollPos] = useState({ x: 0, y: 0 }); useLayoutEffect(() => { const getScrollPos = () => setScrollPos({ x: window.pageXOffset, y: window.pageYOffset }); window.addEventListener("scroll", getScrollPos); return () => window.removeEventListener("scroll", getScrollPos); }, []); return scrollPos; };
여기에서 페이지의 스크롤 위치를 결정하기 위해 사용자 정의 후크를 정의했습니다. 이를 달성하기 위해 먼저 스크롤 위치를 저장하기 위해 scrollPos
상태를 생성했습니다. 이것은 DOM을 수정하기 때문에 useLayoutEffect
대신 useEffect
를 사용해야 합니다. 스크롤 이벤트 리스너를 추가하여 x 및 y 스크롤 위치를 캡처한 다음 이벤트 리스너를 정리했습니다. 마지막으로 스크롤 위치로 돌아갔습니다.
다른 상태를 사용하는 것처럼 이 사용자 정의 후크를 호출하고 사용하여 애플리케이션의 어느 곳에서나 사용할 수 있습니다.
import {useScrollPos} from './Scroll' const App = () =>{ const scrollPos = useScrollPos() console.log(scrollPos.x, scrollPos.y) return ( ... ) } export default App
여기에서 위에서 만든 사용자 정의 후크 useScrollPos
를 가져왔습니다. 그런 다음 초기화한 다음 콘솔에 값을 기록했습니다. 페이지를 스크롤하면 후크가 스크롤의 모든 단계에서 스크롤 위치를 표시합니다.
앱에서 상상할 수 있는 거의 모든 작업을 수행하는 사용자 지정 후크를 만들 수 있습니다. 보시다시피, 일부 기능을 수행하려면 내장된 React 후크를 사용하기만 하면 됩니다. 타사 라이브러리를 사용하여 사용자 지정 후크를 만들 수도 있지만 그렇게 할 경우 후크를 사용할 수 있도록 해당 라이브러리를 설치해야 합니다.
결론
이 튜토리얼에서는 대부분의 애플리케이션에서 사용할 유용한 React 후크를 살펴보았습니다. 우리는 그것들이 무엇을 제시하고 당신의 애플리케이션에서 그것들을 어떻게 사용하는지 조사했습니다. 또한 이러한 후크를 이해하고 애플리케이션에 적용하는 데 도움이 되는 몇 가지 코드 예제를 살펴보았습니다.
이 후크를 더 잘 이해하려면 자신의 응용 프로그램에서 이러한 후크를 시도하는 것이 좋습니다.
React 문서의 리소스
- 후크 FAQ
- 리덕스 툴킷
- 상태 후크 사용
- 효과 후크 사용
- 후크 API 참조
- React Redux 후크
- React 라우터 후크