React Effect的生命周期
Effect 与组件有不同的生命周期。 组件可以挂载、更新或卸载。 Effect 只能做两件事:开始同步某些东西,然后停止同步它。 如果你的 Effect 依赖于随时间变化的属性和状态,这个循环可能会发生多次。 React 提供了一个 linter 规则来检查您是否正确指定了 Effect 的依赖项。 这使您的 Effect 与最新的道具和状态保持同步。
你将学习
- Effect 的生命周期与组件的生命周期有何不同
- 如何孤立地考虑每个单独的 Effect
- 您的 Effect 何时需要重新同步,以及为什么
- 如何确定 Effect 的依赖项
- 值是反应性的意味着什么
- 空的依赖数组意味着什么
- React 如何使用 linter 验证您的依赖关系是否正确
- 当你不同意 linter 时该怎么办
React Effect的生命周期
每个 React 组件都经历相同的生命周期:
- 当一个组件被添加到屏幕上时它就会被挂载。
- 组件在接收到新的属性或状态时更新。 这通常发生在对交互的响应中。
- 当组件从屏幕上移除时,它就会卸载。
这是考虑组件而不是效果的好方法。 相反,请尝试独立于组件的生命周期来考虑每个 Effect。 Effect 描述了如何将外部系统同步到当前的属性和状态。 随着您的代码更改,此同步将需要或多或少地发生。
为了说明这一点,请考虑将您的组件连接到聊天服务器的 Effect:
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]);
// ...
}
你的 Effect 的主体指定了如何开始同步:
// ...
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
// ...
Effect 返回的清理函数指定了如何停止同步:
// ...
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
// ...
直觉上,你可能会认为 React 会在你的组件挂载时开始同步,并在你的组件卸载时停止同步。 然而,这并不是故事的结局! 有时,可能还需要在组件保持挂载状态时多次启动和停止同步。
让我们看看为什么这是必要的,何时发生,以及如何控制这种行为。
注意
有些 Effects 根本不返回清理函数。 通常情况下,你会想要返回一个——但如果你不这样做,React 会表现得好像你返回了一个什么都不做的空清理函数。
为什么同步可能需要发生不止一次
想象一下,这个 ChatRoom 组件接收到用户在下拉列表中选择的roomId属性。 假设最初用户选择“ general”房间作为 roomId。 您的应用程序显示“ general”聊天室:
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId /* "general" */ }) {
// ...
return <h1>Welcome to the {roomId} room!</h1>;
}
显示 UI 后,React 将运行您的 Effect 以开始同步。 它连接到“general”房间:
function ChatRoom({ roomId /* "general" */ }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Connects to the "general" room
connection.connect();
return () => {
connection.disconnect(); // Disconnects from the "general" room
};
}, [roomId]);
// ...
到目前为止,一切都很好。
稍后,用户在下拉列表中选择不同的房间(例如,“ travel”)。 首先,React 将更新 UI:
function ChatRoom({ roomId /* "travel" */ }) {
// ...
return <h1>Welcome to the {roomId} room!</h1>;
}
停下来想想接下来会发生什么。 用户看到“travel”是在 UI 中选择的聊天室。 但是,上次运行的 Effect 仍然连接到“general”房间。 roomId 属性已经改变,所以无论你的 Effect 当时做了什么(连接到“general”房间)都不再与 UI 匹配。
此时,你想让 React 做两件事:
- 停止与旧 roomId 同步(与“普通”房间断开连接)
- 开始与新的 roomId 同步(连接到“旅行”房间)
幸运的是,你已经教会了 React 如何做这两件事! 您的 Effect 主体指定了如何开始同步,而您的清理函数指定了如何停止同步。 React 现在需要做的就是以正确的顺序、正确的 props 和状态调用它们。 让我们看看这是怎么发生的。
React 如何重新同步你的 Effect
回想一下,您的 ChatRoom 组件已收到其 roomId 属性的新值。 以前是“general”,现在是“travel”。 React 需要重新同步您的 Effect 以将您重新连接到不同的房间。
要停止同步,React 将调用您的 Effect 在连接到“general”房间后返回的清理函数。 由于 roomId 是“general”,清理函数与“general”房间断开连接:
function ChatRoom({ roomId /* "general" */ }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Connects to the "general" room
connection.connect();
return () => {
connection.disconnect(); // Disconnects from the "general" room
};
// ...
然后 React 将运行您在此渲染期间提供的效果。 这次,roomId 是“travel”,所以它将开始同步到“travel”聊天室(直到它的清理函数最终也被调用):
function ChatRoom({ roomId /* "travel" */ }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Connects to the "travel" room
connection.connect();
// ...
因此,您现在已连接到用户在 UI 中选择的同一个房间。 灾难化解!
每次您的组件使用不同的 roomId 重新渲染后,您的 Effect 都会重新同步。 例如,假设用户将 roomId 从“travel”更改为“ music”。 React 将再次通过调用其清理功能(将您与“travel”房间断开连接)来停止同步您的 Effect。 然后它将通过使用新的 roomId 属性运行其主体(将您连接到“ music”房间)再次开始同步。
最后,当用户转到另一个屏幕时,ChatRoom 将卸载。 现在根本不需要保持联系。 React 将最后一次停止同步您的 Effect 并将您与“ music”聊天室断开连接。
从 Effect 的角度思考
让我们从 ChatRoom 组件的角度回顾一下发生的一切:
- ChatRoom 安装时 roomId 设置为“general”
- ChatRoom 已更新,roomId 设置为“travel”
- ChatRoom 已更新,roomId 设置为“music”
- 卸载聊天室
在组件生命周期的每个阶段,您的 Effect 都会做不同的事情:
- 你的效果连接到“general”房间
- 您的效果与“general”房间断开连接并连接到“travel”房间
- 您的效果器与“travel”房间断开连接并连接到“music”房间
- 您的效果与“music”房间断开连接
现在让我们从 Effect 本身的角度来思考发生了什么:
useEffect(() => {
// Your Effect connected to the room specified with roomId...
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
// ...until it disconnected
connection.disconnect();
};
}, [roomId]);
此代码的结构可能会启发您将发生的事情视为一系列非重叠时间段:
- 你的效果连接到“ general”房间(直到它断开连接)
- 你的效果连接到“ travel”房间(直到它断开连接)
- 你的效果连接到“ music”房间(直到它断开连接)
以前,您是从组件的角度思考的。 当您从组件的角度来看时,很容易将 Effects 视为在特定时间触发的“回调”或“生命周期事件”,例如“渲染后”或“卸载前”。 这种思维方式会很快变得复杂,所以最好避免。
相反,始终专注于一次启动/停止循环。 组件是挂载、更新还是卸载都无关紧要。 您需要做的就是描述如何开始同步以及如何停止同步。 如果你做得好,你的 Effect 将能够根据需要多次启动和停止。
这可能会提醒您,在编写创建 JSX 的渲染逻辑时,您是如何不考虑组件是在挂载还是在更新的。 您描述屏幕上应该显示什么,React 会计算其余部分。
React 如何验证您的 Effect 可以重新同步
这是一个你可以玩的活生生的例子。 按“Open chat”以安装 ChatRoom 组件:
chat.js
export function createConnection(serverUrl, roomId) {
// A real implementation would actually connect to the server
return {
connect() {
console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...');
},
disconnect() {
console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl);
}
};
}
App.js
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]);
return <h1>Welcome to the {roomId} room!</h1>;
}
export default function App() {
const [roomId, setRoomId] = useState('general');
const [show, setShow] = useState(false);
return (
<>
<label>
Choose the chat room:{' '}
<select
value={roomId}
onChange={e => setRoomId(e.target.value)}
>
<option value="general">general</option>
<option value="travel">travel</option>
<option value="music">music</option>
</select>
</label>
<button onClick={() => setShow(!show)}>
{show ? 'Close chat' : 'Open chat'}
</button>
{show && <hr />}
{show && <ChatRoom roomId={roomId} />}
</>
);
}
请注意,当组件第一次挂载时,您会看到三个日志:
- ✅ Connecting to "general" room at https://localhost:1234... (development-only)
- ❌ Disconnected from "general" room at https://localhost:1234. (development-only)
- ✅ Connecting to "general" room at https://localhost:1234...
前两个日志仅供开发使用。 在开发中,React 总是重新挂载每个组件一次。 换句话说,React 通过在开发中强制 Effect 立即重新同步来验证您的 Effect 是否可以重新同步。 这可能会提醒您如何打开和关闭门以检查门锁是否正常工作。 React 在开发过程中会额外启动和停止您的 Effect 一次,以检查您是否很好地实现了它的清理。
实际上,您的 Effect 将重新同步的主要原因是它使用的某些数据是否已更改。 在上面的沙盒中,更改所选的聊天室。 请注意,当 roomId 更改时,您的 Effect 如何重新同步。
但是,也有更多不寻常的情况需要重新同步。 例如,尝试在聊天打开时编辑上面沙箱中的 serverUrl。 请注意 Effect 如何重新同步以响应您对代码的编辑。 将来,React 可能会添加更多利用重新同步的功能。
React 如何知道它需要重新同步 Effect
您可能想知道 React 如何知道您的 Effect 在 roomId 更改后需要重新同步。 这是因为您告诉 React 这个 Effect 的代码依赖于 roomId,方法是将它包含在依赖项列表中:
function ChatRoom({ roomId }) { // The roomId prop may change over time
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // This Effect reads roomId
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]); // So you tell React that this Effect "depends on" roomId
// ...
这是它的工作原理:
- 你知道 roomId 是一个属性,这意味着它可以随时间变化。
- 您知道您的 Effect 读取 roomId(因此其逻辑取决于稍后可能更改的值)。
- 这就是为什么您将它指定为您的 Effect 的依赖项(以便它在 roomId 更改时重新同步)。
每次你的组件重新渲染后,React 都会查看你传递的依赖项数组。 如果数组中的任何值与您在上一次渲染期间传递的同一位置的值不同,React 将重新同步您的 Effect。 例如,如果您在初始渲染期间传递了 ["general"],然后在下一次渲染期间传递了 ["travel"],React 将比较“general”和“travel”。 这些是不同的值(与 Object.is 相比),因此 React 将重新同步您的 Effect。 另一方面,如果您的组件重新渲染但 roomId 没有改变,您的 Effect 将保持连接到同一个房间。
每个 Effect 代表一个单独的同步过程
不要仅仅因为此逻辑需要与您已编写的效果同时运行而将不相关的逻辑添加到您的效果中。 例如,假设您想在用户访问房间时发送分析事件。 您已经有一个依赖于 roomId 的 Effect,因此您可能想在此处添加分析调用:
function ChatRoom({ roomId }) {
useEffect(() => {
logVisit(roomId);
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]);
// ...
}
但是想象一下,您稍后向此 Effect 添加了另一个需要重新建立连接的依赖项。 如果此 Effect 重新同步,它还会为同一个房间调用 logVisit(roomId),这是您不希望的。 记录访问是一个独立于连接的过程。 这就是为什么它们应该写成两个独立的效果:
function ChatRoom({ roomId }) {
useEffect(() => {
logVisit(roomId);
}, [roomId]);
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
// ...
}, [roomId]);
// ...
}
代码中的每个 Effect 都应该代表一个单独且独立的同步过程。
在上面的示例中,删除一个 Effect 不会破坏另一个 Effect 的逻辑。 这很好地表明它们同步不同的事物,因此将它们分开是有意义的。 另一方面,如果将一个内聚的逻辑片段拆分成单独的 Effect,代码可能看起来“更干净”,但会更难维护。 这就是为什么你应该考虑进程是相同的还是分开的,而不是代码是否看起来更清晰。
效果对反应值“反应”
您的 Effect 读取两个变量(serverUrl 和 roomId),但您只将 roomId 指定为依赖项:
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]);
// ...
}
为什么 serverUrl 不需要是依赖项?
这是因为 serverUrl 永远不会因重新呈现而改变。 无论组件重新渲染多少次以及使用哪些 props 和 state,它总是相同的。 由于 serverUrl 永远不会改变,因此将其指定为依赖项没有意义。 毕竟,依赖关系只会在它们随时间变化时才做一些事情!
另一方面,roomId 在重新渲染时可能会有所不同。 在组件内声明的 Props、state 和其他值是响应式的,因为它们是在渲染期间计算并参与 React 数据流的。
如果 serverUrl 是一个状态变量,它将是反应性的。 反应值必须包含在依赖项中:
function ChatRoom({ roomId }) { // Props change over time
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // State may change over time
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Your Effect reads props and state
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId, serverUrl]); // So you tell React that this Effect "depends on" on props and state
// ...
}
通过将 serverUrl 作为依赖项包含在内,您可以确保 Effect 在更改后重新同步。
尝试更改所选的聊天室或在此沙盒中编辑服务器 URL:
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]);
return (
<>
<label>
Server URL:{' '}
<input
value={serverUrl}
onChange={e => setServerUrl(e.target.value)}
/>
</label>
<h1>Welcome to the {roomId} room!</h1>
</>
);
}
export default function App() {
const [roomId, setRoomId] = useState('general');
return (
<>
<label>
Choose the chat room:{' '}
<select
value={roomId}
onChange={e => setRoomId(e.target.value)}
>
<option value="general">general</option>
<option value="travel">travel</option>
<option value="music">music</option>
</select>
</label>
<hr />
<ChatRoom roomId={roomId} />
</>
);
}
每当您更改 roomId 或 serverUrl 等反应值时,Effect 都会重新连接到聊天服务器。
具有空依赖项的 Effect 意味着什么
如果将 serverUrl 和 roomId 都移到组件外会发生什么情况?
const serverUrl = 'https://localhost:1234';
const roomId = 'general';
function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, []); // ✅ All dependencies declared
// ...
}
现在您的 Effect 代码不使用任何反应值,因此它的依赖项可以为空 ([])。
如果从组件的角度思考,空的 [] 依赖数组意味着这个 Effect 只有在组件挂载时才连接到聊天室,只有在组件卸载时才断开连接。 (请记住,React 仍会在开发过程中额外花时间重新同步它,以对 Effect 的逻辑进行压力测试。)
但是,如果从 Effect 的角度考虑,则根本不需要考虑挂载和卸载。 重要的是您已经指定了 Effect 开始和停止同步的方式。 今天,它没有反应性依赖。 但是,如果您希望用户随着时间的推移更改 roomId 或 serverUrl(因此他们必须变得被动),您的 Effect 代码将不会更改。 您只需要将它们添加到依赖项中。
组件主体中声明的所有变量都是反应式的
Props 和 state 不是唯一的反应值。 您从中计算出的值也是反应性的。 如果属性或状态发生变化,您的组件将重新渲染,并且从中计算出的值也会发生变化。 这就是为什么 Effect 使用的组件体中的所有变量也应该在 Effect 依赖项列表中的原因。
假设用户可以在下拉列表中选择聊天服务器,但他们也可以在设置中配置默认服务器。 假设您已经将设置状态放入上下文中,因此您可以从该上下文中读取设置。 现在你根据从 props 中选择的服务器和从上下文中的默认服务器计算 serverUrl:
function ChatRoom({ roomId, selectedServerUrl }) { // roomId is reactive
const settings = useContext(SettingsContext); // settings is reactive
const serverUrl = selectedServerUrl ?? settings.defaultServerUrl; // serverUrl is reactive
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Your Effect reads roomId and serverUrl
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId, serverUrl]); // So it needs to re-synchronize when either of them changes!
// ...
}
在此示例中,serverUrl 不是 prop 或状态变量。 这是您在渲染期间计算的常规变量。 但它是在渲染期间计算的,因此它可能会因重新渲染而改变。 这就是为什么它是反应性的。
组件内的所有值(包括组件主体中的道具、状态和变量)都是反应性的。 任何反应值都可以在重新渲染时发生变化,因此您需要将反应值作为 Effect 的依赖项包含在内。
换句话说,Effects 对来自组件主体的所有值做出“反应”。
深度阅读:全局或可变值可以是依赖项吗?
可变值(包括全局变量)不是反应性的。
像 location.pathname 这样的可变值不能是依赖项。 它是可变的,因此它可以完全在 React 渲染数据流之外随时更改。 更改它不会触发组件的重新渲染。 因此,即使你在依赖项中指定它,React 也不会知道在 Effect 发生变化时重新同步它。 这也打破了 React 的规则,因为在渲染期间读取可变数据(也就是计算依赖项时)会破坏渲染的纯度。 相反,您应该使用 useSyncExternalStore 读取和订阅外部可变值。
像 ref.current 这样的可变值或者你从中读取的东西也不能是依赖项。 useRef 本身返回的 ref 对象可以是一个依赖项,但它的当前属性是有意可变的。 它使您可以在不触发重新渲染的情况下跟踪某些内容。 但是因为改变它不会触发重新渲染,它不是一个反应值,并且 React 不会知道在它改变时重新运行你的 Effect。
正如您将在本页下方了解到的,linter 将自动检查这些问题。
React 验证您是否将每个反应值指定为依赖项
如果你为 React 项目配置了linter,它会检查你的 Effect 代码使用的每个反应值是否被声明为它的依赖项。 例如,这是一个 lint 错误,因为 roomId 和 serverUrl 都是响应式的:
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) { // roomId is reactive
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl is reactive
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // <-- Something's wrong here!
return (
<>
<label>
Server URL:{' '}
<input
value={serverUrl}
onChange={e => setServerUrl(e.target.value)}
/>
</label>
<h1>Welcome to the {roomId} room!</h1>
</>
);
}
export default function App() {
const [roomId, setRoomId] = useState('general');
return (
<>
<label>
Choose the chat room:{' '}
<select
value={roomId}
onChange={e => setRoomId(e.target.value)}
>
<option value="general">general</option>
<option value="travel">travel</option>
<option value="music">music</option>
</select>
</label>
<hr />
<ChatRoom roomId={roomId} />
</>
);
}
这可能看起来像 React 错误,但实际上 React 指出了您代码中的错误。 roomId 和 serverUrl 都可能随时间发生变化,但是当它们发生变化时,您会忘记重新同步您的 Effect。 因此,即使用户在 UI 中选择了不同的值,您仍将保持与初始 roomId 和 serverUrl 的连接。
要修复此错误,请按照 linter 的建议将 roomId 和 serverUrl 指定为 Effect 的依赖项:
function ChatRoom({ roomId, serverUrl }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl is reactive
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]); // ✅ All dependencies declared
// ...
}
在上面的沙箱中尝试此修复。 验证 linter 错误是否消失,以及聊天是否在需要时重新连接。
注意
在某些情况下,React 知道一个值永远不会改变,即使它是在组件内部声明的。 例如,从 useState 返回的 set 函数和 useRef 返回的 ref 对象是稳定的——它们保证在重新渲染时不会改变。 稳定值不是反应性的,因此 linter 允许您从列表中省略它们。 然而,包括它们是允许的:它们不会改变,所以没关系。
当您不想重新同步时该怎么办
在前面的示例中,您通过将 roomId 和 serverUrl 列为依赖项来修复 lint 错误。
但是,您可以改为向 linter “证明”这些值不是反应值,即它们不会因重新渲染而改变。 例如,如果 serverUrl 和 roomId 不依赖于渲染并且始终具有相同的值,则可以将它们移到组件之外。 现在它们不需要是依赖项:
const serverUrl = 'https://localhost:1234'; // serverUrl is not reactive
const roomId = 'general'; // roomId is not reactive
function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, []); // ✅ All dependencies declared
// ...
}
您也可以将它们移动到 Effect 中。 它们不是在渲染过程中计算的,所以它们不是反应式的:
function ChatRoom() {
useEffect(() => {
const serverUrl = 'https://localhost:1234'; // serverUrl is not reactive
const roomId = 'general'; // roomId is not reactive
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, []); // ✅ All dependencies declared
// ...
}
效果是反应性代码块。 当您在其中读取的值发生变化时,它们会重新同步。 与每次交互只运行一次的事件处理程序不同,Effects 在需要同步时运行。
你不能“选择”你的依赖。 您的依赖项必须包括您在 Effect 中读取的每个反应值。 linter 强制执行此操作。 有时这可能会导致无限循环等问题,并导致您的 Effect 过于频繁地重新同步。 不要通过抑制 linter 来解决这些问题! 以下是尝试的方法:
检查您的 Effect 是否代表一个独立的同步过程。 如果您的 Effect 不同步任何东西,它可能是不必要的。 如果它同步几个独立的东西,把它分开。
如果你想读取道具或状态的最新值而不对其做出“反应”并重新同步效果,你可以将你的效果分成一个反应部分(你将保留在效果中)和一个非反应部分 (您将把它提取到称为事件函数的东西中)。 阅读有关将事件与效果分开的更多信息。
避免依赖对象和函数作为依赖项。 如果您在渲染期间创建对象和函数,然后从 Effect 中读取它们,则它们在每次渲染时都会不同。 这将导致您的效果器每次都重新同步。 阅读有关从 Effects 中删除不必要的依赖项的更多信息。
"陷阱
linter 是你的朋友,但它的权力是有限的。 linter 只知道依赖项何时出错。 它不知道解决每种情况的最佳方法。 如果 linter 提示存在依赖关系,但添加它会导致循环,这并不意味着 linter 应该被忽略。 这意味着您需要更改 Effect 内部(或外部)的代码,以便该值不是反应性的并且不需要是依赖项。
如果你有一个现有的代码库,你可能有一些抑制 linter 的效果,如下所示:
useEffect(() => { // ... // 🔴 Avoid suppressing the linter like this: // eslint-ignore-next-line react-hooks/exhaustive-deps }, []);在接下来的几页中,您将学习如何在不违反规则的情况下修复此代码。 这总是值得修复的!
回顾
- 组件可以挂载、更新和卸载。
- 每个 Effect 都有一个独立于周围组件的生命周期。
- 每个 Effect 都描述了一个可以启动和停止的单独同步过程。
- 当您编写和读取 Effects 时,您应该从每个单独的 Effect 的角度(如何开始和停止同步)而不是从组件的角度(它如何安装、更新或卸载)来思考。
- 在组件体内声明的值是“反应性的”。
- 反应值应该重新同步效果,因为它们会随时间变化。
- linter 验证 Effect 中使用的所有反应值是否都指定为依赖项。
- linter 标记的所有错误都是合法的。 总有一种方法可以修复不违反规则的代码。