TypeScript มากับ React Hooks
Tags: typescript, react
Published: 2020-04-27 10:34:09

ตอนนี้ก็พูดได้เต็มปากเลยว่าคงไม่มีใครไม่ใช้ Hooks แล้ว code อ่านง่ายและสั้นกว่าการเขียน Class Component แบบเดิมมาก ฉะนั้นตอนนี้ถ้าไม่ติดอะไรก็ใช้ Functional Component แล้วใช้ Hooks เถอะของเค้าดีจริงๆ ในบทความนี้จะถือว่าทุกคนรู้จักแล้วว่า Hooks คืออะไร เราแค่มาดูกันว่า Hooks แต่ละแบบถ้าจะใช้กับ TypeScript ต้องมีอะไรเพิ่มเติมกันบ้าง

## useState

ส่วนใหญ่แล้ว useState ก็จะใช้ type ตามที่เรา initial อยู่แล้ว แต่ในบางกรณีเราอาจจะ initial ด้วยค่า undefined, null ไม่ก็ object หรือ array ที่เราต้องการ control type ภายใน ทำให้ไม่รู้ว่า type ที่จะ return ออกมาเป็น type อะไร เราจึงต้องใช้ generic เพื่อกำหนด type ที่จะ return ออกมาให้ useState

```typescript
// เคสปกติใช้ type ตามค่า initial
const [count, setCount] = useState(0); // count จะมี type เป็น number

// เคสอื่นๆ
// count จะมี type เป็น number หรือ undefined
const [count, setCount] = useState<number | undefined>(undefined);
// count จะมี type เป็น Array<number> โดย initial เป็น Array เปล่าๆ
const [count, setCount] = useState<Array<number>>([]);

interface PersonInfo {
  name: string;
  age?: number;
}
// person จะมี type เป็น object ตาม interface PersonInfo
const [person, setPerson] = useState<PersonInfo>({ name: 'My Name' });
```

## useEffect / useLayoutEffect

useEffect และ useLayoutEffect ไม่มี return type สามารถใช้งานได้เหมือนตอนใช้ JavaScript ปกติเลย

```typescript
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]);
```

## useContext

useContext จะใช้ค่า type ตาม context object ตาม argument ที่ส่งเข้าไปอยู่แล้ว สามารถใช้งานได้เหมือนตอนใช้ JavaScript ปกติเลย

```typescript
const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

const App = () => (
  <ThemeContext.Provider value={themes.dark}>
    <Toolbar />
  </ThemeContext.Provider>
);

const Toolbar = () => (
  <div>
    <ThemedButton />
  </div>
);

const ThemedButton = () => {
  const theme = useContext(ThemeContext);

  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}
```

## useMemo / useCallback

ทั้ง useMemo and useCallback จะใช้ type ตามที่ตัวเอง return สามารถใช้งานได้เหมือนตอนใช้ JavaScript ปกติเลย

```typescript
// useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

// useCallback
const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);
```

## useRef

ถ้า ref object ประกอบด้วยค่า current ที่เป็น readonly โดยมาค่าเริ่มต้นเป็น null จนกระทั้ง ref ถูก attached ในกรณีเราจะ initial ค่าเป็น null และใช้ generic เพื่อกำหนด type ที่จะ return ออกมาให้ useRef

```typescript
const TextInputWithFocusButton = () => {
  // initial ค่าเป็น null ใช้ generic กำหนด return type เป็น HTMLInputElement
  const inputEl = useRef<HTMLInputElement>(null);
  const onButtonClick = () => {
    inputEl.current.focus();
  };

  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}
```

## useReducer

กำหนด type ลักษณะเดียวกับการใช้ Redux โดยใช้ type จาก action และ state เป็นตัวกำหนด type ให้กับ useReducer

```typescript
interface State {
  count: number;
}

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

const init = (initialCount: number) => {
  return {count: initialCount};
}

const reducer = (state: State, action: Action) => {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}

const Counter : React.FC<{ initialCount: number }> = ({ initialCount }) => {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
```

## ส่งท้าย
การใช้ Hooks กับ TypeScript ไม่ได้มีอะไรวุ่นวาย ส่วนใหญ่สามารถใช้งานได้เหมือนกับ JavaScript เลย แค่มีบางตัวที่ต้องใช้ generic เพื่อกำหนดให้ Hooks รู้ค่าที่จะ return ออกมา ซึ่งคุ้มมากที่จะ feature ต่างๆ ของ TypeScript มาใช้งาน ขอให้มีความสุขมากมายกับการใช้งาน TypeScript นะครับ