我们如何知道 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 在这个意义上发生了变化的方式。在类示例中,您将使用componentDidMountcomponentDidUpdate 的组合。我更新了答案。 “一个 ref 是一个可以被任何东西改变的可变值”,是的,但同样地,this.state 中的任何东西都可以,但是我们显然避免这样做,因为它不是改变状态的方式。同样,我认为(希望)很明显,我们不应该随意修改 props 或 refs。因此,似乎如果我们只让 React 修改 ref.current(仅通过将 ref 传递到 JSX 标记中),那么我们必须跟踪旧值的想法似乎是唯一的方法。如果 React 在这方面有更多的地方就好了。 使用旧的 refs(基于函数的 refs),只需 setState 在函数内使用新的 ref 很容易,这将触发响应性,而无需手动跟踪旧值。事后看来,这可能更直观(如更明显的如何处理反应性)。 (但是,我讨厌函数的每次调用总是以空 ref 开头,这绝对令人难以置信。他们认为这是为了强制清理,但我认为它造成的问题比防止坏端更多 -用户代码)。

以上是关于我们如何知道 React 的 ref.current 值何时发生了变化?的主要内容,如果未能解决你的问题,请参考以下文章

如何在React MUI中创建带标签的TextField

如何查看管理npm模块--react-native为例

React 组件的生命周期

如何在 react-native 中创建嵌套的故事书结构?

React Apollo:如何在多个地方使用相同查询的结果

我们如何在 React Native for iOS 中调整 RCTRootView 的大小/开始?