目录
- 只在最顶层使用 Hook
不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。
- 只在 React 函数中调用 Hook
-
- 不要在普通的 JavaScript 函数中调用 Hook。
- useState:用于在函数组件中管理状态;
- useEffect:用于处理副作用,如异步请求、订阅事件等;
- useContext:用于访问上下文的值;
- useRef:用于创建可变的引用值;
- useCallback:用于缓存回调函数。
- useReducer:用于复杂的状态管理;
- useMemo:用于性能优化,根据依赖数组缓存计算结果;
- useLayoutEffect:与 useEffect 类似,但在浏览器绘制前同步调用副作用函数。
useState
const [state, setState] = useState(initialState);
返回一个 state,以及更新 state 的函数。
在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。
setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。
useState 可以理解为一个普通 JavaScrip的函数,他的入参有两种类型:
-
- initialValue, 直接传入一个变量,将作为该 state 的初始值。
- () => initialValue, 传入一个函数,函数的返回值将作为 state 的初始值,
const [count, setCount] = useState(0);
代表 count 的初始值是 0.
useState 的返回值是一个数组,格式为 [state, setState], 数组中的第一个元素是 state 的当前值,第二个元素是一个函数,用于设置 state 的值,setState 同样接受两种类型的参数:
-
- stateValue,直接传入变量作为 state 的当前值。
- (prevState) => newState, 传入一个函数,函数的返回值将作为 state 的值,这个函数可以通过闭包拿到上一个 state 的值,这个值往往非常有用。
onClick={()=>setCount(c=>c+1)}
代表,设置 count 的值,为 count 之前值 + 1
useEffect
useEffect(didUpdate);
该 Hook 接收一个包含命令式、且可能有副作用代码的函数。
useEffect, 顾名思义,就是“使用副作用”,通常用到 Effect 的情况包括:设置定时器,发送网络请求等。先来看一下 useEffect 的构成。useEffect 接收两个参数,第一个是必传的 callback,第二个是选传的 dependencies, 类型为列表。例如 [a, b]。
可以理解为,每当 dependencies 中的一个或多个元素发生改变时,都会去执行一遍 callback.
两个特殊情况是:
- deps 为空列表 [], 则 callback 只会在组件 mount 时被执行。
- 不传入 deps 参数,则每当 state 或者 props 发生变化(组件重新渲染)时,callback 都会被执行。
另外针对 useEffect 的 callback,如果 callback 返回了一个函数,则这个函数会在 deps 改变后,下一次 callback 执行之前被执行。
useEffect(()=>{
const timer = window.setInterval(()=>{console.log(a)}, 1000);
return () => window.clearInterval(timer);
}, [a])
当 a 发生改变时,旧的 timer 会被 clear 掉,新的 timer 会重新生成。所以 useEffect callback 的 return 函数是清理副作用的最佳时机。
当 deps 为空列表时,useEffect 相当于 class 组件中的 componentDidMount 生命周期,return 函数的执行时机则相当于 componentWillUnmount.
deps不可传入ref和常量,字符串传入没意义。
清除 effect
通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源。要实现这一点,useEffect 函数需返回一个清除函数。为防止内存泄漏,清除函数会在组件卸载前执行。另外,如果组件多次渲染(通常如此),则在执行下一个 effect 之前,上一个 effect 就已被清除
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清除订阅
subscription.unsubscribe();
};
});
useContext
const value = useContext(MyContext);
- 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
- 当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。即使祖先使用 React.memo 或 shouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。
- useContext 的参数必须是 context 对象本身:正确: useContext(MyContext)
- 调用了 useContext 的组件总会在 context 值变化时重新渲染。如果重渲染组件的开销较大,你可以 通过使用 memoization 来优化。
- useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context。
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={
{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
useLayoutEffect
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。无论 useLayoutEffect 还是 useEffect 都无法在 Javascript 代码加载完成之前执行。
会比usseEffect早一些执行。
useRef
const refContainer = useRef(initialValue);
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。
Refs 为可变对象不会被传递
虽然高阶组件的约定是将所有 props 传递给被包装组件,但这对于 refs 并不适用。那是因为 ref 实际上并不是一个 prop - 就像 key 一样,它是由 React 专门处理的。如果将 ref 添加到 HOC 的返回组件中,则 ref 引用指向容器组件,而不是被包装组件。
通过使用 React.forwardRef API可以解决这个问题
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
返回一个 memoized 值。
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行不应该在渲染期间内执行的操作如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。
const classNames = useMemo(() => {
return [NEW_DESCRIPTION_CLASSNAME, overflowedClassname].filter(Boolean).join(" ");
}, [overflowedClassname])
useCallback
返回一个 memoized 回调函数
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。
const backgroundColorChange = useCallback(() => {
if (!changeBC) {
setChangeBC(true);
setTimeout(() => {
setChangeBC(false)
}, 1000)
}
}, [changeBC])
useImperativeHandle
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值,useImperativeHandle 应当与 forwardRef 一起使用,减少暴露给父组件的属性,useImperativeHandle本身就是一次ref命令代码执行,As always, imperative code using refs should be avoided in most cases.
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
通过forwardRef,父组件获取子组件的ref,子组件在暴露ref中,限制暴露的一些参数
import { useRef,forwardRef,MutableRefObject,useImperativeHandle,Ref} from "react";
//只暴露value、getType、focus给父级
const InputEl = forwardRef((props: {}, ref: Ref<any>): JSX.Element=>{
const inputEl: MutableRefObject<any> = useRef();
useImperativeHandle(ref, ()=>({//第一个参数:暴露哪个ref;第二个参数:暴露什么
value: (inputEl.current as HTMLInputElement).value,
getType: () => (inputEl.current as HTMLInputElement).type,
focus: () => (inputEl.current as HTMLInputElement).focus()
}));
return(
<input ref={inputEl} type="text" {...props}/>
)
})
//暴露整个input节点给父级
const InputEl = forwardRef((props: {}, ref: Ref<any>): JSX.Element=>{
return(
<input ref={ref} type="text" {...props}/>
)
});
//父级
function InputWithFocusButton() {
const inputEl: MutableRefObject<any> = useRef(null);
function onButtonClick() {
console.log('子组件input的对象:', inputEl.current);
inputEl.current.focus();
};
return (
<>
<InputEl ref={inputEl} />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
useReducer
Hook 介绍:useReducer 用于在函数组件中实现复杂的状态管理。它类似于 Redux 中的 reducer,接收一个纯函数和初始状态,并返回当前状态和一个派发函数,用于触发状态更新。它适用于需要处理多个相关状态变化并具有复杂逻辑的场景。
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error('Unexpected action');
}
}
function Example() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
const increment = () => {
dispatch({ type: 'increment' });
};
const decrement = () => {
dispatch({ type: 'decrement' });
};
return (
<div>
<p>Count: {state.count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
export default Example;
文章评论