Suspense

109 阅读2分钟

Suspense

  • react自带组件
  • 使用方法:
    • fallback pending状态展示的内容
    • children 结束状态(成功或失败)展示内容
  • 规则:
    • 会接收后代(包括但不限于子孙...,后边写成子(好记))组件向上抛出的promise实例,并在执行完成(then)后进行fallback与children的切换
    • 如果有缓存情况下跳过上述步骤,直接render子组件

常见应用

配合lazy进行模块异步加载

  • 因在lazy中使用import异步导入,webpack会自动进行代码分割,可以减少首屏的加载时间和代码体积
import { Suspense, lazy } from 'react';
const Child = lazy(() => import('./commponents/Suspense/demo'))
function App() {
  return (
    <Suspense fallback={"加载"}>
      <Child />
    </Suspense>
  )
}

抽离异步

  • 利用抛出promise的特性,将异步逻辑从hook中抽离,也可以减少jsx的三元判断,更像同步写法

旧版演示

// 简易信息获取接口
const fetchUser = (id: number) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({
                name: '奥利给',
                age: id
            })
        }, 1000)
    })
}
// 实现状态切换
const Demo = () => {
    const [data, setData] = useState<any>();
    const [loading, setLoading] = useState<any>(true);
    useEffect(() => {
        fetchUser(1)
            .then((val) => {
                setData(val);
            })
            .finally(() => {
                setLoading(false)
            })
    }, []);
    return (
        <>
            {loading ? '加载中' : (
                <div>
                    <div>姓名:{data.name}</div>
                    <div>年龄:{data.age}</div>
                </div>
            )}
        </>
    )

}

使用Suspense

  • 先渲染children,然后会抛出异常,父级接收到异常,进入loading,展示fallback,then后展示子组件拿到result,父组件的then里进行loading的false,展示子组件,正常渲染
// 抛异常场景,统一封装下
const createUser = (promise: Promise<any>) => {
    let status = 'pending';
    let result: any;
    return {
        read() {
            console.log(status);
            if (status === 'pending') {
                console.log(12323);
                throw promise.then((data) => {
                    status = 'success';
                    result = data;
                }, (err) => {
                    status = 'error';
                    result = err;
                })
            } else {
                console.log(2222);
                return result;
            }
        }
    }
}
// 状态机,通过read拿数据
const UserData = createUser(fetchUser(1));
// 子组件,同步写,没三元
const User = () => {
    let data = UserData.read();
    return (
        <div>
            <div>姓名:{data.name}</div>
            <div>年龄:{data.age}</div>
        </div>
    )

}
// 父组件
const Sus = () => {
    return (
        <Suspense fallback={"加载中"}>
            <User />
        </Suspense>
    )
}
  • 受控请求样板代码,请求时,Suspense会重新进入loading,直到请求回来
const User = (props: Props) => {
    // 将在全局读取改为参数读取
    let data = props.result.read();
    return (
        <div>
            <div>姓名:{data.name}</div>
            <div>年龄:{data.age}</div>
        </div>
    )

}
const Demo = () => {
    const [result, setResult] = useState(createUser(fetchUser(1)));
    return (
        <>
            <Suspense fallback={"加载中"}>
                <User result={result} />
            </Suspense>
            <button onClick={() => {
                // 随机切换用户
                setResult(createUser(fetchUser(Math.random())))
            }}>切换用户</button>
        </>

    );
}

伪代码简单实现

  • 注意:子组件没有向上抛出promise,那么继续渲染子组件
  • 如果抛出的不是promise实例,那么也不应该Suspense处理,而是代码写出bug了(可以在Suspense外再包一层ErrorBoundary)
import React from 'react';
interface Props {
    fallback: React.ReactNode
    children: React.ReactNode
}
export default class extends React.Component<Props> {
    state = { loading: false };
    componentDidCatch(error: any) {
        // 捕获
        this.setState({ loading: true })
        if (error instanceof Promise) {
            error.then(() => {
                this.setState({ loading: false })
            })
        }
    }
    render() {
        const { loading } = this.state;
        const { children, fallback } = this.props;
        return loading ? fallback : children;
    }
}