1. useState
useState
接收一个泛型参数,用于指定初始值的类型
const [name, setName] = useState<string>('张三')
const [age, setAge] = useState<number>(28)
const [isProgrammer, setIsProgrammer] = useState<boolean>(true)
// 如果你在 setName 函数中的参数不符合声明的变量类型,程序会报错
<button onClick={() => setName(100)}>按钮</button>
注意:useState
的类型推断,在使用 useState 的时候,只要提供了初始值,TypeScript 会自动根据初始值进行类型推断,因此 useState
的泛型参数可以省略
2. useEffect
useEffect
是用于我们管理副作用(例如 API 调用)并在组件中使用 React 生命周期的
重点:useEffect
函数不涉及到任何泛型参数,在 TS 中的使用和 JS 中完全一致
// 定时器开启和关闭
useEffect(() => {
let timer = setInterval(() => {
console.log('哈哈哈')
})
return () => {
clearInterval(timer)
}
}, [])
// 事件的绑定和解绑
useEffect(() => {
// 给 window 绑定点击事件
const handleClick = () => {
console.log('哈哈哈')
}
window.addEventListener('click', handleClick)
return () => {
// 给 window 移除点击事件
window.addEventListener('click', handleClick)
}
}, [])
3. 请求数据
如果 useState 没有提供具体类型的初始值,是需要使用泛型参数指定类型的
当然这些的数据通常要放在 redux 中进行状态管理,如果不放,需要定义一个初始状态类型,不然无法使用点语法
// 三种解决方案
// 方案一 使用 any 解决,但不建议
<ul>
{list.map((item: any) => {
return <li key={item.id}>{item.name}</li>
})}
</ul>
// 方案二 给循环时的 item 指定类型
<ul>
{list.map((item: TItem) => {
return <li key={item.id}>{item.name}</li>
})}
</ul>
// 方案三 给 useState 指定泛型参数(推荐)
// 解决1:给个初始值,不推荐
// const [list, setList] = useState([{ name: 'ifer', id: 0 }])
// 解决2:泛型参数
// 一般复杂的类型,需要手动进行指定初始值类型,TS 没法进行推断
interface IList {
name:string,
id:number
}
// const [count,setCount] = useState()// 什么都不声明 泛型参数为 undefined
// const [count,setCount] = useState({})// 泛型参数为 {}
// const [list,setList] = useState([])// 泛型参数为 never[] 后续取用 item 就会报错
const [list,setList] = useState<IList[]>([])// 正确 <> 泛型参数定义 list 的类型,小括号里面的为list 的初始值
// 了解
interface IRes extends Array<{ id: number; name: string }> {}
4. useRef
使用 useRef 配合 TS 操作 DOM
useRef
接收一个泛型参数,泛型参数用于指定 current 属性的值的类型
import { useRef } from 'react'
export default function App() {
// 不推荐 any
// const inputRef = useRef<any>(null)
// 指定了 current 的类型,目的是为了让 current 有属性提示
// 初始为 null 是一个固定写法,制定了泛型参数是为了有提示
const inputRef = useRef<HTMLInputElement>(null)
const aRef = useRef<HTMLAnchorElement>(null)
const get = () => {
// inputRef.current 可能是 null,所以用了 ?.
console.log(inputRef.current?.value)
console.log(aRef.current?.href)
}
return (
<div>
<input type='text' ref={inputRef} />
<a href='https://www.baidu.com' ref={aRef}>
百度
</a>
<button onClick={get}>获取</button>
</div>
)
}
使用鼠标悬停在 ref 上悬停之后可以看到 dom对象 的类型
为什么参数要是 null 或者可以是 null 呢
// 通过类型定义文件得知:参数要么是 T 类型,要么是 null
function useRef<T>(initialValue: T | null): RefObject<T>
不指定具体的类型(泛型参数)的话,inputRef.current 就为 null ,无法赋值。想要赋值必须指定具体的泛型参数
理解:
// 获取 dom 对象或者组件实例
// 为什么 inputRef.current 给了具体的泛型参数的类型,为什么初始值还可以取 null
// 原因:见下图,内部规定了可以并上一个 null 类型,小括号里面给上别的数据 比如不给就报错
const inputRef = useRef<HTMLInputElement>(null)
const inputRef = useRef<InputRef>(null)(第三方组件的
当作全局变量来用(必须要在后面并上一个 null,官方说的,想要这个 ref .current 可以改变需要并上 null 类型,获取的 dom 不需要改变就不用并)
// 存一个定时器的 number
// 给个初始值,推导出来就是 number 类型,才可以赋值定时器 ID
const timeIdRef = useRef(-1)
// 另一种做法
// const timeIdRef =useRef<number | null>(null)
5. 非空断言
- 如果我们明确的知道对象的属性一定不会为空,那么可以使用非空断言
!
- 注意:非空断言一定要确保有该属性才能使用,不然使用非空断言会导致 Bug
// 注意测试的时候要开启 strictNullChecks 模式
function show(name: string | undefined) {
let sName: string = name // Error
}
// 解决
function show(name: string | undefined) {
let sName: string
if (name) {
sName = name
}
}
// 优化
function show(name: string | undefined) {
// name! 意思是从 name 可能的值中断言(假定)没有 null 和 undefined
let sName: string = name!
}
// 应用场景
import { useRef } from 'react'
export default function App() {
const inputRef = useRef<HTMLInputElement>(null)
const get = () => {
// 断言 inputRef.current 不可能为空
/* const current = inputRef.current!
console.log(current.value) */
console.log(inputRef.current!.value)
}
return (
<div>
<input type='text' ref={inputRef} />
<button onClick={get}>获取</button>
</div>
)
}
注意,使用非空断言时要想明白,否则程序可能报错
const str: string | null = null
console.log(str!.length)
6. React 路由
6.1. useHistory
问题:
- from 没有提示和类型校验(跟写 js 没区别了)(即使是随便写的你自己定义的变量,也需要提前定义好类型,写什么才能有提示)
- 不能用 any
变量后面跟着
<>
并且仍然表示类型的一定是泛型接口,如H.History<HistoryLocationState>
seHistory 实现跳转功能,和 JS 中使用语法一致
// /pages/Home.tsx
import { useHistory } from 'react-router-dom'
export default function Home() {
const history = useHistory()
const login = () => {
history.push('/login')
}
return (
<div>
<h2>Home</h2>
<button onClick={login}>登录</button>
</div>
)
}
// useHistory 在跳转时可以通过 state 进行传参,并通过泛型参数来指定 state 的类型
// 记住即可,useHistory 的泛型参数用来指定编程式导航的参数 state 的类型
const history = useHistory<{ from: string }>()
const login = () => {
history.push({
pathname: '/login',
state: {
from: 'ifer',
},
})
}
写 ts 就要慢慢地想类型,如果卡住了不知道写什么类型,先用 any 或者 unknown 保证代码能跑
(location.state as any).form // 这样就不会报错了,any 想写什么就写什么
想要知道原来,可以按住 ctrl + 鼠标左键进行查看源码
6.2. useLocation
useLocation 接收一个泛型参数,用于指定接收 state 的类型,与 useHistory 的泛型参数对应
import { useLocation } from 'react-router-dom'
export default function Login() {
const location = useLocation<{ from: string } | null>()
// 直接点击登录页,没有传参会报错,所以这里用了可选链操作符 ?.
return <div>Login: {location.state?.from}</div>
}
优化:因为 useLocation 和 useHistory 都需要指定 Location 类型,因此可以将类型存放到通用的类型声明文件中,src/types/data.d.ts
// Tip: 这里明确或了一个 null,当后面再书写 location.state.from 的时候,.from 的前面会自动加上 ? 号
export type LocationState = {
from: string
} | null
// src/types/data.d.ts
import { useLocation } from 'react-router-dom'
import { LocationState } from '../types'
export default function Login() {
const location = useLocation<LocationState>()
return <div>Login: {location.state?.from}</div>
}
data.d.ts 和 store.d.ts 使我们自己定义的类型声明文件,并不是同第三库一样的同名文件,需要我们额外的进行 export 使用
location.pathname
不包括参数,只有路径名称
location.search
是后面的参数,?key=a
(/:id
这种直接使用 useParams hook 进行获取)
6.3. useParams
useParams 接收一个泛型参数,用于指定 params 对象的类型
// App.tsx
import { BrowserRouter as Router, Link, Route } from 'react-router-dom'
import Article from './Article'
export default function App() {
return (
<div>
<Router>
<nav>
<Link to='/article/1'>文章1</Link>
<Link to='/article/2'>文章2</Link>
</nav>
<Route path='/article/:id' component={Article} />
</Router>
</div>
)
}
// pages/article.tsx
import { useParams } from 'react-router'
export default function Article() {
const params = useParams<{ id: string }>()
return <div>Article: {params.id}</div>
}
总结:定义任何变量之前都需要写定义类型,不要可能会报错,没有提示,失去了 ts 的意义
7. redux-thunk
react-redux@8.x.x 有些小 bug 使用 @7.2.8 的更加稳定 (useEffect 里面的 dispatch 会报错)
useEffect(()=>{ dispatch(getChannelList()) },[])
redux-thunk @3.x.x 的版本 里面的提示出不来,换成 @2.3.0 的就有了(action 里面的 dispatch 即异步 return 的回调里面,没有提示,但是会有警告)
export const getChannel = (): RootThunkAction => { return async (dispatch) => { // get 的泛型参数其实限制的是 res.data 的类型 const res = await axios.get<IResponse<{ channels: ChannelItem[] }>>('http://geek.itheima.net/v1_0/channels') // res.data 再往后面点的话没有提示 dispatch({ type: 'CHANNEL_SAVE', payload: res.data.data.channels, }) } }
写项目也可以不用 ts 写,用 js 写也可以
如何处理定义在 action 中的异步函数的返回值的类型
ThunkAction 类型的使用,参考文档
// 泛型参数
// 1: 指定内部函数的返回值类型,一般是 void
// 2: 指定 RootState 的类型
// 3: 指定额外的参数类型,这里用不到,一般为 unknown 或 any,可以在配置 redux-thunk 的时候,通过 thunk.withExtraArgument('ifer') 指定
// 4: 指定 dispatch 的 action 的类型
import { ThunkAction } from 'redux-thunk'
export const todoDelAsync = (id: number): ThunkAction<void, RootState, unknown, TodoAction> => {
// 后面三个参数是啥,看下文档
return (dispatch, getState, extraData) => {
// getState().todo // 因为,指定了 RootState 类型,这儿自动具有提示
setTimeout(() => {
dispatch(todoDel(id))
}, 2000)
}
}
不定义这个函数回调的类型,回到函数里面校验也不校验,提示也没有提示
以后的统一写法
import { ThunkAction } from 'redux-thunk'
import store from '../store'
export type TodoAction =
| {
type: 'TODO_ADD'
name: string
id: number
done: boolean
}
| {
type: 'TODO_DEL'
id: number
}
| {
type: 'TODO_CHANGE_DOEN'
id: number
}
export type RootAction = TodoAction // 后面可以并上别的模块的
export type RootState = ReturnType<typeof store.getState>
export type RootThunkAction = ThunkAction<void, RootState, unknown, RootAction>