###### tags: `前端` TyepScript === Ts 是js的一種泛型 我們可以透過下面指令來安裝ts 有時候要注意權限問題,可以前面加上sudo ``` npm install -g typescript ``` 在創建ts檔案的時候,我們常常都要使用tsc來做轉換 去把我們的.ts檔案轉成.js檔案 ``` tsc <file.name> ``` 但後來可以使用tsc --init裡面去設定input跟output的資料夾,這樣可以設定好這些東西 主要針對第29行跟 58來做建立設定input output資料夾位置 ``` { "compilerOptions": { /* Visit https://aka.ms/tsconfig to read more about this file */ /* Projects */ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ "module": "commonjs", /* Specify what module code is generated. */ "rootDir": "./src", /* Specify the root folder within your source files. */ // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */ /* JavaScript Support */ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ /* Emit */ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ // "declarationMap": true, /* Create sourcemaps for d.ts files. */ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ "outDir": "./dist", /* Specify an output folder for all emitted files. */ // "removeComments": true, /* Disable emitting comments. */ // "noEmit": true, /* Disable emitting files from a compilation. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ // "newLine": "crlf", /* Set the newline character for emitting files. */ // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ /* Interop Constraints */ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ /* Type Checking */ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ } } ``` 記得要建立資料夾,名稱可以自己改變 但這樣又太懶惰了 所以我們可以設定--watch在我們每次變更.ts檔案的時候他自動偵測自動轉成js,但記得要丟在你設定的資料夾中歐 ``` tsc --watch ``` 然後因為這樣也很亂,我們也可以透過node.js去抓我們的js檔案來自動執行,node的18.11版本以上才有這樣的功能. ``` node --watch dist/index.js ``` ## Array 我們也可以在不同的datatype做成array 下面分別是一個string跟number的array,若是混用會出現error ``` let names: string[]= ['mario', 'peach', 'luigi', '56'] let ages: number[]= [25, 18, 16, 40] ``` 但若是下面這樣混用,datatype也會混亂 這樣f的datatype就會顯示成number, string , boolean 因為他不知道這個array會怎樣變化,雖然現在是暫態但不確定不改變 ``` let things =[1,'mango', true] let f =things[0] ``` 若是object可以這樣寫 ``` let user: {firstName: string, age: number, id: number} ={ firstName: 'mario', age: 30, id: 1 } ## 修改資料但要符合datatype user.id=5 user.age=35 ``` 若要修改就是照著datatype來做即可 下面寫法也可 ``` let person={ name: 'luigi', scoer: 35 } ``` ## Tuple 早期tuple使用上並沒有辦法用名稱,這導致你只看到datatype不知道你該填入那些位置。 ``` let person: [string, number, boolean] = ['mario', 30, false] ``` ``` function useCoords():[number, number] { const lat = 100 const long = 50 return [lat, long] } const [lat, long] = useCoords() ``` 但近期可以使用了,也更容易讓你知道不同格式分別是哪個variable要被定義 現在可以用下列的方式表述 ``` let user: [ name: string, age: number] user = ['peach', 25] console.log(user[0]) ``` ## INTERFACE interface可以定義一個對象有多少屬性,當然interface裡面也可以內嵌interface ``` interface Author{ name: string, avatar: string } const authorOne: Author ={name:'mario', avatar: '/img/mario.png'} ``` 內嵌interface ``` interface Post { title: string body: string tags: string[] create_at: Date author: Author } const newPost: Post ={ title: 'my post', body: 'somethign good', tags: ['gaming', 'tech'], create_at: new Date(), author:authorOne } ``` function也可以這樣寫入 ``` function createPost(post: Post): void{ console.log(`Created post ${post.title} by ${post.author.name}`) } createPost(newPost) ``` interface也可以寫成ARRAY ``` let posts: Post[] = [] posts.push(newPost) ``` ## Custom Type 除了INTERFACE,我們也可以使用自定義的TYPE 像是自訂tuple(但要注意interface不用用等於,直接括號括起來就好) ``` type Rgb = [number, number, number] function color(x: number): Rgb{ const r = x*255 const g = x*255 const b = x*255 return [r, g, b] } console.log(color(5)) ``` 或是可以自訂object 要記得這種自定義的方法都要給等號 ``` type User ={ name: string score: number } const userOne: User = {name: 'mario', score: 52} function formatuser(user: User): void{ console.log(`${user.name}`) } formatuser(userOne) ``` ## Union 讓同一種variable可以擁有一種以上的datatype ``` let someId: number | string someId=5 someId='he' ``` 若是你要收email也可以這樣寫 ``` let emailAddress: string|null emailAddress = 'hello@gm.tw' emailAddress = null ``` 但有時候你不太確定你放進去的datatype是哪種,你就可以用typeguard的方式來寫 下面的寫法就可以知道裡面你放進去的DATATYPE來做不同的回覆 ``` type Id= number | string function swapIdType(id: Id) { if (typeof id === 'string') { return parseInt(id) } else { id.toString() } } const idOne = swapIdType(55) const idTwo = swapIdType('2') console.log(idOne, idTwo) ``` Type Guard也可以用來辨識兩種不同的interface ``` interface User{ type: 'user' username: string email: string id: Id } interface User{ type: 'person' firstname: string age: number id: Id } function logDetails(value: User | Person): void { if (value.type === 'user') { console.log(value.username, value.email) } if (value.type === 'person') { console.log(value.firstname, value.age) } } ``` ## Interface Type interface可以重複被使用,我們看下面的example hasquantity定義了一個quantity 是number型態 但下面的fruit vehicle or person都有除了quantity以外的attribute,但仍可以被視作是hasQuantity的實作。 接下去看printQuantity若直接把quantity放入也可以直接輸出 但若是直接輸入兩個不同的key value就會報錯,跟上面給的vehicle他們狀況不同,直接給就不能這樣寫,這要注意 ``` interface hasQuantity { quantity: number } const something: hasQuantity = { quantity: 50 } function printQuantity(item: hasQuantity): void { console.log(`the quantity of the itme ${item.quantity}`) } const fruit = { name: 'mango', quantity: 50, } const vehicle = { name: 'car', quantity: 3, } const person ={ name: 'mario', age: 5, quantity: 50 } printQuantity(fruit) printQuantity(vehicle) printQuantity({quantity: 50}) printQuantity({quantity:50, apple: 80}) # not allow ``` ## Function Signature function signature可以讓你重複使用驗證這些type,以下幾個範例是可以走的 joinTwoNumbers儘管輸入相同,但出來的格式是string,所以不行會報錯 squareNumber儘管只有一個argument,但出來的數字也是number,所以會通過。 基本上你的datatype相同,但少於本身規定的argument數量沒關係,不要超過即可,就不會報錯。 ``` type Calculator = (numOne: number, numTwo: number) => number function addTwoNumber(a:number, b: number){ return a + b } function multiplyTwoNumbers(first: number, Second: number){ return first * Second } function squareNumber(num: Number){ return num * num } function joinTwoNumbers(numOne: number, numTwo: number){ return `${numOne}${numTwo}` } let calcs: Calculator[] = [] calcs.push(addTwoNumber) calcs.push(multiplyTwoNumbers) calcs.push(joinTwoNumbers) # not allow calcs.push(squareNumber) ``` 當然,把function放到interface中也是可以的 只要最終的數字相同,也可以這樣把他抓出來計算 ``` interface HasArea { name: string calcArea: (a: number) => number } const shapeOne: HasArea = { name: 'square', calcArea(l: number) { return l * l } } const shapeTwo: HasArea = { name: 'circle', calcArea(r: number) { return Math.PI * r^2 } } shapeOne.calcArea(5) shapeTwo.calcArea(3) ``` 當然,interface也可以改寫法 ``` interface HasArea { name: string calcArea(a: number) :number } ``` ## Extending Interface 比較像繼承的狀況先寫一個HasFormatter然後Bill繼承他 ``` interface HasFormatter { format(): string } interface Bill extends HasFormatter{ id: string | number amount: number server: string } ``` 然後建立兩個常數 ``` const user = { id: 1, format(): string { return `this user has a id of ${this.id}` } } const bill = { id: 2, amount: 50, server: 'mario', format(): string { return `Bill with id ${this.id} has E ${this.amount} to pay` } } ``` 最後下面加上兩個function ``` function printFormatted(val: HasFormatter): void { console.log(val.format()) } function printBill(bill: Bill): void{ console.log(`server: ${bill.server}`) console.log(bill.format()) } printFormatted(user) printFormatted(bill) printBill(bill) printBill(user) #NOT ALLOW ``` 可以發現PrintFormatted 可以使用bill因為bill也是他的部分 但printBill 不能用user,因為他沒有bill的其他attribute ### Extending Type Alias 我們可以使用 & 來擴展type ``` type Person ={ id: string |number first: string } type User = Person & { email: string } ``` 這樣User的type就如同以下 ``` type User ={ id: string |number first: string email: string } ``` 接下來我們寫幾個variable ``` const personOne ={ id: 1, firstName: 'mario' } const personTwo: User ={ id: 1, first: 'Yoshi', email: 'Yoshi.net@dev.com' } const personThree ={ email: 'peach@netninja.dev' } ``` 最後來寫進去一個FUNCTION來測試 可以發現personOne是沒有的,因為他沒有email。 personThree也是不能用的,因為他只有email ``` function printUser(user: User) { console.log(user.id, user.email, user.first) } printUser(personOne) // Not Allow printUser(personTwo) printUser(personThree) //Not Allow ``` ## Generic 一般而言一個function若要重複利用且可以回傳多個type,我們不會使用any,也不會不停的寫出不同return type的 function這會導致使用上很困難且麻煩,這時候就會使用Generic來做使用以下是三個一般 type 跟generic的寫法 ``` function logAndReturnString(val: string): string { console.log(val) return val } function logAndReturnNumber(val: number ): number { console.log(val) return val } function logAndReturnBoolean(val: Boolean): Boolean{ console.log(val) return val } function logAndReturnValue<T>(val: T): T { console.log(val) return val } ``` 同時,一個Generic也可以跟其他種類一起output 寫法如下 ``` interface HasID { id: number } function addIdToValue<T>(val: T):T & HasID { const id = Math.random() return { ...val, id } } ``` 最終也可以跟interface一起結合再輸出 ``` interface Post { title: string thumbsUp: number } const post = addIdToValue<Post>({ title: 'marmite rules', thumbsUp: 250}) console.log(post.title, post.id, post.thumbsUp) ``` #### Generic Interface 可以把interface跟Generic結合 ``` interface Collection<T> { data: T[] name: string } ``` 也可以特別做出指定的type,若沒有輸入type也會完整的顯示你輸入的那種type直接採用 ``` function randomCollectionItem<T>(c: Collection<T>): T { const i = Math.floor(Math.random() * c.data.length) return c.data[i] } const result1 = randomCollectionItem<string>(collectionOne) const result2 = randomCollectionItem(collectionTwo) ``` ## Generic 限制 雖然Gneric 可以輸入任何型態,但你也可以要求他一定要繼承某種型態 可以這樣寫 下面寫法就可以知道不管怎樣他都會有HasId這種型態了。 ``` interface HasId { id: number } class DataCollection<T extends HasId> { constructor(private data: T[]){} loadOne(): T { const i = Math.floor(Math.random() * this.data.length) return this.data[i] } loadALL(): T[] { return this.data } add(val: T): T[] { this.data.push(val) return this.data } deleteOne(id: number): void { this.data = this.data.filter((item) => item.id !== id) } } ``` ## Web 開發使用TS 我們所知道我們現在都使用JS在前端框架來做開發,但我們在TS使用的時候若是要先轉成js再輸出包裝,這都是非常耗時間的行為 所以我們就會下載來使用parcel來做相關的處理直接把ts code跟html連接與打包。 記得html body tag關閉之前,使用parcel都要記得把ts檔案跟html連結 記得要寫下面的script ``` <script src="./ts/index.ts" type="module"></script> ``` 也附上原文 ``` <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" href="./styles/index.css" /> <title>Homepage</title> </head> <body> <nav> <h1>Pizza Palace Menu Editor</h1> <a href="create.html" class="btn">Add new Pizza</a> </nav> <main class="root wrap"> <!-- menu content goes here --> </main> <script src="./ts/index.ts" type="module"></script> </body> </html> ``` 接著我們可以把parcel加入dev其中,可以使用npx指令把他打包 ``` npx parcel src/*.html ``` ### return type typescript中若是想回傳或是輸入dom的資料格式,就要寫Element ``` function rednerTemplates(templates: string[], parent: Element): void { const templateElement = document.createElement('template') for (const t of templates) { templateElement.innerHTML += t } parent.append(templateElement.content) } ``` 有時候寫東西回傳的會是null會沒辦法處理,若要避免他們成為NULL(若是你要回傳string 但ts會判定為string | null),最後面加上!就好 ``` const rootElement = document.querySelector('.root')! ``` 使用ts在dom上 ``` import { PizzaProps, Pizza } from "./models/Pizza" const titleInput = document.querySelector( 'input[name="title"]' ) as HTMLInputElement const descriptionInput = document.querySelector( 'textarea' ) as HTMLTextAreaElement const form = document.querySelector('.create') as HTMLFormElement form.addEventListener('submit', async(e) =>{ e.preventDefault() const data = new FormData(form) const newPizza: PizzaProps = { title: data.get('title') as string, description: data.get('description') as string, toppings: data.getAll('toppings') as string[], price: parseInt(data.get('price') as string) } const res = await Pizza.save(newPizza) if(!res.ok) { console.log('not able to save the pizza') } if (res.ok) { window.location.href ='/' } }) ``` ## Next Js 實作 書寫的時候常常會有一些寫法讓component可以被使用但有時太過複雜就會寫成下面這樣,主要還是postcard的props可以被放入title跟author他們的type為string ``` export default function PostCard({ title, author, }: { title: string author: string }) { return ( <div className='card'> <h2>{title}</h2> <p>Written by {author}</p> </div> ) } ``` 當然也可以利用interface 寫成下面這樣,更加方便 ``` interface PostCardProps { title: string author: string } export default function PostCard({ title, author, }: PostCardProps) { return ( <div className='card'> <h2>{title}</h2> <p>Written by {author}</p> </div> ) } ``` 若要把interface寫出來讓大家共用,有時候會寫一個types.d.ts的檔案,讓所有人都可以拿到這個公開資料檔案的interface可以去繼承跟實作 postcard.tsx ``` interface PostCardProps { post: Post } export default function PostCard({post}: PostCardProps) { return ( <div className='card'> <h2>{post.title}</h2> <p>Written by {post.body}</p> </div> ) } ``` page.tsx ``` import PostCard from "@/components/PostCard" const fetchPosts = async(): Promise<Post[]> => { const res = await fetch('https://jsonplaceholder.typicode.com/users/1/posts') if(!res.ok) { console.log('could fetch the post ') } return res.json() } export default async function Home() { const posts = await fetchPosts() return ( <main> <h2>Home</h2> {posts.map((post) =>( <PostCard key={post.id} post={post} /> ))} </main> ) } ``` type.d.ts ``` interface Post { id: number title: string body: string } ``` ## Set in typescript 一般的set ``` const names = new Set<string>() names.add('mario') names.add('peach') names.add('luigi') names.add('mario') console.log(names) ``` custom interface with set ``` nterface User { email: string score: number } const user1:User = { email:'mario@gmail.com', score: 50 } const user2:User = { email:'apple@gmail.com', score: 80 } const users = new Set<User>() users.add(user1) users.add(user2) users.add(user1) console.log(users) ``` Set with function ``` function logUserEmails(users: Set<User>): void { users.forEach((user) => console.log(user.email)) } logUserEmails(users) ``` ## Enum 有時候我們寫TS會寫下面這function ``` function addTicket(details: string, Priority: number){ } ``` 但你的number有時候是表達先後順序或是大小,但你沒有定義的話就不知道數字含義,所以可以使用Enum ``` enum Priority { Lowest = 0, Low = 1, Medium = 2, High = 3, Urgent = 4, } ``` 這樣就可以把function寫成 ``` function addTicket(details: string, priority:Priority) { if (priority === 0 ) { } if (priority === 1 ) { } if (priority === 2) { } if (priority === 3) { } } ``` 甚至輸出或是if else也可以寫成這樣 ``` function addTicket(details: string, priority:Priority) { if (priority === Priority.Urgent ) { } if (priority === 1 ) { } if (priority === 2) { } if (priority === 3) { } } ```