React Hooks - Ref 在 useEffect 中不可用

React Hooks - Ref is not avaiable inside useEffect

我正在使用 ReactHooks。我正在尝试访问 useEffect 函数中的 User 组件的 ref。但是我得到的 elRef.current 值是 null,尽管我将 elRef.current 作为第二个参数传递给 useEffect。但我想参考 div 元素。但是在 useEffect 的外部(函数体), ref 值是可用的。这是为什么 ?如何在 useEffect 中获取 elRef.current 值?

代码

import React, { Component, useState, useRef, useEffect } from "react";

const useFetch = url => {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(
    () => {
      setIsLoading(true);
      fetch(url)
        .then(response => {
          if (!response.ok) throw Error(response.statusText);
          return response.json();
        })
        .then(json => {
          setIsLoading(false);
          setData(json.data);
        })
        .catch(error => {
          setIsLoading(false);
          setError(error);
        });
    },
    [url]
  );

  return { data, isLoading, error };
};

const User = ({ id }) => {
  const elRef = useRef(null);
  const { data: user } = useFetch(`https://reqres.in/api/users/${id}`);

  useEffect(() => {
    console.log("ref", elRef.current);
  }, [elRef.current]);
  if (!user) return null;
  return <div ref={elRef}>{user.first_name + " " + user.last_name}</div>;
};

class App extends Component {
  state = {
    userId: 1
  };

  handleNextClick = () => {
    this.setState(prevState => ({
      userId: prevState.userId + 1
    }));
  };

  handlePrevNext = () => {
    this.setState(prevState => ({
      userId: prevState.userId - 1
    }));
  };
  render() {
    return (
      <div>
        <button
          onClick={() => this.handlePrevClick()}
          disabled={this.state.userId === 1}
        >
          Prevoius
        </button>
        <button onClick={() => this.handleNextClick()}>Next</button>
        <User id={this.state.userId} />
      </div>
    );
  }
}

export default App;

Codesandbox link

谢谢!

这是一种可以预见的行为。

如前所述 @estus 你遇到这个问题是因为第一次在 componentDidMount 上调用它时你得到 null (初始值)并且在下一次 elRef 改变是因为,实际上,引用仍然是一样的。

如果您需要反映每个用户更改,您应该将 [user] 作为第二个参数传递给函数,以确保在用户更改时触发 useEffect

Here 已更新沙箱。

希望对您有所帮助。

useEffect同时用作componentDidMount和componentDidUpdate, 在组件安装时你添加了一个条件:

if (!user) return null;
return <div ref={elRef}>{user.first_name + " " + user.last_name}</div>;

因为上面的条件在挂载的时候,你没有这个用户,所以returns null 并且div没有挂载在DOM里面您正在添加 ref,因此在 useEffect 中您没有获得 elRef 的当前值,因为它没有被渲染。

然后单击下一步,因为 div 安装在 dom 中,您得到了 elRef.current 的值。

这里的假设是 useEffect 需要检测对 ref.current 的更改,因此需要在依赖项列表中包含 refref.current。我认为这是由于 es-lint 有点过分迂腐。

实际上,useEffect 的全部意义在于它保证在渲染完成并且 DOM 准备就绪之前不会 运行。这就是它处理副作用的方式。

所以在执行 useEffect 时,我们可以确定 elRef.current 已设置。

您的代码的问题在于,在填充 user 之前,您不会 运行 使用 <div ref={elRef}...> 的渲染器。因此,您希望 elRef 引用的 DOM 节点尚不存在。这就是您获得 null 日志记录的原因 - 与依赖项无关。


顺便说一句:一种可能的替代方法是在效果挂钩中填充 div:

useEffect(
  () => {
    if(!user) return;
    elRef.current.innerHTML = `${user.first_name} ${user.last_name}`;
  }, [user]
);

这样,用户组件中的 if (!user) return null; 行就没有必要了。删除它,elRef.current 保证从一开始就填充 div 节点。

您应该使用 useCallback 而不是 reactjs docs.

中建议的 useRef

React will call that callback whenever the ref gets attached to a different node.

替换为:

const elRef = useRef(null);
useEffect(() => {
    console.log("ref", elRef.current);
}, [elRef.current]);

有了这个:

const elRef = useCallback(node => {
    if (node !== null) {
        console.log("ref", node); // node = elRef.current
    }
}, []);

当您使用 function as a ref, it is called with the instance when it is ready. So the easiest way to make the ref observable is to use useState instead of useRef:

const [element, setElement] = useState<Element | null>(null);
return <div ref={setElement}></div>;

然后您可以在其他挂钩的依赖数组中使用它,就像任何其他 const 值一样:

useEffect(() => {
  if (element) console.log(element);
}, [element]);

另见 How to rerender when refs change