故障排除
本节试图概述用户在第一次习惯使用 React Navigation 时经常遇到的问题。这些问题可能与 React Navigation 本身相关,也可能无关。
¥This section attempts to outline issues that users frequently encounter when first getting accustomed to using React Navigation. These issues may or may not be related to React Navigation itself.
在解决问题之前,请确保你已升级到软件包的最新可用版本。你可以通过再次安装软件包来安装最新版本(例如 npm install package-name
)。
¥Before troubleshooting an issue, make sure that you have upgraded to the latest available versions of the packages. You can install the latest versions by installing the packages again (e.g. npm install package-name
).
更新到最新版本后,我收到错误 "无法解析模块"
¥I'm getting an error "Unable to resolve module" after updating to the latest version
发生这种情况可能有 3 个原因:
¥This might happen for 3 reasons:
Metro 打包器的过时缓存
¥Stale cache of Metro bundler
如果模块指向本地文件(即模块名称以 ./
开头),则可能是由于缓存过时造成的。要解决此问题,请尝试以下解决方案。
¥If the module points to a local file (i.e. the name of the module starts with ./
), then it's probably due to stale cache. To fix this, try the following solutions.
如果你使用的是 Expo,请运行:
¥If you're using Expo, run:
expo start -c
如果你不使用 Expo,请运行:
¥If you're not using Expo, run:
npx react-native start --reset-cache
如果这不起作用,你还可以尝试以下操作:
¥If that doesn't work, you can also try the following:
rm -rf $TMPDIR/metro-bundler-cache-*
缺少同伴依赖
¥Missing peer dependency
如果模块指向 npm 包(即模块名称不带 ./
),则可能是由于缺少依赖。要解决此问题,请在项目中安装依赖:
¥If the module points to an npm package (i.e. the name of the module doesn't with ./
), then it's probably due to a missing dependency. To fix this, install the dependency in your project:
- npm
- Yarn
- pnpm
npm install name-of-the-module
yarn add name-of-the-module
pnpm add name-of-the-module
有时甚至可能是由于安装损坏造成的。如果清除缓存不起作用,请尝试删除 node_modules
文件夹并再次运行 npm install
。
¥Sometimes it might even be due to a corrupt installation. If clearing cache didn't work, try deleting your node_modules
folder and run npm install
again.
Metro 配置中缺少分机
¥Missing extensions in metro configuration
有时错误可能如下所示:
¥Sometimes the error may look like this:
Error: While trying to resolve module "@react-navigation/native" from file "/path/to/src/App.js", the package "/path/to/node_modules/@react-navigation/native/package.json" was successfully found. However, this package itself specifies a "main" module field that could not be resolved ("/path/to/node_modules/@react-navigation/native/src/index.tsx"
如果你有 Metro 的自定义配置并且未将 ts
和 tsx
指定为有效分机,则可能会发生这种情况。这些扩展存在于默认配置中。要检查这是否是问题所在,请在项目中查找 metro.config.js
文件,并检查是否指定了 sourceExts
选项。它至少应该有以下配置:
¥This can happen if you have a custom configuration for metro and haven't specified ts
and tsx
as valid extensions. These extensions are present in the default configuration. To check if this is the issue, look for a metro.config.js
file in your project and check if you have specified the sourceExts
option. It should at least have the following configuration:
sourceExts: ['js', 'json', 'ts', 'tsx'];
如果缺少这些扩展,请添加它们,然后清除 Metro 缓存,如上一节所示。
¥If it's missing these extensions, add them and then clear metro cache as shown in the section above.
我收到 "@react-navigation/xxx/xxx.tsx 中的语法错误" 或“语法错误:/xxx/@react-navigation/xxx/xxx.tsx:意外的标记”
¥I'm getting "SyntaxError in @react-navigation/xxx/xxx.tsx" or "SyntaxError: /xxx/@react-navigation/xxx/xxx.tsx: Unexpected token"
如果你有旧版本的 metro-react-native-babel-preset
软件包,则可能会发生这种情况。尝试将其升级到最新版本。
¥This might happen if you have an old version of the metro-react-native-babel-preset
package. Try upgrading it to the latest version.
- npm
- Yarn
- pnpm
npm install --save-dev metro-react-native-babel-preset
yarn add --dev metro-react-native-babel-preset
pnpm add --save-dev metro-react-native-babel-preset
如果你安装了 @babel/core
,请将其升级到最新版本。
¥If you have @babel/core
installed, also upgrade it to latest version.
- npm
- Yarn
- pnpm
npm install --save-dev @babel/core
yarn add --dev @babel/core
pnpm add --save-dev @babel/core
如果升级软件包没有帮助,你还可以尝试删除 node_modules
以及锁定文件并重新安装依赖。
¥If upgrading the packages don't help, you can also try deleting your node_modules
as well as lock the file and reinstall your dependencies.
如果你使用 npm
:
¥If you use npm
:
rm -rf node_modules
rm package-lock.json
npm install
如果你使用 yarn
:
¥If you use yarn
:
rm -rf node_modules
rm yarn.lock
yarn
升级或重新安装软件包后,你还应该按照页面前面的说明清除 Metro 打包程序的缓存。
¥After upgrading or reinstalling the packages, you should also clear Metro bundler's cache following the instructions earlier in the page.
使用 TypeScript 时的 I'我得到“模块'[...]'没有导出成员'xxx'
¥I'm getting "Module '[...]' has no exported member 'xxx' when using TypeScript
如果你的项目中有旧版本的 TypeScript,则可能会发生这种情况。你可以尝试升级它:
¥This might happen if you have an old version of TypeScript in your project. You can try upgrading it:
- npm
- Yarn
- pnpm
npm install --save-dev typescript
yarn add --dev typescript
pnpm add --save-dev typescript
I'我收到错误“null 不是对象(正在评估'RNGestureHandlerModule.default.Direction')"
¥I'm getting an error "null is not an object (evaluating 'RNGestureHandlerModule.default.Direction')"
如果你有一个裸露的 React Native 项目并且未链接库 react-native-gesture-handler
库,则可能会出现此错误和一些类似的错误。
¥This and some similar errors might occur if you have a bare React Native project and the library react-native-gesture-handler
library isn't linked.
从 React Native 0.60 开始链接是自动的,因此如果你手动链接了库,请首先取消链接:
¥Linking is automatic from React Native 0.60, so if you have linked the library manually, first unlink it:
react-native unlink react-native-gesture-handler
如果你在 iOS 上进行测试并使用 Mac,请确保你已在 ios/
文件夹中运行 pod install
:
¥If you're testing on iOS and use Mac, make sure you have run pod install
in the ios/
folder:
cd ios
pod install
cd ..
现在重建应用并在你的设备或模拟器上进行测试。
¥Now rebuild the app and test on your device or simulator.
我收到错误“requireNativeComponent:在 UIManager 中找不到 "RNCSafeAreaProvider"”
¥I'm getting an error "requireNativeComponent: "RNCSafeAreaProvider" was not found in the UIManager"
如果你有一个裸露的 React Native 项目并且未链接库 react-native-safe-area-context
库,则可能会出现此错误和一些类似的错误。
¥This and some similar errors might occur if you have a bare React Native project and the library react-native-safe-area-context
library isn't linked.
从 React Native 0.60 开始链接是自动的,因此如果你手动链接了库,请首先取消链接:
¥Linking is automatic from React Native 0.60, so if you have linked the library manually, first unlink it:
react-native unlink react-native-safe-area-context
如果你在 iOS 上进行测试并使用 Mac,请确保你已在 ios/
文件夹中运行 pod install
:
¥If you're testing on iOS and use Mac, make sure you have run pod install
in the ios/
folder:
cd ios
pod install
cd ..
现在重建应用并在你的设备或模拟器上进行测试。
¥Now rebuild the app and test on your device or simulator.
我收到错误 "尝试注册两个具有相同名称的视图 RNCSafeAreaProvider"
¥I'm getting an error "Tried to register two views with the same name RNCSafeAreaProvider"
如果你安装了多个版本的 react-native-safe-area-context
,则可能会发生这种情况。
¥This might occur if you have multiple versions of react-native-safe-area-context
installed.
如果你使用的是 Expo 管理的工作流程,则你可能安装了不兼容的版本。要安装正确的版本,请运行:
¥If you're using Expo managed workflow, it's likely that you have installed an incompatible version. To install the correct version, run:
npx expo install react-native-safe-area-context
如果它没有修复错误或者你没有使用 Expo 托管工作流程,则需要检查哪个包依赖于不同版本的 react-native-safe-area-context
。
¥If it didn't fix the error or you're not using Expo managed workflow, you'll need to check which package depends on a different version of react-native-safe-area-context
.
如果你使用 yarn
,请运行:
¥If you use yarn
, run:
yarn why react-native-safe-area-context
如果你使用 npm
,请运行:
¥If you use npm
, run:
npm ls react-native-safe-area-context
这将告诉你你使用的软件包是否依赖于 react-native-safe-area-context
。如果它是第三方软件包,你应该在相关存储库的问题跟踪器上打开一个问题来解释问题。一般来说,对于库来说,包含原生代码的依赖应该在 peerDependencies
而不是 dependencies
中定义,以避免此类问题。
¥This will tell you if a package you use has a dependency on react-native-safe-area-context
. If it's a third-party package, you should open an issue on the relevant repo's issue tracker explaining the problem. Generally for libraries, dependencies containing native code should be defined in peerDependencies
instead of dependencies
to avoid such issues.
如果它已经在 peerDependencies
中而不是在 dependencies
中,并且你使用 npm
,则可能是因为为包定义的版本范围不兼容。在这种情况下,库的作者需要放宽版本范围,以允许安装更广泛的版本。
¥If it's already in peerDependencies
and not in dependencies
, and you use npm
, it might be because of incompatible version range defined for the package. The author of the library will need to relax the version range in such cases to allow a wider range of versions to be installed.
如果你使用 yarn
,你还可以暂时覆盖使用 resolutions
安装的版本。在 package.json
中添加以下内容:
¥If you use yarn
, you can also temporarily override the version being installed using resolutions
. Add the following in your package.json
:
"resolutions": {
"react-native-safe-area-context": "<version you want to use>"
}
然后运行:
¥And then run:
yarn
如果你使用的是 iOS 并且不使用 Expo 管理的工作流程,还可以运行:
¥If you're on iOS and not using Expo managed workflow, also run:
cd ios
pod install
cd ..
现在重建应用并在你的设备或模拟器上进行测试。
¥Now rebuild the app and test on your device or simulator.
添加 View
后屏幕上看不到任何内容
¥Nothing is visible on the screen after adding a View
如果将容器封装在 View
中,请确保 View
拉伸以使用 flex: 1
填充容器:
¥If you wrap the container in a View
, make sure the View
stretches to fill the container using flex: 1
:
- Static
- Dynamic
import * as React from 'react';
import { View } from 'react-native';
import { createStaticNavigation } from '@react-navigation/native';
/* ... */
const Navigation = createStaticNavigation(RootStack);
export default function App() {
return (
<View style={{ flex: 1 }}>
<Navigation />
</View>
);
}
import * as React from 'react';
import { View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
export default function App() {
return (
<View style={{ flex: 1 }}>
<NavigationContainer>{/* ... */}</NavigationContainer>
</View>
);
}
我收到警告 "在导航状态中发现不可序列化的值"
¥I get the warning "Non-serializable values were found in the navigation state"
如果你在参数中传递不可序列化的值(例如类实例、函数等),则可能会发生这种情况。在这种情况下,React Navigation 会向你发出警告,因为这可能会破坏其他功能,例如 状态持续性、深层链接 等。
¥This can happen if you are passing non-serializable values such as class instances, functions etc. in params. React Navigation warns you in this case because this can break other functionality such state persistence, deep linking etc.
在参数中传递函数的一些用例示例如下:
¥Example of some use cases for passing functions in params are the following:
-
传递回调以在标题按钮中使用。这可以使用
navigation.setOptions
来实现。请参阅 标题按钮指南 的示例。¥To pass a callback to use in a header button. This can be achieved using
navigation.setOptions
instead. See the guide for header buttons for examples. -
将回调传递到下一个屏幕,它可以调用该回调来传回一些数据。通常可以使用
navigate
来实现。请参阅 参数指南 的示例。¥To pass a callback to the next screen which it can call to pass some data back. You can usually achieve it using
navigate
instead. See the guide for params for examples. -
将复杂数据传递到另一个屏幕。你可以将复杂的数据存储在其他地方(例如全局存储),然后传递 id,而不是传递数据
params
。然后屏幕就可以使用 id 从全局存储中获取数据。参见 参数中应该包含什么。¥To pass complex data to another screen. Instead of passing the data
params
, you can store that complex data somewhere else (like a global store), and pass an id instead. Then the screen can get the data from the global store using the id. See what should be in params. -
将数据、回调等从父屏幕传递到子屏幕。你可以使用 React Context,或者传递子回调来传递它们,而不是使用参数。参见 传递额外的属性。
¥Pass data, callbacks etc. from a parent to child screens. You can either use React Context, or pass a children callback to pass these down instead of using params. See passing additional props.
如果你不使用状态持久性或深层链接到接受参数中的函数的屏幕,则警告不会影响你,你可以安全地忽略它。要忽略警告,可以使用 LogBox.ignoreLogs
。
¥If you don't use state persistence or deep link to the screen which accepts functions in params, then the warning doesn't affect you and you can safely ignore it. To ignore the warning, you can use LogBox.ignoreLogs
.
示例:
¥Example:
import { LogBox } from 'react-native';
LogBox.ignoreLogs([
'Non-serializable values were found in the navigation state',
]);
我收到“无效的钩子调用。钩子只能在函数组件的主体内部调用”
¥I'm getting "Invalid hook call. Hooks can only be called inside of the body of a function component"
当你将 React 组件传递给接受返回 React 元素的函数的选项时,可能会发生这种情况。例如,原生堆栈导航器中的 headerTitle
选项 期望一个函数返回一个 React 元素:
¥This can happen when you pass a React component to an option that accepts a function returning a react element. For example, the headerTitle
option in native stack navigator expects a function returning a react element:
- Static
- Dynamic
const Stack = createNativeStackNavigator({
screens: {
Home: {
screen: Home,
options: {
headerTitle: (props) => <MyTitle {...props} />,
},
},
},
});
<Stack.Screen
name="Home"
component={Home}
option={{ headerTitle: (props) => <MyTitle {...props} /> }}
/>
如果你直接在这里传递一个函数,那么在使用钩子时你会得到这个错误:
¥If you directly pass a function here, you'll get this error when using hooks:
- Static
- Dynamic
const Stack = createNativeStackNavigator({
screens: {
Home: {
screen: Home,
options: {
// This is not correct
headerTitle: MyTitle,
},
},
},
});
<Stack.Screen
name="Home"
component={Home}
option={{
// This is not correct
headerTitle: MyTitle,
}}
/>
这同样适用于 headerLeft
、headerRight
、tabBarIcon
等其他选项以及 tabBar
、drawerContent
等属性。
¥The same applies to other options like headerLeft
, headerRight
, tabBarIcon
etc. as well as props such as tabBar
, drawerContent
etc.
导航期间屏幕正在卸载/重新安装
¥Screens are unmounting/remounting during navigation
有时你可能已经注意到,你的屏幕会卸载/重新安装,或者你的本地组件状态或导航状态会在你导航时重置。如果你在渲染期间创建 React 组件,则可能会发生这种情况。
¥Sometimes you might have noticed that your screens unmount/remount, or your local component state or the navigation state resets when you navigate. This might happen if you are creating React components during render.
最简单的例子如下:
¥The simplest example is something like following:
- Static
- Dynamic
const RootStack = createNativeStackNavigator({
screens: {
Home: () => {
return <SomeComponent />;
},
},
});
const Navigation = createStaticNavigation(RootStack);
function App() {
return <Navigation />;
}
function App() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={() => {
return <SomeComponent />;
}}
/>
</Stack.Navigator>
);
}
component
属性需要一个 React 组件,但在示例中,它得到一个返回 React 元素的函数。虽然从表面上看,组件和返回 React 元素的函数看起来完全相同,但它们在使用时的行为方式并不相同。
¥The component
prop expects a React Component, but in the example, it's getting a function returning an React Element. While superficially a component and a function returning a React Element look the exact same, they don't behave the same way when used.
在这里,每次组件重新渲染时,都会创建一个新函数并将其传递给 component
属性。React 将看到一个新组件并在渲染新组件之前卸载前一个组件。这将导致旧组件中的任何本地状态丢失。React Navigation 将检测并警告这种特定情况,但你可能还可以通过其他方式在渲染期间创建它无法检测到的组件。
¥Here, every time the component re-renders, a new function will be created and passed to the component
prop. React will see a new component and unmount the previous component before rendering the new one. This will cause any local state in the old component to be lost. React Navigation will detect and warn for this specific case but there can be other ways you might be creating components during render which it can't detect.
另一个容易识别的示例是当你在另一个组件内创建一个组件时:
¥Another easy to identify example of this is when you create a component inside another component:
- Static
- Dynamic
function App() {
const Home = () => {
return <SomeComponent />;
};
const RootStack = createNativeStackNavigator({
screens: {
Home: Home,
},
});
const Navigation = createStaticNavigation(RootStack);
return <Navigation />;
}
function App() {
const Home = () => {
return <SomeComponent />;
};
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
</Stack.Navigator>
);
}
或者当你在另一个组件中使用更高阶的组件(例如 Redux 中的 connect
或接受组件的 withX
函数)时:
¥Or when you use a higher order component (such as connect
from Redux, or withX
functions that accept a component) inside another component:
- Static
- Dynamic
function App() {
const Home = () => {
return <SomeComponent />;
};
const RootStack = createNativeStackNavigator({
screens: {
Home: withSomeData(Home),
},
});
const Navigation = createStaticNavigation(RootStack);
return <Navigation />;
}
function App() {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={withSomeData(Home)} />
</Stack.Navigator>
);
}
如果你不确定,最好确保用作屏幕的组件是在 React 组件之外定义的。它们可以在另一个文件中定义并导入,或者在同一文件的顶层范围内定义:
¥If you're unsure, it's always best to make sure that the components you are using as screens are defined outside of a React component. They could be defined in another file and imported, or defined at the top level scope in the same file:
- Static
- Dynamic
const Home = () => {
return <SomeComponent />;
};
const RootStack = createNativeStackNavigator({
screens: {
Home: Home,
},
});
const Navigation = createStaticNavigation(RootStack);
function App() {
return <Navigation />;
}
const Home = () => {
return <SomeComponent />;
};
function App() {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
</Stack.Navigator>
);
}
这不是特定于 React Navigation 的,而是与 React 一般相关。无论你是否使用 React Navigation,你都应该始终避免在渲染期间创建组件。
¥This is not React Navigation specific, but related to React in general. You should always avoid creating components during render, whether you are using React Navigation or not.
连接到 Chrome 调试器时应用无法正常工作
¥App is not working properly when connected to Chrome Debugger
当应用连接到 Chrome 调试器(或使用 Chrome 调试器的其他工具,例如 React Native 调试器)时,你可能会遇到与计时相关的各种问题。
¥When the app is connected to Chrome Debugger (or other tools that use Chrome Debugger such as React Native Debugger) you might encounter various issues related to timing.
这可能会导致问题,例如按下按钮需要很长时间才能注册或根本无法工作,手势和动画缓慢且有问题 等。还可能存在其他功能问题,例如 promise 无法解决,超时和间隔无法正常工作 等。
¥This can result in issues such as button presses taking a long time to register or not working at all, gestures and animations being slow and buggy etc. There can be other functional issues such as promises not resolving, timeouts and intervals not working correctly etc. as well.
这些问题与 React Navigation 无关,而是由 Chrome 调试器的工作方式决定的。连接到 Chrome 调试器时,整个应用在 Chrome 上运行,并通过网络上的套接字与原生应用进行通信,这可能会引入延迟和计时相关问题。
¥The issues are not related to React Navigation, but due to the nature of how the Chrome Debugger works. When connected to Chrome Debugger, your whole app runs on Chrome and communicates with the native app via sockets over the network, which can introduce latency and timing related issues.
因此,除非你尝试调试某些内容,否则最好在不连接到 Chrome 调试器的情况下测试应用。如果你使用的是 iOS,你也可以使用 Safari 来调试你的应用,它直接在设备上调试应用,并且不会出现这些问题,尽管它还有其他缺点。
¥So, unless you are trying to debug something, it's better to test the app without being connected to the Chrome Debugger. If you are using iOS, you can alternatively use Safari to debug your app which debugs the app on the device directly and does not have these issues, though it has other downsides.