一、useState(保存组件状态)
1、基本使用
import { useState } from 'react';
function Example() {
const [initialState, setInitialState] = useState(default);
}
useState
(保存组件状态) :React hooks是function组件(无状态组件) ,所以需要useState
保存组件状态(不要直接在函数组件中使用let定义变量。因为function没有状态,当function由useState或者props等引发更新的时候,let定义的变量会被重置成初始状态,也就不会发生改变)。
useState是异步更新的,
返回一个由两个值组成的数组:
- 当前的 state在首次渲染时,它将与你传递的
initialState
相匹配。 - set函数,它可以让你将 state 更新为不同的值并触发重新渲染。
注意事项
useState
是一个 Hook,因此你只能在组件的顶层或自己的 Hook 中调用它。你不能在循环或条件语句中调用它。如果你需要这样做,请提取一个新组件并将状态移入其中。- 在严格模式中,React 将 两次调用初始化函数,以 帮你找到意外的不纯性。这只是开发时的行为,不影响生产。如果你的初始化函数是纯函数(本该是这样),就不应影响该行为。其中一个调用的结果将被忽略。
2、解决useState异步更新不及时的问题
-
useEffect(推荐)
通过useEffect监听可以获取到最新的值
-
传入函数(推荐)(可以让页面上保持最新的值,但是不可以 立马获取到最新的值)
通过传入函数,可以根据先前的 state 更新 state ,但是不能立马获取值。
这是因为直接调用 set
函数 不会更新已经运行代码中的 count1状态变量。因此,即使调用多个 setCount1(count1
+ 1)
调用都是拿最初始的值+1 。
通过函数调setCount((count2)=>count2 + 1)是更新函数。它获取 待定状态 ,并从中计算 下一个状态,所以每次拿到的都是最新的值.
但是由于useState是异步的,所以我们在下方打印并不能获取到最新的值,如果想获取到最新的值,可以结合useEffect使用。
import React, { useState, useRef } from 'react';
function Example() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const handleClick = () => {
setCount(count1 + 1);
setCount(count1 + 1);
console.log('count1:', count1); // 输出旧的count值 0 ,页面上显示 1
setCount((count2)=>count2 + 1);
setCount((count2)=>count2 + 1);
console.log('count2:', count2); // 输出旧的count值 0,页面上显示 2
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
-
通过useRef解决(不推荐)(可以获取最新的值,但是写起来比较麻烦)
通过useRef可以解决useState更新不及时的问题。useRef是React提供的一个Hook,它可以用来在函数组件中存储和访问可变的值。
当我们使用useState来更新状态时,由于useState是异步的,所以在更新状态后立即访问状态的值可能会得到旧的值。这时可以使用useRef来保存状态的引用,以便在需要时获取最新的值。
具体的解决方法如下:
- 使用useRef创建一个引用变量:
const stateRef = useRef(initialState);
,其中initialState是状态的初始值。 - 在需要获取最新状态值的地方,通过
stateRef.current
来访问最新的状态值。
下面是一个示例代码:
import React, { useState, useRef } from 'react';
function Example() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
const handleClick = () => {
setCount(count + 1);
countRef.current = count + 1;
console.log('count:', count); // 输出旧的count值 0
console.log('countRef:', countRef.current); // 输出最新的count值 1
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
二、useEffect(处理副作用) 和 useLayoutEffect (同步执行副作用)
1、基本使用
useEffect(() => {
//effect在这里执行副作用
return () => {
//cleanup清理代码在这里
};
}, [依赖的状态;空数组,表示不依赖])
useLayoutEffect(() => {
//effect在这里执行副作用
return () => {
//cleanup清理代码在这里
};
}, [依赖的状态;空数组,表示不依赖])
useEffect和useLayoutEffect接收两个参数,第一个参数是一个回调函数,用于定义副作用操作逻辑,第二个参数是一个依赖数组,可以指定副作用操作的触发条件。
ps:
- 依赖数组监听对象和数组是浅比较,只有地址发生变化了,才会进入useEffect,所以每次修改数组和对象,都要返回一个新对象或者新数组。我们也可以使用JSON.stringify()把对象或者数组转换成字符串监听(出问题了再用)。
- 依赖数组为空数组的时候,只在页面初始化时调用。如果此时回调函数里面有return方法,会在离开页面时触发,可以在return方法里面销毁定时器。
2、区别
useEffect(处理副作用)和useLayoutEffect (同步执行副作用)都是在组件渲染完成后执行的副作用函数:
- useEffect(异步):会在浏览器绘制完成后异步执行,不会阻塞页面渲染。因此,它适用于大多数情况下的副作用操作,比如数据获取、订阅事件、修改DOM等。由于是异步执行,可能会导致页面闪烁或者用户看到不一致的UI。
- useLayoutEffect(同步):在浏览器绘制之前同步执行,即会阻塞页面渲染。这使得它更适合处理需要立即更新UI的副作用操作,比如修改DOM样式、测量元素尺寸等。由于是同步执行,可以确保在页面渲染前完成副作用操作,避免了页面闪烁或不一致的UI。
总结:虽然useEffect会造成回流和重绘,但由于它的执行时机是在浏览器完成绘制后,所以对页面性能的影响较小。而useLayoutEffect的同步执行可能会导致页面卡顿。所以官方建议优先使用 useEffect 。
ps:补充浏览器渲染过程如下(详细流程在这)
3、什么时候应该使用 useLayoutEffect(举例)?
-
添加平滑滚动
可以用于向容器元素添加平滑滚动功能。设置事件侦听器以侦听窗口对象上的滚动事件并调用 handlescroll 函数。该函数将使用带有 { top: 0, behavior: 'smooth' } 作为参数的 scrollTo 方法将容器平滑地滚动到顶部。
import React, { useRef, useLayoutEffect } from 'react';
const SmoothScrolling = () => {
const containerRef = useRef(null);
useLayoutEffect(() => {
const container = containerRef.current;
const handleScroll = () => {
// 平滑滚动到容器顶部
container.scrollTo({
top: 0,
behavior: 'smooth',
});
};
// 当组件被挂载时滚动到顶部
handleScroll();
// 添加事件侦听器以在后续滚动时滚动到顶部
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
return (
<div ref={containerRef}>
{/* 你的内容 */}
</div>
);
};
-
动画元素
为元素的不透明度设置动画。元素的初始不透明度设置为 0,然后使用 setTimeout
函数在 1000 毫秒的延迟后将其设置为 1。然后 useLayoutEffect
在组件挂载后应用动画。当组件被卸载时,元素的不透明度重置为 0。
mport React, { useRef, useLayoutEffect } from 'react';
const AnimatingElements = () => {
const elementRef = useRef(null);
useLayoutEffect(() => {
const element = elementRef.current;
// 在挂载时为元素的不透明度设置动画
element.style.opacity = 0;
setTimeout(() => {
element.style.opacity = 1;
}, 1000);
return () => {
// 组件卸载时清理动画
element.style.opacity = 0;
};
}, []);
return <div ref={elementRef}>Animate me!</div>;
};
-
自动对焦输入框
使用 它在组件挂载时自动聚焦到输入字段。我们继续使用 ref 访问输入元素。在 useLayoutEffect 中,我们调用输入元素上的 focus 方法来赋予它焦点。因为我们希望它只运行一次,所以我们将依赖项数组留空 ([])。
注意:对于此示例,没有清理功能,因为在卸载组件时不需要撤消焦点。
import React, { useRef, useLayoutEffect } from 'react';
const AutoFocusInput = () => {
const inputRef = useRef(null);
useLayoutEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;
};
三、useCallback(记忆函数) 和 useMemo (记忆组件)
import React, { useState, useCallback, useMemo } from 'react';
const ExampleComponent = () => {
const [count, setCount] = useState(0);
// 使用useCallback来缓存一个回调函数,用于修改useState的值
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
// 使用useMemo来缓存一个计算结果(相当于vue的计算属性)
const doubledCount = useMemo(() => {
return count * 2;
}, [count]);
return (
<div>
<p>Count: {count}</p>
<p>Doubled Count: {doubledCount}</p>
<button onClick={handleClick}>Increase Count</button>
</div>
);
}
export default ExampleComponent;
useCallback 和 useMemo 都是用来优化性能的hooks(用于优化性能和避免不必要的重新渲染)。在这个例子中,我们使用了useCallback
来缓存handleClick
回调函数,以便在count
更新时不会重新创建函数。而useMemo
则用来缓存doubledCount
的计算结果,以便在count
更新时不会重新计算。这样可以帮助提高组件的性能。
useCallback用于创建一个记忆化的回调函数。它接收一个回调函数和一个依赖数组作为参数,并返回一个记忆化后的回调函数。当依赖数组中的值发生变化时,才会重新创建回调函数。这样可以避免在每次渲染时都创建新的回调函数,提高性能。
useMemo用于创建一个记忆化的值。它接收一个计算函数和一个依赖数组作为参数,并返回一个记忆化后的值。当依赖数组中的值发生变化时,才会重新计算值。这样可以避免在每次渲染时都重新计算值,提高性能。
四、useRef(保存引用值)
import React, { useRef, useEffect } from 'react';
const RefExample = () => {
const inputRef = useRef(null);
useLayoutEffect(() => {
// 在组件加载后自动聚焦到输入框
inputRef.current.focus();
}, []);
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus Input</button>
</div>
);
}
export default RefExample;
useRef
的作用是创建一个可变的ref对象,其current
属性被初始化为传入的参数(可以是任意值)。useRef
返回的ref对象在组件的整个生命周期中保持不变,不会因为组件重新渲染而改变。这使得useRef
非常适合用于存储和访问DOM元素或者在组件渲染周期之间存储任意可变值的情况。
在上面这个例子中,我们使用了useRef
来创建一个引用inputRef
,并将其绑定到一个input元素上。在组件加载后,我们使用useEffect
来自动聚焦到输入框,并且通过按钮点击也可以聚焦到输入框。
useRef 经常被用于管控React hooks中的定时器,我们使用useRef
创建了一个intervalRef
引用,用来存储定时器的ID。当点击"Start Timer"按钮时,我们使用setInterval
启动定时器,并将定时器ID存储在intervalRef.current
中。当点击"Stop Timer"按钮时,我们使用clearInterval
停止定时器。在组件卸载时,我们通过useEffect
的清理函数清除定时器,以避免内存泄漏。如下
import React, { useRef, useEffect } from 'react';
const TimerExample = () => {
const intervalRef = useRef(null);
const startTimer = () => {
intervalRef.current = setInterval(() => {
console.log('Timer tick');
}, 1000);
};
const stopTimer = () => {
clearInterval(intervalRef.current);
};
useEffect(() => {
return () => {
clearInterval(intervalRef.current);
};
}, []);
return (
<div>
<button onClick={startTimer}>Start Timer</button>
<button onClick={stopTimer}>Stop Timer</button>
</div>
);
}
export default TimerExample;