1. useEffect 的使用

目标:能够掌握 useEffect 在 TS 中 的使用

知识点:

useEffect 函数不涉及到任何类型,TS 和 JS 中使用一致

  1. useEffect(() => {
  2. const onResize = () => {
  3. console.log('屏幕大小改变了')
  4. }
  5. window.addEventListener('resize', onResize)
  6. return () => {
  7. window.removeEventListener('resize', onResize)
  8. }
  9. }, [])

2. useState 的使用

目标:能够掌握 useState 在 TS 中的使用

知识点:

  1. useState hook 是一个泛型函数,接收一个类型变量来指定状态的类型 ```typescript // useState 是一个泛型函数,接收一个类型变量用来指定状态的类型

// 指定 count 状态的类型为 number const [count, setCount] = useState(0) // 指定 name 状态的类型为 string const [name, setName] = useState(‘tom’)

  1. 2. 注意:该类型变量,不仅指定了状态的类型,也指定了 setName 等修改状态函数的参数类型
  2. ```typescript
  3. const [name, setName] = useState<string>('tom')
  4. // 此时,setName 的参数的类型也是 string
  5. setName('jerry')
  6. // 错误演示:
  7. // setName(18)
  1. 省略类型变量,简化 useState 的调用
  • 在使用 useState 时,只要提供了初始值,TS 就会自动根据初始值来推断出其类型,因此,可以省略类型变量
  • 注意:如果 TS 自动推断出来的类型不准确,还得显式指定泛型类型
    1. const [count, setCount] = useState(0)
    2. const [name, setName] = useState('tom')

3. useState 明确指定泛型类型

目标:能够明确指定 useState 的泛型类型

知识点:

  1. 需求:获取频道列表数据并渲染
  2. 频道列表数据的接口:http://geek.itheima.net/v1_0/channels ```jsx const App = () => { // 比如,频道列表数据是一个数组,所以,在 JS 中我们将其默认值设置为:[] // 但是,在 TS 中使用时,如果仅仅将默认值设置为空数组, // list 的类型被推断为:never[],此时,无法往数组中添加任何数据 const [list, setList] = useState([])

    return (

      1. {
      2. list.map((item, index) => {
      3. return <li>{item.name}</li>
      4. })
      5. }

    ) }

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/147664/1643130022249-8dbde636-7532-4866-8dec-9b263b342a3e.png#clientId=uc6c338e0-4cab-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=101&id=hXynw&margin=%5Bobject%20Object%5D&name=image.png&originHeight=126&originWidth=960&originalType=binary&ratio=1&rotation=0&showTitle=false&size=12160&status=done&style=none&taskId=u95786311-4f01-4f3c-bb61-5a545a9a9c0&title=&width=768)![image.png](https://cdn.nlark.com/yuque/0/2022/png/147664/1643130361473-e8433656-156f-44e0-b6b0-ad2f0a0ffab2.png#clientId=uc6c338e0-4cab-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=255&id=ue41cc7b3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=319&originWidth=960&originalType=binary&ratio=1&rotation=0&showTitle=false&size=26741&status=done&style=none&taskId=u58c4d996-5e13-4063-b51a-1fd52a25073&title=&width=768)
  2. 注意:
  3. 1. useState 的初始值是数组、对象等复杂的数据类型,需要明确指定泛型类型
  4. 1. 因为虽然都是数组、对象,但是,项目开发中不同需求所需要的数组结构、对象结构是不同的。因此,需要明确指定其类型
  5. ```typescript
  6. type Channel = {
  7. id: number
  8. name: string
  9. }
  10. // 明确指定状态的类型,此时,list 的类型为:Channel[]
  11. // Channel[] 表示 Channel 类型的数组,也就是,数组中只能出现 Channel 类型的数据
  12. const [list, setList] = useState<Channel[]>([])

总结:

  • 使用 TS 时,应该以类型化思维来写代码,简单来说:先有类型,再写逻辑代码来使用该类型的数据
  • 比如,对于 对象、数组 来说,就应该在使用前先明确指定要用到的对象的类型、数组的类型等等

4. useRef 的使用

4.1 useRef 接收类型产生问题

目标:能够使用 useRef 配合 TS 操作 DOM

知识点:

useRef hook 接收一个类型变量,用于指定 current 属性的类型

  1. import { useRef } from "react"
  2. const App = () => {
  3. const inputRef = useRef<HTMLInputElement>(null)
  4. return (
  5. <div>
  6. {/* 假设操作的是 input 标签,则指定类型为:HTMLInputElement */}
  7. <input type="text" ref={inputRef} />
  8. <button onClick={() => {
  9. // 错误:对象可能为 "null"。
  10. // 把鼠标放在变量 inputRef.current 上,
  11. // 可以看到 inputRef.current 属性的类型为: HTMLInputElement | null
  12. // 其中,HTMLInputElement 是我们明确指定的类型,而 null 是参数的类型
  13. // 因为在创建 ref 对象时,JSX 对应的 DOM 还没有创建,所以,参数默认值先指定为 null
  14. console.log(inputRef.current.value)
  15. }}>获取 value</button>
  16. </div>
  17. )
  18. }
  19. export default App

使用 ref 对象,获取进行 DOM 操作,在指定类型后,current 属性的类型是:HTMLInputElement | null,所以,必须要从类型中 “去掉 null “ 才能继续操作,处理方式有两个:

  1. 使用 JS 中的 可选链操作符(?.)来实现
  2. 使用 TS 中的非空断言的使用
  3. 使用流程控制来判断 Ts 类型

4.2 使用可选链操作符解决类型问题

目标:能够使用 useRef 结合可选链操作符操作 DOM

知识点:

使用可选链操作符解决问题的原理:

可选链操作符 ?. 会判断 current 属性是否为空值(null 或 undefined),如果不是空值,就继续访问 value 值;如果是空值,不会继续访问 value

  1. 如果 current 不是空值,其类型为 HTMLInputElement,就可以拿到 value 值
  2. 如果 current 是空值,其类型为 null,不再继续访问 value,代码就不会报错了

    1. const getValue = () => {
    2. // 正确:
    3. console.log(inputRef.current?.value)
    4. // 报错:未处理 current 属性类型为 null 的情况
    5. // console.log(inputRef.current.value)
    6. }

4.3 使用非空断言解决类型问题

目标:能够使用 useRef 结合非空断言操作 DOM

知识点:

使用 ref 进行 DOM 操作时,可以将 ref 对象的 current 的类型 HTMLInputElement | null 中的 null 类型去掉变为:HTMLInputElement

  1. const inputRef = useRef<HTMLInputElement>(null)
  2. const getValue = () => {
  3. console.log(inputRef.current!.value)
  4. }

4.4 使用流程控制来判断 Ts 类型

目标:能够通过 if 判断来控制 TS 类型

知识点:

  1. TS文档-流程控制分析
  2. TS 可以通过 if 判断等操作进行流程控制分析,来得到更加具体的类型,实现类型收缩(缩小类型的取值范围)
  3. 这一检查机制也叫做:type guard - 类型守卫
  • 结果:变量在不同的位置,可以有不同的类型 ```typescript let x: number | string

function handle (x) { if (typeof x === ‘number’) { // 鼠标放在 x 上查看类型为: number console.log(x) } else { // 此时,x 的类型为: string console.log(x) } }

handle(1)

  1. 使用 ref 进行 DOM 操作时,通过 if 判断来排除掉 null 类型
  2. ```typescript
  3. const inputRef = useRef<HTMLInputElement>(null)
  4. const getValue = () => {
  5. // 判断 inputRef.current 是否为空,如果是,直接 return 不再执行后面的代码
  6. if (!inputRef.current) return
  7. // 如果此处代码执行,说明 inputRef.current 不为空,所以,此处可以直接访问 value 属性
  8. console.log(inputRef.current.value)
  9. }

5. Redux 基本使用

目标:能够掌握如何在 TS 项目中初始化 redux

知识点:

  1. 安装依赖包:yarn add redux react-redux redux-devtools-extension redux-thunk
  2. 新建文件 store/index.ts( 后缀为 .ts ) ```typescript import { createStore, applyMiddleware } from ‘redux’ import { composeWithDevTools } from ‘redux-devtools-extension’ import thunk from ‘redux-thunk’ import reducer from ‘./reducers’

const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))

export default store

  1. 3. 新建文件 store/reducers/index.ts
  2. ```tsx
  3. import { combineReducers } from 'redux'
  4. import { todos } from './todos'
  5. const rootReducer = combineReducers({
  6. todos,
  7. })
  8. export default rootReducer
  1. 新建文件 store/reducers/todos.ts ```typescript type TodoList = { id: number text: string done: boolean }

const initialState: TodoList[] = [ { id: 1, text: ‘吃饭’, done: false, }, { id: 2, text: ‘睡觉’, done: true, }, { id: 3, text: ‘打豆豆’, done: false, } ]

export const todos = (state = initialState, action: any) => { return state }

  1. 5. index.tsx
  2. ```tsx
  3. import ReactDOM from 'react-dom'
  4. import { Provider } from 'react-redux'
  5. import App from './App'
  6. import store from './store'
  7. ReactDOM.render(
  8. <Provider store={store}>
  9. <App />
  10. </Provider>,
  11. document.getElementById('root')
  12. )

6. useSelector 的使用

目标:能够掌握 useSelector 在 TS 中的使用

知识点:

useSelector hook 是一个泛型函数,接收两个类型变量,分别来指定:

  1. 第一个类型变量:指定 Redux 仓库 state 的类型
  2. 第二个类型变量:指定要获取状态的类型 ```typescript // useSelector 类型,源码如下:

// TState = DefaultRootState 是 泛型参数 的默认值, // 设置默认值后,将来在调用该函数时,可以省略该泛型参数不写 export function useSelector(

  1. // 第一个参数:回调函数,

// 用来获取 redux 状态的回调函数,通过回调函数的返回值来指定要获取的状态 selector: (state: TState) => TSelected,

// 第二个参数:可以拿到更新前后的两次状态,通过返回的布尔值就可以来知道状态是否发生变化 equalityFn?: (left: TSelected, right: TSelected) => boolean

): TSelected

  1. useSelector 的两种使用方式:
  2. 1. 指定泛型类型:
  3. ```typescript
  4. import { useSelector } from 'react-redux'
  5. type TodosList = {
  6. id: number
  7. text: string
  8. done: boolean
  9. }
  10. type RootState = {
  11. count: number
  12. todos: TodosList
  13. }
  14. const Todos = () => {
  15. const count = useSelector<RootState, number>(RootState => RootState.count)
  16. const todos = useSelector<RootState, TodosList>(RootState => RootState.todos)
  17. console.log(count)
  18. console.log(todos)
  19. return <div>任务管理</div>
  20. }
  21. export default Todos
  1. 不指定泛型类型,只指定回调函数参数 state 的类型
  • react-redux 指定 useSelecotor 的类型

    1. const Todos = () => {
    2. const count = useSelector((state: RootState) => state.count)
    3. const todos = useSelector((state: RootState) => state.todos)
    4. console.log(count)
    5. console.log(todos)
    6. return <div>任务管理</div>
    7. }

7. 获取Redux仓库的状态类型

目标:能够获取 Redux 仓库的状态类型

知识点:

  1. 获取Redux仓库状态类型
  2. 仓库状态分散到每一个 reducer 文件中,而我们需要一次获取 Redux 仓库全部状态
  3. 思路:store.getState() 可以用来获取 Redux 应用的状态,所以只需要获取其类型即可
  4. 同时 使用 ReturnType 是泛型工具类型,用来获取函数的返回值类型 ```typescript function add(n1: number, n2: number): number { return n1 + n2 }

// 获取函数 add 的类型 type AddFn = typeof add

// 获取函数 add 的返回值类型 type AddFnReturnType = ReturnType

// 直接获取 add 函数的返回值类型 type AddFnReturnType = ReturnType

  1. **落地代码:**
  2. store/index.ts 中:
  3. ```typescript
  4. // 获取 Redux 整个仓库的状态类型:
  5. export type RootState = ReturnType<typeof store.getState>

组件中引入模块 RootState

  1. import { RootState } from '../store'
  2. const Todos = () => {
  3. // 获取todos数据
  4. const todos = useSelector((state: RootState) => state.todos)
  5. return <div>
  6. <h4>任务管理</h4>
  7. <ul>
  8. {
  9. todos.map(item => {
  10. return <li key={item.id} style={{
  11. color: item.done ? '#ccc' : '',
  12. textDecoration: item.done ? 'line-through' : ''
  13. }}>{item.text}</li>
  14. })
  15. }
  16. </ul>
  17. </div>
  18. }

8. reducer 函数的类型

目标:能够掌握 reducers 在TS中的写法

action 的类型有两种实现方式:

  1. 自己手动创建 action 的类型
  2. 根据 action creator 来得到(扩展)

8.1 手动创建 action 的类型

  1. 先创建 action 类型
  2. 用 action 类型来约束 action creator
  3. 指定 reducer 的 action 参数和返回值的类型
  • 约定:为 reducer 指定返回值类型,约束 return 的内容必须满足返回值类型的要求,防止返回错误的数据

actions/todos.ts

  1. type AddTodo = {
  2. type: 'todos/add'
  3. payload: string
  4. }
  5. type DelTodo = {
  6. type: 'todos/del'
  7. payload: number
  8. }
  9. export type TodoAction = AddTodo | DelTodo
  10. // 添加任务
  11. export const addTodo = (name: string): AddTodo => ({
  12. type: 'todos/add',
  13. payload: name
  14. })
  15. // 删除任务
  16. export const delTodo = (id: number): DelTodo => ({
  17. type: 'todos/del',
  18. payload: id
  19. })

reducer/todos.ts

  1. import { TodoAction } from '../actions/todos'
  2. // 使用 action 中提供的所有的 action 类型 TodoAction 来作为此处的 action 参数的类型
  3. export const todos = (state = initialState, action: TodoAction): TodoList => {
  4. switch (action.type) {
  5. case 'todos/add':
  6. console.log(action.payload)
  7. break;
  8. case 'todos/del':
  9. console.log(action.payload)
  10. break
  11. default:
  12. break;
  13. }
  14. return state
  15. }

8.2 根据 action creator 得到类型

  1. 先创建 action creator
  2. 然后使用 as const 常量断言,让当前类型固定为字面量本身,不扩大范围
  3. 最后使用 ReturnType 根据 action creator 得到 action 类型 ```typescript // 1 添加任务 export const addTodo = (text: string) => ({ // as const 常量断言,会让当前类型固定为 字面量本身,而不再被扩大类型 // 比如,string 就是 字面量类型 ‘todos/add’ 的扩大类型 // 如果不加 as const,type 被推断为: string // 如果加了 as const,type 被推断为: ‘todos/add’ type: ‘todos/add’ as const, payload: text })

// 2 删除任务 export const delTodo = (id: number) => ({ type: ‘todos/del’ as const, payload: id })

// 根据 action creator 来得到 action 的类型 type AddTodo = ReturnType type DelTodo = ReturnType

export type TodoAction = AddTodo | DelTodo | ToggleTodo

  1. <a name="ZIfXb"></a>
  2. ### 8.3 约束返回类型
  3. 1. 必须明确指定 reducer 的 action 参数和返回值的类型
  4. 1. 约定:为 reducer 指定返回值类型,约束 return 的内容必须满足返回值类型的要求,防止返回错误的数据
  5. ```typescript
  6. // 必须明确指定 reducer 的 action 参数和返回值的类型 TodoList
  7. export const todos = (state = initialState, action: TodoAction): TodoList => {
  8. switch (action.type) {
  9. case 'todos/add':
  10. console.log(action.payload)
  11. break;
  12. case 'todos/del':
  13. console.log(action.payload)
  14. break
  15. default:
  16. break;
  17. }
  18. return state
  19. }

9. useDispatch 的使用

目标:能够掌握 useDispatch 在 TS 中的使用

知识点:

  1. react-redux 指定 useDispatch 的类型
  2. useDispatch hook 是一个泛型函数,接收一个类型变量用于指定 Action 的类型,该泛型类型可以直接省略
  1. const dispatch = useDispatch()
  2. <button onClick={() => dispatch(delTodo(item.id))}>x</button>

10. React 事件对象的类型

目标:能够掌握如何在 TS 中为事件对象指定类型

知识点:

为 JSX 标签绑定事件时,可能需要指定事件对象的类型,分两种情况:

  1. 直接在 JSX 标签上写事件处理程序,此时,不需要手动指定事件对象的类型
  • 技巧:在 JSX 标签上先把事件处理程序写好,然后,鼠标移动到事件对象上面,来查看事件对象的类型

image.png

  1. 如果将事件处理程序抽离出来,需要手动指定函数参数(事件对象)的类型
    1. const change = (e: React.ChangeEvent<HTMLInputElement>) => {
    2. console.log(e.target.value)
    3. }

11. redux-thunk 的使用

目标:能够掌握 Redux-thunk 在 TS 中的使用

知识点:

指定 redux-thunk 的 action 类型
thunk action 的类型处理:

  1. 创建 thunk action 的类型:RootThunkAction ```typescript // 第一个类型参数:thunk action 返回值类型 // 第二个类型参数:Redux 状态的类型 // 第三个类型参数:thunk action 额外参数的类型 // 第四个类型参数:Redux 中所有 action 的类型 export type RootThunkAction = ThunkAction

// 第1、3个类型参数,参照上述文档来指定即可

  1. 2. 使用 thunk action 类型:将该类型作为 thunk action 的返回值类型
  2. ```typescript
  3. // 将删除任务的 action 修改为 thunk action
  4. // 注意:返回的函数,才是 thunk action
  5. // delTodo 是 action creator
  6. export const delTodo = (id: number): RootThunkAction => {
  7. return dispatch => {}
  8. }

12. redux-thunk 新版本特性

注意:redux-thunk@2.4.0 新版使用 TS 重写了框架源码,并且相关的 TS 类型也做了一些调整,变化如下:

  1. 在 redux-thunk@2.3.0 版本中,在 thunk action 中分发对象 action 时有明确的类型提示

    1. export const delTodo = (id: number): RootThunkAction => {
    2. return dispatch => {
    3. setTimeout(() => {
    4. // 此处,在写 对象action 时,输入 type 属性会有明确的代码提示
    5. dispatch({
    6. type: 'todos/del',
    7. payload: id
    8. })
    9. }, 1000)
    10. }
    11. }
  2. 在 redux-thunk@2.4.0 新版本中,上述操作没有了明确的类型提示

所以,如果想要在 thunk action 中 diapatch 对象 action 时有类型提示,可以安装 2.3.0 版本

  1. yarn add redux-thunk@2.3.0

**