오버로딩은 매개변수의 유무로 구분됨 => 매개변수가 있으면 첫번째 오버로딩, 없으면 두번째 오버로딩에 해당함.
파라미터가 없으면 undefined와 S가 모두 적용될 수 있도록 union 타입을 설정해줌, 초기 파라미터가 있다면 S타입을 제네릭을 통해 공유함.
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];
Dispatch, SetStateAction
setStateAction type이 S이거나, S type을 파라미터로 받아서 S로 반환하는 콜백함수이기 때문에,
setCount(1) 이나 setCount((prev) => prev+1)의 형태가 가능한 것임.
type SetStateAction<S> = S | ((prevState: S) => S);
// this technically does accept a second argument, but it's already under a deprecation warning
// and it's not even released so probably better to not define it.
type Dispatch<A> = (value: A) => void;
// Since action _can_ be undefined, dispatch may be called without any parameters.
두번째의 경우 파라미터 기본값이 없을때 => 제너릭으로 type을 표기해야함.
단 value는 string or undefined이므로, undefined일 경우를 처리해줘야함.
lazy init 가능하게 할 수 있음.(초기값을 가공해야할때) => state의 매개변수로 함수를 return 하는 형태
useEffect안에서 가공해주는 거랑 무슨 차이가 있을까?
const [value, setValue] = useState<String>();
const [word, setword] = useState(() => { return 복잡한 함수()});
useEffect는 EffectCallback을 인자값으로 받는다. => return 값은 void
return 값은 void이거나 Destructor이다
Destructor는 callback함수인데, return값이 없는 cleanup 함수이다.
useEffect의 EffectCallback은 async 키워드와 같이 사용할 수 없다.
async함수는 항상 promise를 반환하기 때문에 void나 Destructor를 반환하는 EffectCallback 함수와 호환되지 않는다.
function useEffect(effect: EffectCallback, deps?: DependencyList): void;
type DependencyList = ReadonlyArray<unknown>;
// NOTE: callbacks are _only_ allowed to return either void, or a destructor.
type EffectCallback = () => (void | Destructor);
type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };
[UNDEFINED_VOID_ONLY]: never 가 있는 이유? => History 확인 => View git blame!!
타입스크립트 브랜딩을 사용하기 위해 해당 type을 return 값으로 넣어줌.
TS에서 원시값을 구분하기 위해 사용된다.
ref) https://velog.io/@sjyoung428/Typescript-%EB%B8%8C%EB%9E%9C%EB%94%A9
type Brand<K, T> = K & { __brand: T };
type EUR = Brand<number, "EUR">;
type USD = Brand<number, "USD">;
type KRW = Brand<number, "KRW">;
let eur = 10 as EUR;
let usd = 10 as USD;
let krw = 1000 as KRW;
const euroToUsd = (euro: EUR): USD => {
return (euro * 0.99) as USD;
};
console.log(euroToUsd(usd));
console.log(euroToUsd(krw));
console.log(euroToUsd(eur));
eur = 1000 as EUR;
console.log(euroToUsd(eur));
그래서 해당 type에 원시값을 구분하기위해 brand라는 속성을 새로 만들어주는 기법임.
brand 속성을 만들어준 후 as를 통해 명시적으로 타입을 변환함. => eur type만 함수에 넣을 수 있다.
Readonly Array => 한번 array가 생성되면 length를 수정할 수 없다.
interface ReadonlyArray<T> {
/**
* Gets the length of the array. This is a number one higher than the highest element defined in an array.
*/
readonly length: number;
...
}
function useCallback<T extends Function>(callback: T, deps: DependencyList): T;
function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T;
3개의 오버로딩 존재 => MutableRefObject와 RefObject의 차이는?
MutableRefObject => 컴포넌트에서 값을 저장할때의 type임. (리렌더링 x)
RefObject => 참조값을 갖고있는 객체임 => 제너릭으로 Dom 요소값을 넣어줘야함. + null 값도 넣어줘야 RefObject로 인식함.
타입이 다를때 두번째 오버로딩으로 들어간다.
function useRef<T>(initialValue: T): MutableRefObject<T>;
function useRef<T>(initialValue: T | null): RefObject<T>;
function useRef<T = undefined>(): MutableRefObject<T | undefined>;
MutableRefObject는 current 속성을 수정 할 수 있지만, RejObject는 current 속성을 수정 할 수 없다.
MutableObject가 좀 더 넓은 타입이다.
interface MutableRefObject<T> {
current: T;
}
interface RefObject<T> {
readonly current: T | null;
}
const inputEl = useRef<HTMLHeadElement>(document.querySelector("head")) // 이런식으로도 사용가능