Это 7-й день моего участия в Gengwen Challenge.Подробности о мероприятии:Обновить вызов
КомпаньонReact, расширенная глава для элегантного захвата исключений, включая решения Hooks.
предисловие
Никто не идеален, поэтому код всегда будет ошибаться, ошибки не страшны, главное как с ними справляться.
Я просто хочу спросить вас, как отловить ошибки реагирующего приложения? В настоящее время:
- Xiaobai+++: Как с этим бороться?
- Сяобай++: ErrorBoundary
- Xiaobai+: ErrorBoundary, попробуйте поймать
- Xiaohe#: ErrorBoundary, попробуйте поймать, window.onerror
- 小黑##: Это серьезный вопрос, я знаю N способов справиться с ним, какое у вас есть лучшее решение?
ErrorBoundary
EerrorBoundary вышел в 16 версии.Кто-то спрашивал про 15 версию.Я ее не слушаю.Все равно пользуюсь 16.Конечно,в 15 она есть.unstable_handleError
.
Введение на официальном сайте ErrorBoundary более подробное, дело не в этом, дело в том, какие исключения он может ловить.
- Отрисовка дочерних компонентов
- функция жизненного цикла
- Конструктор
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true });
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
Мир с открытым исходным кодом хорош, его уже упаковал великий богreact-error-boundaryТакая отличная библиотека.
Вам нужно заботиться только о том, о чем вам нужно заботиться после возникновения ошибки, и прийти сноваReset
, Идеально.
import {ErrorBoundary} from 'react-error-boundary'
function ErrorFallback({error, resetErrorBoundary}) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)
}
const ui = (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
// reset the state of your app so the error doesn't happen again
}}
>
<ComponentThatMayError />
</ErrorBoundary>
)
К сожалению, границы ошибок не улавливают эти ошибки:
- обработчик события
- Асинхронный код (например, обратные вызовы setTimeout или requestAnimationFrame)
- Код рендеринга на стороне сервера
- сами границы ошибки выдают ошибки
Оригинальный текст можно найти на официальном сайтеintroducing-error-boundaries
Цель этой статьи — отлов ошибок в обработчиках событий.
У чиновника тоже есть планhow-about-event-handlers, который пытается поймать.
Но, столько обработчиков событий, о боже, сколько писать. . . . . . . . . . . . . . . . . . . .
handleClick() {
try {
// Do something that could throw
} catch (error) {
this.setState({ error });
}
}
За границей ошибки
Давайте сначала посмотрим на таблицу, в которой перечислены средства и объем исключений, которые мы можем перехватывать.
тип исключения | Синхронный метод | асинхронный метод | загрузка ресурсов | Promise | async/await |
---|---|---|---|---|---|
try/catch | √ | √ | |||
window.onerror | √ | √ | |||
error | √ | √ | √ | ||
unhandledrejection | √ | √ |
try/catch
Может перехватывать синхронные и асинхронные/ожидающие исключения.
window.onerror , событие ошибки
window.addEventListener('error', this.onError, true);
window.onerror = this.onError
window.addEventListener('error')
Это можно сравнить сwindow.onerror
Множественные исключения регистрации ресурсов.
Обратите внимание, что последний параметрtrue
, false
Это может быть не так хорошо, как вы ожидаете.
Конечно, если у вас есть вопросы по поводу значения третьего параметра, мне до вас нет дела. до свидания.
unhandledrejection
Обратите внимание, что последний параметрtrue
.
window.removeEventListener('unhandledrejection', this.onReject, true)
Он перехватывает исключение неперехваченного промиса.
XMLHttpRequest и выборка
XMLHttpRequest
С ним очень легко обращаться, и у него есть собственное событие onerror.
Конечно, вы не будете на 99,99% опираться на собственныеXMLHttpRequest
упаковать библиотеку,axios
Действительно ароматный, есть этот полный механизм обработки ошибок.
Что касаетсяfetch
, Беги с уловом сам, если не справишься, то это твоя проблема.
Так много, так тяжело.
К счастью, на самом деле есть библиотекаreact-error-catchЭто компонент, инкапсулированный на основе ErrorBoudary, error и unhandledrejection.
Суть его в следующем
ErrorBoundary.prototype.componentDidMount = function () {
// event catch
window.addEventListener('error', this.catchError, true);
// async code
window.addEventListener('unhandledrejection', this.catchRejectEvent, true);
};
использовать:
import ErrorCatch from 'react-error-catch'
const App = () => {
return (
<ErrorCatch
app="react-catch"
user="cxyuns"
delay={5000}
max={1}
filters={[]}
onCatch={(errors) => {
console.log('报错咯');
// 上报异常信息到后端,动态创建标签方式
new Image().src = `http://localhost:3000/log/report?info=${JSON.stringify(errors)}`
}}
>
<Main />
</ErrorCatch>)
}
export default
Хлопать, хлопать.
На самом деле это не так: самая важная ошибка, которую фиксирует ошибка, — это предоставление информации о стеке ошибок, что довольно недружественно для анализа ошибок, особенно после упаковки.
Так много ошибок, что я сначала позабочусь об обработчиках событий в React.
В остальном продолжение следует.
Перехват исключения обработчиком событий
Пример
Принцип моей идеи очень прост, используйтеdecoratorпереопределить исходный метод.
Первый взгляд на использование:
@methodCatch({ message: "创建订单失败", toast: true, report:true, log:true })
async createOrder() {
const data = {...};
const res = await createOrder();
if (!res || res.errCode !== 0) {
return Toast.error("创建订单失败");
}
.......
其他可能产生异常的代码
.......
Toast.success("创建订单成功");
}
Обратите внимание на четыре параметра:
- сообщение: При возникновении ошибки сообщение об ошибке печатается
- тост: произошла ошибка, будь то тост
- отчет: если есть ошибка, сообщите об этом
- log: используйте console.error для печати
Может быть, вы сказали, что это, это, новости мертвы, необоснованны. Если бы у меня были другие новости.
В этот момент я слегка улыбнулся, не волнуйтесь, давайте посмотрим на кусок кода
@methodCatch({ message: "创建订单失败", toast: true, report:true, log:true })
async createOrder() {
const data = {...};
const res = await createOrder();
if (!res || res.errCode !== 0) {
return Toast.error("创建订单失败");
}
.......
其他可能产生异常的代码
.......
throw new CatchError("创建订单失败了,请联系管理员", {
toast: true,
report: true,
log: false
})
Toast.success("创建订单成功");
}
Да-да, это можно сделать, закинув кастомыCatchError
чтобы переопределить предыдущие параметры по умолчанию.
этоmethodCatch
Ошибки, которые можно поймать, синхронные и асинхронные, давайте посмотрим на полный код.
определение типа
export interface CatchOptions {
report?: boolean;
message?: string;
log?: boolean;
toast?: boolean;
}
// 这里写到 const.ts更合理
export const DEFAULT_ERROR_CATCH_OPTIONS: CatchOptions = {
report: true,
message: "未知异常",
log: true,
toast: false
}
пользовательский CatchError
import { CatchOptions, DEFAULT_ERROR_CATCH_OPTIONS } from "@typess/errorCatch";
export class CatchError extends Error {
public __type__ = "__CATCH_ERROR__";
/**
* 捕捉到的错误
* @param message 消息
* @options 其他参数
*/
constructor(message: string, public options: CatchOptions = DEFAULT_ERROR_CATCH_OPTIONS) {
super(message);
}
}
декоратор
import Toast from "@components/Toast";
import { CatchOptions, DEFAULT_ERROR_CATCH_OPTIONS } from "@typess/errorCatch";
import { CatchError } from "@util/error/CatchError";
const W_TYPES = ["string", "object"];
export function methodCatch(options: string | CatchOptions = DEFAULT_ERROR_CATCH_OPTIONS) {
const type = typeof options;
let opt: CatchOptions;
if (options == null || !W_TYPES.includes(type)) { // null 或者 不是字符串或者对象
opt = DEFAULT_ERROR_CATCH_OPTIONS;
} else if (typeof options === "string") { // 字符串
opt = {
...DEFAULT_ERROR_CATCH_OPTIONS,
message: options || DEFAULT_ERROR_CATCH_OPTIONS.message,
}
} else { // 有效的对象
opt = { ...DEFAULT_ERROR_CATCH_OPTIONS, ...options }
}
return function (_target: any, _name: string, descriptor: PropertyDescriptor): any {
const oldFn = descriptor.value;
Object.defineProperty(descriptor, "value", {
get() {
async function proxy(...args: any[]) {
try {
const res = await oldFn.apply(this, args);
return res;
} catch (err) {
// if (err instanceof CatchError) {
if(err.__type__ == "__CATCH_ERROR__"){
err = err as CatchError;
const mOpt = { ...opt, ...(err.options || {}) };
if (mOpt.log) {
console.error("asyncMethodCatch:", mOpt.message || err.message , err);
}
if (mOpt.report) {
// TODO::
}
if (mOpt.toast) {
Toast.error(mOpt.message);
}
} else {
const message = err.message || opt.message;
console.error("asyncMethodCatch:", message, err);
if (opt.toast) {
Toast.error(message);
}
}
}
}
proxy._bound = true;
return proxy;
}
})
return descriptor;
}
}
в заключении
- Используйте декоратор, чтобы переписать исходный метод для обнаружения ошибок.
- Настраивая класс ошибки и выбрасывая его, вы можете добиться переопределения параметров по умолчанию. Добавлена гибкость.
@methodCatch({ message: "创建订单失败", toast: true, report:true, log:true })
async createOrder() {
const data = {...};
const res = await createOrder();
if (!res || res.errCode !== 0) {
return Toast.error("创建订单失败");
}
Toast.success("创建订单成功");
.......
其他可能产生异常的代码
.......
throw new CatchError("创建订单失败了,请联系管理员", {
toast: true,
report: true,
log: false
})
}
Следующий шаг
Какой следующий шаг, сделай шаг и увидишь шаг.
Нет, дорога впереди еще очень длинная. Это базовая версия.
- Расширенные результаты для поддержки большего количества типов и версий хуков.
@XXXCatch
classs AAA{
@YYYCatch
method = ()=> {
}
}
- абстрактный, абстрактный, абстрактный
Шутка окончена, давайте серьезно:
Проблемы с текущей программой:
- Функциональные ограничения
- Абстракции недостаточно
Параметры получения, прокси-функции и функции обработки ошибок могут быть полностью разделены и стать общими методами. - Синхронные методы преобразуются в асинхронные методы.
Так что теоретически следует различать синхронные и асинхронные схемы. - Что делать, если функция обработки ошибок снова работает неправильно?
После этого мы продолжим развиваться вокруг этих вопросов.
Версия крючков
Некоторые землекопы сказали, что в этом возрасте, кто еще не использует Крючки.
Да, воротилы правы, надо идти в ногу со временем.
Базовая версия Hooks уже доступна, пожалуйста, сначала поделитесь ею и используйте ее, а последующие статьи последуют.
Имя крючка - useCatch
const TestView: React.FC<Props> = function (props) {
const [count, setCount] = useState(0);
const doSomething = useCatch(async function(){
console.log("doSomething: begin");
throw new CatchError("doSomething error")
console.log("doSomething: end");
}, [], {
toast: true
})
const onClick = useCatch(async (ev) => {
console.log(ev.target);
setCount(count + 1);
doSomething();
const d = delay(3000, () => {
setCount(count => count + 1);
console.log()
});
console.log("delay begin:", Date.now())
await d.run();
console.log("delay end:", Date.now())
console.log("TestView", this)
throw new CatchError("自定义的异常,你知道不")
},
[count],
{
message: "I am so sorry",
toast: true
});
return <div>
<div><button onClick={onClick}>点我</button></div>
<div>{count}</div>
</div>
}
export default React.memo(TestView);
Что касается идей, основанных наuseMemo
, вы можете сначала посмотреть на код:
export function useCatch<T extends (...args: any[]) => any>(callback: T, deps: DependencyList, options: CatchOptions =DEFAULT_ERRPR_CATCH_OPTIONS): T {
const opt = useMemo( ()=> getOptions(options), [options]);
const fn = useMemo((..._args: any[]) => {
const proxy = observerHandler(callback, undefined, function (error: Error) {
commonErrorHandler(error, opt)
});
return proxy;
}, [callback, deps, opt]) as T;
return fn;
}
напиши в конце
Писать нелегко, если вы думаете, что это хорошо, лайк и комментарий — моя самая большая мотивация.
error-boundaries
Реагировать на обработку исключений
catching-react-errors
Расширенный механизм обработки исключений React — границы ошибок
decorator
core-decorators
autobind.js