1. 程式人生 > 其它 >3分鐘掌握hook在typescript中的姿勢

3分鐘掌握hook在typescript中的姿勢

3分鐘掌握hook在typescript中的姿勢

hook結合typescript可以說是很香了。本文主要介紹hook結合typescript 如何使用,享受ts帶給我們的編輯器提示和型別約束

useState

useState如果初始值不是null/undefined的話,是具備型別推導能力的,根據傳入的初始值推斷出型別;初始值是 null/undefined的話則需要傳遞型別定義才能進行約束。一般情況下,還是推薦傳入型別(通過useState的第一個泛型引數)。

// 這裡ts可以推斷 value的型別並且能對setValue函式呼叫進行約束
const [value, setValue] = useState(0);

interface MyObject {
  foo: string;
  bar?: number;
}

// 這裡需要傳遞MyObject才能約束 value, setValue
// 一般情況下推薦傳入型別
const [value, setValue] = useState<MyObject>(null);

useContext

useContext一般根據傳入的Context的值就可以推斷出返回值。不需要顯示傳遞型別

type Theme = 'light' | 'dark';
// 我們在createContext就傳了型別了
const ThemeContext = createContext<Theme>('dark');

const App = () => (
  <ThemeContext.Provider value="dark">
    <MyComponent />
  </ThemeContext.Provider>
)

const MyComponent = () => {
    // useContext根據ThemeContext推斷出型別,這裡不需要顯示傳
  const theme = useContext(ThemeContext);
  return <div>The theme is {theme}</div>;

useEffect useLayoutEffect

沒有返回值,無需傳遞型別

useCallback useMemo

useMemo無需傳遞型別,根據函式的返回值就能推斷出型別

useCallback無需傳遞型別,根據函式的返回值就能推斷出型別。但是注意函式的入參需要定義型別,不然就是推斷為any了!

const value = 10;
// 推斷出result是number型別
const result = useMemo(() => value * 2, [value]);

const multiplier = 2;
// 推斷出 (value: number) => number
// 注意函式入參value需要定義型別
const multiply = useCallback((value: number) => value * multiplier, [multiplier]);

useRef

useRef傳非空初始值的時候可以推斷型別,同樣也可以通過傳入第一個泛型引數來定義型別,約束ref.current的型別。

const MyInput = () => {
  // 這裡約束inputRef是一個html元素
  const inputRef = useRef<HTMLInputElement>(null);
  return <input ref={inputRef} />
}
// 自動推斷出 myNumberRef.current 是number型別
const myNumberRef = useRef(0);
myNumberRef.current += 1;

useReducer

只需要對傳入useReducer的reducer函式的入參stateaction進行型別約束就能夠推斷出來

interface State {
  value: number;
}

type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'incrementAmount'; amount: number };

const counterReducer = (state: State, action: Action) => {
  switch (action.type) {
    case 'increment':
      return { value: state.value + 1 };
    case 'decrement':
      return { value: state.value - 1 };
    case 'incrementAmount':
      return { value: state.value + action.amount };
    default:
      throw new Error();
  }
};

// 這裡可以推斷出state為State型別
const [state, dispatch] = useReducer(counterReducer, { value: 0 });

//能夠約束傳入dispatch的引數,符合Action型別
dispatch({ type: 'increment' });
dispatch({ type: 'decrement' });
dispatch({ type: 'incrementAmount', amount: 10 });

// TypeScript compilation error
dispatch({ type: 'invalidActionType' });

useImperativeHandle

useImperativeHandle一般比較少用,一般用來選擇函式元件對外暴露ref屬性被呼叫,需要配合forwardRef使用。

如下例子。需要定義對外暴露的介面MyInputHandles,函式元件使用React.RefForwardingComponent對外暴露的介面呼叫作為泛型引數。然後就會得到約束了

// MyInputHandles 需要給父元件的useRef作為型別使用 和 RefForwardingComponent作為泛型引數傳入約束
export interface MyInputHandles {
  focus(): void;
}

// 使用RefForwardingComponent 型別進行定義元件,第一個泛型引數是對外暴露的handle,第二個是Props
const MyInput: RefForwardingComponent<MyInputHandles, MyInputProps> = (
  props,
  ref
) => {
  const inputRef = useRef<HTMLInputElement>(null);

  useImperativeHandle(ref, () => ({
    // 這裡的返回會自動使用MyInputHandles進行型別約束
    focus: () => {
      if (inputRef.current) {
        inputRef.current.focus();
      }
    },
  }));

  return <input {...props} ref={inputRef} />;
};

// 函式元件必須使用forwardRef才能讓外部元件使用該元件的ref
export default forwardRef(MyInput);
// 父元件
const Autofocus = () => {
  // 能夠約束 myInputRef.current的型別
  const myInputRef = useRef<MyInputHandles>(null);

  useEffect(() => {
    if (myInputRef.current) {
      myInputRef.current.focus();
    }
  });

  return <MyInput ref={myInputRef} />
}

參考:

React Hooks in TypeScript

本文參與騰訊雲自媒體分享計劃,歡迎正在閱讀的你也加入,一起分享。

漫思