# React + Typescript - React compelte guide ###### tags: `Javascript, React, Typescript` # Module Intro > Typescript is a "superset" to Javascript > 指的是 typescript 是 Javascript 的語法延伸 簡單舉個例子可以使用 Typscript ```javascript= function add(a,b){ return a+b; } const result = add(2,5); console.log(result); ``` 這邊是個簡單的 JS function ,並且會在瀏覽器的 console 內顯示 7,這邊沒有問題 但是如果我們在 add 內部放入 `add('2','5')`,顯示的結果會是 25 ,因為JS是動態型別的語言,問題來了如果在大型專案底下,共同協作的人不知道這邊的 add function 應該填入的其實是數字型別的話就會出大錯而找不到問題了,Typescript 這時候就可以進場了! ![](https://i.imgur.com/Mc26nSk.png) 可以從上圖中看出,給於參數設定好型別,下方帶入參數如果沒有對應好則會直接 IDE 告訴你出錯了,非常方便 # Installing & Using Typescript `npm install typescript` 需要注意的地方是 Typescript 的扣不會跑在瀏覽器裡面,因此我們必須 compile Typescript to Javascript ,因此需要使用 compiler `npx tsc 後方填入 typescript設定檔檔名` ![](https://i.imgur.com/d06zJl3.png) 輸入之後會產生一個新的 JS 檔案幾乎跟 TP 的內容一樣,這時候這個 JS 檔就可以引入 index.html 瀏覽器就可以使用它摟 ! # Exploring the Base Types ## Primitives: numbers, strings, boolean, symbols, null, undefined 這邊的型別要注意的是大小寫 **如果 number 寫成 Number 會變 Number 物件在 JS 中** ```typescript= let age:number = 8; let userName:string = 'Mark'; let isInstructor:boolean = true; ``` 當然 null, undefined 也可以這樣處理,只是一般不會這樣使用,會在下面的章節繼續介紹 ## More complex types: arrays, objects 這邊可以針對 array 的內容做型別設定 ```typescript= let hobbies: string[] = ['Sports','Cooking']; // 只能放入字串進這個 array ``` 這邊針對物件做設定,首先我們不做任何設定卻沒有得到錯誤是因為 當不設定 let person 時其實它預設的型別就是 any ,也可以寫進去設定 any 不過一般不會這樣使用因為這樣就是一般的 JS 了 ```typescript= let person; person = { name:'Max', age:32 } ``` 因此一般物件的寫法可以這樣寫,來設定好物件內部 value 的型別,然而只要 key, value 任一個值不符合規定就會顯示錯誤訊息 ```typescript= let person:{ name:string; age:number; } ``` 這邊的這個物件就會顯示錯誤訊息,因為 key, value 的部分都不符合設定 ```typescript= person = { isEmployee:true } ``` ### 結合 array, object 可以這樣設定就會呈現出一個都是 people 物件的 array 搂 ! ```typescript= let people:{ name:string; age:number; }[]; ``` # Type inference > Typescript 有個很強大的功能就是會自動推測你的變數的型別 以下圖為例,明明還沒有定義型別給它,它卻已經自動報錯了 ![](https://i.imgur.com/t3nnU4K.png) 這個特色可以讓使用 Typescript 減少很多打字的時間,因為 Type inference 這個特性 # Using Union Types > Union Types 代表可以允許不只一種 types 存在 讓下方的程式碼不報錯 ```typescript= let course: string | number = 'React'; course = 12341; ``` # Understanding Type Aliases(別名) > 就像是把常用的 type 建立一個模型,用來套入其他的變數當中使用被稱為 Type alias ,可以避免重複撰寫型別設定 以下方程式碼為例,使用 type 關鍵字寫型別設定後使用在 let person 中,這樣一來之後要使用到 Person 這個設定的話只要帶入 Person 即可不用再重複設定型別 ```typescript= type Person = { name:string; age:number; }; let person:Person = { name:'Max', age:32 }; ``` 當然也可以針對設立好的 type 的關鍵字做操作 ```typescript= let people:{ name:string; age:number; }[]; ``` 就會轉變成 ```typescript= let people:Person[]; ``` # Function & Function Types > 參數有型別,funciton 本身也有 這裡可以看到 Types inference 的痕跡,明明我們沒有定義 function 的型別,這邊卻自動加上了 ![](https://i.imgur.com/RpdZBgi.png) 當然我們也可以這樣寫,不過通常前面參數定義好之後後面的 function 可以就讓它推測就好,這邊要強調的是不只是**參數有型別,funciton 本身也有** ```typescript= function add(a:number, b:number):number{ // 也可以使用 Union Type return a + b; } ``` ### function with no return value > > 這樣沒有返回值的函式會被定義為 void,基本上跟 null, undefined 差不多但是只會用在 functions 連接上,代表這個 function 沒有 return ```typescript= function printOutput(value:any) { console.log(value); } ``` ![](https://i.imgur.com/SEqxITx.png) # Diving into Generics 這邊先來解釋什麼情境下要使用 Generics 下方的程式碼因為要在陣列前面加入新的 value ,型別定義上面使用了 any ,畢竟你不確定會加入的是什麼樣的型別的 value ,但是問題來了因為參數型別上面設定了 any,導致 updateArray 在使用 insertAtBeginning 函式時就算使用了 .split 這個使用在字串型別的方法,Typescript 依舊不會報錯,因為目前的型別就是 any 等於關閉了 Typescript 功能,這時候就可以用 Generics 搂 ! ```typescript= function insertAtBeginning(array:any[],value:any){ const newArrray = [value, ...array]; return newArray; } const demoArray = [1,2,3]; const updateArray = insertAtBeginning(demoArray, -1); // [-1,1,2,3] updateArray[0].split('');// 只有引擎會報錯,Typescript 不會有反應 ``` ## Generics > Generics 就像是讓 functions 可以根據參數推測返回值型別的方法,幫助我們撰寫 function 可以 type safe yet flexible 在 function 名稱後面加入一個 `<T>` 並且套用在後面參數使用上面,這麼一來 Typescript 就會明白 demoArray 是 number 型別,而後面的 -1 也是 number 型別,所以 return 的函式一樣也是 number 型別,於是下方的 split 方法就出錯了顯示在 IDE 上!(因為它只接受字串) ![](https://i.imgur.com/GrZrb4W.png) ```typescript= function insertAtBeginning<T>(array:T[],value:T){ const newArrray = [value, ...array]; return newArray; } ``` ![](https://i.imgur.com/aH2PPFb.png) ## Generics 可以推測函式的型別但只限一次一種 從下圖可以看出,這邊另一個 stringArray 放入的都是 string ,因此這個函式的型別就會返回 string 的值 ![](https://i.imgur.com/AAtOXqs.png) 而一次一種的意思代表,參數的值必須是統一的 比方說 : 都是 number 型別會產出 的 function 返回值一定也是 number 型別,以此類推 ## A Closer Look At Generics 這邊要告訴我們的是 Array 其實也是使用 Generics ,例如下面兩個範例其實是一樣的 ```typescript= let numbers: number[] = [1, 2, 3]; ``` ```typescript= let numbers: Array<number> = [1, 2, 3]; ``` 針對課堂上面的範例也可以這樣去寫就可以針對 function 定義型別,但我覺得用關鍵字 T 去寫會有更高的彈性,不過當 Typescript 沒有辦法正確的推測型別時,直接幫它加上 Generics 型別也是一個方法 ```typescript= const stringArray = insertAtBeginning<string>(['a', 'b', 'c'], 'd'); ``` # Creating a React + Typescript Project 基本上就可以直接使用[官網](https://create-react-app.dev/docs/adding-typescript/)的操作下載一個 React app + Typescript ```npm= npx create-react-app my-app --template typescript # or yarn create react-app my-app --template typescript ``` 我們從 source folder 可以觀察出 部分檔案他是以 tsx 結尾,如果檔案中同時使用了 typescript 以及 JSX 就必須這樣寫 ![](https://i.imgur.com/SvXbMHq.png) 接下來可以看看 package.json 內的插件 * 上面四個 types 主要擔任 JS 以及 TS 之間的橋梁 * 下方的紅框就是 TS 以及 compiler 本身 ![](https://i.imgur.com/OX4FnAT.png) # Work with Array & Object Types 這邊會簡單設置一個 Todo.tsx component 並且放置進去 App.tsx 做使用 ## Todos.tsx 目前因為還沒有添加 Typescript 進去所以跟一般的 react component 是一模一樣的 ```jsx= function Todos() { return <ul> <li>Learn React</li> <li>Learn Typescript</li> </ul> } export default Todos; ``` ## App.tsx 既然目前版本的 root component 都不用引入 react 了那麼 TS 版本自然也不需要做這件事情 ```jsx= import Todos from './components/Todos'; import './App.css'; function App() { return ( <div> <Todos/> </div> ); } export default App; ``` # Working with Props & Typescript 首先我們要把 ul 內我們寫死的內容拔掉來透過 props 傳入,從下圖可以看出,由於 props 尚未設定好所以 IDE 已經告訴我們了,這邊是從衍伸 TS 的功能(提示錯誤功能) 並且這個 props 目前的 type 是 any ,針對這個 props 由於我們還沒有定義其型別,所以盡量還是要使用 props:any 來避免這個錯誤發生(但這樣做 TS 就沒有效果所以比較好的寫法在下方),不過這個部分如果覺得太嚴格可以到 tsconfig.json 中做調整 ![](https://i.imgur.com/iPj4wTA.png) 首先我們設定 props 的型別為物件並且包含了字串型別的陣列,這邊很正常沒問題 ```jsx= import React from 'react'; function Todos( props: { items:string[], children }) { return ( <ul> { } </ul> ) } export default Todos; ``` 但是不要忘了還有一個特殊的 prop : children 它的型別尚未定義,當然我們可以依樣畫葫蘆直接給 children 設定型別給它,但是每個我們要使用的 component 都要這樣設定非常麻煩 因為每一 component 其實都有 children prop 因此這邊 TS, React 給它個特殊的寫法做簡化,使用 Generic type 來操作避免重複撰寫一樣的程式碼 **實際的作法是把外面的 Todos functional component 直接轉換為 generic function** 1. 首先把函式本身改為箭頭函式 2. 針對 Todos 函式本身設定 React.FC 也就是 Function component(可以看到其位置於 node_modules/@type/react/index.d.ts 可以找到) ```javascript= type FC<P = {}> = FunctionComponent<P>; ``` 3. 這個時候 children 就變成了 props 的屬性可以來操作 ![](https://i.imgur.com/GyUXXT7.png) ```jsx= import React from 'react'; function Todos: React.FC = (props) => { return ( <ul> {props. } </ul> ) } export default Todos; ``` 4. 下一步即可把 item 的型別設定混和進去使用即可選用 items/children 搂 ![](https://i.imgur.com/IKg4GYg.png) ```jsx= import React from 'react'; function Todos: React.FC<{ item:string[] }> = (props) => { return ( <ul> {props. } </ul> ) } export default Todos; ``` 但是要特別注意跟之前使用過的 T type 不一樣,那邊是我用 T type 來定義之後的型別都會一致,比方說都會是 string, number 等等(**這邊是我們自行定義的 Generics type**) ```jsx= function insertAtBeginning<T>(array:T[],value:T){ const newArrray = [value, ...array]; return newArray; } ``` **然而 React.FC 原本就是 generics type** 5. 接下來就可以操作 map 印出 props 資料瞜 App.tsx 部分要先寫上要 demo 的字串 ```jsx= function App() { return ( <div> <Todos items ={['123','456']}/> </div> ); } ``` Todos.tsx 部分就可以 map 出內容瞜 ! ```jsx= import React from 'react'; const Todos: React.FC<{items:string[]}> = (props) => { return ( <ul> {props.items.map(item => <li key={item}>{item}</li>)} </ul> ) } export default Todos; ``` 印出結果: ![](https://i.imgur.com/tHqO0U5.png) # Adding a Data Model 因為 todo 不可能只有 items 內容而已,而是會有很多資料在其中,比方說建立時間、作者等等,因此這邊我們建立一個 todo.ts 檔案來定義 todos 寫法跟建立物件實體很像 ### todo.ts 用來定義 todo 的 type ```typescript= class Todo { id: string, text: string } export default Todo; ``` 但是卻報錯了,內容是關於這邊建立的 class 的 key 放入 value 只有放入型別,以及沒有使用 `constructor()` 來讓其實體化 ![](https://i.imgur.com/hCXuX3b.png) ```typescript= class Todo { id: string; text:string; //純 JS 不需要使用到型別定義 constructor (todoText: string) { this.text = todoText; this.id = new Date().toISOString(); } } export default Todo; ``` 在純 JS 定義 class 中不會使用上方的型別定義 ## 操作剛剛定義好的型別 Todo 在 App.js 中 操作也跟普通 JS 很像 new 下關鍵字並且帶入參數使用 ```typescript= function App() { const todos =[ new Todo('tttest'), new Todo('test') ]; return ( <div> <Todos items={todos} /> </div> ); } ``` ### Todo.tsx 中 map 出來 items 內容 這邊引入我們製作好的 todo 型別放到我們設定的 generics type 中 ```typescript= import React from 'react'; import Todo from '../models/todo' const Todos: React.FC<{ items: Todo[] }> = (props) => { return ( <ul> {props.items.map( item => ( <li key={item.id}>{item.text}</li> ))} </ul> ) } ``` 呈現的內容跟剛剛一模一樣,不過型別更嚴謹摟! # Time to practice: Exercise Time! > 練習的內容在於把 li 這個元件拉出來去做一個 todoItem 因為其需要的 props 就是單純的 text 就好所以不用拉整個 todo.ts 來使用 ## todoItem.tsx ```typescript= import React from 'react'; const TodoItem: React.FC<{ text:string }> = (props) => { return ( <> <li>{props.text}</li> </> ) } export default TodoItem; ``` ## Todos.tsx 這邊引入剛剛製作的 component TodoItem ,**這邊需要留意的是除了 text 是 TodoItem 本來就需要的 props 之外,因為我們給 Todos.tsx 設定為 React.FC 這個 type 了,因此其 ul 底下的 li 自動就可以使用 key 這個屬性** ```typescript= import React from 'react'; import Todo from '../models/todo' import TodoItem from '../components/TodoItem' const Todos: React.FC<{ items: Todo[] }> = (props) => { return ( <ul> {props.items.map( item => ( <TodoItem key={item.id} text={item.text}></TodoItem> ))} </ul> ) } export default Todos; ``` # Form Submissions In TypeScript Projects > 這邊的練習就是製作新的 component NewTodo.tsx ,以及認識 form 常用到的 FormEvent type ## NewTodo.tsx * 首先把整個 jsx 寫好 * 接下來會注意到 submitHandler 處的參數 event 必須要加上 type FormEvent ,並且從 react 引入使用 * 接下來把函式連接到 onSubmit 上面,這邊如果輸入不是 FromEvent 也會報錯(因為 submit 事件一般使用都是搭配 FormEvent ) ```typescript= import { React,FormEvent } from "react"; const NewTodo = () => { const submitHandler = (event: FormEvent) => { event.preventDefault(); }; return ( <form onSubmit={submitHandler}> <label htmlFor='text'>Todo Text</label> <input type="text" id="text" /> <button>Add Todo</button> </form> ) }; export default NewTodo; ``` # Working with refs & useRef > 操作取得 form input 的值有幾種方式,如 useState 或是 useRef 這邊操作後者 ```typescript= import { FormEvent, useRef } from "react"; const NewTodo = () => { const todoInputRef = useRef(); const submitHandler = (event: FormEvent) => { event.preventDefault(); }; return ( <form onSubmit={submitHandler}> <label htmlFor='text'>Todo Text</label> <input type="text" id="text" ref={todoInputRef}/> // 這邊會出現問題,因為不確定上面的 useRef 會應對哪一種 type <button>Add Todo</button> </form> ) }; export default NewTodo; ``` 從下圖中可以看到,當輸入 type 給 useRef 後可以看到各種 HTML 對應的事件的 type 可以從 MDN 去查詢 ![](https://i.imgur.com/01EgCp1.png) 隨後給予 useRef 預設值填入 null 後錯誤就解除了! 所以當我們在 typescirpt 中使用到 useRef 要特別注意到幾件事情: 1. useRef 要根據對應到的 html 設定它的型別 2. 記得填入預設值,不然會報錯 3. 這種類似的 HTML 系列的 type 可以去 MDN 查找,不過通常是 HTMLInput...\HTMLButton... 接下來需要取用到 useRef 的值,可以發現除了 auto complete 的選項幫你含進去了 input 的一些 properties 很方便之外,當 value 使用 auto complete 時卻出現了一個問號,不加還會報錯(如下圖 ![](https://i.imgur.com/gQ8mOhs.png) * 他代表 typescript 不確定 useRef 是否有連接到任何 html tag 或是他的值會不會是 undefined 因此使用問號 * 如果想要明確的表現 useRef 保證可以正確連接到 html tag 則可以使用驚嘆號 ![](https://i.imgur.com/MHZgWL3.png) 最後一個步驟加上一個簡單的驗證給 input 如果修剪到 enteredText 的空白處使用 `trim()` 其長度還是為 0 則直接結束函式 ```typescript= if (enteredText.trim().length === 0) { return; } ``` 下一個步驟要把這邊 input 輸入的內容回傳到 App.tsx # Working with "Function Props" > 如果要在 NewTodo 把 input 內容回傳回去 App 的話,就必須傳遞函式作為 props ,也就是這個章節要介紹如何使用 function type 在 typescript 中 ## NewTodo.tsx 從扣中可以看到,當設定好 NewTodo FC 後 onAddTodo 是我們從 App 傳下來的 props ,他的形式 `() => ` * 左邊的括號中間要定義的是參數的 type * 右邊的箭頭過去則是指返回值 return 的 type 這邊 retun 因為沒有發生所以使用 void ```typescript= const NewTodo: React.FC<{ onAddTodo: ( text:string ) => void}> = (props) => { //...省略 props.onAddTodo(enteredText); }; ``` ## App.tsx 從這邊可以看到引入 NewTodo 之後來把 onAddTodo 寫上 attr 並且把函式傳下去,記得函式的參數要定義為 string ```typescript= function App() { const addTodoHandler = (todoText:string) => { // 沒有 return 值 }; // 省略 return ( <div> <NewTodo onAddTodo={addTodoHandler} /> <Todos items={todos} /> </div> ); } ``` # Managing State & TypeScript > 這邊我們要操作 useState 來更新畫面,但記得使用 typescript 會有些不一樣的地方! ## App.tsx 這邊我們會按照一般的方式做,解構出 state 跟 setState 最後放入預設值 ```typescript= function App() { const [todos, setTodos] = useState([]); const addTodoHandler = (todoText:string) => { }; return ( <div> <NewTodo onAddTodo={addTodoHandler} /> <Todos items={todos} /> </div> ); } ``` 然而,最後放入預設值空的陣列時, todos state 卻變成 `never []` 這個 type ,代表它永遠都是空陣列,並且不會被修改,這絕對不是我們想要的狀況 ![](https://i.imgur.com/55Zqmgo.png) 這邊的解法也很容易,針對 useState 的內容用 type 做定義,既然裡面的 state 就是 todo 所以就用我們設定好的存在 module 內 Todo type 來設定即可 ![](https://i.imgur.com/oShzAaU.png) 下一步要做的事情是把 NewTodo.tsx 傳上來的 todoText 做實體化 new 它,並且命名為 newTodo 最後操作進去 setTodos 並且 concat 進去 prevTodo 產生新的 Todo 物件來更新 state ```typescript= function App() { const [todos, setTodos] = useState<Todo[]>([]); const addTodoHandler = (todoText:string) => { const newTodo = new Todo(todoText); setTodos( (prevTodo) =>{ return prevTodo.concat(newTodo); }); }; return ( <div> <NewTodo onAddTodo={addTodoHandler} /> <Todos items={todos} /> </div> ); } ``` 哇啦!成功啦~ ![](https://i.imgur.com/Ck40jto.png) # Adding Styling > 這邊的 styling 跟之前課程一樣使用 module 來修飾 todoList 操作過程也一樣 這邊以 TodoItem.tsx 為例子 * 引入 TodoItem.module.css * 在要修飾的 html 使用 className 並帶入使用 ```typescript= import React from 'react'; import classes from './TodoItem.module.css' const TodoItem: React.FC<{ text:string }> = (props) => { return ( <> <li className={classes.item}>{props.text}</li> </> ) } export default TodoItem; ``` # Time to Practice: Removing a Todo > 移除 todo 是這邊的練習 ## TodoItem.tsx * 首先做出 X 按鈕 * 綁定 onClick 事件,因為要把 state 往上傳遞所以使用函式帶入參數的方式 * TodoItem 必須新增函式 type ,這邊因為帶入的參數因為要操作到的參數不在這一層所以不填 ```typescript= const TodoItem: React.FC<{ text:string; onDeleteTodo: () => void }> = (props) => { return ( <> <li className={classes.item}> {props.text} <button onClick={props.onDeleteTodo}>X</button> </li> </> ) } ``` ## Todo.tsx > 這一層會重點操作放入 onDeleteTodo 的參數 因為資料主要在這個 component 中 map 進去 TodoItem 內,所以也在這邊處理 onDeleteTodo 的參數 因為要繼續把 onDeleteTodo 往上傳回去 App.js 修改 state: 1. 使用 onDeleteTodo attr 並且放入指向的函式並且使用 `.bind()` 來放入 item.id 2. 因為確定了放入的參數,因此在這一層開始的函式 type 都必須加上 id 的 type string 3. 因為函式 type 的 return 不操作所以一樣使用 void ```typescript= const Todos: React.FC<{ items: Todo[]; onDeleteTodo: (id: string) => void }> = (props) => { return ( <ul className={classes.todos}> {props.items.map( item => ( <TodoItem key={item.id} text={item.text} onDeleteTodo={props.onDeleteTodo.bind(null, item.id)}/> ))} </ul> ) } ``` ## App.js > 因為更新 todos 的 state 在這一層,因此 deleteHandler 只能操作在這邊 1. 使用 onDeleteTodo attr 並且指向 deleteHandler 2. deleteHandler 函式參數使用 type id: string ,並且使用 setTodos 更新 state 3. setTodos 內操作好 filter 篩選出不是參數 id 的其他 todo 物件並且更新 state ```typescript= function App() { const [todos, setTodos] = useState<Todo[]>([]); const addTodoHandler = (todoText:string) => { const newTodo = new Todo(todoText); setTodos( (prevTodo) =>{ return prevTodo.concat(newTodo); }); }; const deleteHandler = (id: string) => { setTodos( (preTodo) => { return preTodo.filter( todo => todo.id !== id) }); }; return ( <div> <NewTodo onAddTodo={addTodoHandler} /> <Todos items={todos} onDeleteTodo={deleteHandler}/> </div> ); } ``` 刪除功能就完成摟! ![](https://i.imgur.com/68n08VP.png) # The Context API & TypeScript > 因為 prop 傳上傳下很麻煩嗎?這邊來示範操作 useContext 使用在 Typescript 中 * 首先要先建立 todo-context.tsx 檔案,並且把所有的 state 以及傳輸的函式全部撈進來 * 處理 TodoContexgObj 避免一直重複撰寫 TodoContex 的 type 使用 type alias * 要操作 useContext 就必須藉由 createContext 這個 React 的函式來操作並且帶入參數也就是 TodoContext * 創造 TodoContextProvider 來包裹著全部需要使用到 props 的內容 * 接著把 state 以及相關的函式都丟進去 * 最後使用 FC 的特性操作 children 接收所有的 value 也就是 contextValue * 下一步就是把其他 component 中的 props 全部刪掉換成這邊輸出的 value 摟! ```typescript= import React, {useState} from 'react'; import Todo from '../models/todo' type TodoContextObj = { item:Todo[]; addTodo: (text:string) => void; removeTodo: (id:string) => void; }; export const TodoContext = React.createContext<TodoContextObj>({ item: [], addTodo: () => {}, removeTodo: () =>{} } ); const TodoContentProvider: React.FC = (props) => { const [todos, setTodos] = useState<Todo[]>([]); const addTodoHandler = (todoText:string) => { const newTodo = new Todo(todoText); setTodos( (prevTodo) =>{ return prevTodo.concat(newTodo); }); }; const deleteHandler = (id: string) => { setTodos( (preTodo) => { return preTodo.filter( todo => todo.id !== id) }); }; const contextValue: TodoContextObj = { item: todos, addTodo: addTodoHandler, removeTodo: deleteHandler }; return ( <TodoContext.Provider value={contextValue}> {props.children} </TodoContext.Provider> ) } export default TodoContentProvider; ``` ## App.tsx 可以看到 App 變得非常精簡,因為所有的函式以及 state 都被移動到 context 中摟!需要注意的地方是,這邊需要包裹著整個 App 所以要引入 TodoContentProvider ```typescript= function App() { return ( <TodoContentProvider> <NewTodo/> <Todos/> </TodoContentProvider> ); } ``` ## Todo.tsx 這邊示範操作 Todo.tsx 使用 useContext 的方式很簡單,指派給變數之後帶入 TodoContext 這邊記得也要引入 下一步刪掉 props 替換成指派的變數 todoCtx 就大功告成啦 要特別注意的地方,因為原本的 props 被 useContext 的內容取代了,所以 FC 關於 props 的 type 要記得刪掉摟! ```typescript= const Todos: React.FC = () => { const todoCtx = useContext(TodoContext); return ( <ul className={classes.todos}> {todoCtx.item.map( item => ( <TodoItem key={item.id} text={item.text} onDeleteTodo={todoCtx.removeTodo.bind(null, item.id)}/> ))} </ul> ) } ``` # Bonus: Exploring tsconfig.json 這邊簡單介紹幾個設定檔內功能 ```typescript= { "compilerOptions": { "target": "es5", // 這邊代表會把 typescript 編譯為 es5 讓更多瀏覽器使用 "lib": [// 使用的函式庫,第一個 dom 舉例 HTMLInputElement 就是其中的內建 type "dom", "dom.iterable", "esnext" ], "allowJs": true,// 這邊可以讓純 js 跟 typescript 檔案可以並存 "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, // 可以使得當一些地方 type 不正確時產生紅色波浪 ex. 參數沒填入 type 時 "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx" // jsx 必須依賴這個屬性才能夠使用在 typesccript 檔案中 }, "include": [ "src" ] } ```