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

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

异常边界 ErrorBoundary

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

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

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

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

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

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
      return this.props.fallback || <h1>Something went wrong.</h1>;
    }
    // 正常渲染子组件
    return this.props.children;
  }
}

export default ErrorBoundary;

使用时:

function FaultyComponent() {
    const num = 10
    // number 类型没有 concat 方法,这里调用会报错
    return <>{num.concat()}</>
}

function OtherComponent() {
    throw new Error("This component always crashes!");
}

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

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

function handleClick() {
    try {
        throw new Error("Event error!");
    } catch (error) {
        // 这种写法也是很有必要的
        console.error(error);
    }
}

Suspense 与 请求前置

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

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

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>
    );
}

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

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

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

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

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

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
      } else if (status === "error") {
        throw result; // 抛出错误,被 ErrorBoundary 捕获
      } else {
        return result; // 返回结果
      }
    },
  };
}

const resource = wrapPromise(fetchData());

function Dashboard() {
      const data = resource.read();
      return <div>dashboard: {data}</div>;
}

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

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

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

function Dashboard() {
    let data = null;
    useEffect(() => {
       data = resource.read();
    }. [])
    return <div>dashboard: {data}</div>;
}

function Dashboard() {
    let data = null;
    const getData = () => {
       data = resource.read();
    }
    return <div>dashboard: {data} <button onClick={getData}>请求</button></div>;
}

最后

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

你也可以关注我的工种号:码界交流圈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值