将基于 redux class 的组件重构为带有钩子的功能组件。获取有关异步操作的错误。为什么?
Refactoring redux class based component into a functional component with hooks. Get an error about async actions. Why?
我正在学习教程,其中老师使用 react-redux
和 类。为了更好地理解它,我决定使用钩子。
不幸的是,我遇到了一个问题,需要你的帮助。
教程应用使用oauth 登录gapi。为此使用了一个按钮,根据显示的授权状态,可以登录或注销。登录时,可以在弹出窗口中选择 window 一个 google 帐户。
但是当我点击这个按钮登录时,我得到一个错误,上面写着:
错误:操作必须是普通对象。使用自定义中间件进行异步操作。
1) 但在我的应用程序中,我使用名为 useEffect()
的挂钩和 .then 来省略 thunk
等中间件的使用。还是我错了?
2) 我应该在我的应用程序中使用 useEffect()
和 useDispatch()
还是可以使用 useSelector()
(or/and useDispatch()
, or/and useEffect()
)
3) 教程代码使用 { connect }
,相当于 useSelector()
。我不是在用不必要的useDispatch()
and/oruseEffect()
吗?如果是,那么我如何在不发送的情况下更改按钮的文本?
教程代码如下:
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 有一些特定的用例和缺点,但也引入了一些缺点,比如很多样板文件。不知道为什么需要 redux 可能表明你不需要 redux。
但是:
useState 和 useEffect 是两个基本的 React 原语。
[state, setState] = useState(initialState)
允许您使用状态并设置它;
useEffect(callback, [deps])
每当其中一个依赖项发生变化时都会触发回调。
您可能不需要其他任何东西。
Redux 使用不同的方法:
- 它为整个应用程序使用单一的集中式状态(您可以使用
useSelector()
挂钩 进入任何组件
- 它允许您通过普通 javascript 对象(如
{ type: 'SIGN_IN', payload: { token: '42' } }
)更改它,并为这些对象中的每一个定义状态应如何更改(定义所谓的 "reducer" 函数)。您通过 dispatch()
函数发送这些状态变化。
那效果呢?
由于 dispatch 只接受一个对象作为参数(因为业务逻辑是在别处定义的),所以向它传递一个函数或一个 promise 是没有意义的。
但您可以通过多种方式触发它们:
- 直接在回调中,例如
<button onClick={() => auth.signIn()}> Sign in </button>
。那绝对没问题。
- 在 useEffect 钩子中,如果你需要触发效果作为状态变化的反应。例如:
useEffect( () => console.log(`count changed, new value is: ${count}`), [count])
- 通过 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 原语,而不是无缘无故地过度设计您的应用程序。
您收到一条错误消息:
错误:操作必须是普通对象。使用自定义中间件进行异步操作。
没错。如果没有可以处理另一种类型的操作的中间件,那是行不通的。
1) 但是在我的应用程序中,我使用名为 useEffect() 的钩子和 .then 来省略像 thunk 这样的中间件的使用。还是我错了?
useEffect
和 .then
与 redux 无关。你在这里错了。您正在使用的操作是 thunk(return function(dispatch, getState) 的函数)并且您肯定需要 redux-thunk
2) 我应该在我的应用程序中使用 useEffect() 和 useDispatch() 还是可以使用 useSelector() 完成所有操作(or/and useDispatch(), or/and useEffect())
您需要 useEffect
在组件安装时调用第三方 API。
您需要 useDispatch
才能获得 dispatch
功能。
您需要 useSelector
才能从 redux-store 获取值。
3) 教程代码使用了 { connect },它等同于 useSelector()。我没有使用不必要的 useDispatch() and/or 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
的依赖项。
我正在学习教程,其中老师使用 react-redux
和 类。为了更好地理解它,我决定使用钩子。
不幸的是,我遇到了一个问题,需要你的帮助。
教程应用使用oauth 登录gapi。为此使用了一个按钮,根据显示的授权状态,可以登录或注销。登录时,可以在弹出窗口中选择 window 一个 google 帐户。 但是当我点击这个按钮登录时,我得到一个错误,上面写着:
错误:操作必须是普通对象。使用自定义中间件进行异步操作。
1) 但在我的应用程序中,我使用名为 useEffect()
的挂钩和 .then 来省略 thunk
等中间件的使用。还是我错了?
2) 我应该在我的应用程序中使用 useEffect()
和 useDispatch()
还是可以使用 useSelector()
(or/and useDispatch()
, or/and useEffect()
)
3) 教程代码使用 { connect }
,相当于 useSelector()
。我不是在用不必要的useDispatch()
and/oruseEffect()
吗?如果是,那么我如何在不发送的情况下更改按钮的文本?
教程代码如下:
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 有一些特定的用例和缺点,但也引入了一些缺点,比如很多样板文件。不知道为什么需要 redux 可能表明你不需要 redux。
但是:
useState 和 useEffect 是两个基本的 React 原语。
[state, setState] = useState(initialState)
允许您使用状态并设置它;
useEffect(callback, [deps])
每当其中一个依赖项发生变化时都会触发回调。
您可能不需要其他任何东西。
Redux 使用不同的方法:
- 它为整个应用程序使用单一的集中式状态(您可以使用
useSelector()
挂钩 进入任何组件
- 它允许您通过普通 javascript 对象(如
{ type: 'SIGN_IN', payload: { token: '42' } }
)更改它,并为这些对象中的每一个定义状态应如何更改(定义所谓的 "reducer" 函数)。您通过dispatch()
函数发送这些状态变化。
那效果呢? 由于 dispatch 只接受一个对象作为参数(因为业务逻辑是在别处定义的),所以向它传递一个函数或一个 promise 是没有意义的。 但您可以通过多种方式触发它们:
- 直接在回调中,例如
<button onClick={() => auth.signIn()}> Sign in </button>
。那绝对没问题。 - 在 useEffect 钩子中,如果你需要触发效果作为状态变化的反应。例如:
useEffect( () => console.log(`count changed, new value is: ${count}`), [count])
- 通过 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 原语,而不是无缘无故地过度设计您的应用程序。
您收到一条错误消息: 错误:操作必须是普通对象。使用自定义中间件进行异步操作。
没错。如果没有可以处理另一种类型的操作的中间件,那是行不通的。
1) 但是在我的应用程序中,我使用名为 useEffect() 的钩子和 .then 来省略像 thunk 这样的中间件的使用。还是我错了?
useEffect
和 .then
与 redux 无关。你在这里错了。您正在使用的操作是 thunk(return function(dispatch, getState) 的函数)并且您肯定需要 redux-thunk
2) 我应该在我的应用程序中使用 useEffect() 和 useDispatch() 还是可以使用 useSelector() 完成所有操作(or/and useDispatch(), or/and useEffect())
您需要 useEffect
在组件安装时调用第三方 API。
您需要 useDispatch
才能获得 dispatch
功能。
您需要 useSelector
才能从 redux-store 获取值。
3) 教程代码使用了 { connect },它等同于 useSelector()。我没有使用不必要的 useDispatch() and/or 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
的依赖项。