리액트.. 늘 새로워... 짜릿해...
일단 useRef는 앞에 use가 붙어있는걸 보아하니.... 왠지 React hool이 아닌가...했지만
정확히 맞았다. React hook
이 맞다. 그렇다면 또 설명을 참을 수 없기 때문에 개념을 먼저 잡고 가보자.
useState()나 useEffect()처럼 많이 쓰이지는 않지만 특정한 DOM를 잡아서 그 DOM을 변경할 수 있는 장점이 있다. 리액트를 사용할 때 간혹 DOM을 직접 선택해서 엘리먼트에게 특정 스타일을 주거나, 스크롤바 위치를 가져오거나, 페이지를 렌더링 했을때 input에 focus가 되야한다거나 그럴때 useRef라는 hooks함수를 사용한다.
나만 그러는지 모르겠지만 예시를 사용함으로써 보는 사람과 블로그를 작성하는 사람 모두 이해가 좀 더 빨리 된다고 생각해서 예시를 많이 사용하는 편이다.
import React, { useState, useRef } from 'react';
function InputFocus() {
const [inputs, setInputs] = useState({
name: '',
nickname: ''
});
const nameInput = useRef();
const { name, nickname } = inputs;
const onChange = e => {
const { value, name } = e.target;
setInputs({
...inputs,
[name]: value
});
};
const onReset = () => {
setInputs({
name: '',
nickname: ''
});
nameInput.current.focus();
};
return (
<div>
<input
name="name"
placeholder="이름"
onChange={onChange}
value={name}
ref={nameInput}
/>
<input
name="nickname"
placeholder="닉네임"
onChange={onChange}
value={nickname}
/>
<button onClick={onReset}>초기화</button>
<div>
<b>값: </b>
{name} ({nickname})
</div>
</div>
);
}
export default InputFocus;
코드만 보면 되게 간단해 보일 수 있지만 useRef를 사용하기 전에 공식문서를 보게 되면 current를 사용하는걸 볼 수 있다.
current가 쓰이는 방식은 .current
로 사용이 된다. 이게 무슨 말인가? 먼저 useRef를 사용할 때 최상단에 const nameInput = useRef();
로 useRef를 먼저 설정해준다.
왜 이렇게 작성했을까 ?
먼저 DOM에 접근하기 위해서 useRef를 많이 사용하는데 useRef() 자체를 DOM에 넣어서 작업하는건 불가능하고 변수로 지정해주고 사용해줘야 한다. nameInput을 변수로 지정해줬기 때문에 특정 스타일을 줘야하는 DOM에 Attribute로 ref={}
라는 속성을 주면 된다.
위에 예시 코드에선 nameInput.current.focus();
를 줌으로써 화면이 렌더링 될때 input에 focus가 될 수 있도록 만들었다. (제법 유용하다.)
input에 하나만 준 이유는 어차피 키보드를 이용해서 input 두개에 접근하는 건 할 수 없다. 첫번째 input에 접근하게 되면 다음 input으로 가는건 사용자의 선택이기 때문에 처음 포인트만 주어도 된다는 생각이다.
나름의 센스
import React, { useEffect, useState, useRef } from 'react';
const Best = () => {
const bestRef = useRef();
const [bestCount, setBestCount] = useState(1);
const [bestList, setBestList] = useState([]);
const [buttonActive, setButtonActive] = useState(false);
const handleBestDecrease = () => {
setBestCount(bestCount - 1);
bestRef.current.style.transform = `translateX(-${1200 * bestCount}px)`;
if (0 === bestCount) {
setBestCount(1);
setButtonActive(false);
} else {
setButtonActive(true);
}
};
const handleBestIncrease = () => {
setBestCount(bestCount + 1);
bestRef.current.style.transform = `translateX(-${1200 * bestCount}px)`;
if (bestList.length % 4 <= bestCount) {
setBestCount(bestCount - 1);
setButtonActive(true);
} else {
setButtonActive(false);
}
};
return (
<>
<ul ref={bestRef} className="itemList clear">
{bestList.map(best => {
return (
<li key={best.id}>
<div className="imgWrap">
<img src={best.src} alt={best.title} />
</div>
<dl className="textWrap">
<dt>{best.title}</dt>
<dd>{best.price}</dd>
</dl>
</li>
);
})}
</ul>
<div className="buttonWrap">
<button
type="button"
onClick={handleBestDecrease}
disabled={!buttonActive}
className="btnPrev"
>
이전
</button>
<button
type="button"
onClick={handleBestIncrease}
disabled={buttonActive}
className="btnNext"
>
다음
</button>
</div>
</>
);
};
export default Best;
코드에 대해서 간략하진 않지만 설명해보면 slide 오픈 소스 API를 사용하지 않고 버튼을 눌렀을 때 슬라이드가 4개씩 넘어가게 만들어야 했다.
그래서 사용한 hook은 useEffect, useState, useRef 이렇게 사용했다.
(+ 이 포스팅은 useRef를 다루는 곳이기 때문에 다른 설명들이 생략이 되는게 있을 수도 있다.)
우선 증가하는 count를 useState로 클릭시 데이터의 갯수 %(나누기) 4를 해서 올라가는 증감식이 갯수 나누기 4를 한 것과 같거나 크면 다시 카운트 값을 조정하고 버튼을 disabled 속성을 true 값으로 바꿔줬다.
여기서 useRef를 사용한 곳은 <li>
를 잡고 있는 <ul>
태그에 줬다. 그 이유는 클릭이 될때마다 translateX값을 유동적으로 움직여야 했기 때문이다.
next 버튼을 클릭하면 -> count + 1 -> count * ul의 너비 -> 한 리스트 화면씩 이동
이라는 아주 좋은 아웃풋이 나오게 된다.