【问题标题】:Refactoring redux class based component into a functional component with hooks. Get an error about async actions. Why?将基于 redux 类的组件重构为带有钩子的功能组件。获取有关异步操作的错误。为什么?
【发布时间】:2020-06-13 04:38:50
【问题描述】:

我正在学习一个教程,老师在课堂上使用react-redux。为了更好地理解它,我决定使用钩子。

很遗憾,我遇到了一个问题,需要您的帮助。

教程应用使用 oauth 在 gapi 中登录。为此,使用了一个按钮,根据授权状态显示,可以登录或注销。登录时,可以在弹出窗口中选择一个谷歌帐户。 但是当我点击这个按钮登录时,我得到一个错误,上面写着:

错误:动作必须是普通对象。使用自定义中间件进行异步操作。

1) 但在我的应用程序中,我使用名为 useEffect() 的钩子和 .then 来省略使用像 thunk 这样的中间件。还是我错了?

2) 我应该在我的应用程序中使用useEffect()useDispatch() 还是可以使用useSelector()(或/和useDispatch(),或/和useEffect())完成所有操作

3) 教程代码使用{ connect },相当于useSelector()。我没有使用不必要的useDispatch() 和/或useEffect()?如果是,那么我怎么能在没有调度的情况下更改按钮的文本?

这里是教程代码:

import React from 'react';
import { connect } from 'react-redux';
import { signIn, signOut } from '../actions';

class GoogleAuth extends React.Component {
  componentDidMount() {
    window.gapi.load('client:auth2', () => {
      window.gapi.client
        .init({
          clientId:
            '797401886567-9cumct9mrt3v2va409rasa7fa6fq02hh.apps.googleusercontent.com',
          scope: 'email'
        })
        .then(() => {
          this.auth = window.gapi.auth2.getAuthInstance();

          this.onAuthChange(this.auth.isSignedIn.get());
          this.auth.isSignedIn.listen(this.onAuthChange);
        });
    });
  }

  onAuthChange = isSignedIn => {
    if (isSignedIn) {
      this.props.signIn(this.auth.currentUser.get().getId());
    } else {
      this.props.signOut();
    }
  };

  onSignInClick = () => {
    this.auth.signIn();
  };

  onSignOutClick = () => {
    this.auth.signOut();
  };

  renderAuthButton() {
    if (this.props.isSignedIn === null) {
      return null;
    } else if (this.props.isSignedIn) {
      return (
        <button onClick={this.onSignOutClick} className="ui red google button">
          <i className="google icon" />
          Sign Out
        </button>
      );
    } else {
      return (
        <button onClick={this.onSignInClick} className="ui red google button">
          <i className="google icon" />
          Sign In with Google
        </button>
      );
    }
  }

  render() {
    return <div>{this.renderAuthButton()}</div>;
  }
}

const mapStateToProps = state => {
  return { isSignedIn: state.auth.isSignedIn };
};

export default connect(
  mapStateToProps,
  { signIn, signOut }
)(GoogleAuth);

还有我的代码:

import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { signIn, signOut } from "../actions";

const API_KEY = process.env.REACT_APP_API_KEY;

const GoogleAuth = () => {
    const isSignedIn = useSelector((state) => state.auth.isSignedIn);
    console.log("IsSignedIn useSelector: " + isSignedIn);
    const dispatch = useDispatch();

    useEffect(
        () => {
            const onAuthChange = () => {
                if (isSignedIn) {
                    dispatch(signIn(window.gapi.auth2.getAuthInstance().currentUser.get().getId()));
                } else {
                    dispatch(signOut());
                }
            };

            window.gapi.load("client:auth2", () => {
                window.gapi.client
                    .init({
                        clientId: API_KEY,
                        scope: "email"
                    })
                    .then(() => {
                        onAuthChange(window.gapi.auth2.getAuthInstance().isSignedIn.get());
                        console.log("isSignedIn.get(): " + window.gapi.auth2.getAuthInstance().isSignedIn.get());
                        window.gapi.auth2.getAuthInstance().isSignedIn.listen(onAuthChange);
                    });
            });
        },
        [ dispatch, isSignedIn ]
    );

    const onSignInOnClick = () => {
        dispatch(window.gapi.auth2.getAuthInstance().signIn());
    };

    const onSignOutOnClick = () => {
        dispatch(window.gapi.auth2.getAuthInstance().signOut());
    };

    const renderAuthButton = () => {
        if (isSignedIn === null) {
            return null;
        } else if (isSignedIn) {
            return (
                <button onClick={onSignOutOnClick} className="ui red google button">
                    <i className="google icon" />
                    Sign Out
                </button>
            );
        } else {
            return (
                <button onClick={onSignInOnClick} className="ui red google button">
                    <i className="google icon" />
                    Sign In with Google
                </button>
            );
        }
    };

    return <div>{renderAuthButton()}</div>;
};

export default GoogleAuth;

【问题讨论】:

    标签: redux oauth oauth-2.0 react-redux react-hooks


    【解决方案1】:

    您收到一条错误消息: 错误:操作必须是普通对象。使用自定义中间件进行异步操作。

    没错。如果没有可以处理其他类型操作的中间件,那将无法正常工作。

    1) 但在我的应用程序中,我使用名为 useEffect() 的钩子和 .then 来省略使用 thunk 等中间件。还是我错了?

    useEffect.then 与 redux 无关。你在这里错了。您正在使用的操作是 thunk(返回 function(dispatch, getState) 的函数),您肯定需要 redux-thunk

    2) 我应该在我的应用程序中使用 useEffect() 和 useDispatch() 还是可以使用 useSelector()(或/和 useDispatch(),或/和 useEffect())完成所有操作

    您需要useEffect 在组件挂载时调用第 3 方 API。 您需要useDispatch 才能获得dispatch 功能。 你需要 useSelector 来获取 redux-store 的值。

    3) 教程代码使用 {connect},它相当于 useSelector()。我没有使用不必要的 useDispatch() 和/或 useEffect() 吗?如果是,那么我怎么能在不调度的情况下更改按钮的文本?

    代码:(可以破解)

        const GoogleAuth = () => {
            const isSignedIn = useSelector((state) => state.auth.isSignedIn);
            const dispatch = useDispatch();
    
            useEffect(
                () => {
                    const onAuthChange = (isSignedInLocal) => {
                        if (isSignedInLocal) {
      dispatch(signIn(window.gapi.auth2.getAuthInstance().currentUser.get().getId()));
                        } else {
                            dispatch(signOut());
                        }
                    };
    
                    window.gapi.load("client:auth2", () => {
                        window.gapi.client
                            .init({
                                clientId: API_KEY,
                                scope: "email"
                            })
                            .then(() => {
                                onAuthChange(window.gapi.auth2.getAuthInstance().isSignedIn.get());
                                
                                window.gapi.auth2.getAuthInstance().isSignedIn.listen(onAuthChange);
                            });
                    });
                },
                [dispatch]
            );
    
            const onSignInOnClick = () => {
                dispatch(window.gapi.auth2.getAuthInstance().signIn());
            };
    
            const onSignOutOnClick = () => {
                dispatch(window.gapi.auth2.getAuthInstance().signOut());
            };
    
            const renderAuthButton = () => {
                if (isSignedIn === null) {
                    return null;
                } else if (isSignedIn) {
                    return (
                        <button onClick={onSignOutOnClick} className="ui red google button">
                            <i className="google icon" />
                            Sign Out
                        </button>
                    );
                } else {
                    return (
                        <button onClick={onSignInOnClick} className="ui red google button">
                            <i className="google icon" />
                            Sign In with Google
                        </button>
                    );
                }
            };
    
            return <div>{renderAuthButton()}</div>;
        };
    
        export default GoogleAuth;

    确保您不提供 isSignedIn 作为 useEffect 的依赖项。

    【讨论】:

      【解决方案2】:

      Redux 有一些特定的用例和缺点,但也引入了一些缺点,例如大量样板。不知道为什么需要 redux 可能表明你不需要 redux。

      但是: useState 和 useEffect 是两个基本的 React 原语。 [state, setState] = useState(initialState) 允许您使用状态并设置它; useEffect(callback, [deps]) 会在依赖项之一发生更改时触发回调。 您可能不需要其他任何东西。

      Redux 使用不同的方法:

      1. 它为整个应用程序使用单一的集中状态(您可以使用 useSelector() 挂钩进入任何组件
      2. 它允许您通过普通的 javascript 对象(如 { type: 'SIGN_IN', payload: { token: '42' } })对其进行更改,并为每个对象定义状态应如何更改(定义所谓的“reducer”函数)。您可以通过 dispatch() 函数调度这些状态更改。

      那么效果呢? 因为 dispatch 只接受一个对象作为参数(因为业务逻辑是在别处定义的),所以传递给它一个函数或一个 Promise 是没有意义的。 但您可以通过多种方式触发它们:

      1. 直接在回调中,例如&lt;button onClick={() =&gt; auth.signIn()}&gt; Sign in &lt;/button&gt;。这绝对没问题。
      2. 在 useEffect 挂钩中,如果您需要触发效果作为状态更改的反应。例如:useEffect( () =&gt; console.log(`count changed, new value is: ${count}`), [count])
      3. 通过 redux 中间件

      选项 1 和 2 非常好,但缺点是您必须在本地定义业务逻辑,而不是将其与视图解耦。 在不同的地方定义它可以让您更轻松地扩展和维护应用程序。

      自定义中间件增强了dispatch() 的工作方式。 最简单的方法之一是redux-thunk。 它允许您将回调传递给调度:

      // somewhere in the codebase:
      function signIn(){
        window.gapi.auth2.getAuthInstance().signOut()
      }
      
      ...
      
      // Inside you button:
      <button onClick={() => dispatch(signIn())}> Sign Out </button>
      

      还有更复杂的效果模型,但除非你需要更强大的东西,否则你可以坚持使用 redux-thunk。

      请记住,这些框架中的每一个都只是一个具有特定权衡的工具,除非您真的有使用它们的理由,否则您可以坚持使用出色的 React 原语,而不是过度设计您的没有理由的应用程序。

      【讨论】:

        猜你喜欢
        • 2019-08-08
        • 2020-09-26
        • 2019-07-11
        • 2020-05-22
        • 2019-12-29
        • 2020-10-15
        • 1970-01-01
        • 2018-12-05
        • 2020-05-18
        相关资源
        最近更新 更多