TypeScript and React: Hooks
React Hooks
Hooks以前将“无状态”功能组件放到了……基本上,传统类组件都可以做到。使用更干净的API!在React 16.7中发布后不久,DefinitelyTyped中的React类型也得到了更新。了解如何在TypeScript中使用钩子!
useState
import React, { FunctionComponent, useState } from 'react';
const Counter:FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
const [clicks, setClicks] = useState(initial);
return <>
<p>Clicks: {clicks}</p>
<button onClick={() => setClicks(clicks+1)}>+</button>
<button onClick={() => setClicks(clicks-1)}>-</button>
</>
}
useState 的使用方式
const [keys, setKeys] = useState<{ currentOpenSubs: string[], currentSideMenu: string }>({
currentSideMenu: '',
currentOpenSubs: [],
});
useEffect
useEffect在这里有所有副作用。添加事件侦听器,更改文档中的内容,获取数据。一切你所使用的(组件生命周期方法componentDidUpdate
,componentDidMount
,componentWillUnmount
)的方法签名是非常简单的。它接受两个参数:
- 无需任何参数即可调用的函数。这是您要调用的副作用。
- 类型的值数组any。此参数是可选的。如果不提供,则每次组件更新时都会调用提供的功能。如果这样做,React将检查这些值是否确实发生了变化,并且仅在存在差异时才触发函数。
useEffect(() => {
const handler = () => {
document.title = window.width;
}
window.addEventListener('resize', handler);
return true;
return () => {
window.removeEventListener('resize', handler);
}
})
useContext
useContext允许您从组件中的任何位置访问上下文属性。非常类似于Context.Consumer 类组件中的do。类型推断在这里非常出色,您不需要使用任何TypeScript特定的语言功能就可以完成所有工作:
import React, { useContext } from 'react';
export const LanguageContext = React.createContext({ lang: 'en' });
const Display = () => {
const { lang } = useContext(LanguageContext);
return <>
<p>Your selected language: {lang}</p>
</>
}
useRef
function TextInputWithFocusButton() {
const inputEl = useRef<HTMLInputElement>(null);
const onButtonClick = () => {
if(inputEl && inputEl.current) {
inputEl.current.focus();
}
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
useMemo-useCallback
useMemo返回一个 memoized 值。 传递“创建”函数和依赖项数组。useMemo 只会在其中一个依赖项发生更改时重新计算 memoized 值。此优化有助于避免在每个渲染上进行昂贵的计算。
const memoizedValue = useMemo(() => computeExpensiveValue( a, b),[ a, b ]);
useMemo 在渲染过程中传递的函数会运行。不要做那些在渲染时通常不会做的事情。例如,副作用属于 useEffect,而不是 useMemo。
您知道useEffect,可以通过向其传递一些参数来影响某些函数的执行。React检查这些参数是否已更改,并且仅在存在差异的情况下才会执行此功能。
useMemo做类似的事情。假设您有大量计算方法,并且只想在其参数更改时运行它们,而不是每次组件更新时都运行它们。useMemo返回记忆的结果,并且仅在参数更改时才执行回调函数。
function getHistogram(image: ImageData): number[] {
...
return histogram;
}
function Histogram() {
...
const histogram = useMemo(() => getHistogram(imageData), [imageData]);
}
useCallback非常相似。实际上,这也是可以表达的捷径useMemo。但是它返回一个回调函数,而不是一个值。
const memoCallback = useCallback((a: number) => {
// doSomething
}, [a])
useMemo和useCallback的作用有点像啊,那它们之间有什么区别呢?
- useCallback 和 useMemo 都可缓存函数的引用或值。
- 从更细的使用角度来说 useCallback 缓存函数的引用,useMemo 缓存计算数据的值。
useReducer
如果您以前使用过Redux,则应该很熟悉。useReducer接受 3 个参数(reducer,initialState,init)并返回当前的 state 以及与其配套的 dispatch 方法。reducer 是如下形式的函数(state, action) => newState;initialState 是一个 JavaScript 对象;而 init 参数是一个惰性初始化函数,可以让你延迟加载初始状态。
const [state,dispatch] = useReducer(reducer,initialState,init);
type ActionType = {
type: 'reset' | 'decrement' | 'increment'
}
const initialState = { count: 0 };
// We only need to set the type here ...
function reducer(state, action: ActionType) {
switch (action.type) {
// ... to make sure that we don't have any other strings here ...
case 'reset':
return initialState;
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
function Counter({ initialCount = 0 }) {
const [state, dispatch] = useReducer(reducer, { count: initialCount });
return (
<>
Count: {state.count}
{ /* and can dispatch certain events here */ }
<button onClick={() => dispatch({ type: 'reset' })}>
Reset
</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
}
React-redux hooks
React Redux 现在提供了一系列 hook APIs 作为现在 connect() 高阶组件的替代品。这些 APIs 允许你,在不使用 connect() 包裹组件的情况下,订阅 Redux 的 store,和 分发(dispatch) actions。
useSelector()
const result : any = useSelector(selector : Function, equalityFn? : Function)
基本用法
import React from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => {
const counter = useSelector(state => state.counter)
return <div>{counter}</div>
}
通过闭包使用 props 来选择取回什么状态:
import React from 'react'
import { useSelector } from 'react-redux'
export const TodoListItem = props => {
const todo = useSelector(state => state.todos[props.id])
return <div>{todo.text}</div>
}
**使用记忆化的 selectors 函数**
当像上方展示的那样,在使用 useSelector 时使用单行箭头函数,会导致在每次渲染期间都会创建一个新的 selector 函数。可以看出,这样的 selector 函数并没有维持任何的内部状态。但是,记忆化的 selectors 函数 (通过 reselect 库中 的 createSelector 创建) 含有内部状态,所以在使用它们时必须小心。
当一个 selector 函数依赖于某个 状态(state) 时,确保函数声明在组件之外,这样就不会导致相同的 selector 函数在每一次渲染时都被重复创建:
import React from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'
const selectNumOfDoneTodos = createSelector(
state => state.todos,
todos => todos.filter(todo => todo.isDone).length
)
export const DoneTodosCounter = () => {
const NumOfDoneTodos = useSelector(selectNumOfDoneTodos)
return <div>{NumOfDoneTodos}</div>
}
export const App = () => {
return (
<>
<span>Number of done todos:</span>
<DoneTodosCounter />
</>
)
}
被移除的:useActions()
import { useMemo, DependencyList } from 'react';
import { bindActionCreators } from 'redux';
import { useDispatch } from 'react-redux';
// 这个主要是用于配合saga 处理异步请求
import {
bindPromiseCreators,
PromiseCreator,
ActionCreatorFunction,
Routine,
} from 'redux-saga-routines';
type actionType = Routine | PromiseCreator | ActionCreatorFunction;
// hooks 一定要以use开头
function useActions(
actions: {
[kye: string]: actionType;
},
deps?: DependencyList | undefined
): any {
const dispatch = useDispatch();
return useMemo(() => {
const newActions = actions;
const keys = Object.keys(actions);
keys.forEach((key: string) => {
if( newActions[key].length === 2 ) {
newActions[key] = bindPromiseCreators((actions[key] as PromiseCreator), dispatch);
} else {
newActions[key] = bindActionCreators((actions[key] as Routine), dispatch);
}
})
return newActions;
}, deps ? [dispatch, ...deps] : [dispatch]);
}
useAction的使用方式
const { topMenu, currentSidebar, theme, drawer, primaryColor } = useSelector((state: IState) => state.menu);
const [collapsed, setCollapsed] = useState(false);
const actions = useActions({
setMenu: menuAction.setMenu,
setDrawer: menuAction.setDrawer,
setTheme: menuAction.setTheme,
});
const handleSettingClick = useCallback((values) => {
actions.setTheme(values);
}, [actions]);
useDispatch() 这个 hook 返回 Redux store 的 分发(dispatch) 函数的引用。你也许会使用来 分发(dispatch) 某些需要的 action。
import React from 'react'
import { useDispatch } from 'react-redux'
export const CounterComponent = ({ value }) => {
const dispatch = useDispatch()
return (
<div>
<span>{value}</span>
<button onClick={() => dispatch({ type: 'increment-counter' })}>
Increment counter
</button>
</div>
)
}
在将一个使用了 dispatch 函数的回调函数传递给子组件时,建议使用 useCallback 函数将回调函数记忆化,防止因为回调函数引用的变化导致不必要的渲染。
这里的建议其实和 dispatch 没关系,无论是否使用 dispatch,你都应该确保回调函数不会无故变化,然后导致不必要的重渲染。之所以和 dispatch 没关系,是因为,一旦 dispatch 变化,useCallback 会重新创建回调函数,回调函数的引用铁定发生了变化,然而导致不必要的重渲染。
import React, { useCallback } from 'react'
import { useDispatch } from 'react-redux'
export const CounterComponent = ({ value }) => {
const dispatch = useDispatch()
const incrementCounter = useCallback(
() => dispatch({ type: 'increment-counter' }),
[dispatch]
)
return (
<div>
<span>{value}</span>
<MyIncrementButton onIncrement={incrementCounter} />
</div>
)
}
export const MyIncrementButton = React.memo(({ onIncrement }) => (
<button onClick={onIncrement}>Increment counter</button>
))
useStore() 这个 hook 返回传递给 组件的 Redux sotore 的引用。
这个 hook 也许不应该被经常使用。 你应该将 useSelector() 作为你的首选。但是,在一些不常见的场景下,你需要访问 store,这个还是有用的,比如替换 store 的 reducers。
import React from 'react'
import { useStore } from 'react-redux'
export const CounterComponent = ({ value }) => {
const store = useStore()
return <div>{store.getState()}</div>
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)