diff --git "a/\354\234\244\354\203\201\354\244\200/8.1~8.3.md" "b/\354\234\244\354\203\201\354\244\200/8.1~8.3.md" new file mode 100644 index 0000000..537f5af --- /dev/null +++ "b/\354\234\244\354\203\201\354\244\200/8.1~8.3.md" @@ -0,0 +1,495 @@ +```jsx +yarn global add typescript +``` + +```jsx +tsc --init +``` + +위 명령어를 입력하면 tsconfig.json이 생성됩니다. + +- **target**: 컴파일된 코드가 어떤 환경에서 실행될 지 정의합니다. 예를들어서 화살표 함수를 사용하고 target 을 es5 로 한다면 일반 function 키워드를 사용하는 함수로 변환을 해줍니다. 하지만 이를 es6 로 설정한다면 화살표 함수를 그대로 유지해줍니다. +- **module**: 컴파일된 코드가 어던 모듈 시스템을 사용할지 정의합니다. 예를 들어서 이 값을 common 으로 하면 `export default Sample` 을 하게 됐을 때 컴파일 된 코드에서는 `exports.default = helloWorld` 로 변환해주지만 이 값을 es2015 로 하면 `export default Sample` 을 그대로 유지하게 됩니다. +- **strict**: 모든 타입 체킹 옵션을 활성화한다는 것을 의미합니다. +- **esModuleInterop**: commonjs 모듈 형태로 이루어진 파일을 es2015 모듈 형태로 불러올 수 있게 해줍니다. [(참고)](https://stackoverflow.com/questions/56238356/understanding-esmoduleinterop-in-tsconfig-file) + +이런 값들이 있습니다. + +```jsx +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "outDir": "./dist" + } +} +``` + +추가적으로 outDir라는 설정을 추가해줍니다. 타입스크립트는 바로 브라우저에서 동작을 할 수 없기 때문에 컴파일과정을 거쳐서 자바스크립트로 변환해주어야합니다. 이때 변환된 자바스크립트가 생성될 위치를 outDir속성으로 설정해줍니다. + +실제로 tsc—init을 통해 생성된 tsconfig.json은 조금 다른 형식이라서 위에 설정으로 해주겠습니다. + +```jsx +const message: string = "hello world"; +console.log(message); +``` + +자바스크립트와 굉장히 유사하지만 타입스크립트의 이름답게 : 뒤에 string이라는 데이터타입을 붙여줍니다. + +그리고 tsconfig.json이 있는 디렉토리에서 tsc 명령어를 입력하면 아까 설정한 outDir의 dist폴더가 생성되고 그안에 자바스크립트가 생성되었습니다. + +```jsx +"use strict"; +var message = "hello world"; +console.log(message); +``` + +생성된 자바스크립트는 tsconfig.json에서 es5를 기준으로 변환했기 때문에 const 키워드가 아닌 var를 이용해서 변수가 선언되었습니다. + +```jsx +let count = 0; // 숫자 +count += 1; +count = "갑자기 분위기 문자열"; // 이러면 에러가 납니다! + +const message: string = "hello world"; // 문자열 + +const done: boolean = true; // 불리언 값 + +const numbers: number[] = [1, 2, 3]; // 숫자 배열 +const messages: string[] = ["hello", "world"]; // 문자열 배열 + +messages.push(1); // 숫자 넣으려고 하면.. 안된다! + +let mightBeUndefined: string | undefined = undefined; // string 일수도 있고 undefined 일수도 있음 +let nullableNumber: number | null = null; // number 일수도 있고 null 일수도 있음 + +let color: "red" | "orange" | "yellow" = "red"; // red, orange, yellow 중 하나임 +color = "yellow"; +color = "green"; // 에러 발생! +``` + +다른 선언방법은 : 뒤에 데이터 타입을 명시하는 것인데 조금 특이하게 배열은 데이터타입[]으로 명시를 해주면 배열내부의 값들은 명시한 데이터타입만 올 수 있습니다. 그리고 | 을 이용하면 여러가지 데이터타입을 명시할 수 있습니다. 또한 데이터타입이 아닌 명시적인 값을 넣으면 해당 값들만 넣을수 있습니다. + +```jsx +function sum(x: number, y: number): number { + return "ㅎㅎ"; +} + +sum(1, 2); +``` + +함수에서도 동일합니다. 추가적으로 함수의 반환값 또한 타입을 결정할 수 있습니다. 위 코드처럼 변수를 제대로 받더라도 반환값의 타입이 맞지않으면 컴파일 과정에서 오류가 납니다. + +굉장히 친절합니다. + +```jsx +// Shape 라는 interface 를 선언합니다. +interface Shape { + getArea(): number; // Shape interface 에는 getArea 라는 함수가 꼭 있어야 하며 해당 함수의 반환값은 숫자입니다. +} + +class Circle implements Shape { + // `implements` 키워드를 사용하여 해당 클래스가 Shape interface 의 조건을 충족하겠다는 것을 명시합니다. + + radius: number; // 멤버 변수 radius 값을 설정합니다. + + constructor(radius: number) { + this.radius = radius; + } + + // 너비를 가져오는 함수를 구현합니다. + getArea() { + return this.radius * this.radius * Math.PI; + } +} + +class Rectangle implements Shape { + width: number; + height: number; + constructor(width: number, height: number) { + this.width = width; + this.height = height; + } + getArea() { + return this.width * this.height; + } +} + +const shapes: Shape[] = [new Circle(5), new Rectangle(10, 5)]; + +shapes.forEach((shape) => { + console.log(shape.getArea()); +}); +``` + +여기서 Interface라는 키워드가 나오는데 이거는 클래스와 객체의 타입을 추가하는 키워드입니다. 선언된interface는 클래스에 implements 라는 키워드를 통해 이 객체와 클래스가 해당 interface의 타입을 지키지 않을때 오류를 발생시킵니다. + +interface는 커스텀 데이터타입이라고 할 수 있습니다. 배열과 객체안에서도 : 뒤에 interface를 붙이는 동일한 방식으로 타입을 설정 할 수 있습니다. + +```jsx +interface Person { + name: string; + age?: number; // 물음표가 들어갔다는 것은, 설정을 해도 되고 안해도 되는 값이라는 것을 의미합니다. +} +interface Developer extends Person { + skills: string[]; +} + +const person: Person = { + name: "김사람", + age: 20, +}; + +const expert: Developer = { + name: "김개발", + skills: ["javascript", "react"], +}; + +const people: Person[] = [person, expert]; +``` + +interface는 클래스처럼 상속이 가능합니다. extends 키워드를 이용해서 상위 interface의 타입 값을 그대로 받아와서 하위 inteface에서 추가하거나 사용 할 수 있습니다. + +```jsx +type Person = { + name: string, + age?: number, +}; + +type Developer = Person & { + skills: string[], +}; + +const person: Person = { + name: "name", + age: 12, //안써도 됨 +}; + +const dev: Developer = { + name: "name", + age: 123, + skills: ["html", "css", "js"], +}; +``` + +type이라는 키워드가 나왔는데 이것은 해당 틀에 맞춰서 작성해야하는 타입입니다. &의 의미는 type를 합성하겠다는 의미입니다. dev를 보면 Person타입이 가져야할 속성과 새로추가된 skills 속성을 모두 가지고 있으므로 괜찮습니다. 하지만 여기서 name이나 skills의 빼거나 타입의 어긋나게 사용하면 오류가 발생합니다. + +```jsx +function merge(a: A, b: B): A & B { + return { + ...a, + ...b, + }; +} + +const merged = merge({ foo: 1 }, { bar: 1 }); +``` + +Generic은 위 코드와 같이 음 ... 타입을 위임한다? 라고 할수 있습니다. 함수에 어떤 타입이 들어올지 모를때 해당 타입까지 인자로 받아서 반환값의 타입을 결정 할 수 있습니다. + +```jsx +function merge(a: A, b: B): A & B { + return { + ...a, + ...b, + bar: "2", + }; + return { + foo: 1, + bar: 1, + }; +} + +type Foo = { + foo: number, +}; +type Bar = { + bar: number, +}; + +const foo: Foo = { + foo: 1, +}; +const bar: Bar = { + bar: 1, +}; +const merged = merge(foo, bar); +console.log(merged); +``` + +위에 코드가 이상하다. + +# 타입스크립트 리액트 적용 + +```jsx +npx create-react-app my-app --template typescript +``` + +위에 명령어를 입력해야 정상적인 타입스크립트 리액트 프로젝트가 생성됩니다. + +```jsx +import React from "react"; + +type GreetingsProps = { + name: string, +}; + +const Greetings: React.FC = ({ name }) => ( +
Hello, {name}
+); + +export default Greetings; +``` + +React.FC는 인자로 들어오는 props의 타입을 정해줍니다. Generic을 이용해서 타입체크를 해줍니다. React.FC는 화살표함수에서 쓰이는 것이고 함수형에서 쓸경우에는 + +```jsx +import React from "react"; + +type GreetingsProps = { + name: string, + mark: string, + optional?: string, +}; + +function Greetings({ name, mark, optional }: GreetingsProps) { + return ( +
+ Hello, {name} {mark} + {optional &&

{optional}

} +
+ ); +} + +Greetings.defaultProps = { + mark: "!", +}; + +export default Greetings; +``` + +이렇게 쓰면 됩니다. React.FC를 쓰면 자동완성하는 장점이 있는데 안쓰는게 좋습니다. + +```jsx +type GreetingsProps = { + name: string, + mark: string, + optional?: string, + onClick: (name: string) => void, // 아무것도 리턴하지 않는다는 함수를 의미합니다. +}; +function Greetings({ name, mark, optional, onClick }: GreetingsProps) { + const handleClick = () => onClick(name); + return ( +
+ Hello, {name} {mark} + {optional &&

{optional}

} +
+ +
+
+ ); +} + +Greetings.defaultProps = { + mark: "!", +}; + +export default Greetings; +``` + +함수를 받아와서 리턴타입을 지정하는 것도 가능합니다. + +# 타입스크립트로 상태관리 + +## useState 타입스크립트 + +```jsx +import React, { useState } from "react"; + +function Counter() { + const [count, setCount] = useState < number > 0; + const onIncrease = () => setCount(count + 1); + const onDecrease = () => setCount(count - 1); + return ( +
+

{count}

+
+ + +
+
+ ); +} + +export default Counter; +``` + +useState<타입> 형식으로 generic을 설정해주면 해당 타입만을 허용하는 useState가 완성됩니다. + +```jsx +import React, { useState } from "react"; + +function Counter() { + const [count, setCount] = useState(0); + const onIncrease = () => setCount(count + 1); + const onDecrease = () => setCount(count - 1); + return ( +
+

{count}

+
+ + +
+
+ ); +} + +export default Counter; +``` + +근데 generic을 안넣어도 알아서 잘 유추한다고 합니다. 그러면 왜 쓸까요? + +```jsx +type Information = { name: string, description: string }; +const [info, setInformation] = (useState < Information) | (null > null); +``` + +이런 식으로 초기 타입이랑 nextStat의 타입이 다를경우 이런식으로 활용해줍니다. + +```jsx +import React, { useState } from "react"; + +type MyFormProps = { + onSubmit: (form: { name: string, description: string }) => void, +}; + +function MyForm({ onSubmit }: MyFormProps) { + const [form, setForm] = useState({ + name: "", + description: "", + }); + + const { name, description } = form; + + const onChange = (e: any) => { + // e 값을 무엇으로 설정해야할까요? + // 일단 모를떄는 any 로 설정합니다. + }; + + const handleSubmit = (e: any) => { + // 여기도 모르니까 any 로 하겠습니다. + }; + + return ( +
+ + + +
+ ); +} + +export default MyForm; +``` + +React 이벤트 타입을 확인하기 위해서는 onChange에 마우스를 올리면 이벤트 반환 타입을 보여줍니다 + +```jsx +import React, { useState } from "react"; + +type MyFormProps = { + onSubmit: (form: { name: string, description: string }) => void, +}; + +function MyForm({ onSubmit }: MyFormProps) { + const [form, setForm] = useState({ + name: "", + description: "", + }); + + const { name, description } = form; + + const onChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setForm({ + ...form, + [name]: value, + }); + }; + + const handleSubmit = (e: React.FormEvent) => { + // 여기도 모르니까 any 로 하겠습니다. + e.preventDefault(); + onSubmit(form); + setForm({ + name: "", + description: "", + }); // 초기화 + }; + + return ( +
+ + + +
+ ); +} + +export default MyForm; +``` + +```jsx +import React from "react"; +import MyForm from "./MyForm"; + +const App: React.FC = () => { + const onSubmit = (form: { name: string, description: string }) => { + console.log(form); + }; + return ; +}; + +export default App; +``` + +## useReducer 타입스크립트 + +```jsx +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"); + } +} + +function Counter() { + const [count, dispatch] = useReducer(reducer, 0); + const onIncrease = () => dispatch({ type: "INCREASE" }); + const onDecrease = () => dispatch({ type: "DECREASE" }); + + return ( +
+

{count}

+
+ + +
+
+ ); +} + +export default Counter; +``` + +타입스크립트를 사용할 경우 액션의 타입지정과 자동완성이라는 장점이 있습니다.