我们如何知道 React 的 ref.current 值何时发生了变化?
Posted
技术标签:
【中文标题】我们如何知道 React 的 ref.current 值何时发生了变化?【英文标题】:How do we know when a React ref.current value has changed? 【发布时间】:2019-09-14 05:46:38 【问题描述】:通常,有了道具,我们可以写
componentDidUpdate(oldProps)
if (oldProps.foo !== this.props.foo)
console.log('foo prop changed')
为了检测道具变化。
但是如果我们使用React.createRef()
,我们如何检测 ref 何时更改为新的组件或 DOM 元素? React 文档并没有真正提及任何内容。
F.e.,
class Foo extends React.Component
someRef = React.createRef()
componentDidUpdate(oldProps)
const refChanged = /* What do we put here? */
if (refChanged)
console.log('new ref value:', this.someRef.current)
render()
// ...
我们应该自己实现某种旧值吗?
F.e.,
class Foo extends React.Component
someRef = React.createRef()
oldRef =
componentDidMount()
this.oldRef.current = this.someRef.current
componentDidUpdate(oldProps)
const refChanged = this.oldRef.current !== this.someRef.current
if (refChanged)
console.log('new ref value:', this.someRef.current)
this.oldRef.current = this.someRef.current
render()
// ...
这是我们应该做的吗?我原以为 React 会为此添加一些简单的功能。
【问题讨论】:
【参考方案1】:React 文档推荐使用callback refs 来检测ref
值的变化。
挂钩
export function Comp()
const onRefChange = useCallback(node =>
if (node === null)
// DOM node referenced by ref has been unmounted
else
// DOM node referenced by ref has changed and exists
, []); // adjust deps
return <h1 ref=onRefChange>Hey</h1>;
useCallback
用于prevent double calling 的引用回调,带有null
和元素。
您可以通过useState
存储当前 DOM 节点来在更改时 trigger re-renders:
const [domNode, setDomNode] = useState(null);
const onRefChange = useCallback(node =>
setDomNode(node); // trigger re-render on changes
// ...
, []);
类组件
export class FooClass extends React.Component
state = ref: null, ... ;
onRefChange = node =>
// same as Hooks example, re-render on changes
this.setState( ref: node );
;
render()
return <h1 ref=this.onRefChange>Hey</h1>;
注意:useRef
不会通知 ref
的更改。还有no luck 和React.createRef()
/ 对象引用。
这是一个测试用例,在触发onRefChange
回调时删除并重新添加一个节点:
const Foo = () =>
const [ref, setRef] = useState(null);
const [removed, remove] = useState(false);
useEffect(() =>
setTimeout(() => remove(true), 3000); // drop after 3 sec
setTimeout(() => remove(false), 5000); // ... and mount it again
, []);
const onRefChange = useCallback(node =>
console.log("ref changed to:", node);
setRef(node); // or change other state to re-render
, []);
return !removed && <h3 ref=onRefChange>Hello, world</h3>;
ReactDOM.render(<Foo />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.1/umd/react.production.min.js" integrity="sha256-vMEjoeSlzpWvres5mDlxmSKxx6jAmDNY4zCt712YCI0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.1/umd/react-dom.production.min.js" integrity="sha256-QQt6MpTdAD0DiPLhqhzVyPs1flIdstR4/R7x4GqCvZ4=" crossorigin="anonymous"></script>
<script> var useState, useEffect, useCallback = React</script>
<div id="root"></div>
【讨论】:
谢谢。 React 中的 ref 特性并不理想。例如,在 Vue 中引用要容易得多。 @Erwol 是的,你可以这样做。如果需要重新渲染,当节点发生变化时,使用useState
/setState
。如果节点更改不应触发重新渲染,请使用 ref 或仅使用实例变量(在类的情况下)。如果与 refs 一起使用,您通常宁愿写 this.containerRef.current = currentNode
之类的东西。
ref 转发怎么样?我想我们可能仍然可以使用React.createRef()
引用,如果我们接受来自组件外部的引用(Comp(props, ref)
等)。假设每次渲染都会刷新 ref;像这样的东西可以吗?
非常清晰的答案!
这是我第一次在一个对我来说真正有意义的示例中看到 useCallback。谢谢!【参考方案2】:
componentDidUpdate
在组件状态或 props 更改时被调用,因此它不一定会在 ref
更改时被调用,因为它可以根据您的需要进行变异。
如果你想检查一个 ref 是否与之前的渲染相比发生了变化,你可以保留另一个 ref 来检查真实的。
示例
class App extends React.Component
prevRef = null;
ref = React.createRef();
state =
isVisible: true
;
componentDidMount()
this.prevRef = this.ref.current;
setTimeout(() =>
this.setState( isVisible: false );
, 1000);
componentDidUpdate()
if (this.prevRef !== this.ref.current)
console.log("ref changed!");
this.prevRef = this.ref.current;
render()
return this.state.isVisible ? <div ref=this.ref>Foo</div> : null;
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
【讨论】:
那么在基于类的示例中,我什么时候进行检查? 等等,componentDidUpdate
不是在每个render
之后调用吗?那么,componentDidUpdate
不是进行检查的地方吗(即使 componentDidUpdate
是由 prop 或状态更改间接触发的)?
@trusktr 是的,componentDidUpdate
是在 prop 或状态更改后间接调用的,但 ref
是一个可变值,可以被任何东西更改,而 React 没有知道 ref 在这个意义上发生了变化的方式。在类示例中,您将使用componentDidMount
和componentDidUpdate
的组合。我更新了答案。
“一个 ref 是一个可以被任何东西改变的可变值”,是的,但同样地,this.state
中的任何东西都可以,但是我们显然避免这样做,因为它不是改变状态的方式。同样,我认为(希望)很明显,我们不应该随意修改 props 或 refs。因此,似乎如果我们只让 React 修改 ref.current
(仅通过将 ref 传递到 JSX 标记中),那么我们必须跟踪旧值的想法似乎是唯一的方法。如果 React 在这方面有更多的地方就好了。
使用旧的 refs(基于函数的 refs),只需 setState
在函数内使用新的 ref 很容易,这将触发响应性,而无需手动跟踪旧值。事后看来,这可能更直观(如更明显的如何处理反应性)。 (但是,我讨厌函数的每次调用总是以空 ref 开头,这绝对令人难以置信。他们认为这是为了强制清理,但我认为它造成的问题比防止坏端更多 -用户代码)。以上是关于我们如何知道 React 的 ref.current 值何时发生了变化?的主要内容,如果未能解决你的问题,请参考以下文章