前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >简洁明了!你的 React 代码一定要用 throw 关键字的原因找到了,感受React设计哲学的魅力

简洁明了!你的 React 代码一定要用 throw 关键字的原因找到了,感受React设计哲学的魅力

作者头像
萌萌哒草头将军
发布2025-02-19 11:10:28
发布2025-02-19 11:10:28
5000
代码可运行
举报
文章被收录于专栏:前端框架前端框架
运行总次数:0
代码可运行

开始之前,我先问大家一个问题,你写 React代码时,是否使用过 throw 关键字?

异常边界 ErrorBoundary

大家好,今天我们来聊聊为啥我们写 React 代码时一定要使用 throw 关键字。

首先想到的是,React 中存在异常边界的概念。

异常边界是指一种用于捕获子组件树中运行时错误的机制。当程序发生异常时,不会显示白屏,而是在局部显示异常错误提示。

注意,是运行时阶段,也就是 render 阶段,副作用中的异常还是需要手动捕获的。

它的实现原理是使用类组件的生命周期方法:static getDerivedStateFromError(error)

代码语言:javascript
代码运行次数:0
复制
import React from"react";

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

// 更新状态以渲染备用 UI
static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      // 渲染备用 UI
      returnthis.props.fallback || <h1>Something went wrong.</h1>;
    }
    // 正常渲染子组件
    returnthis.props.children;
  }
}

exportdefault ErrorBoundary;

使用时:

代码语言:javascript
代码运行次数:0
复制
function FaultyComponent() {
    const num = 10
    // number 类型没有 concat 方法,这里调用会报错
    return<>{num.concat()}</>
}

function OtherComponent() {
    thrownewError("This component always crashes!");
}

function App() {
return (
      <Layout>
          <Header>Header</Header>
          <Content>
              <ErrorBoundary>
                  <FaultyComponent />
                  <OtherComponent />
              </ErrorBoundary>
          </Content>
      </Layout>
  );
}

下面的异常无法捕获,因为是事件处理程序中的异常。

代码语言:javascript
代码运行次数:0
复制
function handleClick() {
    try {
        throw new Error("Event error!");
    } catch (error) {
        // 这种写法也是很有必要的
        console.error(error);
    }
}

Suspense请求前置

还能想到的一种情况,就是当我们第一次加载页面并发送请求时。相信大家一定遇到过这种情况,加载一个页面,首先看到的是页面加载种,然后会出现一个空表格,也是加载种,这种用户体验,我愿称之为糟糕。

罪魁祸首是对 Suspense 的理解不到位。写出了如下的代码。

代码语言:javascript
代码运行次数:0
复制
function Dashboard() {
    const {data, loading, error} = useRequest(getDashBoardData)
    if (loading) {
        return  <div>dashboard laoding ....</div>
    }
    return <div>dashboard: {data}</div>
}
function App() {
    return (
        <ErrorBoundary fallback={<div>Custom error fallback!</div>}>
            <Suspense fallback={<div>Loading...</div>}>
                <Switch>
                    <Route path={'/'} exact><Redirect to={'/dashboard'}></Redirect></Route>
                    <Route component={Dashboard} path={`/dashboard`} exact></Route>
                    <Route component={Other} path={`/other`} exact></Route>
                </Switch>
            </Suspense>
        </ErrorBoundary>
    );
}

这种代码的逻辑主要是先加载页面,然后加载数据。

代码语言:javascript
代码运行次数:0
复制
graph LR
请求页面资源 --> 加载数据 --> 显示结果

实际上我们可以借助 Suspense 的让请求资源和加载数据同时进行。

代码语言:javascript
代码运行次数:0
复制
graph LR
请求页面资源 --> 显示结果
加载数据 --> 显示结果

Suspense 组件是 React 提供的一种处理异步状态的机制。它可以捕获被包裹组件中的异步行为。并展示为预定的加载中状态,例如:import 引入的组件加载时,或者组件中的数据处于异步中时。

这里还需要注意,这种组件中的异步是需要子组件在渲染(render)阶阶段抛出一个Promise

当我们发送请求时,Promise 处于加载中状态,Suspense 可以识别到加载状态从而显示加载页面。而当Promise处于非加载状态时,则显示结果页面。

代码语言:javascript
代码运行次数:0
复制
function wrapPromise(promise) {
let status = "pending";
let result;

const suspender = promise.then(
    (res) => {
      status = "success";
      result = res;
    },
    (err) => {
      status = "error";
      result = err;
    }
  );

return {
    read() {
      if (status === "pending") {
        throw suspender; // 抛出 Promise
      } elseif (status === "error") {
        throw result; // 抛出错误,被 ErrorBoundary 捕获
      } else {
        return result; // 返回结果
      }
    },
  };
}

const resource = wrapPromise(fetchData());
代码语言:javascript
代码运行次数:0
复制
function Dashboard() {
      const data = resource.read();
      return <div>dashboard: {data}</div>;
}

这么写有很多好处,首先是请求前置了,可以减少首屏加载的时间。

其次,可以将 UI 和业务抽离。不管后续业务逻辑怎么更迭,UI 都无需更改。甚至可以原封不动的运行在WebReact NativeSSR。这或许就是React设计哲学的魅力之处了!

但是需要注意的是,如果将请求放入交互或者副作用函数中,则无法捕获异步状态了,因为此时已经不是 render 阶段了

代码语言:javascript
代码运行次数:0
复制
function Dashboard() {
    let data = null;
    useEffect(() => {
       data = resource.read();
    }. [])
    return <div>dashboard: {data}</div>;
}
代码语言:javascript
代码运行次数:0
复制
function Dashboard() {
    let data = null;
    const getData = () => {
       data = resource.read();
    }
    return <div>dashboard: {data} <button onClick={getData}>请求</button></div>;
}

最后

好啦,今天的分享就这么多了,希望可以帮助到你,如果文章有错误的地方欢迎指正!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-12-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 萌萌哒草头将军 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 异常边界 ErrorBoundary
  • Suspense 与 请求前置
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档