批量做网站,长沙高端网站制作公司,思途智旅游网站开发,wordpress替换主题数据库为什么hook必须在组件内的顶层声明#xff1f;
这是为了确保每次组件渲染时#xff0c;Hooks 的调用顺序保持一致。React利用 hook 的调用顺序来跟踪各个 hook 的状态。每当一个函数组件被渲染时#xff0c;所有的 hook 调用都是按照从上到下的顺序依次执行的。React 内部会…为什么hook必须在组件内的顶层声明
这是为了确保每次组件渲染时Hooks 的调用顺序保持一致。React利用 hook 的调用顺序来跟踪各个 hook 的状态。每当一个函数组件被渲染时所有的 hook 调用都是按照从上到下的顺序依次执行的。React 内部会维护一个状态列表这个列表中的每个状态项都对应一个 hook 的调用包括 useState、useEffect 等。当你调用 useState(initialValue) 时React 会在内部为这个状态分配一个索引。该索引基于 hook 调用的顺序。例如第一次调用 useState 时它会在状态列表的第一个位置存储状态第二次调用会在第二个位置存储以此类推。
考虑下面这个demo
const Component () {const [count, setCount] useState(0);const handleOfClick () setCount(count 1);return button onClick{setCount}{count}/button
}Q既然每次视图的更新都会重新执行整个函数那必然会执行到const [count, setCount] useState(0)这句代码。如果我在上一次更新中把count加到10为什么在新的渲染周期中React能记住这个10而不是传给useState的0呢 A当组件重新渲染时React 会根据组件的调用顺序再次按顺序调用对应的 hook。这样React 可以确保它能够始终访问到正确的状态。例如当第二次渲染时React 知道第一个 useState 是哪个状态因为它在第一次渲染时已经分配了这个状态的索引这个索引是靠hook调用的顺序产生的索引来追踪的。 所以如果在条件语句、循环或嵌套函数中调用 hook可能会导致调用顺序的变化从而产生不可预知的状态。 useImperativeHandle
useImperativeHandle通常是和forwardRef配合使用的用来把子组件中的属性或者方法暴露给父组件在进行组件的封装或者组件间的通信的时候常会使用。如下
// 封装一个可拖拽的组件
const DragComponent forwardRef((props: {children: React.ReactNode, // 求求你不要挂一个很复杂的组件进来// other config...},ref // ref是必须的) {// 复位const resetPosition () {// todo: 可以在父组件中调用让这个可拖拽的组件在父组件中回到第一次渲染的位置}useImperativeHandle(ref, () {resetPosition // 显式声明})return (div{props.children}/div)}
)// 之后在某一个页面中使用它
const Page () {const dragRef useRef(null)const handleOfClick () {dragRef.current?.reset();}return (divDragComponent ref{dragRef}/button onClick{handleOfClick}复位/button/div)
}useCallback
useCallBack用来缓存一个函数的引用它常常配合memo使用以提高渲染的性能。
const Component memo(({ count, setCount }: { count: number; setCount: () void }
) {console.log(CountComponeng render);return (divbutton onClick{() setCount()}CountComponeng: {count}/button/div);})function App() {console.log(App render);const [parentCount, setParentCount] useState(0);const [childCount, setChildCount] useState(0);const addChildCount useCallback(() {setChildCount(childCount 1);}, [childCount]);// 这样也行// const addChildCount useCallback(() {// setChildCount((count) count 1);// }, []);return (div idapph1Hello Vite React!/h1button onClick{() setParentCount(parentCount 1)}parentCount: {parentCount}/buttonCountComponeng setCount{setChildCount} count{childCount} //div);
}export default App; useReducer
类似redux的更规范的写法我用的还不多权当记录该说不说确实优雅
import React, { useReducer } from react;// 定义初始状态
const initialState { count: 0 };// 定义 reducer 函数
const reducer (state, action) {switch (action.type) {case increment:return { count: state.count 1 };case decrement:return { count: state.count - 1 };default:return state;}
};const Counter () {// 使用 useReducerconst [state, dispatch] useReducer(reducer, initialState);return (divpCount: {state.count}/pbutton onClick{() dispatch({ type: increment })}Increment/buttonbutton onClick{() dispatch({ type: decrement })}Decrement/button/div);
};
export default Counter;setState的函数写法和变量写法
我有这样的代码const [count, setCount] useState(0)在普通的情况下setCount(count 1) 和setCount((count) count 1)都能实现count加1并更新视图的操作。
但是考虑下面这个demo
const handleOfClick () {setCount(count1)setCount(count1)setCount(count1)
}每一次点击count最终都只能加1而不能加3这个React官网介绍的很清楚这里不多说。但是如果把上面的setCount(count1)换成setCount((count) count 1)确实能实现点击一次就3并更新视图的功能因为这种函数的写法保证了在进行状态更新时能够获取到最新的状态值特别是在状态更新依赖于之前的状态值时可以避免因为异步执行导致的潜在问题。 始终记住setState是异步的而且不是没setState一次就更新一次视图的涉及到React为了优化渲染性能而使用的批量更新策略。
再考虑一个更普遍的场景
const Page () {const [count, setCount] useState(1);useEffect(() {const scrollableContainer document.getElementById(scrollable-container);scrollableContainer?.addEventListener(scroll, () {setCount(count 1)});}, [])return div idscroll-container{count}/div
}你会发现任凭你滚动的再快count只会加到2然后就一直不变了。因为此处的useEffect只会执行一次当你使用 addEventListener 直接绑定事件时你得到的是一个闭包。在这个闭包中count 的值是在事件绑定时捕获的(1)。 但是把setCount(count1)换成setCount((count) count 1)就能每次滚动的时候都加1因为它会接受当前状态作为参数这样每次更新都会基于最新的状态进行计算从而避免因为闭包问题导致的状态不正确的问题。