DEV Community

Cover image for ⚛️ React+TypeScript 101 🥡
Nantipat
Nantipat

Posted on • Updated on

⚛️ React+TypeScript 101 🥡

บันทึกความเข้าใจจาก

React ไปวันๆ EP.4 - Typescript in React 101

GitHub logo React-in-Thai / react-ts-101

สิ่งที่ควรต้องรู้ในการเขียน react ด้วย typescript

This project was bootstrapped with Create React App.

Purpose

This project is designed for people who want to upskill from JavaScript to TypeScript to write code better.

Recommended for people who just started using TypeScript!

Contributing

Feel free to send PR or contact siriwat if you have more awesome ideas.




Topic

Why TypeScript

เมื่อก่อน JavaScript ถูกก design ให้ใช้งานง่ายๆสำหรับ brower แต่ตอนนี้มันอยู่ทุกส่วนแล้ว


Type

เหมือน interface แต่สามารถกำหนดชื่อให้ primitives type ได้ ใช้ในรูป unions ได้ ในรูป tuples ได้

Tuple คืออะไร
Tuple เป็น Data Structure แบบหนึ่งที่เป็น Array แบบ Fixed Size โดยที่ Data ที่อยู่ภายใน Tuple ไม่จำเป็นต้องเป็น Type ชนิดเดียวกัน

unions คืออะไร ดูข้างล่างนี้

type User = {
  firstname: string;
  lastname: string;
};
type NumberOrStringOrStudent = string | number | User;

let myValue: NumberOrStringOrStudent;
const sampleUser: User = {
  firstname: "Sippakorn",
  lastname: "Raksakiart",
};
Enter fullscreen mode Exit fullscreen mode
// ประกาศ Tuple
let option: [string, boolean, number];

option = ["uppercase", true, 1]; // OK
option2 = [false, 2, "lowercase"]; // ERROR
Enter fullscreen mode Exit fullscreen mode

UseState

export type ProfileProps = {
  name: string,
  age: number,
};
// todo: create Prop type for this component, also optional type
export function BasicProps({ name, age }: ProfileProps) {
  return (
    <div>
      <div>Name: {name}</div>
      <div>Age: {age}</div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

ถ้าอยากได้แบบ optional ละก็เพิ่ม ?

export type ProfileProps = {
  name?: string,
  age?: number,
};
Enter fullscreen mode Exit fullscreen mode
const [boolOrNum, setBoolOrNum] = useState();
const [boolOrNum, setBoolOrNum] = useState<boolean | number>(); // need to be boolean | number

<button
  onClick={() =>
    setBoolOrNum((c) => {
      if (typeof c === "number") {
        return c + 1;
      }
    })
  }
>
  Increment
</button>;
Enter fullscreen mode Exit fullscreen mode

อย่าลืมที่จะใส่ Item[]


interface Item {
  id: string
  name: string
}

export function Example2() {
  const [data, setData] = useState<Item[]>([]);

  return data.map((item, i) => (
    <div>
      Item {i} is {item.id}
    </div>
  ));
}
Enter fullscreen mode Exit fullscreen mode

Promise

ดูนาทีที่ 45.00

import React, { useEffect, useState } from "react";
import { getData } from "../js/api";

const api = (id: string) => new Promise<string>((resolve, reject) => {});

// todo : create custom hook that set response from api to state and return the state
function useData(id: number) {
  const [data, setData] = useState<number>();
  useEffect(() => {
    (async function () {
      setData(await getData(id));
    })();
  }, [id]);
  return data;
}

// what if api dont have type?
Enter fullscreen mode Exit fullscreen mode

จากข้างบนเราจะไม่รู้ว่า getData เป็น type อะไร เราอาจจะใส่ type ให้มันด้วยก็ได้
หรือไม่ก็ได้เพราะ reusult ออกไปก็ถูกต้องอยู่ดี

Chidren types

เข้าไปดู React.ReactNode

type ReactChild = ReactElement | ReactText;

type ReactNode =
  | ReactChild
  | ReactFragment
  | ReactPortal
  | boolean
  | null
  | undefined;

interface ReactElement<
  P = any,
  T extends string | JSXElementConstructor<any> =
    | string
    | JSXElementConstructor<any>
> {
  type: T;
  props: P;
  key: Key | null;
}
Enter fullscreen mode Exit fullscreen mode

เข้าไปดู PropsWithChildren (นาที52)

type PropsWithChildren<P> = P & { children?: ReactNode };
Enter fullscreen mode Exit fullscreen mode

หรือเราจะเขียน render prop

import React from "react";

type Props = {
  header: React.ReactNode; // can react  Children
  sidebar: React.ReactElement; // jsx
  footer: string;
  render: (value: boolean) => React.ReactNode;
};

export function Example({
  header,
  sidebar,
  footer,
  children,
}: React.PropsWithChildren<Props>) {
  const [state, setState] = useState(false);
  return (
    <div>
      <header>{header}</header>
      <div>{sidebar}</div>
      <div>{children}</div>
      <footer>{footer}</footer>
      {render(state)}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Compound component

มาดูกันว่าแบบนี้จะเขียน type ได้อย่างไร

// Grid.tsx
import React from "react";

// todo : create Grid component that contain Row & Column

// outside
function App() {
  return (
    <Grid.Row>
      <Grid.Column>
        <div>Content 1</div>
      </Grid.Column>
      <Grid.Column>
        <div>Content 2</div>
      </Grid.Column>
    </Grid.Row>
  );
}
Enter fullscreen mode Exit fullscreen mode

เพิ่มอันนี้เข้าไป

const Grid = ({ children }: React.PropsWithChildren<{}>) => {
  return <div>{children}</div>;
};

const Row = ({ children }: React.PropsWithChildren<{}>) => {
  return <div>{children}</div>;
};

const Column = ({ children }: React.PropsWithChildren<{}>) => {
  return <div>{children}</div>;
};

// อย่าลืมที่ขะบอกว่า Row อยู่ใน Grid
Grid.Row = Row;
Grid.Column = Column;
Enter fullscreen mode Exit fullscreen mode

หรือจะทำแบบนี้แทนก็ได้ (นาทที่1hr.02 ผมเองก็ยังไม่เข้าใจจุดนี้)

interface Grid {
  ({ children }: React.PropsWithChildren<{}>): React.ReactNode;
  Row: typeof Row;
  Column: typeof Column;
}

const Grid: Grid = () => {
  return <div />;
};
Enter fullscreen mode Exit fullscreen mode

Bye Bye Enum

นาทีที่ 1hr.04

import React from "react";

enum Colors {
  Red = "red",
  Blue = "blue",
  Green = "green",
  White = "white",
}

type Props = {
  color: Colors;
};
Enter fullscreen mode Exit fullscreen mode

นอกจากนี้ท่าที่ทำไม่ได้

cons c1:Color = 'red'
//ต้องใช้ท่านี้
const c1 = 'red' as Colors

Enter fullscreen mode Exit fullscreen mode

หรือจะทำเป็น Object

const Colors = {
 Red: 'red'
 Blue: 'blue'
}

type Prop ={
 color:Colors   // มันจะ error บอกว่าเอา Object มาทำ Type ไม่ได้
}

Enter fullscreen mode Exit fullscreen mode

ต้องแปลง Object เป็น type

const Colors = {
 Red: 'red'
 Blue: 'blue'
}
//typeof Colors = (typeof Colors) // ได้ struct Colors ละ
typeof Colors = (typeof Colors)[keyof typeof Colors] //return key

Enter fullscreen mode Exit fullscreen mode

code จะใช้ได้เหมือน enum

const Colors = {
  Red: "red",
  Blue: "blue",
} as const;

type Colors = typeof Colors[keyof typeof Colors];

type Props = {
  color: Colors;
};
Enter fullscreen mode Exit fullscreen mode

เราสามารถทำง่ายกว่านี้โดยใช้ enum

const Colors = {
  Red: "red",
  Blue: "blue",
} as const;
type Enum<T> = T[keyof T];
type Colors = Enum<typeof Colors>;

type Props = {
  color: Colors;
};
Enter fullscreen mode Exit fullscreen mode

ต่อจากนี้จะเป็น part 2

⚛️ React ไปวันๆ EP.5 - Typescript in React 101 part 2

React & html type that you should know

ลองดูตัวอย่างต่อไปนี้ที่จำทำให้ cursor ไป focus ที่ input

import React, { useRef, useEffect } from "react";

// todo : create input that will be focused if autoFocus prop is true
function Example({ autoFocus }: { autoFocus?: boolean }) {
  const inputRef = useRef();
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  return (
    <form>
      <label htmlFor="input">Label</label>
      <input id="input" ref={inputRef} />
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

แต่มันยังผิดอยู่ต้องเพิ่ม HtmlInputElement เพิ่ม optinal auto ให้ด้วย

import React, { useRef, useEffect } from "react";

// todo : create input that will be focused if autoFocus prop is true
function Example({ autoFocus }: { autoFocus?: boolean }) {
  const inputRef = useRef<HTMLInputElement>(null);
  useEffect(() => {
    inputRef.current?.focus();
  }, [autoFocus ]);
  return (
    <form>
      <label htmlFor="input">Label</label>
      <input id="input" ref={inputRef} />
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

ค่อยมาเขียนต่อเนื่องจากเนื้อหาเริ่มจะซับซ้อนขึ้น

Top comments (0)