순수 JS | React | |
---|---|---|
프로그래밍 방식 | - 명령형 - 화면을 어떻게 그릴 것인지 - UI가 어떻게 보일지 한 눈에 보이지 않는다. | - 선언형 - 화면에 무엇을 그릴 것인지 - UI가 어떻게 변경될지 한 눈에 보인다.(컴포넌트가 복잡하지 않을때..) |
추상화 | - 추상화 단계가 낮다. - 다른 환경에서 코드를 재활용하기 어렵다. | - 추상화 단계가 높다. - 다른 환경에서 코드를 재활용하기 쉽다 (ex. react native) |
import ReactDOM from 'react-dom';
...
export defualt function App() {
...
{
ReactDOM.createPortal(
<div>나는 포탈!</div>,
document.getElementById('portalId')
)
}
}
리액트가 JSX에서 실제돔 변경까지의 과정
1. JSX는 바벨에 의해 리액트 요소로 변경되고,
2. 리액트 요소는 하위 리액트 요소의 type이 모두 문자열(DOM형식)이 될 때까지 호출하며 리액트 요소 트리를 생성한다.
3. 모든 값이 변경되면 실제 돔으로 변경될 수 있는 상태이고 이 상태가 바로 가상돔 상태다.
4. 가상 돔은 이전 가상돔과 메모리에서 비교과정을 거치며, 변경된 사항만 실제 돔에 적용한다.
5. key값의 변경, Component가 조건부 렌더링등을 통한 재생성은 요소 부분이 아닌 해당 DOM 전체를 다시 그린다.React.memo를 이용하면, 가상돔을 그리는 과정에서 다시 컴포넌트를 랜더하여 그리지 않고, 이전의 리액트 요소를 가져와 쓰기 때문에, 효율적.
Babel은 JSX을 React.createElement로 바꿔주는 역할도 수행
리액트 요소는 UI의 표현 수단.
리액트 요소 =
React.createElement(
'a',
{ href: 'http://google.com' },
'click here'
)
JSX도 Babel에 의해 결국 React.createElement를 내부적으로 호출하는 결과
리액트는 memory에 가상돔을 올려두고
렌더링 할 때마다 이전 가상돔과 최신 가상돔을 비교해 변경된 사항만 실제돔에 반영한다.
React에서 key값이 변경되면 아예 새로운 컴포넌트로 인식한다.
{
type: 'a' or Component함수, // Dom은 문자열, 컴포넌트는 함수 이름이 들어간다.
key: key에 지정된 값,
ref: '',
props: {
children,
다른 속성 키: 값
}
//...
}
React에서 데이터 변경에 의한 화면 업데이트느느
2가지 단계를 가지는데,
랜더 단계와 커밋 단계이다.
를 통해 해당 컴포넌트가 보유한 props가 변경되지 않는 이상, 이전에 memort에 기록한 결과를 재사용
리액트 랜더 단계는 보통 2가지 이유로 호출됨
1. ReactDOM.render 호출
2. 컴포넌트 내부의 상태값 변경
🙄아직까지 이 사실이 얼마나 유용한 정보인지는 모르겠다.
- 상태 값 변경 함수는 "비동기"이며 "배치"적으로 작동한다
- 하나의 함수에서 같은 상태값 변경함수 2번 적용할 수 있다.(원래는 없다.)
- unstable_batchedUpdates()를 통해 외부 함수도 배치적으로 작동시킬 수 있다.
- 화면이 그려지기전에 부수함수가 호출되어야 한다면 useLayout를 쓸 수 있다.
훅.
ex) 상태 값 추가, 자식 요솟 접근
이재승님의 kakao if conference 강의 영상자료
만약 상태 값 변경 함수가 동기방식을 취하게 된다면
하나의 상태값 변경 함수가 호출될 때마다. 화면을 다시 그려야 한다.
그렇지 않다면 실제 데이터와 화면의 상태가 불일치 하는 상황이 그려진다.
function addCountDouble() {
setCount(count + 1);
setCount(count + 1);
}
// count는 1만 증가한다.
이는 상태 값 변경 함수는 "비동기"이며 "배치"적으로 작동하기 때문인데,
어떤 이유로 하나의 함수에서 같은 상태값 변경함수를 두번 호출하고 싶을 때가 있다.
그러면 할 수 있는 방법이 상태값 변경 함수에서 함수로 변경하는 것이다.
function addCountDouble() {
setCount(count => count + 1);
setCount(count => count + 1);
}
// count는 2가 증가된다!!
그리고 이런 비동기적이고 배치적인 방식의 적용 범위는 해당 컴포넌트 내부에서 선언된 함수일 때만 그렇다.
그렇다면 리액트외부에서 선언된 함수는 "배치"방식으로 처리하고 싶다면 이 함수를 이용하면 된다.
ReactDOM.unstable_batchedUpdates(() => {
setCount();
setCount();
})
앞에 unstable이라는 무시무시한 preffix가 붙여져 있지만, 많은 사람들이 사용한다고 하니 일단은 필요하면 쓰면 될 것 같다.
참고로 미래의 react concurrent모드는 외부의 함수도 배치방식으로 바뀐다고 한다.
useEffect는 렌더링 후 화면이 그려지고 나서 비동기적으로 호출된다.
만약 화면이 그려지기 전에 부수효과를 처리해야 한다면 useLayoutEffect가 있다.
useEffect | useLayoutEffect | |
---|---|---|
render | 렌더가 화면에 그려진 후 비동기적으로 실행 | 렌더링 후 화면이 업데이트 되기 전에 동기적으로 실행 |
사용 경우 | ☘️ 일부 상태를 즉시 발생할 필요가 없을 경우 ☘️ 페이지에 시각적으로 영향을 주지 않는 무언가를 동기화 할 경우 ☘️ 이벤트 핸들러를 설정하는 경우 ☘️ 모달 상자가 나타나거나 사라질 때 일부 상태를 재설정하는 경우 | ☘️ 상태가 업데이트 될 때 요소가 깜박이는 경우 ☘️ DOM을 변경하려는 경우 |
재승님의 말로는 useLayout은 가급적 사용하지 말라고 설명했다.
이유는 화면이 그려지기 전에 동기적으로 일을 처리하기 때문에, 사용자에게 화면을 보여주기까지 시간이 더 오래 소요되기 때문이다.
의존성배열을 잘 관리하지 못하면 여러 에러가 발생할수 있는데,
넣는 값으로는
useEffect에서 사용되는
그런데 지역 함수의 경우 실제로 의존성 배열에 넣게 된면, 너무 많이 호출된다는 경고문구가 나오는데,
이를 해결하기 위해 useCallback훅을 이용해야 한다고 했다.
아직 써본적 없어서 봐야 한다.
대략적인 기능은 해당 지역함수가 언제 호출될지 결정하는 함수 메모이제이션 기능이라고 한다.
그렇게 어려울 것 같지는 않은데.. 많이 써봐야 작 적용되지 않을까 싶다.
나중에 의존성 배열을 합리적으로 넣는 방법에 대해 소개해 주신다고 한다.
- 프로그래밍에서 함수로 로직을 기능별로 나누는 것 처럼 커스텀 훅이 그런 역할을 맡는다.
use
로 시작하는 것이 관례이다.- 훅에 props가 변경되면 자동으로 훅이 다시 호출되고,
- 훅의 내부 상태가 변경되면, 해당 훅을 사용하는 컴포넌트도 다시 렌더링한다.
리액트에서 훅들은 호출된 순서로 기억한다.
따라서 호출된 순서가 바뀌어 버리면 예상치 못한 버그가 발생할 수 있다.