상호작용성 더하기
Description: 클릭이나 숫자변경과 같은 상호작용 발생시 컴포넌트를 다루는 기술에 대한 글입니다.
현재 노트: KR-010.20 b
상위 분류: KR-010.20 React
https://ko.react.dev/learn/adding-interactivity#whats-next
챕터의 목차
이벤트에 대한 응답
onClick과 같은 이벤트에 대해
handle
이름이 붙은 함수를 통해 상호작용하기
State: 컴포넌트의 메모리
일반적인 지역변수의 한계를 극복하며 컴포넌트 단위로 상태를 관리하는
useState
렌더링과 반영
주문을 받는 Trigger, React DOM에 그리는 Rendering, 그리고 실제 반영되는 Commit
snapshot으로서의 state
state의 경우 그 때 당시의 UI snapshot형태로 작동한다. 그래서 일반적으로 해당 변수는 변하지 않는 것을 기준으로 처리를 하게 된다
state 업데이트를 연속으로 대기열에 추가하기
연속으로 처리를 하기위해서는 큐에 추가하는 방식으로 그 당시의 변수를 사용하는 snapshot방식에서 연속 업데이트가 가능하다
state 내 객체 업데이트
객체는 spread방식을 통해 업데이트해야한다
state 내 배열 업데이트
객체와 동일하게 얕은 복사방식이기 떄문에 spred방식이나 map, filter를 통해 복사한 후 업데이트해야한다.
이벤트 핸들러 함수 특징
이벤트 핸들러 함수는 다음 특징을 가집니다.
- 주로 컴포넌트 _내부_에서 정의됩니다.
handle
로 시작하고 그 뒤에 이벤트명을 붙인 함수명을 가집니다.
이벤트 핸들러 props 이름 규칙
관습적으로 이벤트 핸들러 prop의 이름은
on
으로 시작하여 대문자 영문으로 이어집니다.
그 예시로,Button
컴포넌트의onClick
prop은onSmash
라는 이름으로 호출할 수도 있습니다.
이벤트핸들러와 HTML 태그가 적절히 선택됐는지 고려할 필요가있다
이벤트 핸들러에 적절한 HTML 태그를 사용하고 있는지 확인하세요. 예를 들어 클릭을 처리하기 위해서는
div onClick={handleClick}
대신button onClick={handleClick}
을 사용하세요. 실제 브라우저에서button
은 키보드 내비게이션과 같은 빌트인 브라우저 동작을 활성화 합니다. 만일 버튼의 기본 브라우저 스타일링이 싫어서 링크나 다른 UI 요소처럼 보이도록 하고 싶다면 CSS를 통해 그 목적을 이룰 수 있습니다. 접근성을 위한 마크업 작성법에 대해 더 알아보세요.
이벤트가 전파돈다는 의미(버블)
이벤트 핸들러는 해당 컴포넌트가 가진 어떤 자식 컴포넌트의 이벤트를 수신할 수도 있습니다. 이를 이벤트가 트리를 따라 “bubble” 되거나 “전파된다”고 표현합니다. 이때 이벤트는 발생한 지점에서 시작하여 트리를 따라 위로 전달됩니다.
주의부여된 JSX 태그 내에서만 실행되는onScroll
을 제외한 React 내의 모든 이벤트는 전파됩니다.
이벤트가 부모 컴포넌트로 전파되지 않는 것을 원할 떄는 stopPropagation사용
전파 멈추기
이벤트 핸들러는 이벤트 오브젝트를 유일한 매개변수로 받습니다. 관습을 따르자면 “event”를 의미하는 e로 호출되는 것이 일반적입니다. 이 오브젝트를 이벤트의 정보를 읽어들이는데 사용할 수 있습니다
이러한 이벤트 오브젝트는 전파를 멈출 수 있게 해줍니다. 이벤트가 부모 컴포넌트에 닿지 못하도록 막으려면 아래 Button 컴포넌트와 같이e.stopPropagation()
를 호출합니다.
이벤트 핸들러는 사이드 이펙트를 유발할 수 있는 대표적인 위치이다
함수를 렌더링하는 것과 다르게 이벤트 핸들러는 순수할 필요가 없기에 무언가를 변경하는데 최적의 위치입니다. 예를 들어 타이핑에 반응해 입력 값을 수정하거나, 버튼 입력에 따라 리스트를 변경할 때 적절합니다. 그러나 일부 정보를 수정하기 위해서는 먼저 그 정보를 저장하기 위한 수단이 필요합니다. React에서는 컴포넌트의 기억 역할을 하는 state를 이용할 수 있습니다. 해당 기능의 모든 것에 대해 다음 페이지에서 배울 것입니다.
훅인란? 에대한 정의 2줄로 정리하기
훅은 React가 오직 렌더링 중일 때만 사용할 수 있는 특별한 함수입니다. (이에 대해서는 다음 페이지에서 자세히 알아보겠습니다) 이를 통해 다양한 React 기능을 “연결”할 수 있습니다.
useState가 값을 업데이트하는 방식은 마법이이아니란 단순한 배열사용
다음과 같이 글로벌 배열 훅에 글로번 인덱스 변수를 통해 업데이트 하는 방식
let componentHooks = [];
let currentHookIndex = 0;
// How useState works inside React (simplified).
function useState(initialState) {
let pair = componentHooks[currentHookIndex];
if (pair) {
// This is not the first render,
// so the state pair already exists.
// Return it and prepare for next Hook call.
currentHookIndex++;
return pair;
}
// This is the first time we're rendering,
// so create a state pair and store it.
pair = [initialState, setState];
function setState(nextState) {
// When the user requests a state change,
// put the new value into the pair.
pair[0] = nextState;
updateDOM();
}
// Store the pair for future renders
// and prepare for the next Hook call.
componentHooks[currentHookIndex] = pair;
currentHookIndex++;
return pair;
}
지역변수와는 다른 state의 가장 큰 장점은 컴포넌트별 격리
또한 Page 컴포넌트가 Gallery의 state에 대해 아무것도 “알지” 않는다는 점과 심지어 그것이 있는지도 모른다는 것에 주목하세요. Props와 달리, state는 선언한 컴포넌트에 완전히 비공개입니다. 부모 컴포넌트는 이를 변경할 수 없습니다. 이로써 다른 컴포넌트에 영향을 미치지 않고 어떤 컴포넌트에든 state를 추가하거나 제거할 수 있게 됩니다.
일반 변수가 잘 작동되면 state를 도입할 필요가 없습니다
컴포넌트가 다시 렌더링 될 때만 정보를 유지하기 위해 state 변수가 필요합니다. 단일 이벤트 핸들러 내에서는 일반 변수가 잘 작동합니다. 일반 변수가 잘 동작할 때 state 변수를 도입하지 마세요.
import { useState } from 'react';
export default function FeedbackForm() {
const [name, setName] = useState(''); <-필요없음
function handleClick() {
setName(prompt('What is your name?'));
alert(`Hello, ${name}!`);
}
return (
<button onClick={handleClick}>
Greet
</button>
);
}
초기렌더링시 일반적으로 숨겨져있지만, 명시해서 가능
import Image from './Image.js';
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'))
root.render(<Image />);
혹시 나중에 사용될지 모르는 용어 브라우저 렌더링인 페인팅
렌더링이 완료되고 React가 DOM을 업데이트한 후 브라우저는 화면을 다시 그립니다. 이 단계를 “브라우저 렌더링”이라고 하지만 이 문서의 나머지 부분에서 혼동을 피하고자 “페인팅”이라고 부를 것입니다.
렌더링이랑 그 시점 당시의 UI 스냅샷
“렌더링”이란 React가 컴포넌트, 즉 함수를 호출한다는 뜻입니다. 해당 함수에서 반환하는 JSX는 시간상 UI의 스냅 샷과 같습니다. prop, 이벤트 핸들러, 로컬 변수는 모두 렌더링 당시의 state를 사용해 계산됩니다.
다음의 코드 이해하면 스냅샷이 무엇인지 이해하기 쉽습니다.
과연 표시되는 숫자는 몇일까요?
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>
</>
)
}
큐에 넣어 한번의 렌더링동자겡 값을 업데이트하기
해당 값들의 결과 어떻게 렌더링값이 나올까요?
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 5);
setNumber(n => n + 1);
setNumber(42);
}}>Increase the number</button>
</>
)
}
업데이트 인수의 이름도 명명규칙이 있습니다.
명명 규칙
업데이터 함수 인수의 이름은 해당 state 변수의 첫 글자로 지정하는 것이 일반적입니다.
setEnabled(e => !e);
setLastName(ln => ln.reverse());
setFriendCount(fc => fc * 2);
좀 더 자세한 코드를 선호하는 경우 setEnabled(enabled => !enabled)와 같이 전체 state 변수 이름을 반복하거나, setEnabled(prevEnabled => !prevEnabled)와 같은 접두사를 사용하는 것이 널리 사용되는 규칙입니다.
state Batch에 대해 이해하기
다음 코드에 대해 2가지 설정을 변경 가능 하다면 state batch작업에 대해 조금 이해함
- pending이 제대로 3초후 -1이 되도록 설정
- 빠르게 2번 눌러도 completed가 정상적으로 동작하도록 설정
import { useState } from 'react';
export default function RequestTracker() {
const [pending, setPending] = useState(0);
const [completed, setCompleted] = useState(0);
async function handleClick() {
setPending(pending + 1);
await delay(3000);
setPending(pending - 1);
setCompleted(completed + 1);
}
return (
<>
<h3>
Pending: {pending}
</h3>
<h3>
Completed: {completed}
</h3>
<button onClick={handleClick}>
Buy
</button>
</>
);
}
function delay(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
객체들은 사실 중첩되어 있지 않다
중첩된 것처럼 보이는 객체일지라도, 사실은 서로다른 객체이며 이를 가리킴으로서 중첩된것처럼동작함
배열도 함부로 업데이트하거나 조작해서는 안된다
JavaScript에서 배열은 다른 종류의 객체입니다. 객체와 마찬가지로 React state에서 배열은 읽기 전용으로 처리해야 합니다. 즉 arr[0] = 'bird'처럼 배열 내부의 항목을 재할당해서는 안 되며 push()나 pop()같은 함수로 배열을 변경해서는 안됩니다.