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: 文中有任何错误,欢迎掘友指正
往期精彩📌