1. 배운 내용
리액트 시작
- 리액트는 View만 신경쓰는 라이브러리이다.
- 리액트는 Virtual DOM을 사용하여 이전 내용과 현재 내용을 비교하고, 바뀐 부분만 실제 DOM에 적용한다.
- create-react-app으로 간편하게 프로젝트 작업환경을 구축할 수 있다.
JSX
- JSX란 자바스크립트의 확장 문법이다. JSX를 통해 JI를 매우 편하게 렌더링할 수 있다.
- 다음과 같은 규칙을 가진다.
1) 컴포넌트에 여러 요소가 있다면 반드시 부모 요소 하나로 감싸야 한다.
2) JSX 안에서는 자바스크립트 표현식을 작성할 수 있으며 해당 코드를 {}로 감싸면 된다.
3) JSX 내부의 자바스크립트 표현식에서는 if문을 쓸 수 없다. 따라서 조건부 연산자(삼항 연산자)를 사용하거나 JSX 밖에서 if문을 써야 한다.
4) DOM 요소에 스타일 적용 시 객체 형태로 넣어주어야 하며, 카멜 표기법을 따라야 한다.
5) class 대신 className으로 설정해야 한다.
컴포넌트
- 클래스형 컴포넌트: state, 라이프사이클 기능, 임의 메서드 정의가 가능하다. render함수가 꼭 있어야 하고 그 안에서 보여줄 JSX를 반환한다.
- 함수형 컴포넌트: 선언하기 편하고, 메모리 자원도 덜 사용한다. state와 라이프사이클 API를 사용할 수 없지만, Hooks로 대체할 수 있다.
- props: 컴포넌트 속성을 설정할 때 사용하는 요소. props 값은 부모 컴포넌트에서 설정할 수 있으며, 자식 컴포넌트는 읽기 전용으로 받을 수 있다. children, 비구조화 할당 문법, propTypes에 대해서도 배움.
- state: 컴포넌트 내부에서 배뀔 수 있는 값. 클래스형 컴포넌트는 state로, 함수형 컴포넌트는 useState라는 함수로 사용할 수 있다.
- state 값을 바꿔야 할 때에는 setState 혹은 useState로부터 전달받은 세터 함수를 사용해야 한다. 배열이나 객체 업데이트 시에는 사본을 만들어야 한다.
이벤트 핸들링
- input 값을 state에 넣고, onChange, onClick 함수에서 setState를 실행하는 이벤트 핸들링을 실습.
- 여러개의 input 값을 관리할 때, 함수형 컴포넌트는 클래스형 컴포넌트와 달리, useState에서 form 객체를 사용할 수도 있다.
ref: DOM에 이름 달기
- ref는 DOM을 꼭 직접적으로 건드려야 할 때 사용한다. (예: 특정 input에 포커스 주기, 스크롤 박스 조작하기 등)
- ref를 사용하는 방법은 2가지이다. 콜백함수를 통한 ref 설정과 createRef를 통한 ref 설정.
- 리액트에서는 컴포넌트에 ref를 달 수도 있다.
컴포넌트 반복
- 자바스크립트 배열 객체의 내장함수인 map()함수를 사용하여, 리액트 프로젝트에서 반복적인 내용을 효율적으로 보여주고 관리할 수 있다. map 함수를 통해 배열 내 각 요소를 원하는 규칙에 따라 변환한 후 그 결과로 새로운 배열을 생성한다.
- map함수를 쓸 때에는 key 값을 설정해야 한다. key는 고유한 값이며, key를 통해 변화 감지를 더욱 효율적으로 할 수 있다.
- 상태 안에서 배열을 변형할 때는, 배열에 직접 접근하지 않고, 배열 내장 함수를 사용하여 새로운 배열을 만든 후 이를 새로운 상태로 설정해주어야 한다.
- 데이터 추가 기능시 concat 사용. 데이터 제거 기능시 filter 사용. 이 함수들을 통해 불변성 유지 할 수 있다.
컴포넌트의 라이프사이클 메서드
- 라이프사이클 메서드는 컴포넌트 상태에 변화가 있을 때마다 실행하는 메서드이다.
- 라이프사이클은 다음과 같이 나뉜다.
1) 마운트 (페이지에 컴포넌트가 나타날 때): constructor, getDerivedStateFromProps, render, componentDidMount
2) 업데이트 (컴포넌트 정보가 업데이트될 때): getDerivedStateFromProps, shouldComponentUpdate, render, getSnapshotBeforUpdate, componentDidUpdate
3) 언마운트 (페이지에서 컴포넌트가 사라질 때): componentWillUnmount
Hooks
- Hooks는 기존의 함수형 컴포넌트에서 할 수 없었던 다양한 작업들을 가능하게 한다.
- useState: 함수형 컴포넌트에서도 상태 관리를 가능하게 해준다. useState는 여러 번 사용할 수도 있다.
- useEffect: 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook. useEffect의 두 번째 파라미터에 값을 넣음으로써, 마운트될 때만 혹은 특정 값이 업데이트될 때만 실행하게 만들 수도 있다.
- useReducer: useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트하고 싶을 때 사용하는 Hook. 컴포넌트 업데이트 로직을 컴포넌트 바깥으로 빼낼 수 있다는 장점이 있다.
- useMemo: 함수형 컴포넌트 내부에서 발생하는 연산을 최적화할 수 있다. 렌더링하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하게 만들 수 있다.
- useCallback: 이벤트 핸들러 함수를 필요할 때만 생성하게 하여, 렌더링 성능을 최적화할 수 있다.
- useRef: 함수형 컴포넌트에서 ref를 쉽게 사용하도록 해준다.
- custom hooks: hook들과 일반함수, 값을 조합해서 새로운 hook을 만들어내는 것. 여러 컴포넌트에서 비슷한 기능을 공유할 경우, 커스텀 훅을 만들어 로직을 재사용할 수 있다.
2. 질문 공유와 해결
useRef()
-일반적인 사용방법
useRef() Hook은 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있도록 해주는 용도로 사용한다. 이 메서드를 사용하는 방법은 다음과 같다.
const ref = useRef(initialValue)
useRef는 .current 프로퍼티로 전달된 인자로 초기화된 변경 가능한 ref객체를 반환한다.
일반적인 사용용도는
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
와 같이 DOM의 특정 Element를 참조를 저장하기 위해 사용한다.
위 코드에서
<input ref={inputEl} type="text" />
를 실행하게 되면 input.current에는 input객체의 참조가 할당된다. 따라서
input.current.something()
과 같이 해당 DOM element에 접근 해줄 수 있는 것이다.
-다른 사용 방법
useRef()는 컴포넌트 내에서 로컬변수를 사용해야 할 때도 사용된다.
사용예를 보자면
import React,{useRef} from 'react';
const RefSample=()=>{
const id=useRef(1);
const setId=(n)=>{
id.current=n;
}
const printId=()=>{
console.log(id.current);
}
return(
<div>
refSample
</div>
);
};
export default RefSample
이렇게 코드를 작성하면
const id=useRef(1);
이렇게 초기화된 useRef의 파라메터1은 id.current에 할당된다.
이렇게 할당된 값은 2가지 특징을 가진다.
- 해당컴포넌트가 업데이트 되어도 값을 유지(full LifeTime)
- 해당값을 변경해도 컴포넌트가 다시 렌더링되지 않는다.
이러한 특징 때문에 컴포넌트 내부에서 컴포넌트가 재 랜더링이 되어도 컴포넌트의 모든 lifetime 동안 지속적으로 값을 유지하고 있는 변수로서 사용하기도 한다.
Q:
여기서 한가지 의문이 생길 수 있다. 컴포넌트 내부에서
import React,{useRef} from 'react';
const RefSample=()=>{
let id=1;//이부분을 유심히 보자.
const setId=(n)=>{
id.current=n;
}
const printId=()=>{
console.log(id.current);
}
return(
<div>
refSample
</div>
);
};
export default RefSample
과 같이 일반 let 변수를 이용해서 로컬변수를 이용하면 안되는 것인가?
A:
그냥, ‘로컬변수’를 이용할 것이라면 상관이 없다. 하지만 let을 이용해 로컬변수를 설정해줄 경우 컴포넌트가 다시 렌더링 될 때 마다 let id=1; 이 실행되기 때문에 id값은 항상 1이 될 것이다. 하지만 위에서 설명했듯이 useRef()를 이용해 값을 초기화 해줄경우 .current에 할당된 값은 해당 컴포넌트가 unmount될때 까지 유지때문에 해당 변수에 동적으로 업데이트 해준 값을 유지해줄 수 있다.
React에서 ref의 역할
ref는 특정 DOM에 작업을 사용할 때 사용한다. 비슷한 예로 HTML에서는 DOM 요소에 id를 달고 특정 id에 접근하여 스타일을 적용하거나 자바스크립트에서 해당 id를 가진 요소를 찾아서 작업을 한다. 다시 말해 ‘DOM을 직접적으로 건드려야 할 때’ ref를 사용한다. 그렇다면 특정 DOM에 작업을 하는 경우에는 어떤 것들이 있을까? 크게 세 가지가 있다.
- 특정 input/textarea에 포커스 주기
- 스크롤 박스 조작하기
- Canvas 요소에 그림 그리기
이 중에서 ‘Canvas 요소에 그림 그리기’라는 부분이 이해가 잘 되지 않았다. 처음엔 css 관련 작업을 할 때 해당 요소에 className을 지정하여 state를 조작하는 방식으로 이해했었다. 하지만, HTML에는 canvas라는 요소가 있다는 사실을 알 게 되었고 이 요소를 통해 그림을 그릴 때 ref가 사용된다는 것을 알 게 되었다. Jquery를 사용할 땐 해당 요소의 id를 통해 접근을 할 수 있었는데 리액트에선 React.createRef()라는 메서드를 통해 ref를 이용해야 한다. 왜냐하면 리액트는 virtual DOM을 사용하기 때문에 canvas와 같은 실제 엘리먼트에 접근하기 위해서는 ref를 통해 참조를 해야 한다. 아래 코드 참조.
class refPrac extends Component { context = null; input = React.createRef(); componentDidMount() { this.context = this.input.current.getContext(‘2d’); } render() { return ( <canvas ref={this.input} /> ) }; }
12월 30일 내용
(7장) Q1. getSnapshotBeforeUpdate이 가지는 의미와 사용법
예시로 스크롤 위치를 위해 쓰인다는 말이 있지만 구체적으로 어떤 방식으로 사용되는지 이해가 안됐으나 React 공식 문서에서 그 사용법을 알 수 있었다.
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 어떤 값이 추가됐다면 그때의 스크롤 위치를 snapshot으로 반환
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
// snapshot 값으로 Return
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// snapshot을 통해 받아온 스크롤 위치로 바꿔준다.
// (위 getSnapshot.. 함수에서 넘어온 스크롤 값이 snapshot에 저장되어 있다)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
getSnapshotBeforeUpdate 와 componentDidUpdate는 둘 다 render가 이뤄진 후라는 점은 같으나 React DOM 및 refs 업데이트 의 전후라는 차이가 있다. state의 변화가 반영되어 있지만 실제 화면에 뿌려지기 전후라고 이해할 수 있다. 위 예시에선 DOM이 업데이트 되면서 스크롤이 최상단으로 가기 때문에 그 전에 그 위치를 저장하고 업데이트 이후 다시 그 위치로 변경시켜주는 것이다. 그 외에도 DOM 변화와 관련된 업데이트에 대해서 사용할 수 있을 것이다.
(4장) 이벤트핸들링 함수의 다른 방안
기존의 target.name을 활용한 onChange 함수에서 더 많은 인자를 받을 수 있는 함수로 변환해볼 수 있다.
const [data, setData] = useState({user:'cho', pw: 123});
// key를 받아 해당 key값의 state를 변경
const onChange = key => e => {
setData({...data, [key]: e.target.value});
};
const onChange2 = e => {
setData({...data, [e.target.name]: e.target.value});
};
return (<>
<input
type="text"
placeholder="유저명"
value={data.user}
onChange={onChange("user")} // {(event) => (onChnage("user"))(event)} 도 똑같이 동작한다.
/>
<input
type="text"
name="pw"
placeholder="패스워드"
value={data.pw}
onChange={onChange2}
/>)
(8장) useCallback 함수에 대한 심층 이해
useCallback 함수는 useMemo 함수와 비슷하게 어떤 state의 변화가 생겼을 때에만 함수가 생성되도록 하는 Hook이다. 이해가 힘들었던 것은 어떤 것은 처음 렌더링 될 때만 함수를 생성하고, 어떤 함수는 state 변화에 따라 함수를 생성하는데 이 차이가 무엇인지 와닿지 않았다. 이 그 두 함수는 아래와 같다.
const onChange = useCallback(e => {
setNumber(e.target.value);
console.log('number:', number);
}, []); // 컴포넌트가 처음 렌더링 될 때만 함수 생성
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
inputEl.current.focus();
}, [number, list]); // number 혹은 list 가 바뀌었을 때만 함수 생성
return (
<div>
<input value={number} onChange={onChange} ref={inputEl} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>평균값:</b> {avg}
</div>
</div>
);
이를 더 잘 이해하기 위해 몇가지 실험을 하였다.
먼저 console.log('number:', number);
를 통해 onChnage 속에서 number state를 보면 어떤 값을 입력하더라도 값이 없다고 나온다.
즉 onChange가 처음 생겼을 때 number 상태 그대로를 가지고 있다 반면 onInsert는 실행할 때마다 list, number의 값이 바뀌었다.
그리고 onChnage 두번째 인자로 [number]
를 넣으면 number가 계속 변한다.
두번째로 onInsert의 두번째 인자를 모두 삭제하면 아래와 같이 된다.
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
inputEl.current.focus();
}, []); // 컴포넌트가 처음 렌더링 될 때만 함수 생성
위 상태에선 “Warning: Received NaN for the children
attribute. If this is expected, cast the value to a string.” 란 오류가 뜬다. 이 말인 즉슨 Nan 을 li 에서 받아 경고창을 보냈다. 즉 위 함수에서 parseInt(number)의 number가 Nan이였기 때문이다. number의 처음상태가 빈값이라 그 이후에도 실행시켜도 빈값이 반환된 것이다.
정리하자면, 어떤 state의 값을 이용하여 새로운 값으로 변환하는 것이라면 state 변화에 따라 함수를 생성하고, static 하게 단순한 함수 실행이라면 처음 렌더링될때만 함수가 생성하도록 한다.