React+Typescript开发

257 阅读3分钟

相应版本"react": "^18.2.0""typescript": "^4.9.3"

前置知识

类型推断(Type Inference)

let x = 3;

image.png

即使不声明也会类型推断为number类型

image.png

再赋值为string类型会报错

Type Inference

函数组件声明

接口类型

interface AppProps {
    message: string;
}

不需要children Props

// 通过类型推断返回值JSX.Element
export const App = ({ message }: AppProps) => {
    return <div>{message}</div>;
};

需要children Props

type PropsWithChildren<P = unknown> = P & { children?: ReactNode | undefined };
import { PropsWithChildren } from 'react';
// 通过类型推断返回值JSX.Element
export const AppWithChildren = ({ message, children }: PropsWithChildren<AppProps>) => {
  return (
    <div>
      <div>{message}</div>
      <div>{children}</div>
    </div>
  );
};

关于为啥不用React.FC<Approps>声明

Hook

useState

接口类型

// index.d.ts
type SetStateAction<S> = S | ((prevState: S) => S);
type Dispatch<A> = (value: A) => void;

function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];

使用

import { useState } from 'react';

interface User {
  name: string;
  age: number;
}

const App = () => {
  // 空值推断
  // useState<undefined>(): [undefined, React.Dispatch<React.SetStateAction<undefined>>]
  const [undefinedVal1, setUndefinedVal1] = useState();

  // 类型推断
  // useState<undefined>(initialState: (() => undefined) | undefined): [undefined, React.Dispatch<React.SetStateAction<undefined>>]
  const [undefinedVal2, setUndefinedVal2] = useState(undefined);
  // useState<null>(initialState: (() => null) | null): [null, React.Dispatch<React.SetStateAction<null>>]
  const [nullVal, setNullVal] = useState(null);

  // 声明类型-赋值为空
  // useState<number>(): [number | undefined, React.Dispatch<React.SetStateAction<number | undefined>>]
  const [val, setVal] = useState<number>();

  // 推荐声明方式
  const [user1, setUser1] = useState<User | null>(null);
  const [user2, setUser2] = useState<User>({} as User);

  const usageOfUserData = () => {
    console.log(user1?.age);
    console.log(user2.age);
  };

  return (
    <div>
      <div>test</div>
    </div>
  );
};

export default App;

useRef

接口类型

interface RefObject<T> {
  readonly current: T | null;
}
interface MutableRefObject<T> {
  current: T;
}
function useRef<T>(initialValue: T|null): RefObject<T>;
function useRef<T = undefined>(): MutableRefObject<T | undefined>;

使用

import { useRef, useEffect, forwardRef, useImperativeHandle } from 'react';

// App1
// dom ref
export const App1 = () => {
  // useRef<HTMLInputElement>(initialValue: HTMLInputElement | null): React.RefObject<HTMLInputElement>
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  return <input ref={inputRef} type="text" />;
};

// App2
// dom forwardRef
export const App2 = () => {
  // useRef<HTMLInputElement>(initialValue: HTMLInputElement | null): React.RefObject<HTMLInputElement>
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  return (
    <div className="App">
      <ForwardRefMyInput ref={inputRef} />
    </div>
  );
};

const ForwardRefMyInput = forwardRef<HTMLInputElement>((props, ref) => {
  return <input {...props} ref={ref} type="text" />;
});

// App3 useImperativeHandle
type CountdownHandleRef = {
  start: () => void;
};

export const App3 = () => {
  // useRef<CountdownHandleRef>(initialValue: CountdownHandleRef | null): React.RefObject<CountdownHandleRef>
  const countdownEl = useRef<CountdownHandleRef>(null);

  useEffect(() => {
    countdownEl.current?.start();
  }, []);

  return <Countdown ref={countdownEl} />;
};

const Countdown = forwardRef<CountdownHandleRef>((...[, ref]) => {
  useImperativeHandle(ref, () => ({
    start() {
      console.log('Start');
    },
  }));

  return <div>Countdown</div>;
});

// App4 获取最新值
export const App4 = () => {
  // useRef<number>(initialValue: number): React.MutableRefObject<number>
  const intervalRef = useRef<number>(0);

  useEffect(() => {
    const timer = setInterval(() => {
      intervalRef.current++;
    }, 1000);
    return () => clearInterval(timer);
  }, []);

  const getTempVal = () => {
    console.log(intervalRef.current);
  };

  return (
    <div>
      <button onClick={getTempVal}>getTempVal</button>
    </div>
  );
};

useCallBack

接口类型

type DependencyList = ReadonlyArray<unknown>;
function useCallback<T extends Function>(callback: T, deps: DependencyList): T;

使用

import { useCallback, useState } from 'react';

export const App = () => {
  const [callBackDeps] = useState<number>(0);

  const memoizedCallback = useCallback<(param1: string, param2: number) => { test: number }>(
    (param1, param2) => {
      console.log(param1, param2);
      return { test: param2 };
    },
    [callBackDeps],
  );
  return (
    <div>
      <button onClick={() => memoizedCallback('test', 111)}></button>
    </div>
  );
};

useMemo

接口类型

function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T;

使用

import { useMemo, useState } from 'react';
interface CalcResult {
  val: number;
}
export const App1 = () => {
  const [memoDeps] = useState<number>(0);

  const computedVal = useMemo<CalcResult>(() => {
    return { val: memoDeps };
  }, [memoDeps]);

  return (
    <div>
      <div>{computedVal.val}</div>
    </div>
  );
};

useEffect

接口类型

declare const UNDEFINED_VOID_ONLY: unique symbol;
type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };
type EffectCallback = () => (void | Destructor);
function useEffect(effect: EffectCallback, deps?: DependencyList): void;

不支持泛型

使用

import { useEffect, useState } from 'react';

export const App1 = () => {
  const [effectDep] = useState<number>(0);

  useEffect(() => {
    console.log('test...');
  }, [effectDep]);

  useEffect(() => {
    const timer = setInterval(() => {
      console.log('test.......');
    }, 1000);
    return () => clearInterval(timer);
  }, []);

  return (
    <div>
      <div>useEffect</div>
    </div>
  );
};

useLayoutEffect

和useEffect接口定义一致

接口定义

declare const UNDEFINED_VOID_ONLY: unique symbol;
type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };
type EffectCallback = () => (void | Destructor);
function useLayoutEffect(effect: EffectCallback, deps?: DependencyList): void;

使用

import { useLayoutEffect, useState } from 'react';

export const App1 = () => {
  const [effectDep] = useState<number>(0);

  useLayoutEffect(() => {
    console.log('test...');
  }, [effectDep]);

  useLayoutEffect(() => {
    const timer = setInterval(() => {
      console.log('test.......');
    }, 1000);
    return () => clearInterval(timer);
  }, []);

  return (
    <div>
      <div>useLayoutEffect</div>
    </div>
  );
};

uesContext

接口类型

 type WeakValidationMap<T> = {
  [K in keyof T]?: null extends T[K]
      ? Validator<T[K] | null | undefined>
      : undefined extends T[K]
      ? Validator<T[K] | null | undefined>
      : Validator<T[K]>
};
interface ExoticComponent<P = {}> {
  (props: P): (ReactElement|null);
  readonly $$typeof: symbol;
}
interface ProviderExoticComponent<P> extends ExoticComponent<P> {
  propTypes?: WeakValidationMap<P> | undefined;
}
interface ProviderProps<T> {
  value: T;
  children?: ReactNode | undefined;
}
interface ConsumerProps<T> {
  children: (value: T) => ReactNode;
}
type Provider<T> = ProviderExoticComponent<ProviderProps<T>>;
type Consumer<T> = ExoticComponent<ConsumerProps<T>>;
interface Context<T> {
    Provider: Provider<T>;
    Consumer: Consumer<T>;
    displayName?: string | undefined;
}

function createContext<T>(
    defaultValue: T,
): Context<T>;

使用

import { createContext, useContext } from 'react';
interface User {
  name: string;
  age: number;
}
const UserContext = createContext<User | null>(null);
export const App1 = () => {
  return (
    <UserContext.Provider value={{ name: 'test', age: 19 }}>
      <SonApp />
    </UserContext.Provider>
  );
};

const SonApp = () => {
  const context = useContext(UserContext);
  return <div>{context?.age}</div>;
};

useReducer

接口类型

type Dispatch<A> = (value: A) => void;
type Reducer<S, A> = (prevState: S, action: A) => S;
type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any> ? S : never;
type ReducerAction<R extends Reducer<any, any>> = R extends Reducer<any, infer A> ? A : never;

function useReducer<R extends Reducer<any, any>>(
    reducer: R,
    initialState: ReducerState<R>,
    initializer?: undefined
): [ReducerState<R>, Dispatch<ReducerAction<R>>];

应用

import { useReducer } from 'react';

const initialState = { count: 0 };

type ACTIONTYPE = { type: 'increment'; payload: number } | { type: 'decrement'; payload: string };

function reducer(state: typeof initialState, action: ACTIONTYPE) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + action.payload };
    case 'decrement':
      return { count: state.count - Number(action.payload) };
    default:
      throw new Error();
  }
}

function Counter() {
  // 定义好reducer后通过类型推断可得到state、dispatch类型
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'decrement', payload: '5' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment', payload: 5 })}>+</button>
    </>
  );
}

更多参考

事件相关

import { ChangeEvent, FocusEvent } from 'react';

export const App1 = () => {
  const onchangeEvent = (e: ChangeEvent<HTMLInputElement>) => {
    console.log('onchangeEvent::', e);
    console.log('onchangeEvent.target::', e.target.value);
  };

  const onBlurEvent = (e: FocusEvent<HTMLInputElement>) => {
    console.log('onBlurEvent::', e);
    console.log('onBlurEvent:target::', e.target.value);
  };
  return (
    <div>
      <input onChange={onchangeEvent} onBlur={onBlurEvent} />
    </div>
  );
};