React 양방향 useState / 단방향 REF 데이터 흐름 사용하기
1. React 리렌더링
React 리랜더링이 되는 상황
1) 컴포넌트의 state가 변경되었을 때
2) 컴포넌트가 상속받은 props가 변경되었을 때
3) 부모 컴포넌트가 리렌더링이 된 경우 자식 컴포넌트는 모두 리렌더링
2. 양방향 데이터 바인딩 구현
state 를양방향 가져오기
: 서버로 붙어 가져온 정보를 인풋으로 저장하고 ->
변하된 것을 반영하기 위해 양방향 처리 해줘야한다.
- AJAX로 데이터 가져오기: 서버에서 데이터를 받아와 state에 저장.
- 양방향 바인딩: input 필드와 state를 연결하여 양방향 데이터 바인딩을 구현.
- onChange 이벤트 처리: 입력 값이 변경되면 state를 업데이트하여 실시간 반영.
- 폼 검증: 사용자가 올바른 값을 입력했는지 확인.
- state의 객체를 변경할 때는 deep copy 후 변경된 내용을 setState함수를 통해 변경
3. React에서 map() 함수 활용
- map()함수
: 반복되는 컴포넌트를 렌더링하기 위해서 자바스크립트 내장 함수인 map() 사용
배열.map(callback(currentValue, index, array)
,[this])
callback
currentValue : 현재 처리하고 있는 요소
index : 현재 처리하고 있는 요소의 index
array : 원본 배열
this : callback함수에서 사용할 this
key :
컴포넌트의 배열을 랜더링했을 때 어떤 원소에 변동이 있었는지 알아내기 위한 속성
key는 컴포넌트를 식별할 유일값으로 사용해야 한다. 없다면 index를 사용하기
const name =["눈사람" , "얼음", "바람", "봄", "꽃망울"];
const nameList =name.map((name, index) => <li key ={index}>{index}</li>);
return <ul>{nameList}</ul>;
3-1. 이벤트 함수와 map을 이용하여 간단한 목록관리 작성 해보기
json 데이터 map 을 이용해서 ui 나오게 작성 + state 이벤트로 양방향 까지(폼검증, 수정)
- 상태 관리: useState 훅을 사용하여 배열 names, 문자열 inputText, 그리고 숫자 nextId를 상태로 관리
- 이벤트 처리: 입력값을 처리하고, 버튼을 클릭하여 항목을 추가하거나 삭제하는 함수 작성
- 렌더링: map()을 사용하여 names 배열을 순회하면서 li 항목들을 만들어 화면에 표시
- 상태 초기화
: useState를 사용하여 상태를 초기화합니다. 세 가지 상태(names, inputText, nextId)를 관리
const [names, setNames] = useState([
{ id: 1, text: "눈사람" },
{ id: 2, text: "얼음" },
{ id: 3, text: "바람" },
{ id: 4, text: "봄" },
]);
const [inputText, setInputText] = useState(""); //사용자가 입력한 텍스트를 관리
const [nextId, setNextId] = useState(5); //다음에 추가할 항목의 id를 관리
- handleRemove 함수
: 항목이 클릭될 때마다 해당 항목의 id를 이용해 목록에서 삭제 하는 함수 작성
const handleRemove = (id) => {
const newNameList = names.filter((name) => id !== name.id);
setNames(newNameList);
};
- handleClick 함수
: "추가" 버튼을 클릭했을 때 새 이름을 목록에 추가
배열 concat():
기존의 배열을 직접 수정하지 않고 새로운 배열을 생성해서 반환!!
concat()은 원본 배열을 변경하지 않고 새 배열을 반환
const handleClick = () => {
const newNames = names.concat({ id: nextId, text: inputText }); // 새로운 객체 추가
setNames(newNames); // 상태 업데이트
setNextId(nextId + 1); // nextId 증가
setInputText(""); // 입력값 초기화
};
- handleChange 함수
: 텍스트 입력창에서 사용자가 입력하는 값을 상태로 관리
const handleChange = (e) => {
setInputText(e.target.value);
};
- map()을 사용
: map()을 사용하여 names 배열의 각 항목을 HTML li 요소로 변환
//names.map() => names 배열을 순회하여 각 항목을 <li> 태그로 변환
const nameList = names.map((name) => (
<li
key={name.id}
onClick={() => {
handleRemove(name.id);
}}
>
{name.text}
</li>
));
- return
return (
<div>
<input type="text" value={inputText} onChange={handleChange} />
<button onClick={handleClick}>추가</button>
<ul>{nameList}</ul>
</div>
);
=> 사용자가 텍스트 입력란에 값을 입력하고
"추가" 버튼을 클릭하면 해당 값이 목록에 추가되며, 각 항목을 클릭하면 삭제
4. React에서 ref 사용법
1.REF
: react에서 DOM요소에 직접 접근할 수 있도록 연결하는 기능
=> 양방향으로 처리하지 않고 단방향으로 가죠오기
React에서 단방향 데이터 흐름이란
상태(state)가 상위 컴포넌트에서 하위 컴포넌트로 흐르는 방식
이 흐름은 props를 통해 이루어 진다
그러나 ref는 이 흐름과는 별개로 DOM 요소에 직접 접근할 수 있게 해주는 방법
- Ref를 사용해야 할 때 !!
- 포커스, 텍스트 선택영역, 혹은 미디어의 재생을 관리할 때.
- 애니메이션을 직접적으로 실행시킬 때.
- 서드 파티 DOM 라이브러리를 React와 같이 사용할 때.
input 요소에 focus 주기
스크롤 박스 조작하기
canvas 요소에 그림 그리기
클래스형 컴포넌트
형식 1]
<요소 ref={ ref =>{this.input=ref}}/>
형식 2]
속성명 = React.createRef();
<요소 ref={this.속성명}/>
함수형
const 변수명 = useRef(null);
<요소 ref={변수명}/>
- 클래스형 컴포넌트로 ref 작성해보기
import React, { Component} from 'react'
export default class OneWayBind extends Component {
// ref는 React 컴포넌트의 DOM 요소나 클래스 인스턴스를 직접 참조하는 방법입니다.
// createRef()는 각 input 요소에 대한 참조를 생성합니다.
inputId = React.createRef(); // 아이디 입력 필드에 대한 ref 생성
inputPw = React.createRef(); // 비밀번호 입력 필드에 대한 ref 생성
// 버튼 클릭 시 호출되는 함수
handleClick = () => {
// ref를 사용하여 DOM 요소의 값에 접근합니다.
const id = this.inputId.current.value; // inputId에 해당하는 값 가져오기
const pw = this.inputPw.current.value; // inputPw에 해당하는 값 가져오기
// 콘솔에 id와 pw 값을 출력합니다.
console.log("id", id, "pw", pw);
// 입력 후 값 초기화 (input 요소의 value를 빈 문자열로 설정)
this.inputId.current.value = "";
this.inputPw.current.value = "";
};
render() {
return (
<div>
{/*
JSX에서 <label> 요소는 htmlFor 속성을 사용하여 연결해야 합니다.
기존의 HTML에서는 'for' 속성을 사용하지만, JSX에서는 'htmlFor'를 사용합니다.
*/}
<label htmlFor='id'>
아이디:
<input type="text" id="id" ref={this.inputId} /> {/* 아이디 입력란 */}
</label>
<label htmlFor='pw'>
비밀번호:
<input type="password" id="pw" ref={this.inputPw} /> {/* 비밀번호 입력란 */}
</label>
{/* 검증하기 버튼을 클릭하면 handleClick 함수가 호출됩니다. */}
<button onClick={this.handleClick}>검증하기</button>
</div>
);
}
}
- 함수형 컴포넌트로 ref 작성
: useRef 훅 사용
=> useRef는 함수형 컴포넌트에서 DOM 요소나 값을 참조하는 데 사용
import React, { useRef } from 'react';
const OneWayBind1 = () => {
// useRef는 React에서 DOM 요소나 값을 참조하기 위해 사용하는 Hook입니다.
// 여기서는 input 요소들을 참조하기 위해 사용합니다.
const inputId = useRef(null); // 아이디 입력 필드를 참조하는 ref
const inputPw = useRef(null); // 비밀번호 입력 필드를 참조하는 ref
// handleClick 함수는 버튼 클릭 시 실행됩니다.
const handleClick = () => {
// inputId.current.value를 통해 input 요소의 값을 가져옵니다.
const id = inputId.current.value; // 아이디 값 가져오기
const pw = inputPw.current.value; // 비밀번호 값 가져오기
console.log("id", id, "pw", pw); // 콘솔에 id와 pw 출력
// 값을 입력 후 초기화 (각각의 input 값 비우기)
inputId.current.value = ""; // 아이디 입력란 비우기
inputPw.current.value = ""; // 비밀번호 입력란 비우기
}
return (
<div>
{/* JSX에서 <label> 요소는 htmlFor 속성을 사용하여 input 요소와 연결해야 합니다.
'for' 대신 'htmlFor'를 사용해야 합니다. */}
<label htmlFor='id'>
아이디:
{/* ref는 input 요소에 대한 참조를 설정합니다. */}
<input type="text" id="id" ref={inputId} />
</label>
<label htmlFor='pw'>
비밀번호:
{/* ref를 사용하여 비밀번호 입력란에 대한 참조 설정 */}
<input type="password" id="pw" ref={inputPw} />
</label>
{/* 버튼 클릭 시 handleClick 함수 호출 */}
<button onClick={handleClick}>검증하기</button>
</div>
);
};
export default OneWayBind1;
=> useRef는 상태 변경 없이 DOM을 직접 조작할 수 있는 방법으로,
주로 폼 값 처리나 애니메이션, 타이머 등에 사용
함수형 컴포넌트와 클래스형 컴포넌트의 차이!!!!
- this:
- 클래스형 컴포넌트: this를 사용하여 상태(this.state)와 메서드(this.setState)에 접근해야 합니다.
- 함수형 컴포넌트: useState, useRef 등을 사용하여 상태를 관리하고, this를 사용할 필요가 없습니다.
- render():
- 클래스형 컴포넌트에서는 render() 메서드 내에서 JSX를 반환합니다.
- 함수형 컴포넌트에서는 return을 바로 사용하여 JSX를 반환합니다.
- 구조 분해 할당 위치:
- 클래스형 컴포넌트에서는 render() 메서드 내에서 this.state를 구조 분해 할당하여 상태를 사용해야 합니다.
- 함수형 컴포넌트에서는 useState 훅을 통해 얻은 data 객체를 바로 구조 분해 할당하여 사용합니다.
결론
- 클래스형 컴포넌트에서는 this를 사용하여 상태와 메서드에 접근해야 하기 때문에 render() 메서드 내에서 this.state를 사용해야 합니다.
- 함수형 컴포넌트에서는 this가 없으며, useState로 상태를 관리하고, 함수 내에서 직접 변수처럼 상태를 참조합니다.
React에서 함수형 컴포넌트를 처음 도입할 때,
상태 관리를 위한 기능이 부족했기 때문에 클래스형 컴포넌트만 사용
=> 클래스형 컴포넌트는 React의 초기 설계에 맞춰 객체지향적인 방식으로 상태를 다루게 되어 this를 사용
=> 함수형 컴포넌트의 장점을 살리기 위해 Hooks API를 도입
=> React가 도입한 **useState**와 useEffect 훅을 통해 상태와 라이프사이클을 관리
- input 요소에 focus 주기
validationSample.css
.success {
background-color: lightgreen;
}
.failure {
background-color: lightcoral;
}
validationSample.js
1. 클래스 컴포넌트
import React, { Component } from "react"; // React 라이브러리와 Component 모듈을 임포트
import "./ValidationSample.css"; // CSS 파일을 임포트하여 스타일 적용
class ValidationSample extends Component {
// ref를 사용하여 input 요소에 접근하기 위해 createRef()로 ref를 생성
inputId = React.createRef(); // 아이디 입력 필드의 ref
inputPw = React.createRef(); // 비밀번호 입력 필드의 ref
// 컴포넌트의 상태를 정의 (id, password, clicked, validated)
state = {
id: "", // id 입력 필드의 값
password: "", // password 입력 필드의 값
clicked: false, // '검증하기' 버튼이 클릭되었는지 여부
validated: false, // 입력 값이 유효한지 여부
};
// 입력 값 검증 함수
validation = () => {
// 비밀번호가 비어 있거나 8자리 미만인 경우
if (this.state.password === "" || this.state.password.length < 8) {
alert('비밀번호를 8자리 이상 입력하세요'); // 경고 메시지
this.inputPw.current.focus(); // 비밀번호 입력 필드에 focus
return false; // 유효하지 않음
}
// 아이디가 비어 있는 경우
if (this.state.id === "") {
alert("아이디를 입력하세요"); // 경고 메시지
this.inputId.current.focus(); // 아이디 입력 필드에 focus
return false; // 유효하지 않음
}
return true; // 모든 검증을 통과하면 true 반환
};
// 입력 값 변경을 처리하는 함수
handleChange = (e) => {
this.setState({
[e.target.id]: e.target.value, // 입력된 값을 state에 반영
});
};
// 검증하기 버튼 클릭 시 호출되는 함수
handleClick = () => {
// 검증을 수행하고 결과에 따라 상태 업데이트
this.setState({
clicked: true, // 버튼 클릭 상태를 true로 설정
validated: this.validation(), // validation 결과로 상태 업데이트
});
};
render() {
// 구조 분해 할당을 통해 상태 값을 가져옴
const { id, password, clicked, validated } = this.state;
return (
<div>
{/* JSX에서 label 태그의 'for' 속성은 'htmlFor'로 작성 */}
<label htmlFor="id">
아이디 :
{/* 입력 필드에 클래스 동적으로 추가 */}
<input
type="text"
className={clicked ? (validated ? "success" : "failure") : ""}
id="id"
ref={this.inputId} // ref를 통해 아이디 입력 필드에 접근
value={id} // 상태의 id 값을 입력 필드에 반영
onChange={this.handleChange} // 입력 값 변경 시 handleChange 호출
/>
</label>
<label htmlFor="password">
비밀번호 :
<input
type="password"
className={clicked ? (validated ? "success" : "failure") : ""}
id="password"
ref={this.inputPw} // ref를 통해 비밀번호 입력 필드에 접근
value={password} // 상태의 password 값을 입력 필드에 반영
onChange={this.handleChange} // 입력 값 변경 시 handleChange 호출
/>
</label>
{/* 검증하기 버튼 클릭 시 handleClick 호출 */}
<button onClick={this.handleClick}>검증하기</button>
</div>
);
}
}
export default ValidationSample; // 컴포넌트를 외부에서 사용할 수 있도록 export
2. 함수 컴포넌트
확인 포인트!!
- data 객체로 id, password, clicked, validated 상태를 관리
const [data, setData] = useState({
id: "", // 아이디 입력 값
password: "", // 비밀번호 입력 값
clicked: false, // '검증하기' 버튼 클릭 여부
validated: false, // 입력 값이 유효한지 여부
// 구조 분해 할당을 통해 상태 값 쉽게 사용
const { id, password, clicked, validated } = data;
- useState, useRef 등을 사용하여 상태를 관리하고, this를 사용하지 않음
- return을 바로 사용하여 JSX를 반환
=> 클래스 컴포넌트의 경우 render() 메서드 내에서 JSX를 반환
=> render() 메서드 내에서 this.state를 구조 분해 할당하여 상태를 사용!!!!!!!
import React, { useRef, useState } from 'react'; // React, useRef, useState 임포트
import "./ValidationSample.css"; // CSS 파일을 임포트하여 스타일 적용
const ValidationSample1 = () => {
// input 요소에 접근하기 위한 ref 생성
const inputId = useRef(null); // 아이디 입력 필드에 대한 ref
const inputPw = useRef(null); // 비밀번호 입력 필드에 대한 ref
// 상태를 관리하기 위해 useState를 사용
// data 객체로 id, password, clicked, validated 상태를 관리
const [data, setData] = useState({
id: "", // 아이디 입력 값
password: "", // 비밀번호 입력 값
clicked: false, // '검증하기' 버튼 클릭 여부
validated: false, // 입력 값이 유효한지 여부
});
// 입력 값 검증 함수
const validation = () => {
// 비밀번호가 비어 있거나 8자 미만일 경우
if (data.password === "" || data.password.length < 8) {
alert('비밀번호를 8자리 이상 입력하세요'); // 경고 메시지
inputPw.current.focus(); // 비밀번호 입력 필드에 focus
return false; // 유효하지 않음
}
// 아이디가 비어 있는 경우
if (data.id === "") {
alert("아이디를 입력하세요"); // 경고 메시지
inputId.current.focus(); // 아이디 입력 필드에 focus
return false; // 유효하지 않음
} else {
return true; // 검증 통과
}
};
// 입력 값 변경 함수 (양방향 데이터 바인딩)
const handleChange = (e) => {
setData({
...data, // 기존 상태를 유지하면서, 변경된 값만 갱신
[e.target.id]: e.target.value, // 입력된 값으로 상태 업데이트
});
};
// 검증하기 버튼 클릭 시 호출되는 함수
const handleClick = () => {
// 버튼 클릭 시 검증을 수행하고, 결과를 상태에 반영
setData({
...data,
clicked: true, // '검증하기' 버튼 클릭 상태
validated: validation(), // 검증 결과 저장
});
};
// 구조 분해 할당을 통해 상태 값 쉽게 사용
const { id, password, clicked, validated } = data;
return (
<div>
{/* 아이디 입력 필드 */}
<label htmlFor="id">
아이디 :
<input
type="text"
className={clicked ? (validated ? "success" : "failure") : ""}
// 검증 후 상태에 따라 클래스 변경
id="id"
ref={inputId} // ref를 사용하여 input에 직접 접근
value={id} // 상태 값으로 입력 필드의 값을 설정
onChange={handleChange} // 입력 값 변경 시 handleChange 호출
/>
</label>
{/* 비밀번호 입력 필드 */}
<label htmlFor="password">
비밀번호 :
<input
type="password"
className={clicked ? (validated ? "success" : "failure") : ""}
// 검증 후 상태에 따라 클래스 변경
id="password"
ref={inputPw} // ref를 사용하여 input에 직접 접근
value={password} // 상태 값으로 입력 필드의 값을 설정
onChange={handleChange} // 입력 값 변경 시 handleChange 호출
/>
</label>
{/* 검증하기 버튼 */}
<button onClick={handleClick}>검증하기</button>
</div>
);
};
export default ValidationSample1;
- 결과 확인
- 비밀번호가 8자리 이하 일때 알람
- 검증 되었을 때 포커스 및 색상 변화
- 스크롤 박스 조작하기
=> overflow 에 대한 이해!!!!
- overflow:
- overflow는 콘텐츠가 요소를 벗어날 때 어떻게 처리할지를 결정합니다. 예를 들어, 요소의 크기가 고정되어 있는데 그 안에 담을 수 있는 콘텐츠가 그보다 많으면 스크롤을 표시할 수 있도록 설정할 수 있습니다.
- CSS 속성 overflow: auto는 콘텐츠가 요소보다 클 때 자동으로 스크롤을 추가하는 역할을 합니다.
- clientHeight:
- 요소의 보이는 영역의 높이를 의미합니다.
- 예를 들어, div의 실제 높이는 300px일 수 있지만, 그 안의 콘텐츠가 너무 많아 스크롤이 생기면, 보이는 영역의 높이는 300px 그대로입니다.
- clientHeight는 요소의 전체 높이에서 스크롤바와 보더(border) 를 제외한 실제 보이는 높이를 말합니다.
- scrollHeight:
- 요소 내부의 전체 콘텐츠의 크기입니다.
- 콘텐츠가 div의 크기보다 클 때, 스크롤을 사용하여 나머지 콘텐츠를 볼 수 있게 됩니다.
- scrollHeight는 콘텐츠의 전체 높이입니다. 스크롤이 필요한 양을 말합니다.
- scrollTop:
- 현재 요소의 스크롤 위치입니다.
- scrollTop은 스크롤이 얼마나 내려갔는지를 의미하는데, 0이면 맨 위에 있다는 뜻입니다. 예를 들어, 스크롤을 맨 아래로 내리면 scrollTop 값은 scrollHeight - clientHeight 값이 됩니다.
- ref 부모 -> 자식 연결이 가능!!!!
app에 ref를 연결하여 활용할 수 있다.
app.js
import React, { useRef } from "react";
import ScrollBox from "./ScrollBox"; // ScrollBox 컴포넌트 가져오기
function App() {
const scrollBoxRef = useRef(null); // ref 생성
return (
<div className="App">
<ScrollBox ref={scrollBoxRef} /> {/* ScrollBox 컴포넌트 렌더링 */}
<button onClick={() => {
scrollBoxRef.current.scrollToBottom(); // 버튼 클릭 시 스크롤 맨 아래로 이동
}}>
맨 아래로
</button>
</div>
);
}
export default App;
scrollBox.js
import React, { Component } from 'react'
export default class ScrollBox extends Component {
// 부모 컴포넌트에서 호출하는 scrollToBottom 함수
scrollToBottom = () => {
const { scrollHeight, clientHeight } = this.box; // 콘텐츠 높이와 뷰포트 높이 가져오기
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: "1000px", // 콘텐츠 길이 설정
background: "linear-gradient(white, black)", // 그라디언트 배경
};
return (
<div
style={style}
ref={(ref) => { this.box = ref; }} // ref로 DOM에 접근
>
<div style={innerStyle}></div> {/* 스크롤이 가능한 콘텐츠 */}
</div>
);
}
}
- 결과물
- 맨 아래로 클릭 시