안녕하세요! delay100입니다. 이번 포스팅에서는 ref에 대해 공부해봅시다.
대부분의 설명은 주석으로 달아놓았으니 코드에 대한 설명은 주석을 확인해주세요!
책 리액트를 다루는 기술, 개정판의 5장 내용을 다루고 있습니다.
이번 포스팅의 Github 링크
https://github.com/delay-100/study-react/tree/main/ch5/hello-react
1. ref(reference)?
ref(reference)란?
- 리액트 프로젝트 내부에서 DOM에 이름을 다는 방법(HTML에서 id를 사용하여 DOM에 이름을 다는 것과 유사)
- DOM을 꼭 직접적으로 건드려야할 때 사용함
- 간단한 ref 예시
// id가 root인 요소에 리액트 컴포넌트를 렌더링하는 코드
ReactDOM.render(<App />, document.getElementById('root'));
리액트 컴포넌트 안에도 id를 사용할 수 있지만, 사용을 권장하지 않습니다. 아래의 예시는 사용을 권장하지 않는 이유이자, 잘못된 사용의 예시입니다.
잘못된 사용 EXAMPLE)
같은 컴포넌트를 여러 번 사용해야한다고 가정해봅시다.
이 때, HTML에서 DOM의 id는 유일(unique)해야 하는데 중복 id를 가진 DOM이 여러 개 생기게 됩니다.
대신, DOM을 꼭 사용해야 하는 상황이 있습니다.
- 특정 input에 포커스 주기
- 스크롤 박스 조작하기
- Canvas 요소에 그림 그리기
등등이 있습니다. 이 떄는 DOM에 직접적으로 접근해야하는데, 이 때 ref를 사용합니다. 위의 상황에서는 state만으로 해결하기 어려운 기능들입니다.
이번 장에서는 클래스형 컴포넌트에서 ref를 사용하는 방법을 공부해봅시다. 추후에 8장에서 함수 컴포넌트에서 ref를 사용하는 방법을 다루게 됩니다.(함수 컴포넌트는 Hooks를 다뤄야하기 때문에 나중에 공부합니다.)
2. DOM의 요소에 ref 달기
DOM의 요소 중 하나인 input에 ref를 달아보겠습니다. 아래의 예시에서 2-1과 2-2는 모두 input에 ref를 달았습니다.
2-1. 콜백 함수 이용
ref를 만드는 가장 기본적인 방법인 콜백 함수를 사용해봅시다.
ref를 달고자 하는 요소에 ref라는 콜백 함수(제일 처음의 ref, 아래에서는 inputRef)를 props로 전달하면 됩니다. 이 콜백 함수는 ref 값을 파라미터로 전달받습니다. 그리고 함수 내부에서 파라미터로 받은 ref를 컴포넌트의 멤버 변수로 설정해줍니다.
- 간단한 콜백 함수 예시
<input ref={(ref) => {this.input=ref}} />
이 콜백 함수를 사용하면 this.input은 input 요소의 DOM을 가리킵니다. 그리고 ref의 이름인 this.input은 원하는 것으로 자유롭게 지정할 수 있습니다. (DOM 타입에 관계없이 this.superman=ref 처럼 마음대로 지정가능)
책에 이렇게 적혀있는데, 무슨 말인지 잘 이해가 가질 않더라구요..
이해를 하기위해 검색하니 아래의 내용을 찾을 수 있었습니다.
출처 및 자세한 추가설명: https://ko.reactjs.org/docs/refs-and-the-dom.html
- 콜백 함수를 이용한 ref 적용 예시 - src/ValidationSample.js
import { Component } from "react";
import "./ValidationSample.css";
class ValidationSample extends Component {
state = {
password: "",
clicked: false,
validated: false,
};
// state의 password 값을 업데이트함
handleChange = (e) => {
this.setState({
password: e.target.value,
});
};
// clicked의 값을 참으로 설정, validated 값을 검증 결과로 설정
handleButtonClick = () => {
this.setState({
clicked: true,
validated: this.state.password === "0000",
});
this.inputfocus.focus(); // 버튼 클릭 시 input으로 focus가 이동하도록 함
};
render() {
return (
<div>
<input
ref={(ref) => (this.inputfocus = ref)} // 콜백 함수를 사용하여 ValidationSample 컴포넌트에 ref를 달아줌, this.input이 컴포넌트 내부의 input 요소를 가리키고 있음 => 일반 dom 다루듯 코드를 작성하면
type="password"
value={this.state.password}
onChange={this.handleChange} // onChange 이벤트가 발생하면 handleChange를 호출
className={
// className값: button 누르기 전 - 비어 있는 문자열 전달, button 누른 후 - 검증 결과에 따라 success 또는 failure 값 설정
// success인 경우 input 색상이 초록색, failure인 경우 input 색상이 빨간색으로 변함
this.state.clicked
? this.state.validated
? "success"
: "failure"
: ""
}
/>
<button onClick={this.handleButtonClick}>검증하기</button>
{/* onClilck 이벤트가 발생하면 handleButtonClick을 호출*/}
</div>
);
}
}
export default ValidationSample;
- 콜백 함수를 이용한 ref 적용 예시 - src/ValidationSample.css
.success {
background-color: lightgreen;
}
.failure {
background-color: lightcoral;
}
- 콜백 함수를 이용한 ref 적용 예시 - src/App.js
import { Component } from "react";
import ValidationSample from "./ValidationSample";
class App extends Component {
render() {
return <ValidationSample />;
}
}
export default App;
2-2. createRef 이용
리액트에 내장되어 있는 createRef 함수를 이용해 ref를 만들어봅시다. 이 함수를 사용하면 2-1보다 적은 코드로 만들 수 있습니다. (이 기능은 리액트 v16.3부터 사용 가능합니다.)
- 간단한 createRef 예시
import { Component } from 'react';
class RefSample extends Component {
input = React.createRef(); // createRef를 사용하여 ref를 만들려면 컴포넌트 내부에서 멤버 변수로 React.createRef()를 담아주어야 합니다.
handleFocus = () => {
this.input.current.focus(); // 설정한 뒤 나중에 ref를 설정해 준 DOM에 접근하려면 this.input.current를 조회하면 됩니다.
// 콜백함수와 다른 점은 .current를 넣어주어야 합니다.
}
render() {
return (
<div>
<input ref={thisinput} /> {/* 그리고 해당 멤버 변수를 ref를 달고자하는 요소에 ref props로 넣어주면 ref 설정이 완료됩니다. */}
</div>
)
}
}
export default RefSample;
3. 컴포넌트에 ref 달기
이 방법은 주로 컴포넌트 내부에 있는 DOM을 컴포넌트 외부에서 사용할 때 씁니다. 컴포넌트에 ref를 다는 방법은 DOM에 ref를 다는 방법과 동일합니다.
- 컴포넌트에 ref 달기 예시
<MyComponent
ref={(ref) => {this.myComponent=ref}} // MyComponent 내부의 메서드 및 멤버 변수에도 접근 가능해짐. 즉, 내부의 ref에 접근 가능(ex.myComponent.handleClick, myComponent.input 등)
/>
- 컴포넌트에 ref를 달고, 스크롤바 적용 예시 - src/ScrollBox.js
import { Component } from "react";
class ScrollBox extends Component {
scrollToBottom = () => {
const { scrollHeight, clientHeight } = this.box;
/* 앞 코드에는 비구조화 할당 문법을 사용했습니다.
다음 코드와 같은 의미입니다.
const scrollHeight = this.box.scrollHeight;
const clientHeight = this.box.clientHeight;
*/
this.box.scrollTop = scrollHeight - clientHeight;
};
render() {
const style = {
border: "1px solid black",
height: "300px",
width: "300px",
overflow: "auto",
position: "relative",
};
const innerStyle = {
width: "100%",
height: "650px",
background: "linear-gradient(white, black)",
};
return (
<div
style={style}
ref={(ref) => {
this.box = ref;
}}
>
<div style={innerStyle} />
</div>
);
}
}
export default ScrollBox;
- 컴포넌트에 ref를 달고, 스크롤바 적용 예시 - src/App.js
import { Component } from "react";
// import ValidationSample from "./ValidationSample";
import ScrollBox from "./ScrollBox";
class App extends Component {
render() {
// return <ValidationSample />;
return (
<div>
<ScrollBox ref={(ref) => (this.scrollBox = ref)} />
<button onClick={() => this.scrollBox.scrollToBottom()}>
{" "}
{/* 컴포넌트가 처음 렌더링될 때는 this.scrollBox 값이 undefined이므로 this.scrollBox.scrollToBottom 값을 읽어 오는 과정에서 오류가 발생
-> 화살표 함수를 만들어서 그 내부에서 this.scrollBox.scrollToBottom 메서드를 실행하면 버튼을 누를 때 (이미 한번 렌더링을 했기 때문에 - this.scrollBox를 설정한 시점) 오류가 발생하지 않음 => 그래서 화살표함수를 만들어준 것임*/}
{/* onClick = {this.scrollBox.scrollToBottom() 해도 문법상 문제는 없음*/}
맨 밑으로
</button>
</div>
);
}
}
export default App;
ref라는 개념을 이해하기 위해서는 Component의 props에 대한 이해가 필수라는 것을 꺠달았습니다..ㅠㅠ
또한 책 마지막 정리에 적혀있듯, 컴포넌트에서 다른 컴포넌트로 데이터를 교류할 때는 ref를 사용하지 않고, 컴포넌트 내부에서 DOM에 직접 접근해야할때 ref를 사용합니다. ref를 사용하기 전에 ref를 사용하지 않아도 원하는 기능을 구현할 수 있느지 반드시 고려해야합니다.
컴포넌트끼리 데이터를 교류할 때는 데이터를 부모 <-> 자식 흐름으로 교류해야 하는데, 추후에 리덕스 혹은 Context API 등 효율적으로 교류하는 방법을 배운다고 하니 우선은,, 이해만 하는 것으로 넘어가보려 합니다...
위에 간단한 콜백 함수 예시에서 막혀서 고생을 좀 했지만ㅠㅠ 계속 props에 대해 되돌아보고, react 스승님께 질문을 엄청 하면서 결국 조금이나마 이해를 했습니다..!
그래도 오늘 하루도 알차게 공부했습니당! 내일도 열심히 달려보겠습니다!
리액트 생각보다 쉽지 않을지도..?
읽어주셔서 감사합니다. 잘못된 정보는 댓글로 알려주세요!
'Study > React' 카테고리의 다른 글
7. Lifecycle method(라이프사이클 메서드) (0) | 2022.07.14 |
---|---|
6. map (0) | 2022.07.13 |
4. 이벤트 핸들링 (0) | 2022.07.11 |
3. Component(컴포넌트) (0) | 2022.07.10 |
2. JSX (0) | 2022.07.05 |