React useEffect hook load onsnapshot 有条件地反应

Posted

技术标签:

【中文标题】React useEffect hook load onsnapshot 有条件地反应【英文标题】:React useEffect hook load onsnapshot conditionally 【发布时间】:2020-01-06 10:19:08 【问题描述】:

我正在尝试执行以下操作,但似乎无法获得最合适的解决方案: 在我的 React 应用程序(应用程序组件)的顶层,我正在加载 firebase 和 firestore。我有一个反应上下文,其中存储了我的身份验证信息。身份验证上下文不会立即加载,而是在一段时间后加载。我正在使用 onAuthStateChanged(来自 firebase)来等待它。我的代码如下所示:

const firebase = new Firebase(); //custom class, instantiates firebase
const [authData, setAuthData] = useState(
  firebase,
  user: null,
  isInitializing: true,
  userdata:  info: null, data: null 
);

useEffect(() => 
  // listen for auth state changes
  const unsubscribe = firebase.auth.onAuthStateChanged(async returnedUser => 
    //set Auth data into state to pass into context
    setAuthData( ...authData, user: returnedUser, isInitializing: false );
  );
  // unsubscribe to the listener when unmounting
  return () => unsubscribe();
, []);

现在我想为这个组件添加一个监听器来监听 firebase cloud firestore 中的配置文件数据。他们为此提供了一个 onShapshot 函数。

在哪里添加该侦听器?因为我不想打太多电话。我可以安全地将侦听器放在取消订阅功能中吗?卸载时如何确保监听器订阅?

首先我尝试了一个额外的 useEffect 钩子,如下所示:

useEffect(() => 
  if (authData.isInitializing || authData.user === null) return;
  const ref = firebase.db.doc("/env/" + process.env.REACT_APP_FIRESTORE_ENVIRONMENT + "/users/" + authData.user.uid);
  return ref.onSnapshot(doc => 
    setAuthData( ...authData, userinfo: doc.data() );
  );
, []);

这不起作用,因为它只运行一次(在挂载时),然后它什么也不返回,因为它仍在初始化,我不能将它设置为在每次更新时运行,因为它会一直查询 firestore、更新状态和查询(无限循环)。

在上下文中传递状态有效,我可以在任何地方使用它(身份验证状态)。我只想能够在上下文中拥有基本的用户数据。现在,我可以手动添加它(当用户登录时,获取用户配置文件 + 数据并存储在状态中),但我也想处理这些数据的更新,所以我需要 onSnapshot 监听器。如何确保我只调用一次监听器?

【问题讨论】:

评论是因为我没有时间在答案中充实它:使用 React Context 在上下文中传递对 firebase 的引用。使用挂钩来检索引用并添加侦听器。在需要“监听”firebase 状态的组件中使用该钩子。您应该将 authData.isInitializing 添加到您的第二个钩子 deps 数组中,以使您的钩子在每次更改时重新运行。 最后一句,将 authData.isInitializing 添加到 useEffect deps,可能是整个解决方案!为什么我没有想到这个?那个只会改变一次(在设置时),所以可能正是我需要的。我会告诉你的 哦,如何以这种方式返回(ubsubscribe 监听器)?我可以像我在原来的第二个代码块中写的那样做吗?那么 if isInitializing === true --> 只返回,否则返回取消订阅函数? @EricJansen - 请你分享你是如何解决这个问题的。我正在尝试做同样的事情 嗨@EricJansen - 是的 - 我仍然卡住了。我试图遵循其他人已经弄清楚如何使用的示例,结果一团糟:***.com/questions/59977856/… 【参考方案1】:

在上下文中传递状态有效,我可以在任何地方使用它(身份验证状态)。我只想能够在上下文中拥有基本的用户数据。现在,我可以手动添加它(当用户登录时,获取用户配置文件 + 数据并存储在状态中),但我也想处理这些数据的更新,所以我需要 onSnapshot 监听器。如何确保我只调用一次监听器?

听起来您想使用React.useEffect() 中的数组参数。 React.useEffect() 的第二个参数是一个依赖数组。当这些依赖中的任何一个发生变化时,效果将再次执行。

如果您使用的是 eslint,react-hooks eslint plugin 将在您使用在闭包中捕获变量但不将这些变量放入 deps 数组中的钩子时突出显示。

以下更改将确保onSnapshot 只添加一次每个组件

useEffect(() => 
  if (authData.isInitializing || authData.user === null) return;
  const ref = firebase.db.doc("/env/" + process.env.REACT_APP_FIRESTORE_ENVIRONMENT + "/users/" + authData.user.uid);
  return ref.onSnapshot(doc => 
    setAuthData( ...authData, userinfo: doc.data() );
  );
, [authData.isInitializing, authData.user]);

执行的提取次数将与您拥有的组件数量成线性关系。如果您预计此钩子会被多次使用,您可能需要考虑一个模型,其中 firebase.db.doc() 调用仅在 one 组件中调用,但许多组件可以订阅商店。这与 React Redux 的工作方式非常相似,我建议您查看他们的代码以了解如何实现这一点。

哦,如何以这种方式返回(ubsubscribe 监听器)?我可以像我在原来的第二个代码块中写的那样做吗?那么如果 isInitializing === true --> 只返回,否则返回取消订阅函数?

是的,这会很好。除非调用onSnapshot(),否则不需要取消订阅功能。

【讨论】:

最后一个小问题:如果用户注销了,那么这个返回的 ref.onShapshot 会运行吗?因为那时必须取消监听器。

以上是关于React useEffect hook load onsnapshot 有条件地反应的主要内容,如果未能解决你的问题,请参考以下文章

UseEffect - React Hook useEffect 缺少依赖项:

React hooks之useEffect

有条件地调用 React Hook “useEffect”。在每个组件渲染中,必须以完全相同的顺序调用 React Hooks

如何使用 useEffect() 更改 React-Hook-Form defaultValue?

React Hook useEffect 缺少依赖项:'props' 由于 useEffect 中有一行

React常用hook的优化useEffect浅比较