需要

家喻户晓,利用如果少了loading,交互就显得生硬。

本文分享如何在React中从零到一实现并应用loading

实现

一个loading,应该始终呈现在视口的正中

同时为了示意加载过程的动态性,须要适当的动画

以及,胜利和失败的提醒与loading其实实质上是一回事,所以,实现loading的同时,也顺便将另外两个一并实现。

严格意义上,这是toast的实现。

Toast.jsx

import l from './toast.module.css'import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'import {faCheck, faSpinner, faTriangleExclamation} from '@fortawesome/pro-solid-svg-icons'import PropTypes from 'prop-types'import {memo} from 'react';function Toast({type, message}) {    return (        <div className={l.toast} >            <div className={l.container} >                {                    type === 'success'                        ? <FontAwesomeIcon icon={faCheck} />                        : type === 'fail'                            ? <FontAwesomeIcon icon={faTriangleExclamation} />                            : <FontAwesomeIcon icon={faSpinner} className={l.loading} />                }            </div>            {                message                    ? message                    : type === 'success'                        ? '实现'                        : type === 'fail'                            ? '失败'                            : '加载中'            }        </div>    )}Toast.propTypes = {    type: PropTypes.oneOf(['loading', 'success', 'fail']),    message: PropTypes.string}export default memo(Toast)

toast.module.css

:root {    --toastSize: 136px;    --containerSize: 40px;}.toast {    width: var(--toastSize);    height: var(--toastSize);    position: fixed;    top: calc(50vh - var(--toastSize)/2);    left: calc(50vw - var(--toastSize)/2);    background: #4C4C4C;    border-radius: 12px;    display: flex;    flex-direction: column;    align-items: center;    justify-content: center;    color: rgba(255, 255, 255, .9);}.container {    width: var(--containerSize);    display: inherit;    flex-direction: inherit;    align-items: inherit;    margin: 0 auto 16px;}.container > svg {    height: var(--containerSize);}.loading {    animation: rotate linear 1s infinite;}@keyframes rotate {    from {        transform: rotate(0);    }    to {        transform: rotate(360deg);    }}

应用

路由

loading的首要利用场景就是页面间的切换。

应用lazy()Suspense配合React Router实现页面间切换时新页面加载loading的显隐:

// App.jsximport React, {Suspense, lazy} from 'react'import {BrowserRouter, Routes, Route} from 'react-router-dom'const Home = lazy(() => import('./routes/Home'))const About = lazy(() => import('./routes/About'))const App = () => (    <Router>        <Suspense fallback={<Toast/>}>            <Routes>                <Route path="/" element={<Home />} />                <Route path="/about" element={<About />} />            </Routes>        </Suspense>    </Router>);

不过,目前这个组合有一个致命毛病

即新组件尚未实现加载时,旧组件曾经暗藏,且不论新组件加载得多快,Suspensefallback都会执行,这将导致页面切换时呈现闪动

尽管官网推出了startTransition()解决闪动问题,但该计划目前尚未实用于路由。

所以这个组合实际中不举荐应用,因为以页面闪动为代价实现loading得失相当。

过渡

React新增的useTranstion() Hook,搭配useState(),实现动静组件加载时loading的显隐。

import {useEffect, useState, useTransition} from 'react'import {url} from '../../configuration'import Toast from '../../../components/toast/Toast'import Table from '../../../components/table/Table'export default function User() {    const [res, setRes] = useState({})    const [loading, startTransition] = useTransition() // loading示意过渡工作的期待状态    useEffect(() => {        fetch(`${url}`, {method: 'GET'})            .then(r => r.json())            .then(d => {                if (d.hasOwnProperty('err'))                    alert(`${d.text}:${d.err}`)                else if (                    d.hasOwnProperty('data')                )                    startTransition(() => {setRes(d)}) // 将setRes()标记为过渡工作            })            .catch(e => {alert(e)})    }, [])    return (        <div>            {loading && <Toast/>}            <Table                res={res}            />        </div>    )}

useTransition()简直能笼罩loading的绝大部分场景,因为在React中,组件的更新根本都是通过在useEffect()的依赖数组中增加state来实现。

总结

本着官网反对的绝不本人再封装的准则,loading的需要算是根本实现了,后续开发中若有新播种再来同步。

若有有余,欢送斧正。