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