-
[React.js] Hooks - useState()Front-End(Web)/React - 프레임워크(React, Next) 2020. 11. 27. 22:54반응형
🤷 state.hooks
함수형 컴포넌트에서도 클래스처럼 state를 사용할 수 있게 해주는 기능이다. (React v16.8에서 추가됨)
클래스형 컴포넌트가 지원기능은 많지만 아래같은 단점들도 분명 존재했다.
- 매우 난해함(생성자 함수, super(props) 등). 컴포넌트끼리의 연동이 어려움(this 개념).
- 많은 버그를 유발할 수 있고, 그렇기에 테스팅 시간이 길어진다.
그래서 함수형 컴포넌트에서도 state 등 기존 class 기능을 사용할 수 있도록 지원된 기능이 React Hooks다.
useState(), useEffect(), useContext(), useReducer(), useRef() 등이 있다. (Hooks 종류: reactjs.org/docs/hooks-reference.html)
📒 useState(): Hooks의 시작
React 라이브러리에서 useState Hook을 가져옴으로서 사용할 수 있다. 이는, 두 개의 값을 가진 Array로 각각의 변수명을 지정해준다.
- 이전 state(current state) - the current value of this state
- state 변경함수(state setter) - a function that we can use to update the value of this state
// useState 가져오기 import React, { useState } from 'react'; export default function ColorPicker() { // 이전 state, state setter 이름 지정 const [color, setColor] = useState(); const divStyle = {backgroundColor: color}; return ( <div style={divStyle}> <p>The color is {color}</p> <button onClick={() => setColor('Aquamarine')}> Aquamarine </button> <button onClick={() => setColor('BlueViolet')}> BlueViolet </button> <button onClick={() => setColor('Chartreuse')}> Chartreuse </button> <button onClick={() => setColor('CornflowerBlue')}> CornflowerBlue </button> </div> ); }
이렇게, color로 state값을, setColor()로 state update를 진행할 수 있다. 문제는, 초기값을 아직 설정해놓지 않았다.
1. initial state in useState()
useState()의 초기값은 아래와 같이 괄호 안에 작성해주면 된다.
const [color, setColor] = useState('Tomato');
초기값은 첫 렌더링 시에만 적용되며, 다른 이유로 컴포넌트가 재실행되면 바로 이전의 state를 초기값으로 사용한다. (필요없으면 null)
2. state setter를 활용한 이벤트핸들러 만들기
state setter가 복잡해질수록, 위처럼 JSX 내에 직접 쓰기보단 별도의 이벤트 핸들리 함수를 만든다.
이벤트 핸들러에는 state setter가 포함된다.
import React, { useState } from 'react'; // regex to match numbers between 1 and 10 digits long const validPhoneNumber = /^\d{1,10}$/; export default function PhoneNumber() { const [phone, setPhone] = useState(''); // 이벤트 핸들러 함수 const handleChange = ({ target })=> { const newPhone = target.value; const isValid = validPhoneNumber.test(newPhone); if (isValid) { setPhone(newPhone) } return; }; return ( <div className='phone'> <label for='phone-input'>Phone: </label> <input id='phone-input' value={phone} onChange={handleChange} /> // handleChange </div> ); }
3. 이전 state 활용하기
이전 state에 따라 현재 state가 정해지는 경우가 많다.
이 경우는, state setter를 callback function 형태로 만들면 인자에 자동으로 이전 state가 들어간다.
import React, { useState } from 'react'; export default function QuizNavBar({ questions }) { const [questionIndex, setQuestionIndex] = useState(0); // 이벤트 핸들러 내 state setter를 callback function const goBack = () => setQuestionIndex(prevQuestionIndex => prevQuestionIndex - 1); const goToNext = () => setQuestionIndex(prevQuestionIndex => prevQuestionIndex + 1); const onLastQuestion = questionIndex === questions.length - 1; const onFirstQuestion = questionIndex === 0; return ( <nav> <span>Question #{questionIndex + 1}</span> <div> <button onClick={goBack} disabled={onFirstQuestion}> Go Back </button> <button onClick={goToNext} disabled={onLastQuestion}> Next Question </button> </div> </nav> ); }
이벤트 핸들러인 goBack(), goToNext() 를 보자. questionIndex라는 state를 더하거나 빼는 기능의 함수이다.
여기서 내부가, goBack(prevQuestionIndex => prevQuestionIndex - 1) 로 표현되어 있다.
여기서 prevQuestionIndex 인자에는, 자동적으로 이전 state(questionIndex) 값이 들어가게 된다.
* 참고로, onFirstQuestion, onLastQuestion은 인덱스 처음과 끝을 감지하는 boolean 함수. JSX 내 disabled={}는 비활성화 기능.
4-1. 여러 개의 State(Array 형태)
usestate() 기본값은 빈 배열 [], Array 메소드인 .map, .filter 및 spread syntax 등을 활용하여 변경된 array 값을 반환하면 된다.
// 📂index.js import React, { useState } from "react"; import ItemList from "./ItemList"; import { produce, pantryItems } from "./storeItems"; export default function GroceryCart() { const [cart, setCart] = useState([]); // default를 빈 배열[] const addItem = (item) => { setCart((prev) => { return [item, ...prev]; }); }; const removeItem = (targetIndex) => { // index 활용해야 하므로, (item, index) 배열을 filter setCart((prev) => { return prev.filter((item, index) => index !== targetIndex); }); }; return ( <div> <h1>Grocery Cart</h1> <ul> {cart.map((item, index) => ( <li onClick={() => removeItem(index)} key={index}> {item} </li> ))} </ul> <h2>Produce</h2> <ItemList items={produce} onItemClick={addItem} /> <h2>Pantry Items</h2> <ItemList items={pantryItems} onItemClick={addItem} /> </div> ); }
4-2. 여러 개의 State(Object 형태)
import React, { useState } from "react"; export default function EditProfile() { const [profile, setProfile] = useState({}); // default 빈 객체{} const handleChange = ({ target }) => { const { name, value } = target; // input의 name(=key값)과 value 객체화 setProfile((prevProfile) => ({ ...prevProfile, // Spread Syntax [name]: value // Computed property names })); }; const handleSubmit = (event) => { event.preventDefault(); alert(JSON.stringify(profile, '', 2)); }; return ( <form onSubmit={handleSubmit}> <input value={profile.firstName || ''} onChange={handleChange} name="firstName" type="text" placeholder="First Name" /> <input value={profile.lastName || ''} onChange={handleChange} type="text" name="lastName" placeholder="Last Name" /> <input value={profile.bday || ''} onChange={handleChange} type="date" name="bday" /> <input value={profile.password || ''} onChange={handleChange} type="password" name="password" placeholder="Password" /> <button type="submit">Save Profile</button> </form> ); }
마찬가지로, usestate() 기본값은 빈 객체 {}, 이전 값들은 Spread syntax로 보상하고, 현재 값을 target에서 받아 state setter에서 추가.
target에서 받을땐, input의 전체 property를 객체 형태로 가져온 다음에 { name, value }로만 추린 것이다.
* Spread Syntax: Array와 마찬가지로 이전 값들을 업데이트하는 용도로 사용되었다. 이를 가져오려면 prev의 callback function 필수!
* Computed Property Names: [variables]으로 key값을 변수(string)로 간편하게 지정할 수 있도록 추가된 기능.(ES6 추가)
5. 복수의 useState() 활용하는 경우
복잡한 Object 구조의 state를 가지고 있다면, 수정되지 않은 값들도 일일히 업데이트 해줘야 되기에 코드가 길어지고 효율도 떨어진다.
function Musical() { const [state, setState] = useState({ title: "Best Musical Ever", actors: ["George Wilson", "Tim Hughes", "Larry Clements"], locations: { Chicago: { dates: ["1/1", "2/2"], address: "chicago theater"}, SanFrancisco: { dates: ["5/2"], address: "sf theater" } } }) }
// location - SanFrancisco - dates 만 추가하는 경우 setState((prev) => ({ ...prev, locations: prev.SanFrancisco.dates.push(updateDate) });
이처럼 이전 값을 ...prev 해야 하고, 추가가 아닌 수정인 경우 locations 안에서 ...prev를 또 돌리고 바뀌는 index, key값을 찾아야 한다.
이러한 번거로움을 줄이고 효율성을 높이기 위해, 요소들을 각각의 useState()로 나눈다.
function MusicalRefactored() { const [title, setTitle] = useState("Best Musical Ever"); const [actors, setActors] = useState(["George Wilson", "Tim Hughes", "Larry Clements"]) const [locations, setLocations] = useState({ Chicago: { dates: ["1/1", "2/2"], address: "chicago theater"}, SanFrancisco: { dates: ["5/2"], address: "sf theater" } }) }
어렵다... 하면 할수록 어렵다... useState() Hook을 사용함으로써 this.setState = state setter 함수로 대체되는 것을 숙지해야겠다.
클래스형 컴포넌트를 함수형 컴포넌트로 바꾸는 마지막 과제를 혼자 최대한 진행해보면서 클래스형 - 함수형 변환에 익숙해지자!
반응형'Front-End(Web) > React - 프레임워크(React, Next)' 카테고리의 다른 글
[React.js] Stateful vs Stateless Components (0) 2020.11.29 [React.js] Hooks - useEffect() (0) 2020.11.28 [React.js] Lifecycle (0) 2020.11.27 [React.js] State (0) 2020.11.26 [React.js] Props (0) 2020.11.26