引言React v16.8 引入了 Hooks,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。这些功能可以在应用程序中的各个组件之间使用,从而易于共享逻辑。
Hook 令人兴奋并迅速被采用,React 团队甚至想象它们最终将替换类组件。
以前在 React 中,共享逻辑的方法是通过高阶组件和 props 渲染。Hooks 提供了一种更简单方便的方法来重用代码并使组件可塑形更强。本文将展示 Typescript 与 React 集成后的一些变化,以及如何将类型添加到 Hooks 以及你的自定义 Hooks 上。
引入 Typescript 后的变化有状态组件API 对应为:React.Componentclass MyComponent extends React.Component { ...
以下是官网的一个例子,创建 Props 和 State 接口,Props 接口接受 name 和 enthusiasmLevel 参数,State 接口接受 currentEnthusiasm 参数:import * as React from "react";export interface Props { name: string; enthusiasmLevel : number;}interface State { currentEnthusiasm: number;}class Hello extends React.Component { constructor {super;this.state = { currentEnthusiasm: props.enthusiasmLevel || 1 }; } onIncrement = => this.updateEnthusiasm; onDecrement = => this.updateEnthusiasm; render {const { name } = this.props;if { throw new Error;}return } ); } updateEnthusiasm {this.setState; }}export default Hello;function getExclamationMarks { return Array.join;}
Typescript 可以对 JSX 进行解析,充分利用其本身的静态检查功能,使用泛型进行 Props、 State 的类型定义。
定义后在使用 this.state 和 this.props 时可以在编辑器中获得更好的智能提示,并且会对类型进行检查。react 规定不能通过
http://
this.props.xxx
和http://
this.state.xxx
直接进行修改,所以可以通过 readonly 将 State 和 Props 标记为不可变数据:interface Props { readonly number: number;}interface State { readonly color: string;}export class Hello extends React.Component { someMethod {this.props.number = 123; // Error: props 是不可变的this.state.color = 'red'; // Error: 你应该使用 this.setState }}
无状态组件API 对应为:// SFC: stateless function componentsconst List: React.SFC = props => null// v16.8起,由于hooks的加入,函数式组件也可以使用state,所以这个命名不准确。新的react声明文件里,也定义了React.FC类型^_^React.FunctionComponent or React.FC。const MyComponent: React.FC = ...
无状态组件也称为傻瓜组件,如果一个组件内部没有自身的 state,那么组件就可以称为无状态组件。在@types/react
已经定义了一个类型type SFC = StatelessComponent
先看一下之前无状态组件的写法:import React from 'react'const Button = =>
如果采用 ts 来编写出来的无状态组件是这样的:import React, { MouseEvent, SFC } from 'react';type Props = { onClick: void };const Button: SFC = => ;
事件处理我们在进行事件注册时经常会在事件处理函数中使用 event 事件对象,例如当使用鼠标事件时我们会通过 clientX、clientY 去获取指针的坐标。大家可以想到直接把 event 设置为 any 类型,但是这样就失去了我们对代码进行静态检查的意义。
function handleMouseChange { console.log}
试想下当我们注册一个 Touch
事件,然后错误的通过事件处理函数中的 event
对象去获取其 clientY
属性的值,在这里我们已经将 event 设置为 any 类型,导致 Typescript 在编译时并不会提示我们错误, 当我们通过 event.clientY 访问时就有问题了,因为 Touch 事件的 event 对象并没有 clientY 这个属性。通过 interface
对 event 对象进行类型声明编写的话又十分浪费时间,幸运的是 React 的声明文件提供了 Event 对象的类型声明。
- 通用的 React Event Handler
React.ReactEventHandler
简单的示例:const handleChange: React.ReactEventHandler = => { ... }nChange={handleChange} ... />
- 特殊的 React Event Handler
ClipboardEvent 剪贴板事件对象DragEvent 拖拽事件对象ChangeEvent Change 事件对象KeyboardEvent 键盘事件对象MouseEvent 鼠标事件对象TouchEvent 触摸事件对象WheelEvent 滚轮事件对象AnimationEvent 动画事件对象TransitionEvent 过渡事件对象
简单的示例:const handleChange = => { ... }
React 元素API 对应为:React.ReactElement or JSX.Element
简单的示例:// 表示React元素概念的类型: DOM元素组件或用户定义的复合组件const elementOnly: React.ReactElement = || nent />;
React NodeAPI 对应为:React.ReactNode
表示任何类型的 React 节点(基本上是 ReactElement + 原始 JS 类型的合集)简单的示例:const elementOrComponent: React.ReactNode = 'string' || 0 || false || null || undefined || || nent />;
React CSS 属性API 对应为:React.CSSProperties
用于标识 jsx 文件中的 style 对象(通常用于 css-in-js
)简单的示例:const styles: React.CSSProperties = { display: 'flex', ...const element = Hooks 登场首先,什么是 Hooks 呢?React 一直都提倡使用函数组件,但是有时候需要使用 state 或者其他一些功能时,只能使用类组件,因为函数组件没有实例,没有生命周期函数,只有类组件才有。Hooks 是 React 16.8 新增的特性,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。默认情况下,React 包含 10 个钩子。其中 3 个挂钩被视为是最常使用的“基本”或核心挂钩。还有 7 个额外的“高级”挂钩,这些挂钩最常用于边缘情况。
10 个钩子如下:
- 基础
useState
useEffect
useContext
- 高级
useReducer
useCallback
useMemo
useRef
useImperativeHandle
useLayoutEffect
useDebugValue
useState with TypescriptAPI 对应为:// 传入唯一的参数: initialState,可以是数字,字符串等,也可以是对象或者数组。// 返回的是包含两个元素的数组:第一个元素,state 变量,setState 修改 state值的方法。const [state, setState] = useState;
useState
是一个允许我们替换类组件中的 this.state 的挂钩。我们执行该挂钩,该挂钩返回一个包含当前状态值和一个用于更新状态的函数的数组。状态更新时,它会导致组件的重新 render。下面的代码显示了一个简单的 useState 钩子:import * as React from 'react';const MyComponent: React.FC = => { const [count, setCount] = React.useState; return => setCount}> {count} );};
useEffect with TypescriptAPI 对应为:// 两个参数// 第一个是一个函数,是在第一次渲染以及之后更新渲染之后会进行的副作用。这个函数可能会有返回值,倘若有返回值,返回值也必须是一个函数,会在组件被销毁时执行。// 第二个参数是可选的,是一个数组,数组中存放的是第一个函数中使用的某些副作用属性。
用来优化 useEffectuseEffect => { // 需要在componentDidMount执行的内容 return function cleanup { // 需要在componentWillUnmount执行的内容 } }, [])
useEffect
是用于我们管理副作用(例如 API 调用)并在组件中使用 React 生命周期的。useEffect 将回调函数作为其参数,并且回调函数可以返回一个清除函数。回调将在第一次渲染 和组件更新时内执行,清理函数将组件被销毁内执行。useEffect => { // 给 window 绑定点击事件 window.addEventListener; return => { // 给 window 移除点击事件 window.addEventListener; }});
默认情况下,useEffect 将在每个渲染时被调用,但是你还可以传递一个可选的第二个参数,该参数仅允许您在 useEffect 依赖的值更改时或仅在初始渲染时执行。第二个可选参数是一个数组,仅当其中一个值更改时才会 reRender。如果数组为空,useEffect 将仅在 initial render时调用。
useEffect => { // 使用浏览器API更新文档标题 document.title = `You clicked ${count} times`;}, [count]);// 只有当数组中 count 值发生变化时,才会执行这个useEffect。
useContext with TypescriptuseContext
允许您利用React context
这样一种管理应用程序状态的全局方法,可以在任何组件内部进行访问而无需将值传递为 props。useContext 函数接受一个 Context 对象并返回当前上下文值。当提供程序更新时,此挂钩将触发使用最新上下文值的重新渲染。
import { createContext, useContext } from 'react';props ITheme { backgroundColor: string; color: string;}const ThemeContext = createContext
useReducer with Typescript对于更复杂的状态,您可以选择将该 useReducer 函数用作的替代 useState。const [state,dispatch] = useReducer(reducer,initialState,init);
如果您以前使用过Redux
,则应该很熟悉。useReducer
接受 3 个参数并返回当前的 state 以及与其配套的 dispatch 方法。
reducer 是如下形式的函数 => newState
;initialState 是一个 Javascript 对象;而 init 参数是一个惰性初始化函数,可以让你延迟加载初始状态。这听起来可能有点抽象,让我们看一个实际的例子:const initialState = 0;function reducer { switch {case 'increment': return {number: state.number + 1};case 'decrement': return {number: state.number - 1};default: throw new Error; }}function init{return {number:initialState};}function Counter{const [state, dispatch] = useReducer;return => dispatch}>+ >)}
看完例子再结合上面 useReducer 的 api 是不是立马就明白了呢?useCallback with TypescriptuseCallback
钩子返回一个 memoized
回调。这个钩子函数有两个参数:第一个参数是一个内联回调函数,第二个参数是一个数组。数组将在回调函数中引用,并按它们在数组中的存在顺序进行访问。
const memoizedCallback = useCallback(()=> {doSomething(a,b); },[ a,b ],);
useCallback 将返回一个记忆化的回调版本,它仅会在某个依赖项改变时才重新计算 memoized 值。当您将回调函数传递给子组件时,将使用此钩子。这将防止不必要的渲染,因为仅在值更改时才执行回调,从而可以优化组件。
可以将这个挂钩视为与shouldComponentUpdate
生命周期方法类似的概念。useMemo with TypescriptuseMemo
返回一个 memoized 值。 传递“创建”。