有时组件中的数据需要与外部系统的数据或操作同步,React提供了Hook Effect。
Effect 会在组件渲染后运行一些代码,以便将组件与 React 之外的某些系统同步,包比如浏览器 API、第三方小部件,以及网络请求等。
如以下的video播放器的简单加载:
// 声明 Effect
import { useEffect, useRef } from 'react';
/*
* src:来自父组件的props,视频资源的地址
* isPlaying:来自父组件的props,视频是否播放,boolean值
* */
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
/*
Effect 依赖 isPlaying prop 控制逻辑
如果 isPlaying 在上一次渲染时与当前相同,跳过运行 Effect
如果不添加依赖,组件每次渲染后都会执行 Effect
如果依赖为[],组件只在挂载后执行 Effect
*/
useEffect(() => {
// 组件渲染之后执行以下代码
// 否则video dom尚未渲染,不存在play() 和 pause()方法
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
}, [isPlaying]);
// 同步到 React state 的"外部系统"是浏览器媒体 API
return <video ref={ref} src={src} loop playsInline />;
}
export default function App() {
const [isPlaying, setIsPlaying] = useState(false);
return (
<>
<button onClick={() => setIsPlaying(!isPlaying)}>
{isPlaying ? 'Pause' : 'Play'}
</button>
<VideoPlayer
isPlaying={isPlaying}
src="water.mp4"
/>
</>
);
}
有时 Effect 需要指定如何停止、撤销,或者清除它的效果。例如,"连接"操作需要"断连","获取"既需要"取消"也需要"忽略"。这时候就需要使用清理函数。
每次重新执行 Effect 之前,React 都会调用清理函数;组件被卸载时,也会调用清理函数。
严格模式时,在开发环境下,Effect出现额外的执行,是 React 正在调试你的代码。
通常的解决办法是实现清理函数,停止或撤销 Effect 正在执行的任何操作。
在生产环境下,Effect只会执行一次。
如下面的模拟聊天室功能:
App.js
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
export default function ChatRoom() {
useEffect(() => {
//建立连接
const connection = createConnection();
connection.connect();
//清理函数,断开连接
return () => connection.disconnect();
}, []);
return <h1>欢迎来到聊天室!</h1>;
}
chat.js
export function createConnection() {
// 连接的示例
return {
connect() {
console.log('连接中......');
},
disconnect() {
console.log('连接断开。');
}
};
}
Effect应用举例:
1、订阅事件
如果 Effect 订阅了某些事件,清理函数应该退订这些事件:
useEffect(() => {
function handleScroll(e) {
console.log(window.scrollX, window.scrollY);
}
//页面滚动事件
window.addEventListener('scroll', handleScroll);
//清理页面滚动事件
return () => window.removeEventListener('scroll', handleScroll);
}, []);
2、触发动画
如果 Effect 对某些内容加入了动画,清理函数应将动画重置:
useEffect(() => {
const node = ref.current;
node.style.opacity = 1; // 触发动画
return () => {
node.style.opacity = 0; // 重置为初始值
};
}, []);
3、获取数据
如果 Effect 将会获取数据,清理函数应该要么终止数据获取操作,要么忽略其结果:
useEffect(() => {
let ignore = false;
async function startFetching() {
const json = await fetchTodos(userId);
//不忽略结果,更新数据
if (!ignore) {
setTodos(json);
}
}
startFetching();
return () => {
ignore = true;
};
}, [userId]);
还有一些情形看似需要使用 Effect 实则并不需要,比如:初始化应用时,某些逻辑只需要在应用程序启动时运行一次。比如,验证登陆状态和加载本地程序数据,可以将其放在组件之外:
if (typeof window !== 'undefined') { // 检查是否在浏览器中运行
checkAuthToken();
loadDataFromLocalStorage();
}
function App() {
// ......
}