React Hooks 可以说是 React 新版本里比较重要的特性了,函数式组件的风格,易于管理的业务 Hooks 封装,不错的性能都是 Hooks 带来的好处。随着技术栈的更新,怎么用 TypeScript 来写 React Hooks 组件又是一个小难题,这边分享一些实战的小经验。
类型值
React.FC
既然谈到了函数式组件,就不得不提下函数式组件的返回的类型了, React 提供了一个叫做 FunctionComponent 的类型,这是用来定义组件的类型,可以简写成 React.FC 。
JS版本:
import React from 'react'
const Header = ({msg}) => {
return (
<div>
{msg}
</div>
)
}
export default Header
TSX版本:
import React from 'react'
const Header: React.FC = ({msg}) => {
return (
<div>
{msg}
</div>
)
}
export default Header
Props
在 vscode 中,如果鼠标悬浮在 React.FC 上面的话会有相关类型的提示, <P = {}> 说明了 FC 还支持输入一个泛型来指定 Props 属性的值,默认情况下不填泛型就表示该组件不接收 Props 。
在代码里可以看到其实我们需要接收一个
msg 的值,所以这么写 IDE 肯定会报错,需要改成以下这种:
import React from 'react'
const Header: React.FC<{msg: string}> = ({msg}) => {
return (
<div>
{msg}
</div>
)
}
export default Header
或者可以用一个接口指定 props 的类型值,实际开发中建议使用接口来定义对象的类型:
// ...
interface Props {
msg: string;
}
const Header: React.FC<Props> = ({msg}) => {
// ...
把 Props 接口的类型值定义完整后,就可以在组件的入参处得到代码提示,这一点还是相当方便的。
useState
useState 为函数式组件提供了状态管理的能力,但是为 useState 定义类型却不能像普通定义变量类型一样定义,类型定义需要传入一个泛型:
const [count, setCount] = useState<number | null>(0)
如果不传入类型的话, useState 也能根据初始值自动推导出 count 支持的类型是 number ,但是传入 <number | null> 以后,调用 setCount 的时候就需要注意一下入参的类型了。
比如:
setCount((count as number) + 1)
这里 setCount 的入参有可能是 number ,也有可能是 null ,所以需要使用 as 关键词进行类型断言,将 count 定为 number 才能进行加减运算。
定义完类型后,假如团队中的其他小伙伴修改代码的时候就会有相关的提示,保证了代码的健壮性。
useRef
useRef 一般是用来在函数式组件内取得子组件元素上下文或者维持组件持久化状态(不受生命周期影响),所以使用频率也是很高的,给 useRef 定义类型也和 useState 类似,不同的是需要根据不同的元素拿到不同的类型值。
鼠标悬浮在元素的
ref 上, ide 就会提示 ref 这个属性的类型可以接收 string ,入参为 HTMLButtonElement 实例或者 null 的函数,传入泛型 HTMLButtonElement 的Ref 对象, null , undefined 。根据这里的信息就可以推断出 btnRef.current 的类型应该为 HTMLButtonElement 。
const btnRef = useRef<HTMLButtonElement>(null)
如果元素不同, ref 的类型值也会跟着不同变化,比如 HTMLDivElement , HTMLInputElement 等等。
如果 useRef 用来做组件状态使用,用起来和 useState 就无差别了。
const numRef = useRef<number>(0)
numRef.current += 1 // corrent
numRef.current = 'test' // wrong
useReducer
useReducer 是另一种状态管理的 hooks ,如果用过 redux ,那么 useReducer 的使用就会非常熟悉了,那么 useReducer 怎么和 TypeScript 结合呢?
interface Item {
id: number;
text: string;
}
type State = Item[]
type Actions = {
type: 'add';
text: string;
} | {
type: 'remove';
id: number;
}
const ItemReducer = (state: State, action: Actions) => {
switch(action.type) {
case 'add':
return [...state, {
id: state.length + 1,
text: action.text
}]
case 'remove':
return state.filter(item => item.id !== action.id)
default:
return state
}
}
// -----组件分割线-----
const [items, dispatch] = useReducer(ItemReducer, [])
// -----组件分割线-----
首先,我们要对定义好 reducer 的入参类型,这里就定义好了 state 和 action 的入参类型, state 是 item 类型的列表, action 则是有几个固定的调用模式。
这样在组件中调用 dispatch 的时候,就会提示 dispatch 到底接收哪些参数了:
同时在这里也能看到在 TypeScript 中 type 和 interface 的区别, interface 组要用于对象 Object 类型数据的类型定义,而 type 关键词主要是应对一些复杂类型的骚操作:
type StringOrNumber = string | number;
type Text = string | { text: string };
type NameLookup = Dictionary<string, Person>;
type Callback<T> = (data: T) => void;
type Pair<T> = [T, T];
type Coordinates = Pair<number>;
type Tree<T> = T | { left: Tree<T>, right: Tree<T> };
小技巧 💡
TypeScript 带来强类型检查的同时,也让开发者花费了一些精力来规范组件,变量的类型,所以这里推荐一个小技巧来提升日常的生产效率。
首先打开 vscode 的首选项配置,然后点击用户片段
然后输入
typescriptreact.json ,点击下面提示的内容进入设置界面
配置成以下的JSON
{
"Init Template": {
"prefix": "rtfc",
"body": [
"import React from 'react'",
"",
"const Component: React.FC<Props> = () => {",
"",
" return (",
" <div>",
" init txt",
" </div>",
" )",
"}"
],
"description": "init tempalte for tsx"
}
}
保存后,新建一个 index.tsx ,然后输入 rtfc ,回车确定
页面中就会出现之前 json 配置中的 body 内容,当然 body 的内容可以由开发者自行设定
这里的"用户片段"其实就是根据不同的文件类型,进行自定义hint的设置,这样就极大方便了
TypeScript 开发者的类型设定。
结束
结合 typescript 是未来前端发展的趋势,现在掘金上也有很多优秀的 typescript 教程,感兴趣的掘友也赶紧学起来吧。
PS: 文中有任何错误,欢迎掘友指正
往期精彩📌