
실제 리액트의 코드는 이와 다릅니다.
제가 작성한 코드는 학습하기 위해 작성한 코드임을 미리 알립니다!
전체 코드는 깃허브 에서 확인 해보실 수 있습니다.
이번에도 역시 공식 문서를 참고해봅니다. 공식문서만한게 없죠

즉, useRef를 사용한 값을 변경하면 렌더링에 영향을 주지 않습니다.
그리고 useRef는 두가지 사용 방법이 있습니다.
값을 참조하는 방식으로 쓸려면 아래와 같은 방식으로 사용해야 합니다.
useRef를 호출해서 안에 initialValue를 넣으면 ref.current로 값의 읽기/쓰기가 가능합니다.
const numberRef = useRef(10);
console.log(numberRef.current); // 10
numberRef.current = 20;
console.log(numberRef.current); // 20
만약 DOM을 조작 해주고 싶다면 initialValue로 null을 넣으면 됩니다.
그러면 ref.current로 해당 돔노드의 접근이 가능해집니다.
const divRef = useRef(null);
return (
<div ref={divRef} />
);
useRef 함수는 useState와 비슷하게 생겼을겁니다.
하지만 그 안에서 리렌더링을 일으키는 함수는 실행되지 않죠
useRef함수는 initialValue가 null이면 배열에 넣어주기만 하고
커밋 페이즈(참고)에서 key값중 ref가 있다면 해당 ref안에 현재 DOM Node를 넣어주면 될겁니다.
우선 비교적 간단해 보이는 값 참조 부분부터 구현 해보겠습니다.
정말 간단하게 구현할 수 있으므로 따로 설명을 하진 않겠습니다.
export interface Ref<T> {
current: T;
}
const refs: any[] = [];
let refIdx = 0;
export function useRef<T>(ref: T | null): Ref<T> {
if (refs.length === refIdx) {
refs.push({ current: ref });
}
return refs[refIdx++];
}
export function resetRefIdx() {
refIdx = 0;
}
이 함수 역시 idx 기반으로 작동하므로 render함수가 실행되기 전 idx를 초기화 해주도록 하겠습니다.
export function render() {
resetStateIdx();
resetEffectIdx();
resetRefIdx(); // << 추가!
...
}
제대로 작동하는지 한번 확인해 보도록 하겠습니다.
// App.tsx
function App() {
const counterRef = useRef(10);
const incrementCounter = () => {
counterRef.current = counterRef.current + 1;
console.log(counterRef.current);
};
return (
<div>
<button onclick={incrementCounter}>
카운터 토글 {counterRef.current}
</button>
</div>
);
}

원하던대로 리렌더링은 되지 않고 값만 증가하고 있습니다.
리액트의 렌더 페이즈가 끝난 후 커밋 페이즈도 끝나면 ref에 돔노드가 들어가게 됩니다. 저희가 만든 함수중 createDOM함수가 돔을 그려주는데 이 함수를 조금 수정하면 될것같습니다.
export function createDOM(element: VirtualDom): RealDom | Text {
...
if (element.props) {
Object.keys(element.props).forEach((key) => {
// key에 ref가 존재할 경우 ref.current에 실제 dom을 할당한다.
if (key === "ref") {
element.props[key].current = dom;
}
...
});
}
}
정말 간단한 코드인데 실제로 리액트에서는 이렇게 간단하게 동작하지는 않고 비슷하게 동작한다는걸 생각해볼 수 있습니다. 실제 리액트 코드에서는 어떻게 다뤄지는지에 대해서는 다음 기회에 다뤄보도록 하습니다.
그럼 진짜 동작하는지 확인할 차례입니다.
// App.tsx
function App() {
const divRef = useRef<HTMLDivElement>(null);
const changeColor = () => {
divRef.current.style.backgroundColor = "blue";
};
return (
<div ref={divRef} style="padding: 10px; background-color: red;">
<button onclick={changeColor}>색상을 바꾸는 버튼!</button>
</div>
);
}

예상대로 잘 작동합니다!🎉🎉🎉🎉🎉🎉🎉
이렇게 useState, useEffect, useRef와 함께 가상돔을 간단하게 구현 해 보았습니다.
계기는 네이버 부스트 캠프 과정에서 미션이었습니다. 구현을 해야하는데 제한사항이 바닐라JS만으로 구현을 하는거라 너무 귀찮았던것 같아요. 그래서 리액트를 따라만들자! 해서 그때 과정을 글로 옮겨보았습니다.
https://ko.react.dev/learn/render-and-commit#step-2-react-renders-your-components