如何仅使用 useRef 和 useEffect 重新渲染单个子组件
How to only re-render individual child components with useRef and useEffect
tl;dr - 如何通过跟踪强制重新渲染一个特定的子组件 ref
?
我有 table 行。我希望能够将鼠标悬停在行上并 show/hide 行中的一个单元格,但只能在一段时间后进行。
只有将鼠标悬停在整个 table 上一段时间后,您才能显示隐藏的悬停内容 - 由 onMouseEnter
和 onMouseLeave
触发。
将鼠标悬停在特定的 <Row>
上后,如果家长允许,它应该会显示额外的内容。
鼠标悬停在 table 上的序列:
- 将鼠标悬停在行上
- 行的
isHovered
现在是 true
- 1000ms后,
allowHover
变为true
- 因为
allowHover
和isHovered
都是true
,显示额外的行内容
鼠标OUT的顺序table:
- 鼠标移出父级 container/table/row
- 之前悬停的行
isHovered
设置为 false
- 之前悬停行的隐藏内容被隐藏
- 1000ms后,
allowHover
变为false
此时,如果重新进入table,我们必须再次等待1秒才能使allowHover
为真。一旦 isHovered
和 allowHover
都为真,显示隐藏内容。一旦允许悬停,就不会有延迟:悬停在行上的行应立即显示隐藏的内容。
我正在尝试使用 useRef
来避免改变父行的状态并导致重新呈现所有子行
在行级别,悬停时,一行应该能够检查是否允许悬停,而无需使用道具重新呈现整个列表。我假设 useEffect
可以设置为跟踪该值,但它似乎不会在单个组件级别触发重新渲染。
换句话说,预期的行为是当前悬停在行上的行检测父项中的更改,并且仅重新呈现自身以显示内容。然后,一旦允许悬停,行为就很简单了。将鼠标悬停在行上?显示其内容。
以下是涉及的代码片段:
function Table() {
const allowHover = useRef(false);
const onMouseEnter = (e) => {
setTimeout(() => {
allowHover.current = true; // allow hovering
}, 1000);
};
const onMouseLeave = (e) => {
setTimeout(() => {
allowHover.current = false; // dont allow hovering
}, 1000);
};
return (
<div className="App" style={{ border: '3px solid blue' }}>
<h1>table</h1>
{/* allow/disallow hovering when entering and exiting the table, with a delay */}
<table onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<tbody>
<AllRows allowHover={allowHover} />
</tbody>
</table>
</div>
);
}
function Rows(props) {
return [1, 2, 3].map((id) => (
<Row id={id} allowHover={props.allowHover} />
));
}
function Row(props) {
let [isHovered, setIsHovered] = useState(false);
useEffect(() => {
// Why isn't this re-rendering this component?
}, [props.allowHover]);
const onMouseEnter = ({ target }) => {
setIsHovered(true);
};
const onMouseLeave = ({ target }) => {
setIsHovered(false);
};
console.log('RENDERING ROW');
return (
<tr key={props.id} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<td style={{ border: '1px solid red' }}>---------------- {props.id}</td>
<td style={{ border: '1px solid green' }}>
{props.allowHover.current && isHovered ? (
<button>ACTIONS</button>
) : null}
</td>
</tr>
);
}
基础知识
反应
一个,如果不是 THE,React 最大的优点是它让我们可以管理带有状态的应用程序,而无需通过手动更新和事件侦听器与 DOM 交互.每当 React“重新渲染”时,它都会渲染一个虚拟的 DOM,然后将其与实际的 DOM 进行比较,并在必要时进行替换。
挂钩
useState
:returns一个状态和一个setter,只要setter以新状态执行,组件就会重新渲染。
useRef
:提供了在渲染之间保持对值或 object 的引用的可能性。它就像一个变量的包装器,它存储在 ref 的 current
属性 中,只要您不更改它,它在渲染之间是相同的。为 ref 设置一个新值 NOT 会导致组件重新渲染。您可以将实际的 DOM 节点附加到 refs,但您实际上可以将任何内容附加到 ref.
useEffect
:在组件呈现后编码为 运行。 useEffect
在 DOM 更新后执行。
memo
:增加了在 parent 重新呈现后 child 重新呈现时手动控制的可能性。
您的代码
我将假设这是某种玩具示例,并且您想了解 React 的工作原理,否则,通过直接操作 DOM 节点来执行此操作不是可行的方法.事实上,您应该 ONLY 使用 memo
和性能改进以及直接操作 DOM 节点不可能以另一种方式完成你想要的,或者如果用 React 的常规方式做它不会产生 acceptable 性能。在你的情况下,性能就足够了。
有些库可以在 React 之外完成大部分工作。其中之一是 react-spring
动画 DOM 元素。在 React 中执行这些动画会减慢它们的速度并使它们滞后,因此 react-spring
使用 refs 并直接更新 DOM 节点,在元素上设置不同的 CSS 属性。
您的问题
- 您想知道为什么当您更改 ref 的内容时
Row
中的 useEffect
不会被触发。好吧,这仅仅是因为 useEffect
运行s 在渲染之后,并不能保证组件会因为你更改 allowHover
ref 的内容而重新渲染。传递给 AllRows
和 Row
的 ref 始终是相同的 属性(只有它的 current
属性 发生变化),因此它们永远不会因道具而重新渲染被改变。由于 Row
仅在设置 isHovered
时单独重新呈现,因此无法保证 useEffect
会因为您更改 allowHover
ref 的内容而触发。 WHEN Row
重新渲染,效果会运行 IFallowHover.current
的值与上次不同
- 使用
memo
在这里也无济于事,因为 Table
或 AllRows
也不会重新呈现。 memo
允许我们在 parent 重新渲染时跳过重新渲染 children,但是在这里,parent 不会重新渲染,所以 memo
什么都不做。
总而言之,useEffect
和memo
都不是什么神奇的函数,随时跟踪变量,然后在这些变量发生变化时做一些事情,相反,它们只是函数在 React 生命周期中的给定时间执行,评估当前上下文。
您的用例
基本上,Row
是否可见取决于两个条件:
allowHover.current
是否设置为true
?
- 是否
isHovered
设置为true
?
由于这些不相互依赖,理想情况下,我们应该希望能够修改附加到两个事件的事件侦听器的条件内容,从而更改这些属性的值。
在普通的 Javascript 环境中,我们可能会根据此将每个元素存储在一个数组中,并从事件侦听器中设置它的 display
或 visibility
,这将检查这两个这些条件;最后触发的事件侦听器将负责显示或隐藏组件/行。
在 React 中做同样的事情,但绕过 React,只要您可以将此状态存储在某个 ref 中,应该会非常简单。由于在 Table
级别和 Row
级别上发生的事件都必须能够修改相关元素,因此在这两个组件中都必须可以访问这些 DOM 元素。您可以通过将 Table
、Row
和 AllRows
的代码合并到一个组件中或将 refs 从 children 返回到 parent 来实现这一点某些精心设计的方案中的组成部分。这里的重点是,如果你想在 React 之外进行,ALL 应该在 React 之外进行。
您当前在代码中的问题是您想更新 Re 之外的条件之一 (allowHover
)ct 但你希望 React 处理其他情况 (isHovered
)。这会造成一种奇怪的情况,无论您是否真的想在 React 之外执行此操作(我建议在所有情况下都不要这样做,但玩具场景除外)都是不可取的。 React 不知道什么时候 allowHover
被设置为 true
因为这是在 React 之外完成的。
解决方案
1。使用 React
只需将 useState
用于 allowHover
,以便 Table
在 allowHover
更改时重新呈现。这将更新 children 中的道具,它也会重新渲染。还要确保将超时存储在 ref 中,以便在将鼠标移入和移出 table.
时清除它
使用此解决方案,只要鼠标进出 table(1 秒后),Table
及其所有 children 就会重新呈现,然后单独 Row
s 将在 isHovered
的 Row
更改时重新呈现。结果是 Row
s 将在控制它们是否应包含条件内容的两个条件下重新呈现。
function Table() {
const [allowHover, setAllowHover] = useState(false);
const [currentRow, setCurrentRow] = useState(null);
const hoverTimeout = useRef(null);
const onHover = (id) => setCurrentRow(id);
const onMouseEnter = (e) => {
if (hoverTimeout.current !== null) clearTimeout(hoverTimeout.current);
hoverTimeout.current = setTimeout(() => {
console.log("Enabling hover");
setAllowHover(true); // allow hovering
}, 1000);
};
const onMouseLeave = (e) => {
if (hoverTimeout.current !== null) clearTimeout(hoverTimeout.current);
hoverTimeout.current = setTimeout(() => {
console.log("Disabling hover");
setAllowHover(false); // dont allow hovering
setCurrentRow(null);
}, 1000);
};
console.log("Rendering table");
return (
<div className="App" style={{ border: "3px solid blue" }}>
<h1>table</h1>
{/* allow/disallow hovering when entering and exiting the table, with a delay */}
<table
style={{ border: "3px solid red" }}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<tbody>
<Rows onHover={onHover} currentRow={allowHover ? currentRow : null} />
</tbody>
</table>
</div>
);
}
function Rows(props) {
console.log("Rendering rows");
return [1, 2, 3].map((id) => (
<Row
id={id}
key={id}
isActive={props.currentRow === id}
onHover={() => props.onHover(id)}
/>
));
}
function Row(props) {
console.log("Rendering row");
return (
<tr key={props.id} onMouseEnter={props.onHover}>
<td style={{ border: "1px solid red" }}>---------------- {props.id}</td>
<td style={{ border: "1px solid green" }}>
{props.isActive ? <button>ACTIONS</button> : null}
</td>
</tr>
);
}
这里没有有趣的事情,只有普通的 React 风格。
代码沙箱:https://codesandbox.io/s/bypass-react-1a-i3dq8
2。绕过 React
即使不推荐,如果你这样做,你应该在 React 之外进行所有更新。这意味着当您在 React 之外更新 Table
的状态时,您不能依赖 React 来重新呈现 child 行。您可以通过多种方式执行此操作,但一种方法是让 child Row
s 将其 refs 传递回 Table
组件,该组件通过 refs 手动更新 Row
s .这几乎就是 React 在幕后所做的事情。
在这里,我们向 Table
组件添加了很多逻辑,这变得更加复杂,但是 Row
组件丢失了一些代码:
function Table() {
const allowHover = useRef(false);
const timeout = useRef(null);
const rows = useRef({});
const currentRow = useRef(null);
const onAddRow = (row, id) => {
rows.current = {
...rows.current,
[id]: row
};
onUpdate();
};
const onHoverRow = (id) => {
currentRow.current = id.toString();
onUpdate();
};
const onUpdate = () => {
Object.keys(rows.current).forEach((key) => {
if (key === currentRow.current && allowHover.current) {
rows.current[key].innerHTML = "<button>Accept</button>";
} else {
rows.current[key].innerHTML = "";
}
});
};
const onMouseEnter = (e) => {
if (timeout.current !== null) clearTimeout(timeout.current);
timeout.current = setTimeout(() => {
console.log("Enabling hover on table");
allowHover.current = true; // allow hovering
onUpdate();
}, 1000);
};
const onMouseLeave = (e) => {
if (timeout.current !== null) clearTimeout(timeout.current);
timeout.current = setTimeout(() => {
console.log("Disabling hover on table");
allowHover.current = false; // dont allow hovering
currentRow.current = null;
onUpdate();
}, 1000);
};
console.log("Rendering table");
return (
<div className="App" style={{ border: "3px solid blue" }}>
<h1>table</h1>
{/* allow/disallow hovering when entering and exiting the table, with a delay */}
<table onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<tbody>
<Rows onAddRow={onAddRow} onHoverRow={onHoverRow} />
</tbody>
</table>
</div>
);
}
function Rows(props) {
console.log("Rendering rows");
return [1, 2, 3].map((id) => (
<Row
key={id}
id={id}
onAddRow={props.onAddRow}
onHoverRow={() => props.onHoverRow(id)}
/>
));
}
function Row(props) {
console.log("Rendering row");
return (
<tr onMouseEnter={props.onHoverRow} key={props.id}>
<td style={{ border: "1px solid red" }}>---------------- {props.id}</td>
<td
ref={(ref) => props.onAddRow(ref, props.id)}
style={{ border: "1px solid green" }}
></td>
</tr>
);
}
代码沙箱:https://codesandbox.io/s/bypass-react-1b-rsqtq
您可以在控制台中自己看到每个组件只渲染一次。
结论
始终首先以通常的方式在 React 中实现事物,然后在绝对必要的地方使用 memo
、useCallback
、useMemo
和 refs 来提高性能。请记住,更复杂的代码也是有代价的,所以仅仅因为您使用 React 节省了一些重新渲染并不意味着您已经找到了更好的解决方案。
更新
我更改了代码,一旦悬停了一行,它就会被注册为当前悬停的行。此行将不会停止悬停,直到另一行悬停或直到 table 被禁用(鼠标离开 table 后 1 秒)。这里的一个小问题是,我们可能会输入 table 但 NOT 悬停一行,这样我们就在 table 中没有任何活动行。这也意味着,如果我们将 table 保留在该状态,则没有行将处于“活动”状态,因为 none 一开始就处于活动状态。这是因为 table 中有额外的空间未分配给任何行。尽管如此,它仍然运行良好,但最好注意一下。 las,table
s 并不是真正的布局组件,您的布局越具体,显示的越多。如果这是一个问题,我会使用 flex-box 或没有单元格间距和边框的 table
。如果需要边框,可以使用绝对定位在每个单元格内添加边框,而不是依赖于 table 的间距和边框。
tl;dr - 如何通过跟踪强制重新渲染一个特定的子组件 ref
?
我有 table 行。我希望能够将鼠标悬停在行上并 show/hide 行中的一个单元格,但只能在一段时间后进行。
只有将鼠标悬停在整个 table 上一段时间后,您才能显示隐藏的悬停内容 - 由 onMouseEnter
和 onMouseLeave
触发。
将鼠标悬停在特定的 <Row>
上后,如果家长允许,它应该会显示额外的内容。
鼠标悬停在 table 上的序列:
- 将鼠标悬停在行上
- 行的
isHovered
现在是true
- 1000ms后,
allowHover
变为true
- 因为
allowHover
和isHovered
都是true
,显示额外的行内容
鼠标OUT的顺序table:
- 鼠标移出父级 container/table/row
- 之前悬停的行
isHovered
设置为false
- 之前悬停行的隐藏内容被隐藏
- 1000ms后,
allowHover
变为false
此时,如果重新进入table,我们必须再次等待1秒才能使allowHover
为真。一旦 isHovered
和 allowHover
都为真,显示隐藏内容。一旦允许悬停,就不会有延迟:悬停在行上的行应立即显示隐藏的内容。
我正在尝试使用 useRef
来避免改变父行的状态并导致重新呈现所有子行
在行级别,悬停时,一行应该能够检查是否允许悬停,而无需使用道具重新呈现整个列表。我假设 useEffect
可以设置为跟踪该值,但它似乎不会在单个组件级别触发重新渲染。
换句话说,预期的行为是当前悬停在行上的行检测父项中的更改,并且仅重新呈现自身以显示内容。然后,一旦允许悬停,行为就很简单了。将鼠标悬停在行上?显示其内容。
以下是涉及的代码片段:
function Table() {
const allowHover = useRef(false);
const onMouseEnter = (e) => {
setTimeout(() => {
allowHover.current = true; // allow hovering
}, 1000);
};
const onMouseLeave = (e) => {
setTimeout(() => {
allowHover.current = false; // dont allow hovering
}, 1000);
};
return (
<div className="App" style={{ border: '3px solid blue' }}>
<h1>table</h1>
{/* allow/disallow hovering when entering and exiting the table, with a delay */}
<table onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<tbody>
<AllRows allowHover={allowHover} />
</tbody>
</table>
</div>
);
}
function Rows(props) {
return [1, 2, 3].map((id) => (
<Row id={id} allowHover={props.allowHover} />
));
}
function Row(props) {
let [isHovered, setIsHovered] = useState(false);
useEffect(() => {
// Why isn't this re-rendering this component?
}, [props.allowHover]);
const onMouseEnter = ({ target }) => {
setIsHovered(true);
};
const onMouseLeave = ({ target }) => {
setIsHovered(false);
};
console.log('RENDERING ROW');
return (
<tr key={props.id} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<td style={{ border: '1px solid red' }}>---------------- {props.id}</td>
<td style={{ border: '1px solid green' }}>
{props.allowHover.current && isHovered ? (
<button>ACTIONS</button>
) : null}
</td>
</tr>
);
}
基础知识
反应
一个,如果不是 THE,React 最大的优点是它让我们可以管理带有状态的应用程序,而无需通过手动更新和事件侦听器与 DOM 交互.每当 React“重新渲染”时,它都会渲染一个虚拟的 DOM,然后将其与实际的 DOM 进行比较,并在必要时进行替换。
挂钩
useState
:returns一个状态和一个setter,只要setter以新状态执行,组件就会重新渲染。useRef
:提供了在渲染之间保持对值或 object 的引用的可能性。它就像一个变量的包装器,它存储在 ref 的current
属性 中,只要您不更改它,它在渲染之间是相同的。为 ref 设置一个新值 NOT 会导致组件重新渲染。您可以将实际的 DOM 节点附加到 refs,但您实际上可以将任何内容附加到 ref.useEffect
:在组件呈现后编码为 运行。useEffect
在 DOM 更新后执行。memo
:增加了在 parent 重新呈现后 child 重新呈现时手动控制的可能性。
您的代码
我将假设这是某种玩具示例,并且您想了解 React 的工作原理,否则,通过直接操作 DOM 节点来执行此操作不是可行的方法.事实上,您应该 ONLY 使用 memo
和性能改进以及直接操作 DOM 节点不可能以另一种方式完成你想要的,或者如果用 React 的常规方式做它不会产生 acceptable 性能。在你的情况下,性能就足够了。
有些库可以在 React 之外完成大部分工作。其中之一是 react-spring
动画 DOM 元素。在 React 中执行这些动画会减慢它们的速度并使它们滞后,因此 react-spring
使用 refs 并直接更新 DOM 节点,在元素上设置不同的 CSS 属性。
您的问题
- 您想知道为什么当您更改 ref 的内容时
Row
中的useEffect
不会被触发。好吧,这仅仅是因为useEffect
运行s 在渲染之后,并不能保证组件会因为你更改allowHover
ref 的内容而重新渲染。传递给AllRows
和Row
的 ref 始终是相同的 属性(只有它的current
属性 发生变化),因此它们永远不会因道具而重新渲染被改变。由于Row
仅在设置isHovered
时单独重新呈现,因此无法保证useEffect
会因为您更改allowHover
ref 的内容而触发。 WHENRow
重新渲染,效果会运行 IFallowHover.current
的值与上次不同 - 使用
memo
在这里也无济于事,因为Table
或AllRows
也不会重新呈现。memo
允许我们在 parent 重新渲染时跳过重新渲染 children,但是在这里,parent 不会重新渲染,所以memo
什么都不做。
总而言之,useEffect
和memo
都不是什么神奇的函数,随时跟踪变量,然后在这些变量发生变化时做一些事情,相反,它们只是函数在 React 生命周期中的给定时间执行,评估当前上下文。
您的用例
基本上,Row
是否可见取决于两个条件:
allowHover.current
是否设置为true
?- 是否
isHovered
设置为true
?
由于这些不相互依赖,理想情况下,我们应该希望能够修改附加到两个事件的事件侦听器的条件内容,从而更改这些属性的值。
在普通的 Javascript 环境中,我们可能会根据此将每个元素存储在一个数组中,并从事件侦听器中设置它的 display
或 visibility
,这将检查这两个这些条件;最后触发的事件侦听器将负责显示或隐藏组件/行。
在 React 中做同样的事情,但绕过 React,只要您可以将此状态存储在某个 ref 中,应该会非常简单。由于在 Table
级别和 Row
级别上发生的事件都必须能够修改相关元素,因此在这两个组件中都必须可以访问这些 DOM 元素。您可以通过将 Table
、Row
和 AllRows
的代码合并到一个组件中或将 refs 从 children 返回到 parent 来实现这一点某些精心设计的方案中的组成部分。这里的重点是,如果你想在 React 之外进行,ALL 应该在 React 之外进行。
您当前在代码中的问题是您想更新 Re 之外的条件之一 (allowHover
)ct 但你希望 React 处理其他情况 (isHovered
)。这会造成一种奇怪的情况,无论您是否真的想在 React 之外执行此操作(我建议在所有情况下都不要这样做,但玩具场景除外)都是不可取的。 React 不知道什么时候 allowHover
被设置为 true
因为这是在 React 之外完成的。
解决方案
1。使用 React
只需将 useState
用于 allowHover
,以便 Table
在 allowHover
更改时重新呈现。这将更新 children 中的道具,它也会重新渲染。还要确保将超时存储在 ref 中,以便在将鼠标移入和移出 table.
使用此解决方案,只要鼠标进出 table(1 秒后),Table
及其所有 children 就会重新呈现,然后单独 Row
s 将在 isHovered
的 Row
更改时重新呈现。结果是 Row
s 将在控制它们是否应包含条件内容的两个条件下重新呈现。
function Table() {
const [allowHover, setAllowHover] = useState(false);
const [currentRow, setCurrentRow] = useState(null);
const hoverTimeout = useRef(null);
const onHover = (id) => setCurrentRow(id);
const onMouseEnter = (e) => {
if (hoverTimeout.current !== null) clearTimeout(hoverTimeout.current);
hoverTimeout.current = setTimeout(() => {
console.log("Enabling hover");
setAllowHover(true); // allow hovering
}, 1000);
};
const onMouseLeave = (e) => {
if (hoverTimeout.current !== null) clearTimeout(hoverTimeout.current);
hoverTimeout.current = setTimeout(() => {
console.log("Disabling hover");
setAllowHover(false); // dont allow hovering
setCurrentRow(null);
}, 1000);
};
console.log("Rendering table");
return (
<div className="App" style={{ border: "3px solid blue" }}>
<h1>table</h1>
{/* allow/disallow hovering when entering and exiting the table, with a delay */}
<table
style={{ border: "3px solid red" }}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<tbody>
<Rows onHover={onHover} currentRow={allowHover ? currentRow : null} />
</tbody>
</table>
</div>
);
}
function Rows(props) {
console.log("Rendering rows");
return [1, 2, 3].map((id) => (
<Row
id={id}
key={id}
isActive={props.currentRow === id}
onHover={() => props.onHover(id)}
/>
));
}
function Row(props) {
console.log("Rendering row");
return (
<tr key={props.id} onMouseEnter={props.onHover}>
<td style={{ border: "1px solid red" }}>---------------- {props.id}</td>
<td style={{ border: "1px solid green" }}>
{props.isActive ? <button>ACTIONS</button> : null}
</td>
</tr>
);
}
这里没有有趣的事情,只有普通的 React 风格。
代码沙箱:https://codesandbox.io/s/bypass-react-1a-i3dq8
2。绕过 React
即使不推荐,如果你这样做,你应该在 React 之外进行所有更新。这意味着当您在 React 之外更新 Table
的状态时,您不能依赖 React 来重新呈现 child 行。您可以通过多种方式执行此操作,但一种方法是让 child Row
s 将其 refs 传递回 Table
组件,该组件通过 refs 手动更新 Row
s .这几乎就是 React 在幕后所做的事情。
在这里,我们向 Table
组件添加了很多逻辑,这变得更加复杂,但是 Row
组件丢失了一些代码:
function Table() {
const allowHover = useRef(false);
const timeout = useRef(null);
const rows = useRef({});
const currentRow = useRef(null);
const onAddRow = (row, id) => {
rows.current = {
...rows.current,
[id]: row
};
onUpdate();
};
const onHoverRow = (id) => {
currentRow.current = id.toString();
onUpdate();
};
const onUpdate = () => {
Object.keys(rows.current).forEach((key) => {
if (key === currentRow.current && allowHover.current) {
rows.current[key].innerHTML = "<button>Accept</button>";
} else {
rows.current[key].innerHTML = "";
}
});
};
const onMouseEnter = (e) => {
if (timeout.current !== null) clearTimeout(timeout.current);
timeout.current = setTimeout(() => {
console.log("Enabling hover on table");
allowHover.current = true; // allow hovering
onUpdate();
}, 1000);
};
const onMouseLeave = (e) => {
if (timeout.current !== null) clearTimeout(timeout.current);
timeout.current = setTimeout(() => {
console.log("Disabling hover on table");
allowHover.current = false; // dont allow hovering
currentRow.current = null;
onUpdate();
}, 1000);
};
console.log("Rendering table");
return (
<div className="App" style={{ border: "3px solid blue" }}>
<h1>table</h1>
{/* allow/disallow hovering when entering and exiting the table, with a delay */}
<table onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<tbody>
<Rows onAddRow={onAddRow} onHoverRow={onHoverRow} />
</tbody>
</table>
</div>
);
}
function Rows(props) {
console.log("Rendering rows");
return [1, 2, 3].map((id) => (
<Row
key={id}
id={id}
onAddRow={props.onAddRow}
onHoverRow={() => props.onHoverRow(id)}
/>
));
}
function Row(props) {
console.log("Rendering row");
return (
<tr onMouseEnter={props.onHoverRow} key={props.id}>
<td style={{ border: "1px solid red" }}>---------------- {props.id}</td>
<td
ref={(ref) => props.onAddRow(ref, props.id)}
style={{ border: "1px solid green" }}
></td>
</tr>
);
}
代码沙箱:https://codesandbox.io/s/bypass-react-1b-rsqtq
您可以在控制台中自己看到每个组件只渲染一次。
结论
始终首先以通常的方式在 React 中实现事物,然后在绝对必要的地方使用 memo
、useCallback
、useMemo
和 refs 来提高性能。请记住,更复杂的代码也是有代价的,所以仅仅因为您使用 React 节省了一些重新渲染并不意味着您已经找到了更好的解决方案。
更新
我更改了代码,一旦悬停了一行,它就会被注册为当前悬停的行。此行将不会停止悬停,直到另一行悬停或直到 table 被禁用(鼠标离开 table 后 1 秒)。这里的一个小问题是,我们可能会输入 table 但 NOT 悬停一行,这样我们就在 table 中没有任何活动行。这也意味着,如果我们将 table 保留在该状态,则没有行将处于“活动”状态,因为 none 一开始就处于活动状态。这是因为 table 中有额外的空间未分配给任何行。尽管如此,它仍然运行良好,但最好注意一下。 las,table
s 并不是真正的布局组件,您的布局越具体,显示的越多。如果这是一个问题,我会使用 flex-box 或没有单元格间距和边框的 table
。如果需要边框,可以使用绝对定位在每个单元格内添加边框,而不是依赖于 table 的间距和边框。