react-native 和 yarn 工作区的“钩子只能在函数组件的主体内调用”错误

Posted

技术标签:

【中文标题】react-native 和 yarn 工作区的“钩子只能在函数组件的主体内调用”错误【英文标题】:"Hooks can only be called inside the body of a function component" error with react-native and yarn workspace 【发布时间】:2020-10-19 23:27:02 【问题描述】:

我目前正在开发一个基于 yarn 工作区的项目。我整天在 react-native 中正确安装 apollo 时遇到问题,最后,我设法通过更改 metro.config.js 文件的配置来做到这一点(即使我认为它不会影响问题)。但是,当我开始实现@apollo/react-hooks 库时,我遇到了以下错误:

钩子只能在函数组件内部调用。

我在网上冲浪时遇到了其他类似的问题,官方的反应文档说:

There are three common reasons you might be seeing it:

- You might have mismatching versions of React and React DOM.
- You might be breaking the Rules of Hooks.
- You might have more than one copy of React in the same app.

我认为第二个选项被排除在外,因为我实现的代码非常简单,而且我认为不可能出现错误。即使是第一个选项对我来说似乎也是不可能的,因为我使用的是 react-native,因此我没有使用 React DOM 模块(如果我错了,请为我的无知道歉)。

最后一个选项是最受认可的选项。事实上,检查两个 node_modules 文件夹(一个位于工作空间的根目录,一个位于移动应用程序包中),我意识到“react”模块存在于两个目录中。

通过尝试,我意识到只有在 react-native 应用程序的 package.json 文件中使用“nohoist”属性时才会安装两次 react 模块。一旦我将 react-native 模块插入到“nohoist”数组中,react 也会安装在“本地”node_modules 文件夹以及项目根目录中。

  "workspaces": 
    "nohoist": [
      "react-native",   <---------
      "react-native/**",  <---------
      "@react-native-mapbox-gl",
      "@react-native-mapbox-gl/**",
      "react-native-gesture-handler",
      "react-native-gesture-handler/**",
      "react-native-reanimated",
      "react-native-reanimated/**",
      "@react-navigation",
      "@react-navigation/**",
      "react-native-safe-area-context",
      "react-native-safe-area-context/**",
      "react-native-vector-icons",
      "react-native-vector-icons/**",
      "react-native-pose",
      "react-native-pose/**",
      "@react-native-community",
      "@react-native-community/**",
      "react-native-elements",
      "react-native-elements/**"
    ]
  

我无法理解“react-native”模块如何影响“react”模块。我想这里的解决方案是防止这种情况发生,但我不知道如何。

这是 metro.config.js 文件(如果有人想检查它,即使我认为它与这个问题无关):

const path = require("path");
const getWorkspaces = require("get-yarn-workspaces");
const blacklist = require("metro-config/src/defaults/blacklist");

const workspaces = getWorkspaces(__dirname);

module.exports = 
  projectRoot: path.resolve(__dirname, "."),

  watchFolders: [path.resolve(__dirname, "../../node_modules"), ...workspaces],

  resolver: 
    blacklistRE: blacklist(
      workspaces.map(
        (workspacePath) =>
          `/$workspacePath.replace(
            /\//g,
            "[/\\\\]",
          )[/\\\\]node_modules[/\\\\]react-native[/\\\\].*/`,
      ),
    ),
    extraNodeModules: 
      "react-native": path.resolve(__dirname, "node_modules/react-native"),
    ,
  ,
;

相反,这是错误来源的文件:

import React, useState from "react";
import View, Text, Button from "react-native";
import StackScreenProps from "@react-navigation/stack";
import AuthStackParamList from "../../../navigation";
import useRegisterMutation from "../../../generated/graphql";
import InputText from "../../../components/InputText";

type SignUpScreenProps = StackScreenProps<AuthStackParamList, "SignUp">;

export const SignUpScreen: React.FC<SignUpScreenProps> = (navigation) => 
  const [username, setUsername] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");


  const [signup] = useRegisterMutation();
  

  const handleSubmit = async () => 
    const data, errors = await signup(
      variables: username, email, password,
    );
    console.log(data, errors);
  ;

  return (
    <View style=flex: 1, justifyContent: "center", alignItems: "center">
      <Text>SignUp</Text>
      <InputText
        label="Username"
        value=username
        onChangeText=setUsername
        textContentType="username"
      />
      <InputText
        label="Email"
        value=email
        onChangeText=setEmail
        textContentType="emailAddress"
      />
      <InputText
        label="Email"
        value=password
        onChangeText=setPassword
        textContentType="password"
        secureTextEntry
      />
      <Button title="Crea account" onPress=handleSubmit />
    </View>
  );
;

在此先感谢大家。

更新:

通过在工作区根目录的 package.json 中的“nohoist”字段中添加“react”,我已经(目前)解决了该错误。但是,我确信当我将 Web 应用程序 (reactjs) 添加到工作区时,问题会再次出现。因此,如果有人有更好的解决方案,无疑是可以接受的。


  "name": "root",
  "private": true,
  "workspaces": 
    "packages": [
      "packages/*"
    ],
    "nohoist": [
      "**/react",
      "**/react/**"
    ]
  ,
  "scripts": ,
  "devDependencies": 
    "lerna": "^3.20.2"
  

【问题讨论】:

【参考方案1】:

你必须这样使用

请导入 useMutation

import  useMutation  from '@apollo/react-hooks';

这样使用

const [signup] = useMutation(useRegisterMutation)

现在你必须打电话

const handleSubmit = async () => 
    const data, errors = await signup(
      variables: username, email, password,
    );
    console.log(data, errors);
  ;

更多信息请点击Link

【讨论】:

非常感谢您对我的问题的关注,但是,我相信这不是错误的原因。对于我的错误,我忘了写我正在使用一个名为 @graphql-codegen/cli 的包,它会自动为模式中定义的查询生成挂钩。在这种情况下,“useRegisterMutation”函数是从一个文件中导入的,该文件随后会自行生成。我也尝试按照您的方法进行操作,但仍然收到相同的错误。

以上是关于react-native 和 yarn 工作区的“钩子只能在函数组件的主体内调用”错误的主要内容,如果未能解决你的问题,请参考以下文章

将 react-native 添加到 monorepo 后,React apollo hooks 失败

在react-native 项目中使用yarn link

Jenkins 运行 fastlane 脚本时未安装 Yarn

react-native:从纱线切换到 npm

使用模拟器调试react-native步骤(安卓机)

React-native安装发布流程Android篇