React Hook介绍
简介
介绍:legacy.reactjs.org/docs/hooks-...
什么是 Hook?
Hook 是 React 16.8 的新增特性,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。其本质上就是一类特殊JavaScript函数,它们约定以 use 开头,可以为 React Function Component 注入一些功能,赋予 Function Component 一些类似 Class Component 所具备的能力,比如状态管理和生命周期
为什么引入 Hook?
- 在组件之间复用状态逻辑很难
- 复杂组件变得难以理解
- 难以理解的 class
常见Hooks
useState-
- 作用:用于在函数组件中添加状态。可以给任何类型的值添加状态(基础数据类型、引用数据类型、自定义数据结构等)
- 如何使用:
- 使用场景:当你需要在组件中存储一些可变的值,并且当这些值变化时需要重新渲染组件
- 注意事项:1、更新状态会使用新的状态值请求另一个渲染,但并不影响你已经运行的函数中的 状态的 的值。
2、引用数据类型更新时,需赋值新对象,而不是在旧对象上做修改。
3、存储 一个函数,需要加上 () => ,否则会被直接调用(见下方源码)
scss
// 声明一个叫 "count" 的 state 变量。
const [count, setCount] = useState(0);
function addCount(){
console.log("addCount start:", count);
setCount(count + 1);
console.log("addCount end:", count);
//先保存在一个变量中
// const nextCount = count + 1;
// setCount(nextCount);
// console.log(count); // 0
// console.log(nextCount); // 1
}
function startCountInterval1(){
setInterval(() => {
console.log("startCountInterval start:", count);
setCount(count + 1);
}, 1000);
}
function startCountInterval2() {
let count = 10;
setInterval(() => {
console.log("startCountInterval start:", count);
count++;
}, 1000);
}
/**
* React 会在 `person` 对象的内存引用发生变化时触发更新。
* 如果直接修改 `person` 对象的属性而没有生成一个新的对象引用,
* React 将不会识别状态已经改变,因此不会重新渲染组件。
*/
const [person,setPreson] = useState({name: '张三', age: 18});
function onSetPreson(){
//页面会刷新
setPreson({...person, name: '李四'});
//页面不会刷新
person.name = '李四';
setPreson(person);
}
//源码:如果传入一个函数,会被直接调用,生存初始值,所以想存储一个函数的状态,需要使用()=>
function mountStateImpl<S>(initialState: (() => S) | S): Hook {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
const initialStateInitializer = initialState;
// $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
initialState = initialStateInitializer();
if (shouldDoubleInvokeUserFnsInHooksDEV) {
setIsStrictModeForDevtools(true);
// $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
initialStateInitializer();
setIsStrictModeForDevtools(false);
}
}
useEffect-
- 作用:用于处理副作用,类似于类组件的
componentDidMount,componentDidUpdate, 和componentWillUnmount生命周期方法。副作用是指那些与组件的渲染无直接关联的操作,比如数据获取、订阅、或手动更改DOM等。 - 如何使用:
- 使用场景:1、数据获取:在组件渲染后获取异步数据。
- 作用:用于处理副作用,类似于类组件的
2、订阅事件:在组件订阅外部事件,比如 Redux store 的变化。
3、手动操作 DOM:在组件渲染后对 DOM 进行操作。
- 注意事项: 1、注意闭包陷阱,正确添加依赖(所有响应式值的列表),安装eslint插件,会帮助检测依赖
2、正确处理副作用的清理,特别是订阅和定时器
3、分离不想依赖的响应式值(useEffectEvent)
scss
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
const [seconds, setSeconds] = useState(0)
useEffect(() => {
console.log("每次页面渲染都会执行")
});
useEffect(() => {
console.log("只在页面初次渲染执行")
}, []);
useEffect(() => {
console.log("只在 count 变化时执行")
}, [count])
//注意闭包陷阱
useEffect(() => {
//设置一个定时器
console.log("设置一个定时器")
let timer = setInterval(() => {
setSeconds(preSec => {
console.log('preSec:', preSec)
return preSec + 1
})
//setSeconds(seconds + 1);
//这里会打印什么
//console.log('seconds:', seconds)
}, 1000)
// 移除副作用
return () => {
console.log("移除定时器")
clearInterval(timer)
}
}, [])
function Page({ url ,numberOfItems}) {
useEffect(() => {
logVisit(url, numberOfItems);
}, [url]); // 🔴 React Hook useEffect 缺少依赖项: 'numberOfItems'
// ...
}
useContext-
- 作用:用于让你访问 React 的 Context API,以便在组件树中传递数据。
- 如何使用:1、创建context:createContext;2、使用CountContext.Provider 给value 赋值;3、在子组件中使用useContext
- 使用场景:当需要在 React 应用中跨多层组件传递数据时,比如修改主题,用户身份验证,多语言支持
- 注意事项:1、确保不要滥用 context,可能会导致组件不必要的重新渲染。当需要在多层嵌套的组件中共享全局数据时使用
2、确保提供了默认值,避免在没有 Provider 的情况下出错
javascript
//1、创建context:createContext
const ThemeContext = createContext('light');
function UseContextComponent() {
const [theme,setTheme]= useState('light');
return (
//2、使用CountContext.Provider 给value 赋值
<ThemeContext.Provider value={theme}>
<div >
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>修改主题</button>
<ChildComponent/>
</div>
</ThemeContext.Provider>
);
}
export default UseContextComponent;
function ChildComponent() {
//3、在子组件中使用useContext
const theme = useContext(ThemeContext);
return (
<div className={`div-${theme}`}>
<p>agajfi</p>
<br/>
<button className={`button-${theme}`}>添加按钮</button>
<br/>
<br/>
</div>
);
}
useRef-
- 作用:用于在函数组件中创建一个可变的ref对象,可以用来保存任何可变值,类似于在类组件中使用实例字段
- 如何使用:
- 使用场景:1、通过 ref 操作 DOM;2、当需要在函数组件中保存一个可变值,但又不希望因为该值的改变而导致组件重新渲染时
- 注意事项:1、当 ref 对象内容变化时,不会触发组件重新渲染
2、除了初始化外不要在渲染期间写入或者读取ref.current,否则会使组件行为变得不可预测。
3、无法获取自定义组件的dom节点 ,需要借助forwardRef
javascript
//1、通过 ref 操作 DOM
//1.1、声明一个 初始值 为 null 的 ref 对象
const inputRef = useRef<HTMLInputElement>(null);
//1.3 当 React 创建 DOM 节点并将其渲染到屏幕时,React 将会把 DOM 节点设置为 ref 对象的 current 属性。
//现在可以借助 ref 对象访问 <input> 的 DOM 节点
const showDom = () => {
if (inputRef.current !== null) {
console.log(inputRef.current.value);
}
};
<label>
请输入文本内容
{/* 1.2 将 ref 对象作为 ref 属性传递给想要操作的 DOM 节点的 */}
<input type="text" ref={inputRef} />
<button onClick={showDom}>获取dom</button>
</label>
//2、下面代码中 timeoutID是普通变量,在组件重新渲染时,会重新置为null,所以停止不了延时消息,这个时候需要使用useRef
const [isSending, setIsSending] = useState(false);
let timeoutID: NodeJS.Timeout;
// const timeoutID = useRef<NodeJS.Timeout>();
console.log("渲染");
function handleSend() {
setIsSending(true);
timeoutID = setTimeout(() => {
console.log("已发送");
setIsSending(false);
}, 3000);
console.log("点击发送", timeoutID);
}
function handleUndo() {
setIsSending(false);
clearTimeout(timeoutID);
console.log("终止发送", timeoutID);
}
useCallback-
- 作用:用于在函数组件中缓存函数,以避免不必要的重复渲染。
- 如何使用:
- 使用场景:1、将函数作为 props 传递给包装在 [memo] 中的组件。如果 props 未更改,则希望跳过重新渲染。Memoization允许组件仅在依赖项更改时重新渲染
2、传递的函数可能作为某些 Hook 的依赖。比如依赖于useEffect中的函数。
3、优化自定义 Hook
- 注意事项:1、过度使用可能会导致性能问题,useCallback 钩子需要在内存中保持函数的引用,这意味着增加了内存的使用,如果你不确定是否需要它,最好是先不用,等到性能成为问题时再考虑引入 useCallback
2、正确添加依赖,注意闭包陷阱。遗漏了依赖项,那么当这些依赖项改变时,缓存的函数可能会引用旧的变量或状态值,导致错误或不一致的行为。添加了不必要的依赖项,函数可能会过于频繁地重新创建。
3、不允许在循环中为每一个列表项调用 useCallback 函数
javascript
function UseCallBackComponent() {
const [count,setCount] = useState(0);
const [count1,setCount1] = useState(0);
const callbackFun = useCallback(()=>{
console.log('count',count);
},[count]);
// const callbackFun = ()=>{
// console.log('count',count);
// };
return (
<div >
<button onClick={() => setCount(count + 1)}>click count: {count}</button>
<button onClick={() => setCount1(count1 + 1)}>click count1: {count1}</button>
<ChildComponent callbackFun={callbackFun}/>
</div>
);
}
const ChildComponent = memo(function ChildComponent(props:any) {
console.log('child render',props.callbackFun);
return <div>
我是子组件
<button onClick={props.callbackFun}>callback</button>
</div>
});
useMemo-
- 作用:用于在函数组件中缓存计算结果
- 如何使用:
- 使用场景:1、跳过代价昂贵的重新计算(如何衡量计算过程的开销是否昂贵)
你在 useMemo 中进行的计算明显很慢,而且它的依赖关系很少改变
- 注意事项:
1、页面初次渲染或者依赖发生变化时,会执行useMemo的计算。
2、不保证被记忆,React 可能会在必要时清除记忆化的值。
3、仅在性能优化成为问题时使用,不要过度使用以避免增加复杂性
4、正确添加依赖。
5、不允许为循环中的每个列表项调用 useMemo
javascript
function UseMemoComponent() {
const [count, setCount] = useState(0);
const [price, setPrice] = useState(10);
const countSum = useMemo(() => {
//复杂运算
console.log("useMemo count", count, "price", price);
return count * price;
}, [count]);
// const countSum = ()=>{
// console.log("count", count,'price',price);
// return count * price;
// }
const getSum = () => {
console.log("countSum", countSum);
// console.log("countSum", countSum());
};
return (
<div>
<button onClick={() => setCount(count + 1)}>click count: {count}</button>
<button onClick={() => setPrice(price + 1)}>changePrice: {price}</button>
<button onClick={getSum}>getAdd</button>
</div>
);
}
useReducer-
- 作用:用于在函数组件中添加状态,适用于状态逻辑复杂的场景,或者当下一个状态依赖于之前的状态时。
- 如何使用:
- 使用场景:1、状态逻辑复杂,涉及多个子值或下一个状态依赖于之前的状态。
2、状态更新逻辑可能在多个回调中重用。
3、你想要将组件的业务逻辑外提到组件以外,以便进行单元测试。
总的来说,useState 适用于简单的状态管理,而 useReducer 则适用于处理更复杂的状态逻辑
- 注意事项:1、包含useState的注意事项
2、对于简单的状态管理,使用 useState 就足够了,只有当状态逻辑变得复杂时才考虑使用 useReducer。
javascript
// 1、定义reducer函数
function reducer(state: { count: number }, action: any) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error();
}
}
export default function UseReducerComponanent() {
// 2、使用useReducer并传入reducer函数和初始状态
const [state, dispatch] = useReducer(reducer, { count: 5 });
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</>
);
}
自定义hook
react hooks中的自定义hook
Hooks 使用的注意事项
- 只在最顶层使用 Hook,不要在循环、条件或嵌套函数中调用 Hook:确保 Hook在每次组件渲染时都以相同的顺序被调用。
- 只在 React 函数中调用 Hook:在 React 的函数组件中调用 Hook,在自定义 Hook 中调用其他 Hook。
- 为了避免不必要的渲染,正确使用依赖项数组 :尤其是在
useEffect,useCallback, 和useMemo中。 - 优化性能 :使用
useCallback和useMemo时,确保它们是必要的,因为它们会增加应用程序的内存使用。 - 规则的遵守:使用 ESLint 插件来强制执行 Hooks 规则,帮助避免常见错误。
- 严格模式:在开发模式下,当组件被包裹在 <React.StrictMode> 组件中时,React 会对组件进行两次初始化,useEffect 和 useLayoutEffect 的回调会在每次渲染后执行两次,useState 和其他 Hooks 的初始化会调用两次,这是为了帮助开发者发现组件中的一些常见问题,比如副作用(side effects)不应该在组件的初始化阶段执行。
hook源码解析:cloud.tencent.com/developer/a...