Hook은 React 16.8부터 도입된 기능으로, 기존의 class component에서 벗어나 functional component로 상태를 관리하고 react의 다른 기능을 사용할 수 있게 한다. 하지만 어떤 원리로 동작하는 걸까? 어떻게 컴포넌트가 계속 리렌더링되고 변경되는 것과 무관하게 상태를 관리하고 가져올 수 있는 걸까?
답은 Closure!!
JavaScript Closure
- lexical environment와 함수의 조합으로 함수 내부의 lexical scope에서 정의한 변수는 함수 바깥에서는 직접적으로 참조하거나 수정할 수 없다
Vanilla JavaScript로 useState 구현
- 기본적인 구조는 useState를 사용하는 컴포넌트의 바깥에서 즉, 외부 스코프에서 모든 상태 변수를 정의하고 관리하여 특정 상태 값을 유지하고 변경할 수 있도록 하는 것이다.
- React를 하나의 큰 함수로 본다면, React라는 함수의 lexical scope 안에서 state에 대한 변수를 선언, 이 state에 대한 변수를 참조하거나 변경할 수 있는 useState 함수를 React로부터 제공받아 관리할 수 있다
하나의 상태만 관리한다고 하면 간단하게 구현할 수 있다.
export function createHooks(callback) {
let state; // 관리하고자 하는 상태 (클로저)
function useState(initialValue) {
// 최초로 선언할 때만 할당
if(!state) state = initialValue
// state를 변경할 수 있는 setState 함수
const setState = (newState) => {
// setState 호출 시 newValue로 변경
state = newState;
// state가 변경될 때마다 callback 함수 실행
callback();
};
return [state, setState];
}
return { useState };
}
- 상태를 사용하는 state는 클로저로 외부에 선언
여러 상태를 가질 수 있는 useState 구현
// 배열 사용
export function createHooks(callback) {
let idx = 0; // useState가 실행 된 횟수
let stateArr = []; // state를 보관할 배열
const useState = (initState) => {
const currIdx = idx;
// 초기값 설정
if (stateArr.length === currIdx) {
stateArr.push(initState);
}
// state 할당
const state = stateArr[currIdx];
const setState = (newState) => {
// 직접 수정하는 것이 아닌, 내부의 값을 새로운 값으로 할당
stateArr[currIdx] = newState;
callback();
};
// 끝나고 나면 실행 된 횟수를 1올려 stateArr 다음 값으로 push 할 수 있게
idx++;
return [state, setState];
}
return { useState };
};
- stateArr 배열에 idx라는 인덱스로 접근해서 초기 상태들을 리턴 해 줍니다 stateArr배열 길이는 useState를 사용할 때마다 +1이 된다.
- 상태 업데이트 역시 클로저로 선언된 idx로 접근애서 해당 useState의 상태인 stateArr[idx]를 수정한 다음 리렌더링을 진행한다.
// 객체 사용
export function createHooks(callback) {
let idx = 0; // useState가 실행된 횟수
let stateObj = {}; // 상태를 보관할 객체
const useState = (initState) => {
const currIdx = idx;
// 초기값 설정
if (!stateObj.hasOwnProperty(currIdx)) {
stateObj[currIdx] = initState;
}
// 상태 할당
const state = stateObj[currIdx];
const setState = (newState) => {
// 새로운 상태를 객체에 할당
stateObj[currIdx] = newState;
callback();
};
// 실행된 횟수를 1 증가시켜 다음 값으로 push 할 수 있게 함
idx++;
return [state, setState];
}
return { useState };
};
- 상태를 객체로 관리하고, 각 상태는 객체의 특정 키에 매핑된다. 객체를 사용함으로써 각 상태에 보다 명시적인 키를 지정할 수 있게 됐다.
- 가독성을 향상 시키고, 인덱스를 관리하는 부분을 배열보다는 더 간단하게 처리할 수 있다.
- 객체의 속성을 문자열로도 사용할 수 있다.
배열 사용
장점
- 간단하고 직관적이다.
- useState 훅이 반환하는 배열과 유사한 방식으로 상태를 다룰 수 있다.
단점
- 배열의 각 요소는 인덱스를 기반으로 하기 때문에 각 상태에 명시적인 이름을 부여할 수 없어서 가독성이 좋지 않다.
객체 사용
장점
- 각 상태에 명시적인 키를 지정하여 가독성을 높일 수 있다.
- 상태의 추가/제거가 발생해도 인덱스를 조정할 필요가 없다.
단점
- 상태를 업데이트할 때 키를 사용해야 하기 때문에 코드가 복잡해 질 수도 있다.
useMemo 구현
export function createHooks(callback) {
let idx = 0;
let stateObj = {};
const cacheValue = {};
const useState = (initState) => {
// ...이하 동일
}
const useMemo = (fn, refs) => {
// 인자로 받은 deps 배열을 문자열로 변환하여 캐시 키로 사용
const key = JSON.stringify(refs);
// 만약 cacheValue 저장된 결과가 있다면 이를 반환
if (key in cacheValue) {
return cacheValue[key];
}
// cacheValue 저장된 결과가 없다면 새로운 결과를 계산하고 캐시에 저장한 후 반환
const result = fn();
cacheValue[key] = result;
return result;
};
return { useState, useMemo };
};
// or
const useMemo = (fn, refs) => {
// 캐시 객체 생성
useMemo.cache = useMemo.cache || {};
// 인자로 받은 deps 배열을 문자열로 변환하여 캐시 키로 사용
const key = JSON.stringify(refs);
// // 만약 캐시에 저장된 결과가 있다면 이를 반환
if (key in useMemo.cache) {
return useMemo.cache[key];
}
const result = fn();
useMemo.cache[key] = result;
return result;
};
위에 코드는 일반적인 객체를 만들고 결과값을 비교 후 저장 혹은 반환하는 형태이고 얘기하고 싶은 부분은 밑에 함수 .cache 객체이다.
useMemo 함수의 .cache 속성은 결과를 캐시하는 데 사용되는 객체이다. 즉, useMemo 함수가 호출될 때마다 새로운 값을 계산하고 이를 저장하는 데 활용된다. 즉, 이 객체에는 계산된 결과가 저장되어 동일한 인자로 함수가 호출될 때 다시 계산하지 않고 이전에 계산된 결과를 반환할 수 있게 한다.
.cache 객체의 역할
- 이전 결과 저장 : 새로운 인자로 함수가 호출될 때마다 이전에 계산된 결과를 지정
- 결과 확인 : 동일한 인자로 함수가 호출될 때마다 캐시에 저장된 결과를 확인하여 이전에 계산된 결과가 있으면 반환
- 최적화 : 함수의 재 계산을 피하며 성능을 최적화
캐시 객체는 함수가 호출될 때마다 계속해서 참조되기 때문에 함수의 스코프 내에서만 접근할 수 있다. 즉, 함수 내부에서만 사용할 수 있다.
Reference
https://velog.io/@aborile/React-Hooks-with-Vanilla-JavaScript
'Front-End > JavaScript' 카테고리의 다른 글
프로토타입 (Prototype)과 객체지향 프로그래밍 (0) | 2024.03.25 |
---|---|
[JavaScript] Map Object (맵) (0) | 2022.04.03 |
[JavaScript] this란? (0) | 2022.03.13 |
[JavaScript] Closure (클로저) (0) | 2022.03.09 |
[JavaScript] var / let / const 변수 (0) | 2022.03.09 |