React Hooks - Ref 在 useEffect 中不可用

IT技术 reactjs react-hooks
2021-05-10 17:17:02

我正在使用 ReactHooks。我正在尝试访问功能中refUser组件useEffect但我得到的elRef.currentvalue为null,虽然我elRef.current作为第二个参数传递useEffect. 但我想得到对div元素的引用但在 之外(函数体)useEffectref值是可用的。这是为什么 ?我怎样才能得到elRef.current里面的valueuseEffect

代码

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;

代码沙盒链接

谢谢 !

4个回答

您应该按照 reactjs文档中的建议使用useCallback而不是 useRef

每当 ref 附加到不同的节点时,React 都会调用该回调。

替换这个:

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
    }
}, []);

这是一种可预测的行为。

如前所述,@estus您面临这个问题是因为第一次调用它时,componentDidMount您正在获取null(初始值)并且在下次elRef更改时仅更新一次,因为实际上引用仍然相同。

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

是更新的沙箱。

希望它有所帮助。

useEffect 用作​​ componentDidMount 和 componentDidUpdate,在组件挂载时添加了一个条件:

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

由于挂载时的上述条件,您没有用户,因此它返回 null 并且 div 未挂载在您添加 ref 的 DOM 中,因此在 useEffect 中,您没有获得 elRef 的当前值未呈现。

当 div 安装在 dom 中时,单击 next 时,您将获得 elRef.current 的值。

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

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

所以到了useEffect执行的时候,我们可以确定elRef.current已经设置好了。

您的代码的问题在于,您<div ref={elRef}...>user填充之后才运行渲染器所以你要elRef引用的 DOM 节点还不存在。这就是您获得null日志记录的原因 - 与依赖项无关。


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

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

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