react hooks 직접 구현해보기
2021. 11. 20. 13:27ㆍ코반주반
어떤 기술을 맨날 사용하다보면, 스스로가 잘 알고 있다고 착각을 하기 쉽다.
늘 경계하고 '정말 잘 알고 사용하고 있는가? 누구에게 설명할 수 있을 정도로 알고 있는가?' 에 대한 질문을 스스로에게 던지는게 중요한 것 같다.
스스로가 잘 알고 있다고 생각하는 부분은 공부를 소홀히 하게 되기 마련이니까...
오늘은 react hooks 를 간단하게 직접 만들어봤다.
단일 state 및 effect deps를 관리하는 singleton 방식의 react, hooks 배열을 통해 조금 더 실제 react hooks와 비슷하게 구현한 방식으로 만들었다.
첫 번째 예제는 hook의 작동방식과 closer의 연관성에 대해 이해하기에 좋은 것 같고, 두 번째 예제는 'hooks는 마법이 아니라 배열일 뿐' 이라는 말을 이해하기에 좋은 것 같다.
myReactSingleton.js
const React = (function () {
let _state;
let _deps;
return {
// 기본적인 렌더함수 생성
render(component) {
// functional component 호출
const Component = component();
// 렌더함수 호출
Component.render();
// 컴포넌트 반환
return Component;
},
// useState Hook 생성
useState(initial) {
// 기존 state 없을 경우 initial
_state = _state || initial;
// 값 업데이트는 값을 직접 넣던지, prev update 함수를 넣던지.
function setState(updatedValueOrCb) {
if (typeof updatedValueOrCb === "function") {
_state = updatedValueOrCb(_state);
} else {
_state = updatedValueOrCb;
}
}
return [_state, setState];
},
// useEffect는 콜백, deps Array를 받는다.
useEffect(cb, deps) {
// 의존성 존재여부 확인
const noDeps = !deps;
// 업데이트 필요여부
const isDepsChange = _deps ? !deps.every((d, i) => d === _deps[i]) : true;
// 실행 후 deps 상태 갱신
if (noDeps || isDepsChange) {
cb();
_deps = deps;
}
},
};
})();
function Home() {
const [clickCount, setClickCount] = React.useState(0);
const handleDoubleClick = () => {
setClickCount((prev) => prev + 2);
};
const handleClick = () => {
setClickCount(clickCount + 1);
};
const handleNoClick = () => {
setClickCount(clickCount);
};
React.useEffect(() => {
console.log("useEffect 클릭 횟수 --> ", clickCount);
}, [clickCount]);
//! 싱글톤 방식이라 같은 deps를 받는 2번째 useEffect는 실행되지 않음. 이미 위의 effect 에서 deps가 업데이트 되었기 때문
React.useEffect(() => {
console.log("실행되지 않는 useEffect --> ", clickCount);
}, [clickCount]);
const render = () => {
console.log(`render 클릭 횟수 --> ${clickCount}`);
return `HOME`;
};
return { render, handleNoClick, handleClick, handleDoubleClick };
}
let App;
// 컴포넌트 마운트
App = React.render(Home);
//useEffect 클릭 횟수 --> 0
//render 클릭 횟수 --> 0
// setState 값을 직접 넣는 방식
App.handleClick();
App = React.render(Home);
//useEffect 클릭 횟수 --> 1
//render 클릭 횟수 --> 1
// setState 업데이트 방식
App.handleDoubleClick();
App = React.render(Home);
//useEffect 클릭 횟수 --> 3
//render 클릭 횟수 --> 3
// effect callback 실행되지 않음
App.handleNoClick();
App = React.render(Home);
//render 클릭 횟수 --> 3
myReactHooksArray.js
const React = (function () {
// let _state; let _deps;
// 2개 이상의 컴포넌트에서 훅을 호출하기 위해 배열로 변환
let _hooks = [];
// 훅 호출을 위한 인덱스 관리
let _currentHookIndex = 0;
return {
// 기본적인 렌더함수 생성
render(component) {
// component effect 실행
const Component = component();
// 렌더함수 호출
Component.render();
// 훅 인덱싱 초기화
_currentHookIndex = 0;
// 컴포넌트 반환
return Component;
},
// useState Hook 생성
useState(initial) {
// hooks 배열에서 기존 state를 찾아온다.
_hooks[_currentHookIndex] = _hooks[_currentHookIndex] || initial;
// hook index 더해주기 전에 클로저로 접근할 수 있게 빼준다
const prevHookIndex = _currentHookIndex;
// 미리 빼준 index를 사용해서 값을 업데이트한다.
const setState = (updatedValueOrCb) => {
if (typeof updatedValueOrCb === "function") {
_hooks[prevHookIndex] = updatedValueOrCb(_hooks[prevHookIndex]);
} else {
_hooks[prevHookIndex] = updatedValueOrCb;
}
};
// 다음 훅을 위해 인덱스를 올려준다.
_currentHookIndex++;
// state와 setState 반환.
return [_hooks[prevHookIndex], setState];
},
useEffect(cb, deps) {
// 의존성 존재여부 확인
const noDeps = !deps;
// 업데이트 필요여부
const effectHookDeps = _hooks[_currentHookIndex];
const isDepsChange = effectHookDeps
? !deps.every((d, i) => d === effectHookDeps[i])
: true;
// 실행 후 deps 상태 갱신
if (noDeps || isDepsChange) {
cb();
_hooks[_currentHookIndex] = deps;
}
// 다음 훅을 위해 인덱스를 올려준다.
_currentHookIndex++;
},
};
})();
function Home() {
const [clickCount, setClickCount] = React.useState(0);
const handleDoubleClick = () => {
setClickCount((prev) => prev + 2);
};
const handleClick = () => {
setClickCount(clickCount + 1);
};
const handleNoClick = () => {
setClickCount(clickCount);
};
React.useEffect(() => {
console.log("useEffect 클릭 횟수 --> ", clickCount);
}, [clickCount]);
// ? 이제 동일 deps를 가진 useEffect 역시 실행이 가능하다.
React.useEffect(() => {
console.log("useEffect 클릭 횟수 --> ", clickCount);
}, [clickCount]);
const render = () => {
console.log(`render 클릭 횟수 --> ${clickCount}`);
return `HOME`;
};
return { render, handleNoClick, handleClick, handleDoubleClick };
}
let App;
// 컴포넌트 마운트
App = React.render(Home);
//useEffect 클릭 횟수 --> 0
//useEffect 클릭 횟수 --> 0
//render 클릭 횟수 --> 0
// setState 값을 직접 넣는 방식
App.handleClick();
App = React.render(Home);
//useEffect 클릭 횟수 --> 1
//useEffect 클릭 횟수 --> 1
//render 클릭 횟수 --> 1
// setState 업데이트 방식
App.handleDoubleClick();
App = React.render(Home);
//useEffect 클릭 횟수 --> 3
//useEffect 클릭 횟수 --> 3
//render 클릭 횟수 --> 3
// effect callback 실행되지 않음
App.handleNoClick();
App = React.render(Home);
//render 클릭 횟수 --> 3
참고 포스팅 :