有些组件需要与外部系统同步。例如,你可能希望根据 React state 控制非 React 组件、设置服务器连接或在组件出现在屏幕上时发送分析日志。Effects 会在渲染后运行一些代码,以便可以将组件与 React 之外的某些系统同步。
简单理解,就是需要操作外部非React元素,但React未渲染完时是不允许操作原生DOM的,所以需要一个类似渲染完成后的回调函数。其实也可以在root渲染完成后硬编码实现,但这样的话代码显的不工整了。
Effect使用
一个播放器的示例,注意React很多功能全是在开发环境中执行两次,生产环境中执行一次,这主要是为了性能调优用的。
- 生产环境中:挂载-清理
- 开发环境中:挂载-清理-挂载
import { useState, useRef, useEffect } from 'react';function VideoPlayer({ src, isPlaying }) {const ref = useRef(null);useEffect(() => {if (isPlaying) { //增加判断,防止每次更改界面都刷新console.log('Calling video.play()');ref.current.play();} else {console.log('Calling video.pause()');ref.current.pause();}return () => {connection.disconnect(); //清理函数,在组件卸载时执行};}, [isPlaying]); //这个参数后面有详细解释//ref引用return <video ref={ref} src={src} loop playsInline />;
}export default function App() {const [isPlaying, setIsPlaying] = useState(false);const [text, setText] = useState('');return (<><input value={text} onChange={e => setText(e.target.value)} /><button onClick={() => setIsPlaying(!isPlaying)}>{isPlaying ? 'Pause' : 'Play'}</button><VideoPlayerisPlaying={isPlaying}src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"/></>);
}
useEffect(() => {// 这里的代码会在每次渲染后执行
});useEffect(() => {// 这里的代码只会在组件挂载后执行
}, []);useEffect(() => {//这里的代码只会在每次渲染后,并且 a 或 b 的值与上次渲染不一致时执行,这个特性非常重要,可以不需要参数来判断是否要重新加载,其实也没太大关系。
}, [a, b]);
利用effect订阅事件
简单的函数不建议这样来做,可以单用ref即可,但这样会比较规范一些。
useEffect(() => {function handleScroll(e) {console.log(window.scrollX, window.scrollY);}window.addEventListener('scroll', handleScroll);//组件卸载时执行,防止内存溢出return () => window.removeEventListener('scroll', handleScroll);
}, []);
利用effect初始化数据
比如远程访问获取数据等
useEffect(() => {let ignore = false;async function startFetching() {const json = await fetchTodos(userId);if (!ignore) {setTodos(json);}}startFetching();return () => {ignore = true;};
}, [userId]);
昂贵的计算
如果上述远程计算的时间会比较长的话,就不适合用effect来做了,可以用userMemo
来执行。这会告诉 React,除非 todos 或 filter 发生变化,否则不要重新执行传入的函数。React 会在初次渲染的时候记住 getFilteredTodos() 的返回值。在下一次渲染中,它会检查 todos 或 filter 是否发生了变化。如果它们跟上次渲染时一样,useMemo 会直接返回它最后保存的结果。如果不一样,React 将再次调用传入的函数(并保存它的结果)。
import { useMemo, useState } from 'react';function TodoList({ todos, filter }) {const [newTodo, setNewTodo] = useState('');const visibleTodos = useMemo(() => {// 除非 todos 或 filter 发生变化,否则不会重新执行return getFilteredTodos(todos, filter);}, [todos, filter]);// ...
}
编写 Effect 需要遵循以下三个规则
- 声明 Effect。默认情况下,Effect 会在每次 commit 后都会执行。
- 指定 Effect 依赖。大多数 Effect 应该按需执行,而不是在每次渲染后都执行。例如,淡入动画应该只在组件出现时触发。连接和断开服务器的操作只应在组件出现和消失时,或者切换聊天室时执行。文章将介绍如何通过指定依赖来控制如何按需执行。
- 必要时添加清理(cleanup)函数。有时 Effect 需要指定如何停止、撤销,或者清除它的效果。例如,“连接”操作需要“断连”,“订阅”需要“退订”,“获取”既需要“取消”也需要“忽略”。你将学习如何使用 清理函数 来做到这一切。
关于Effect的内容非常多,主要是这东西属于脱围机制的一种,而且还需要和React生命周期相吻合,还要考虑好性能问题。所以具体情况需要具体分析,无法统一下结论。需要多思考尝试。