● ref는 어떤 상황에 사용해야할까?
- 직접적으로 DOM을 다뤄야할 때 사용해야한다. (즉 DOM을 참조해야할 때)
- 컴포넌트 인스턴스에 대한 참조를 해야할 때
- 컴포넌트 간의 참조를 공유해야할 때 사용한다.
왜 쓰는지에 대한 이유와, 실제 ref라는 것이 JavaScript에서 어떤 기능을 대체한 것인지에 대해서 생각해보니, 우리는 HTML에서 직접적으로 id를 추가해주고, 그 id를 가진 DOM 요소를 조작했었는데, 리액트에서는 그런 기능을 권장하지 않는다.
왜냐하면 리액트의 virtual DOM을 우회하는 것이기 때문에 권장하지 않는다는 것이다. 어차피 DOM 요소가 변한 부분이 있다면 virtual DOM으로 비교해서 필요한 부분만 렌더링 해주는데, 직접적으로 접근한다면 불필요한 렌더링이 생길 수 있기 때문이다.
따라서 ref를 사용하는 것인데, ref는 DOM에 직접적으로 접근하는 것이 아니다. DOM 요소를 참조를 생성하는 것이다.
⚬ 예제 컴포넌트
App.js
import { Component } from "react";
import ValidationSample from "./ValidationSample";
class App extends Component {
render() {
return (
<div>
<ValidationSample />
</div>
);
}
}
export default App;
ValidationSample.css
.success {
background-color: lightgreen;
}
.failure {
background-color: lightcoral;
}
ValidationSample.js
import React from "react";
import { Component } from "react";
import "./ValidationSample.css";
class ValidationSample extends Component {
state = {
password: "",
clicked: false,
validated: false,
};
handleChange = (e) => {
this.setState({ password: e.target.value });
};
handleClick = (e) => {
this.setState({
clicked: true,
validated: this.state.password === "0000",
});
};
render() {
return (
<div>
<input
type="text"
value={this.state.password}
onChange={this.handleChange}
className={
this.state.clicked
? this.state.validated
? "success"
: "failure"
: null
}
/>
<button onClick={this.handleClick}>검사하기</button>
</div>
);
}
}
export default ValidationSample;
⚬ 콜백 함수를 통한 ref 설정
ref를 만드는 가장 기본적인 방법은 콜백함수를 사용하는 것이다.
ref라는 콜백함수를 props로 전달해주면 된다. 콜백함수는 ref 값을 파라미터로 전달받는다. 그리고 함수 내부에서 파라미터로 받은 ref를 컴포넌트 멤버 변수로 설정해준다.
<input ref={(ref)=> { this.input = ref }} />
ref이름은 원하는 대로 설정할 수 있다. this.super = ref•••
⚬ React.createRef()를 통한 ref 설정
class ValidationSample extends Component {
input = React.createRef();
state = {
password: "",
clicked: false,
validated: false,
};
handleChange = (e) => {
this.setState({ password: e.target.value });
};
handleClick = (e) => {
this.setState({
clicked: true,
validated: this.state.password === "0000",
});
};
handleFocus = (e) => {
this.input.current.focus();
};
render() {
return (
<div>
<input
type="text"
value={this.state.password}
onChange={this.handleChange}
className={
this.state.clicked
? this.state.validated
? "success"
: "failure"
: null
}
ref={this.input}
/>
<button onClick={this.handleClick}>검사하기</button>
</div>
);
}
}
- createRef를 사용하여 컴포넌트 내부에서 멤버변수로 React.createRef()를 담아준다.
- 해당 멤버 변수를 달고자하는 요소에 ref props로 넣어주면 ref 설정이 완료된다.
- 설정 뒤 나중에 접근하려면 this.input.current를 조회하면 된다.
- 콜백함수와 다른 점은 current를 넣어주어야한다는 것이다.
위의 예제를 기반으로 input을 누르면 커서가 깜빡인다. 하지만 버튼을 누르면 커서가 깜빡이지 않는다.
버튼을 눌러도 input으로 input에서 커서가 깜박이도록 input에 포커싱을 해보자.
<input
type="text"
value={this.state.password}
onChange={this.handleChange}
className={
this.state.clicked ? (this.state.validated ? "success" : "failure") : null
}
ref={(ref) => {
this.input = ref;
}}
/>;
콜백함수로 ref를 설정한다.
그다음 handleClick 함수에 this.input.focus()를 추가해준다.
handleClick = (e) => {
this.setState({
clicked: true,
validated: this.state.password === "0000",
});
this.input.focus();
};
만약, React.createRef()로 활용했다면, this.input.current.focus()로 써줘야 했을 것이다.
버튼을 눌러도 커서가 깜빡인다 ㅎㅎ
⚬ 컴포넌트에 ref달기
<MyComponent ref={ (ref)=>{ this.mycomponent = ref } }
이렇게 하면 내부의 ref에도 접근할 수 있다.. (예시 : mycomponent.handleClick, mycomponent.input) 등.
⚬ 컴포넌트에 예제
App.js
import { Component } from "react";
import ScrollBox from "./ScrollBox";
class App extends Component {
render() {
return (
<div>
<ScrollBox />
</div>
);
}
}
export default App;
ScrollBox.js
import { Component } from "react";
class ScrollBox extends Component {
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;
위의 예제를 렌더링하면 아래같은 모습을 볼 수 이따.
ScrollBox.js
class ScrollBox extends Component {
scrollToBottom() {
const { scrollHeight, clientHeight } = this.box;
this.box.scrollTop = scrollHeight - clientHeight;
render()...
}
이렇게 적어주고..
App.js
import { Component } from "react";
import ScrollBox from "./ScrollBox";
class App extends Component {
render() {
return (
<div>
<ScrollBox
ref={(ref) => {
this.scrollBox = ref;
}}
/>
<button
onClick={() => {
this.scrollBox.scrollToBottom();
}}
>
맨밑으로
</button>
</div>
);
}
}
export default App;
app.js에서 컴포넌트 ref를 설정해주고, 버튼을 누르면 그 ref의 메서드를 사용할 수 있게 설정하면 된다••• 충격적..
● 정리
ref는 정말 써야하는 것인지 충분히 고려하고 써야한다. (DOM에 직접적으로 접근할 때니까) 하지만, 직접적으로 접근하는건 아니긴함. 참DOM 요소의 참조라고 생각하면 된다.
서로 다른 컴포넌트끼리 데이터를 교류할 때 ref를 사용한다면 이는 잘못 사용 하는 것입니다.!! 다른 컴포넌트에서 ref로 전달 받은 컴포넌트의 메서드를 실행하고.. 이런 방법은 리액트 사상에 어긋난 설계이다.