如何在每个 Relay.js 网络请求上添加加载指示器

How to add a loading indicator on every Relay.js network request

我目前正在构建一个 Relay/React 网络应用程序。除了一个例外,它非常简单。当我的任何组件发出网络请求时,我无法弄清楚如何获得全局通知。当有网络 activity 时,我希望在我的应用程序顶部添加一个微调器,因为我的一些突变需要很长时间才能加载。

这是我尝试解决此问题的尝试,但它仅适用于新路由页面加载。

function renderer(info)
{
    let {props, error, element} = info;
    if (error) {
        return (<ServerError errors={error}/>);
    } else {
        if (props) {
            return React.cloneElement(element, props);
        } else {
            return (<Loading />);
        }
    }
}


ReactDOM.render(
    <Router
        history={browserHistory}
        render={applyRouterMiddleware(useRelay)}
        environment={Relay.Store}
    >
        <Route
            path="/"
            queries={ViewerQuery}
            component={App}
        >
            <IndexRoute
                queries={ViewerQuery}
                component={Libraries}
                render={renderer}
            />
        <Route path="*" component={Error}/>

    </Route>
</Router>

理想情况下,我可以获得一些回调,我可以将其传递给我的 App 组件,该组件呈现我所有的页面页眉和页脚。对此的任何帮助将不胜感激。我已经在互联网上搜索了一段时间,试图找到一个好的解决方案。

您可以在 React 中创建自定义微调器组件,并根据您的数据加载状态显示或隐藏微调器。

一个例子可以是 -

您的 Spinner 组件可能是这样的 -

    let SpinMe
    = (
        <div className="spinner-container">
            <div className="loader">
                <svg className="circular">
                    <circle className     = "path"
                        cx                = "50"
                        cy                = "50"
                        r                 = "20"
                        fill              = "none"
                        strokeWidth      = "3"
                        strokeMiterLimit = "10"
                    />
                </svg>
            </div>
        </div>
    );

此微调器组件应比其他组件 z-index 稍高,以便在加载时用户无法单击其他组件或与其他组件交互。

还在样式中显示微调器的一些透明深色背景。

例如

.spinner-container {

    position         : absolute;
    background-color : rgba(12, 12, 12, 0.61);
    width            : 100%;
    min-height       : 100%;
    background-size  : 100% 100%;
    text-align       : center;
    z-index          : 3;
    top              : 0;
    left             : 0;
}

现在您要使用微调器的另一个组件,并在此组件中发出网络请求。

import React from 'react';

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

  sendNetworkRequest(URL, dataToSend) {
      return $.ajax({
            type        : 'POST',
            url         : URL,
            data        : JSON.stringify(dataToSend),
            dataType    : 'json'
            });
  }

  componentDidMount() {
        const URL = "test url";
        const dataToSend = { param1:"val", param2:"val" };

        this.setState({isLoading:true});
        this.sendNetworkRequest(dataToSend)
            .then(
            () => {
                // success response now remove spinner
                this.setState({isLoading:false});
            },
            () => {

                // error response again remove spinner and 
                // show error message to end user
                this.setState({isLoading:false});
            });
    }

    render() {
        return (
                <div>
                { this.state.isLoading ? <SpinMe/> : null }
                    <div>
                        <h1>
                            Remaining Component structure 
                            or Jsx
                        </h1>
                        <p>
                            To be show after loading is done.
                        </p>
                    </div>
                </div>
               );
    }
}

export default SpinTestComponent;

您也可以尝试添加自定义 Relay Network Layer 以在每次请求时呈现加载指示器组件。我认为 "global" 加载指标要考虑的主要问题不仅在于指标的设计,还在于其对全球 UI 的影响。它会阻挡 UI 吗?只是其中的一部分?它会取代其他元素 up/down 吗?等等

在此期间,您可以执行以下操作:

  handleSubmit(event) {
    event.preventDefault();
    this.setState({isLoading: true});

    this.props.relay.commitUpdate(
      new LoginMutation({
        email: this.refs.email.value,
        password: this.refs.password.value
      }), {
        onSuccess: response => {
          Auth.login(response.login.accessToken);
          const { location, router } = this.props;

          if (location.state && location.state.nextPathname) {
            router.replace(location.state.nextPathname)
          } else {
            router.replace('/')
          }
        },
        onFailure: transaction => {
          this.setState({hasError: true});
          this.setState({isLoading: false});
        }
      }
    );
  }

以上片段摘自here。您可以在该存储库中看到其余的逻辑。

我最终扩展了 Relay 的默认网络层,这样我就可以创建一些我的主要根组件监听的 Flux 事件。如果我愿意,这使我可以将所有加载和错误消息处理放在一个地方。

这是我的最终解决方案。不确定它是否是最干净的,但它工作得很好。

import Relay from "react-relay";
import RelayMutationRequest from "react-relay/lib/RelayMutationRequest";
import AppDispatcher from "./AppDispatcher";

export default class CustomNetworkLayer extends Relay.DefaultNetworkLayer {

    constructor(uri, init)
    {
        super(uri, init);
    }

    networkLoadingEvent()
    {
        AppDispatcher.dispatch({
            actionType: 'LOADING'
        });
    }

    networkDoneLoadingEvent()
    {
        AppDispatcher.dispatch({
            actionType: 'DONE_LOADING'
        });
    }

    networkErrorEvent(error)
    {
        AppDispatcher.dispatch({
            actionType: 'ERROR',
            payload: error
        });
    }

    sendQueries(requests)
    {
        this.networkLoadingEvent();
        return Promise.all(requests.map(request => (
            this._sendQuery(request).then(
                result => result.json()
            ).then(payload =>
            {
                if (payload.hasOwnProperty('errors')) {
                    const error = CustomNetworkLayer.createRequestError(request, '200', payload);
                    this.networkErrorEvent(payload.errors[0].message);
                    request.reject(error);
                } else {
                    if (!payload.hasOwnProperty('data')) {
                        const error = new Error(
                            'Server response was missing for query ' +
                            `\`${request.getDebugName()}\`.`
                        );
                        this.networkErrorEvent(error);
                        request.reject(error);
                    } else {
                        this.networkDoneLoadingEvent();
                        request.resolve({response: payload.data});
                    }
                }
            }).catch(
                error =>
                {
                    this.networkErrorEvent(error);
                    request.reject(error)
                }
            )
        )));
    }

    sendMutation(request)
    {
        this.networkLoadingEvent();
        return this._sendMutation(request).then(
            result => result.json()
        ).then(payload =>
        {
            if (payload.hasOwnProperty('errors')) {
                const error = CustomNetworkLayer.createRequestError(request, '200', payload);
                this.networkErrorEvent(payload.errors[0].message);
                request.reject(error);
            } else {
                this.networkDoneLoadingEvent();
                request.resolve({response: payload.data});
            }
        }).catch(
            error =>
            {
                this.networkErrorEvent(error);
                request.reject(error)
            }
        );
    }


    /**
     * Formats an error response from GraphQL server request.
     */
    static formatRequestErrors(request, errors)
    {
        const CONTEXT_BEFORE = 20;
        const CONTEXT_LENGTH = 60;

        const queryLines = request.getQueryString().split('\n');
        return errors.map(({locations, message}, ii) =>
        {
            const prefix = (ii + 1) + '. ';
            const indent = ' '.repeat(prefix.length);

            //custom errors thrown in graphql-server may not have locations
            const locationMessage = locations ?
                ('\n' + locations.map(({column, line}) =>
                {
                    const queryLine = queryLines[line - 1];
                    const offset = Math.min(column - 1, CONTEXT_BEFORE);
                    return [
                        queryLine.substr(column - 1 - offset, CONTEXT_LENGTH),
                        ' '.repeat(Math.max(0, offset)) + '^^^',
                    ].map(messageLine => indent + messageLine).join('\n');
                }).join('\n')) :
                '';

            return prefix + message + locationMessage;

        }).join('\n');
    }

    static createRequestError(request, responseStatus, payload)
    {
        const requestType =
            request instanceof RelayMutationRequest ? 'mutation' : 'query';
        const errorReason = typeof payload === 'object' ?
            CustomNetworkLayer.formatRequestErrors(request, payload.errors) :
            `Server response had an error status: ${responseStatus}`;
        const error = new Error(
            `Server request for ${requestType} \`${request.getDebugName()}\` ` +
            `failed for the following reasons:\n\n${errorReason}`
        );
        error.source = payload;
        error.status = responseStatus;
        return error;
    }
}

然后在我的 index.js 文件中我这样做:

Relay.injectNetworkLayer(new CustomNetworkLayer("/graphql",
    {
        fetchTimeout: 35000, // timeout after 35 seconds
        retryDelays: [2000] // retry after 2 seconds
    }));

快速说明:AppDispatcher 只是一个 flux js 调度程序,我正在我的主要包装器组件中监听这些事件。

希望这对其他人有所帮助。我当然在这上面花了太多时间。

也感谢所有帮助我得出这个解决方案的人。