【问题标题】:How do we know when a React ref.current value has changed?我们如何知道 React 的 ref.current 值何时发生了变化?
【发布时间】: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 会为此添加一些简单的功能。

【问题讨论】:

    标签: javascript reactjs react-ref


    【解决方案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 luckReact.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 开头,这绝对令人难以置信。他们认为这是为了强制清理,但我认为它造成的问题比防止坏端更多 -用户代码)。
    猜你喜欢
    • 2022-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多