[译] 在React应用当中如何处理错误?

写在最前

看官们好,我叫JetTsang,最近会尝试翻译一些外网文章,如有错误欢迎批评指正,如有歧义以原文为主。

免责声明

免责声明:

本文仅供参考和学习之用。除非另有说明,本文中引用的所有文字、图片、图表和其他媒体素材均属于其各自的版权所有者。这些材料仅出于说明和教育目的被使用,并不构成对其版权所有者的认可或推荐。

如果您是版权所有者,认为本文中的内容侵犯了您的版权,请您与我们联系。我们将配合调查并根据相关法律和规定采取适当的措施进行处理。

最后,我们保留对本文内容进行修改、更新和更正的权利。我们建议读者在阅读本文的同时,尊重他人的知识产权,遵守适用的法律和规定。

谢谢!

原文地址:How to Handle Errors in React Applications (freecodecamp.org)

正文

错误处理是开发用户友好的React应用程序的关键方面。 作为一个开发者,你不能总是预测或者阻止到所有的错误,但可以肯定的是你可以控制它们是如何被处理的。

在这篇文章里,我们将会在React当中,探索高效、实用的错误处理策略。我们将会覆盖多种类型的错误,从简单的运行时错误到异步错误,然后讨论如何去用一个清晰和友好的态度去构建用户和错误之间的沟通桥梁。

不同类型的React错误

语法错误 Syntax Errors

语法错误通常发生在 当你的代码结构当中的有错误时。它们通常发生在错别字、缺少字母或者字母错位,或者错误的运用编程语言的元素/关键字。这些错误会让代码无法被正确的编译或解析,从而导致程序无法运行

下面是一些导致语法错误的常见场景:

不匹配的花括号,括号,方括号

JS 复制代码
// Incorrect
function myFunction() {
  if (true) {
    console.log('Hello World';
  }
}

// Correct
function myFunction() {
  if (true) {
    console.log('Hello World');
  }
}

由于在console.log语句当中缺少一个右括号,这会导致语法错误。

缺少分号

js 复制代码
// Incorrect
const greeting = 'Hello World'
console.log(greeting)

// Correct
const greeting = 'Hello World';
console.log(greeting);

JavaScript 使用分号来分离声明,忽略他们则会导致语法错误

打字或者拼写错误

js 复制代码
// Incorrect
funtion myFunction() {
  console.log('Hello World');
}

// Correct
function myFunction() {
  console.log('Hello World');
}

因为将关键字 function拼写错误成funtion,语法错误就发生了

意料之外的标识 Unexpected Tokens

js 复制代码
// Incorrect
const numbers = [1, 2, 3]
numbers.forEach(number => console.log(number))

// Correct
const numbers = [1, 2, 3];
numbers.forEach(number => console.log(number));

这种错误可能发生在因为缺少或者额外的字符从而导致代码结构异常。

修复语法错误,需要仔细的审视你的代码,密切关注由浏览器控制台和开发环境上提供的错误信息。这些信息通常会表明错误的行和位置。从而帮助你识别和纠正错误

记住,即便1个小小的语法错误都可以对你代码的功能产生重大的影响。所以关键是要迅速地设法解决它

js 复制代码
// Corrected syntax
function MyComponent() {
  return (
    <div>
      <p>Hello World</p>
    </div>
  );
}

引用错误 Reference Errors

引用错误通常发生在当你编写程序时使用了未定义的变量或者函数。本质上,这是解释器或者编译器无法在当前作用域下找到变量或者函数的引用,导致了运行时错误。

下面是一些会导致引用错误的场景

未定义的变量

arduino 复制代码
// Incorrect
console.log(myVariable);

// Correct
const myVariable = 'Hello World';
console.log(myVariable);

这个例子当中,引用错误发生了,因为myVariable在声明前使用。要解决这个问题,需要在使用前声明该变量。

拼写错误的函数或者变量

js 复制代码
// Incorrect
const greeting = 'Hello World';
console.log(greting);

// Correct
const greeting = 'Hello World';
console.log(greeting);

如果对变量或者函数名有1个打字错误或拼写错误,在这个例子当中,纠正拼写可以解决这个问题

作用域问题

js 复制代码
// Incorrect
function myFunction() {
  if (true) {
    const message = 'Hello World';
  }
  console.log(message);
}

// Correct
function myFunction() {
  let message;
  if (true) {
    message = 'Hello World';
  }
  console.log(message);
}

在这里也有引用错误发生,因为 messageif块内被定义,并且不能在外部被访问到。

将这个变量声明放到1个更广的作用域当中可以解决这个问题

访问未定义对象的属性

js 复制代码
// Incorrect
const person = { name: 'John' };
console.log(person.age);

// Correct
const person = { name: 'John' };
console.log(person.age || 'Age not available');

如果你想访问1个undefined 对象的属性,引用错误就会发生。 使用条件检查或者确保该对象的被正确的初始化可以避免同类型错误。

解决引用错误,需要仔细低检查你代码中变量和函数的正确拼写。

确保变量被定义在合适的作用域以及当访问它们的属性时,对象被正确的初始化。 注意控制台的错误信息,因为他们通常会提供有价值的信息,这里信息当中有关于位置和引用错误的种类。

js 复制代码
// Corrected reference
function MyComponent() {
  const undefinedVariable = "I am defined now!";
  return (
    <div>
      <p>{undefinedVariable}</p>
    </div>
  );
}

类型错误

当1个运算被执行在不正确数据类型的值时,程序当中会发生类型错误

JS是一个动态类型语言,在它当中,变量的数据类型不需要明确的声明,同时可以在运行时改变。

在尝试对1个不支持特殊运算的值来进行运算时,类型错误通常就发生了。

这是一些常见的导致类型错误的场景:

不正确的数据类型进行运算

js 复制代码
// Incorrect
const number = 42;
const result = number.toUpperCase();

// Correct
const number = 42;
const result = String(number).toUpperCase();

因为toUpperCase() 是1个字符串的方法,同时你不能用于number类型,所以类型错误发生了。 解决这个问题,需要在使用toUpperCase()方法之前,转换成string

不匹配的数据类型进行算术运算

js 复制代码
// Incorrect
const result = 'Hello' * 5;

// Correct
const result = 'Hello'.repeat(5);

尝试去运行1个乘法运算,使用正确的stirng方法去修复这个问题

空值运算

js 复制代码
// Incorrect
const value = undefined;
const result = value.toLowerCase();

// Correct
const value = undefined;
const result = String(value).toLowerCase();

试着去对1个undefined的值进行运算时,会导致类型错误。要解决这个问题,可以在运算之前转变该值为string。

函数的错误使用

js 复制代码
// Incorrect
const number = 42;
const result = String.toUpperCase(number);

// Correct
const number = 42;
const result = String(number).toUpperCase();

错误的使用函数会导致类型错误,比如对String构造函数尝试调用toUpperCase()。 正确的用法是先创建一个字符串实例。

尝试解决类型错误时,了解变量的数据类型和正在执行的操作是至关重要的。 在你运算之前,你可以使用函数或者方法显示转换成你想要的数据类型。集中注意在控制台的错误信息上,因为它们通常指出了你代码中的类型错误的性质和位置。

组件生命周期错误

React的组件生命周期错误发生在当这些问题与React组件的生命周期方法有关时。

React组件生命周期经历了不同的阶段,每个阶段都是和特定的方法相关联。如果不正确地使用这些方法,或者方法里有错误,这会导致你的React应用程序出现不符合预期的行为和错误。

React组件的生命周期由3个主要的阶段组成:

挂载阶段:

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()

更新阶段:

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

卸载阶段:

  • componentWillUnmount()

以下是一些可能导致组件生命周期错误的常见情况

不正确地使用 setState:

js 复制代码
// Incorrect
componentDidMount() {
  this.setState({ data: fetchData() });
}

componentDidMount里使用setState而不考虑异步行为会导致问题。推荐使用回调函数去确保setState在数据被取回后调用。

js 复制代码
// Correct
componentDidMount() {
  fetchData().then(data => {
    this.setState({ data });
  });
}

没有正确的处理异步操作:

js 复制代码
// Incorrect
componentDidUpdate() {
  fetchData().then(data => {
    this.setState({ data });
  });
}

componentDidUpdate里直接执行异步操作会导致无限循环

有条件的去检查是否需要更新以及适当的处理异步操作是至关重要的

js 复制代码
// Correct
componentDidUpdate(prevProps, prevState) {
  if (this.props.someValue !== prevProps.someValue) {
    fetchData().then(data => {
      this.setState({ data });
    });
  }
}

componentWillUnmount里没有清除资源:

js 复制代码
// Incorrect
componentWillUnmount() {
  clearInterval(this.intervalId);
}

忘记在 componentWillUnmount方法当中去清除资源,比如说定时器和事件监听,会导致内存溢出。 始终保证清除资源以避免不合预期的行为

js 复制代码
// Correct
componentWillUnmount() {
  clearInterval(this.intervalId);
}

理解并正确实施React组件生命周期方法对于避免错误和确保组件在整个生命周期中按预期运行是至关重要的。关于组件生命周期的最新指南,请始终参阅官方React文档。

如何实现全局错误处理

1. Window 的错误事件:

在web应用里, JavaScript当中的window.onerror事件处理程序可以允许你在全局层面捕抓并且处理未被处理的错误。每当有未被捕获异常发生时,这个事件将被触发,它提供了1个方式让你集中地记录或者处理这些错误。这对于全局错误处理和错误排查而言是1个强大的工具。

下面是如何使用window.onerror去实施全局错误处理的例子:

js 复制代码
window.onerror = function (message, source, lineno, colno, error) {
  // Log the error details or send them to a logging service
  console.error('Error:', message);
  console.error('Source:', source);
  console.error('Line Number:', lineno);
  console.error('Column Number:', colno);
  console.error('Error Object:', error);

  // Return true to prevent the default browser error handling
  return true;
};

让我们分解window.onerror回调函数的参数:

  • message: 1个包含错误信息的字符串
  • source: 1个代表错误发生的脚本URL的字符串
  • lineno: 1个表明发生错误位置的行数
  • colno: 1个表明发生错误位置的列数
  • error: 1个包含有关错误附加信息的错误对象

window.onerror处理程序中,您可以执行各种操作,例如将错误详细信息记录到服务器、显示用户友好的错误消息或执行其他清理。return true;语句用于阻止浏览器默认的错误处理,允许你以自定义的方式去处理错误。

以下是使用window.onerror将错误记录到远程服务器的示例:

js 复制代码
window.onerror = function (message, source, lineno, colno, error) {
  // Log the error details to a remote server
  const errorData = {
    message,
    source,
    lineno,
    colno,
    error: error ? error.stack : null,
  };

  // Send errorData to a logging service (e.g., via an HTTP request)

  // Return true to prevent the default browser error handling
  return true;
};

需要牢记的是, window.onerror 有一些限制在于它不能捕获所有类型的错误,比如说语法错误或者异步代码当中的错误。

对于综合的错误处理解决方案,可以考虑使用工具例如try...catch块,React当中的Error Boundaries组件或者专门的错误埋点服务。

2. 未处理的 Promise 拒绝:

当使用异步代码时,尤其是使用JavaScript中的promise时,通过处理错误去防止未处理的promise rejections是至关重要的。

未被处理的promise rejections发生在这个promise被rejected,但没有相应的.catch() 或者 await去处理rejection时。这会导致意外的行为和让排查应用程序当中的问题变得更有挑战性。

实施对未处理的promise rejections进行全局错误处理时,你可以使用unhandledrejection事件。它将被触发在每当有promise被 rejected,但没有关联的rejection处理程序时。

这是1个教你如何设置对未处理的 promise rejections进行全局错误处理的例子:

js 复制代码
// Setup global error handling for Unhandled Promise Rejections
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
  // Additional logging or error handling can be added here
});

// Example of a Promise that is not handled
const unhandledPromise = new Promise((resolve, reject) => {
  reject(new Error('This Promise is not handled'));
});

// Uncomment the line below to see the global error handling in action
// unhandledPromise.then(result => console.log(result));

// Example of a Promise with proper error handling
const handledPromise = new Promise((resolve, reject) => {
  reject(new Error('This Promise is handled'));
});

handledPromise
  .then(result => console.log(result))
  .catch(error => console.error('Error:', error));

在这个例子当中:

事件被注册到 process对象中。每当promise被rejected但没有对应的rejection处理程序时,这个事件会被触发。

创建了1个未被处理的promiseunhandledPromise示例。取消注释这个缺少.catch()的promise将会触发unhandledRejection事件

创建了1个正确地处理promisehandledPromise示例,它包含了 .catch()去处理任何的rejection。

当promise被rejected但却缺少处理程序,unhandledRejection事件会被触发,同时它会记录有关于这个被rejected的promise信息,比方说promise本身和rejection的原因。

这方式在你的应用当中允许你在全局去捕抓未被处理的promise rejections,让你更容易去区分和解决和异步代码有关的问题。始终为promise提供合适的错误处理,以确保应用健壮可靠。

怎么向用户传达错误

1. 用户友好的错误信息:

当错误发生时,将这个问题以容易理解的方式传达给用户是至关重要的。避免展示技术性的细节,提供1个简单和清晰的信息。

javascript 复制代码
function ErrorComponent() {
  return <div>Oops! Something went wrong. Please try again later.</div>;
}

2. Error Boundaries:

使用React的Error Boundaries组件可以捕抓到所有在子组件树下的JS错误。这允许你优雅的处理错误以及展示1个错误处理UI给用户。

js 复制代码
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, errorInfo) {
    logErrorToService(error, errorInfo);
    this.setState({ hasError: true });
  }

  render() {
    if (this.state.hasError) {
      return <ErrorComponent />;
    }
    return this.props.children;
  }
}

被包裹组件可能会丢出错误到ErrorBoundary 组件去优雅的处理错误。

jsx 复制代码
<ErrorBoundary>
  <MyComponent />
</ErrorBoundary>

如何去处理异步错误

1. Try-Catch 和 Async/Await:

使用异步代码时,将trycatch块与async/await一起使用可以帮助更有效地处理错误。

js 复制代码
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Error fetching data:', error);
    throw error; // Re-throw the error to propagate it further
  }
}

2. Promise.catch():

当处理Promise时,使用.catch() 方法允许你用简洁的方式去处理错误。

js 复制代码
fetch('https://api.example.com/data')
  .then((response) => response.json())
  .then((data) => {
    // Process the data
  })
  .catch((error) => {
    console.error('Error fetching data:', error);
    // Display a user-friendly error message
    alert('An error occurred while fetching data.');
  });

怎么为调试记录错误日志

打印错误对调试以及提高你React应用的稳定性很重要。 使用浏览器开发者工具或者外部的日志记录服务去捕抓和分析错误。

js 复制代码
function logErrorToService(error, errorInfo) {
  // Send the error to a logging service (e.g., Sentry, Loggly)
  // Include additional information like errorInfo for better debugging
  // loggingService.logError(error, errorInfo);
  console.error('Logged error:', error, errorInfo);
}

Conclusion

总的来说,在React应用程序当中,有效率的错误处理包括了预防措施,全局错误处理,用户友好的交互,以及正确的调试实践的组合。

通过理解不同的错误类型和实施合适的策略,可以提高你的React应用的可靠性和用户体验。

请记住,错误信息的简单性和将错误信息与用户进行清晰的传达对建立信任和满意度有很大的帮助。 在处理React项目中的错误时,始终优先考虑用户体验。同时确保你随时了解React的最新特性以及最佳实践去确保你应用的稳定性和健壮性。

相关推荐
zqx_716 分钟前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己33 分钟前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称1 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色1 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2342 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河2 小时前
CSS总结
前端·css
BigYe程普2 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H2 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍2 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai2 小时前
网站开发的发展(后端路由/前后端分离/前端路由)
前端