리액트를 다루는 기술 + typescript 5장 1
5장 - ref: DOM 에 이름 달기 - Typescript
React 프로젝트에서 ref
를 사용하는 이유
-
ref
는 프로젝트 전역적으로 작동하지 않고, 컴포넌트 내부에서만 작동합니다. -
리액트 프로젝트 내부에서
DOM
에 이름을 다는 방법 추천 ->ref
-
id
프로퍼티는 유일해야 하기에,ref
를 써서 중복 문제를 미리 예방합니다!
예제 컴포넌트 생성 - Typescript Version
ValidationSample.css
.success {
background-color : lightgreen;
}
.failure {
background-color : lightcoral;
}
-
이 파일을 불러오는 컴포넌트 내부에서
className
이
.success
or.failure
이라면, 정해진 색깔로 배경을 변경합니다.
ValidationSample.tsx
(...)
import { Component } from 'react';
// 위에서 작성한 css 파일 가져옴
import './ValidationSample.css';
// state 타입 지정을 위한 인터페이스
interface StateIFace{
password : string;
clicked : boolean;
validated : boolean;
}
class ValidationSample extends Component {
state : StateIFace = {
password : '',
clicked : false,
validated : false,
}
handleChanged = (e : React.ChangeEvent<HTMLInputElement>) => {
this.setState({
password : e.target.value
});
}
// 클릭했으므로 clicked : true 하고,
// password가 '0000'과 같은지 확인한다.
handleButtonClick = () => {
this.setState({
clicked : true,
validated : this.state.password === '0000'
})
}
render() {
return (
<div>
<input
type="password"
value={this.state.password}
onChange={this.handleChange}
className={ (this.state.clicked) ?
(this.state.validated ? 'success' : 'failure'): ''
}
/>
<button onClick={this.handleButtonClick}>검증하기</button>
</div>
)
}
}
export default ValidationSample;
-
<input/>
의 속성부터 살펴 보도록 하겠습니다! -
className
:
this.state.clicked ? (this.state.validated ? 'success' : 'failure') : ''
하나씩 떼서 보도록 하겠습니다.this.state.clicked
:state.clicked
의 값은true
orfalse
값을 가지게 됩니다(인터페이스).this.state.validated
:state.validated
의 값도true
orfalse
의 값을 가집니다.- (
true or false
) ? (true 일 때 실행
) : (false 일 때 실행
) 형식을 가졌습니다.
-
메서드 설명 :
clicked
가false
라면className
은''
가 됩니다. 하지만,
clicked
가true
라면(this.state.validated ? 'success' : 'failure')
를 실행하게 됩니다.
clicked
:true
인 상황에서,validated
가true
혹은false
이냐에 따라
className
:success
orfailure
가 됩니다. -
바로 다음 예제에서
react
의ref
속성을 이용해DOM
을 접근 해 보겠습니다.
React
의 ref
속성을 이용하기
ValidationSample.tsx
import React, {createRef} from 'react';
import './ValidationSample.css';
import { Component } from "react";
class ValidationSample extends Component {
// HTMLInputElement 인터페이스를 적용하여
// 접근하는 'DOM' 객체가 <input>라는 것을 typescript에게 알립니다.
input = createRef<HTMLInputElement>();
state = {
password : '',
clicked : false,
validated : false
}
handleChanged = (e : React.ChangeEvent<HTMLInputElement>) => {
this.setState({
password : e.target.value
})
}
handleButtonClicked = () => {
this.setState({
clicked : true,
validated : this.state.password === '0000'
})
// input을 선언했지만, 해당 객체가 null일 가능성이 있어 "?"로 Typescript에 알려줍니다.
this.input.current?.focus();
}
render() {
return (
<div>
<input
type='password'
value={this.state.password}
onChange={this.handleChanged}
className={this.state.clicked ? (this.state.validated ? 'success' : 'failure') : ''}
ref={this.input}
/>
<button onClick={this.handleButtonClicked}>검증하기</button>
</div>
);
}
}
export default ValidationSample;
컴포넌트에 ref
달기 - Typescript
-
컴포넌트에도
ref
를 달 수 있습니다. -
상위 컴포넌트에서 하위 컴포넌트의
DOM
을 사용해야 할 때 쓸 수 있습니다. -
컴포넌트에
ref
를 다는 방법은DOM
에ref
를 다는 것과 동일합니다. -
다만,
Typescript
에서는 추후 배울 리덕스를 이용하여
굳이 DOM을 건드릴 상황을 만들지 않는 것이 좋지 않을까 생각합니다.
ScrollBox.tsx
import React, { CSSProperties } from 'react'
import { Component } from 'react'
class ScrollBox extends Component{
// JSX 작성 시, 상위 <div> 에서 this.box 선언 시
// 컴포넌트 내부에서 box 변수를 찾을 수 없어 따로 선언해 주어야 합니다.
// null로 "this.box"를 초기화 해줍니다!
box: HTMLDivElement | null = null;
render() {
// 1. 에서 설명
const style : CSSProperties = {
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;
JSX
문법 작성 시,(ref)
에 타입이 설정되지 않은 이유는,ref={}
내부에서(ref)
작성 시 자동으로
HTMLDivElement | null
로 인식하기 때문입니다.
-
const style : CSSProperties
로 따로 타입을 설정한 이유는,
position : 'relative'
속성 선언 시 오류가 뜨므로,
CSSProperties
를 선언해 확실하게 만들어 줍니다.
컴포넌트 내부 메서드를 ref
를 통해 사용하기 - Typescript
DOM
노드가 가진 고유의 값이 있습니다
scrollTop
: 세로 스크롤바 위치(0 ~ 350)
scrollHeight
: 스크롤이 있는 박스 안의 div 높이(650)
clientHeight
: 스크롤이 있는 박스의 높이(300)
....
: 등등의 많은 속성
- 스크롤을 맨 아래로 내리기 위해
scrollHeight - clientHeight
합니다.
ScrollBox.tsx
import React, { createRef, CSSProperties } from "react";
import { Component } from "react";
class ScrollBox extends Component{
// HTMLDivElement 인터페이스를 적용하여
// scrollToBottom 메서드에서 this.box를 참조할 수 있게 만듭니다.
box = createRef<HTMLDivElement>();
scrollToBottom = () => {
// this.box.current 를 선언해야 직접적인 속성에 접근 할 수 있습니다.
// 1. 에서 자세히 설명
const {scrollHeight, clientHeight} = this.box.current as {scrollHeight : number, clientHeight : number};
// 2. 에서 자세히 설명
if(this.box.current) {
this.box.current.scrollTop = scrollHeight - clientHeight
};
}
render() {
const style : CSSProperties = {
border : '1px solid black',
width : '300px',
height : '300px',
overflow : 'auto',
position : 'relative'
}
const innerStyle = {
width : '100%',
height : '650px',
background : 'linear-gradient(white, black)'
}
// ref={(ref) => this.box = ref}에서 변형
// Typescript에서는 먹히지가 않습니다..
return(
<div
style={style}
ref={this.box}
>
<div style={innerStyle}/>
</div>
)
}
}
export default ScrollBox;
-
this.box.current
선언 시,HTMLDivElement | null
이 따라옵니다..current
사용 시,ref
로 선언 된 컴포넌트가 현재 존재하는지 모르기 때문입니다.- 따라서,
as {scrollHeight : number, clientHeight : number}
하여
current
가null
이 아니라는 것을 명시합니다. -Typescript
이기 때문.
-
if(this.box.current)
가 선언된 것도 같은 맥락입니다.this.box.current
가null
이라면,scrollTop
을 설정하는 것도 의미 없는 것이 됩니다.- 이런 오류를
Typescript
가 미리 체크하기 때문에, 에러를 없애주기 위해
이처럼 선언 해 줍니다.
이번엔 상위 컴포넌트에서 ref
를 이용하여 조작해 보겠습니다
App.tsx
import React from "react";
import { Component } from "react";
import ScrollBox from "./ScrollBox5_2";
class App extends Component{
scrollBox : ScrollBox | null = null;
render() : JSX.Element{
return (
<div>
<ScrollBox ref = {(ref) => this.scrollBox = ref}/>
<button onClick={() => this.scrollBox?.scrollToBottom()}>
맨 밑으로
</button>
</div>
);
}
}
export default App;
-
<ScrollBox>
내부에서ref
선언 시HTMLDivElement | null
가 아닌
ScrollBox | null
로 인식됩니다. -
이에 따라
ref
를null
로 초기화 해줍니다. -
button
내부의onClick
메서드에this.scrollBox?
로서?
가 들어간 이유는
모든 메서드 및 JSX 문법이 동시에 실행된다고 가정되었을 떄, 당장 존재하지 않을 수도 있기 때문입니다. -
이는
Javascript
,Typescript
가 가지고 있는 비동기 특성 때문입니다. -
?
를 넣어 이 변수가null
로서 존재 할 수도 있다는 것을Typescript
에 알려줍니다!
결과물
정리
나중에 리덕스 써서 한꺼번에 조정합시다..
근데 리덕스를 Type 적용하려면 엄청난 양의 코드가 추가 될 예정입니다 ㅎ하ㅏ하하