191101 TIL TypeScript

» 1.5막, TIL (Today I Learned)

오늘 할 일

  1. TS 배우기
  2. MobX && TS && React 프로젝트 구현해보기

벨로퍼트님 강의 실습 정리

Greetings.tsx 만들기

npx create-react-app {프로젝트명} --typescript 

// Greetings.tsx
import React from 'react';

type GreetingsProps = {
    name: string;
    mark: string;
    optional?: string;
    onClick: (name: string) => void
};

const Greetings = ({name, mark, optional, onClick}: GreetingsProps) => {
    const handleClick = () => onClick(name);
    return (
        <div>
            Hello, {name} {mark}
            {optional && <p> {optional} </p>}
            <div>
                <button onClick={handleClick}>
                    click me
                </button>
            </div>
        </div>
    );
};

export default Greetings;

// App.tsx
import React from 'react';
import Greetings from "./components/Greetings";

const App: React.FC = () => {
  const onClick = (name: string) => console.log(name);

  return (
      <Greetings name={'soom'} mark={'!'} onClick={onClick}/>
  );
}

export default App;

greetings에서 왜 handleClick을 선언해줘야하나, 하고 그냥 내 맘대로 아래처럼 했는데,


<button onClick={onClick}>
    click me
</button>

에러남

Type '(name: string) => void' is not assignable to type '(event: MouseEvent<HTMLButtonElement, MouseEvent>) => void'.
Types of parameters 'name' and 'event' are incompatible.
Type 'MouseEvent<HTMLButtonElement, MouseEvent>' is not assignable to type 'string'.

e 인자를 받아오는데, 그게 string이 아니라는 뜻 같음. 그래서 아래처럼 해주면 된다.

<button onClick={(e) => onClick(name)}>
    click me
</button>

MyForm.tsx 만들기

// MyForm.tsx
import React, { useState } from 'react';

type FormType = {
    name: string;
    description: string
}

type MyFormProps = {
    onSubmit: (form: FormType) => void;
}

export default function MyForm({ onSubmit }: MyFormProps) {
    const [form, setForm] = useState({ name: '', description: '' });
    const { name, description } = form;

    const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const { name, value } = e.target;
        setForm({ ...form, [name]: value })
    }

    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        onSubmit(form);
        setForm({ name: '', description: '' })
    }

    return (
        <form onSubmit={handleSubmit}>
            <input name="name" value={name} onChange={onChange}/>
            <input name="description" value={description} onChange={onChange}/>
            <button type="submit">등록</button>
        </form>
    )
}

// App.tsx 
const App: React.FC = () => {
    const onSubmit = (form: { name: string; description: string}) => {
        console.log(form);
    }
    return <MyForm onSubmit={onSubmit}/>
}

Counter.tsx 만들기

// Counter.tsx
import React, { useReducer } from 'react';

type Action = {type: 'INCREASE'} | {type: 'DECREASE'}

function reducer(state: number, action:Action): number {
    switch (action.type) {
        case "INCREASE": return state + 1;
        case "DECREASE": return state - 1;
        default: throw new Error ('unhandled action type...')
    }
}

export default function Counter() {
    const [count, dispatch] = useReducer(reducer, 0)
    const onIncrease = () => dispatch({type: "INCREASE"});
    const onDecrease = () => dispatch({type: "DECREASE"});

    return (
        <div>
            <h1>{count}</h1>
            <div>
                <button onClick={onIncrease}> +1 </button>
                <button onClick={onDecrease}> -1 </button>
            </div>
        </div>
    )
}

###Context 만들기

// SampleContext.tsx
import React, { createContext, Dispatch, useContext, useReducer } from "react";

type Color = 'red' | 'yellow' | 'black';

type State = {
    count: number;
    text: string;
    color: Color;
    isGood: boolean
}

type Action =
    | { type: 'SET_COUNT'; count: number }
    | { type: 'SET_TEXT'; text: string }
    | { type: 'SET_COLOR'; color: Color }
    | { type: 'TOGGLE_GOOD' }


function reducer(state: State, action: Action): State {
    switch (action.type) {
        case "SET_COUNT":
            return { ...state, count: action.count };
        case "SET_TEXT":
            return { ...state, text: action.text };
        case "SET_COLOR":
            return { ...state, color: action.color };
        case "TOGGLE_GOOD":
            return { ...state, isGood: !state.isGood };
        default:
            throw new Error('에 러 발 생!')
    }
}

const SampleStateContext = createContext<State | null>(null);
const SampleDispatchContext = createContext<Dispatch<Action> | null>(null);

type SampleProviderProps = {
    children: React.ReactNode;
}

export function SampleProvider({children} : SampleProviderProps) {
    const [state, dispatch] = useReducer(reducer, {
        count: 0,
        text: 'hihihihi',
        color: 'red',
        isGood: true
    });

    return (
        <SampleStateContext.Provider value={state}>
            <SampleDispatchContext.Provider value={dispatch}>
                {children}
            </SampleDispatchContext.Provider>
        </SampleStateContext.Provider>
    )
}


export function useSampleState() {
    const state = useContext(SampleStateContext);
    if (!state) throw new Error('error')
    return state;
}

export function useSampleDispatch() {
    const dispatch = useContext(SampleDispatchContext);
    if (!dispatch) throw new Error('errorrrrr');
    return dispatch;
}

//ReduceSample.tsx
import React from 'react';
import { useSampleDispatch, useSampleState } from "../SampleContext";

export default function ReducerSample() {
    const state = useSampleState();
    const dispatch = useSampleDispatch();

    const setCount = () => dispatch({ type: "SET_COUNT", count: 5 });
    const setText = () => dispatch({ type: "SET_TEXT", text: '룰루' });
    const setColor = () => dispatch({ type: "SET_COLOR", color: 'red' });
    const toggleGood = () => dispatch({ type: "TOGGLE_GOOD" });

    return (
        <div>
            <p> <code> Count : </code> {state.count} </p>
            <p> <code> Text : </code> {state.text} </p>
            <p> <code> Color : </code> {state.color} </p>
            <p> <code> isGood : </code> {state.isGood ? 'true' : 'false'} </p>

            <div>
                <button onClick={setCount}> set count </button>
                <button onClick={setText}> set text </button>
                <button onClick={setColor}> set color </button>
                <button onClick={toggleGood}> toggle </button>
            </div>
        </div>
    )
}

//App.tsx 
<SampleProvider> 
    <ReducerSample/>
</SampleProvider>

으으.. contextAPI 너무 어려워…