reactjs - 如何在 React 功能组件内的嵌套函数内使用类型化选择器?
问题描述
我正在尝试使用 TypeScript 更新 React/Redux 项目中的用户凭据。我正在使用 Firebase,因此在方法.then()
块中返回了用户凭据signInWithPopup
。
这是组件。它被打破是因为它打破了钩子的规则。我找不到解决方法。我认为使用useEffect
名为的状态对象可以让我在块内的本地 ( ) 状态更新currentUser
后在功能组件的根目录中运行钩子。currentUser
.then
功能组件
import React, { useState, useEffect } from 'react';
import {
getAuth,
signInWithPopup,
GithubAuthProvider,
Auth,
} from 'firebase/auth';
import 'bulmaswatch/superhero/bulmaswatch.min.css';
import { storeState } from '../state';
import { UserData } from '../state/action-creators';
import { useTypedSelector } from '../hooks/use-typed-selector';
const UserAuth: React.FC = () => {
const [currentUser, setCurrentUser] = useState<UserData | null>(null);
const [loginButtonText, setLoginButtonText] = useState<string>('Log In');
const provider: GithubAuthProvider = new GithubAuthProvider();
const auth: Auth = getAuth();
useEffect(() => {
if (currentUser !== null) {
useTypedSelector(({ user }) => currentUser);
}
}, [currentUser]);
const signinHandler = () => {
signInWithPopup(auth, provider)
.then(({ user: { uid, email, displayName, photoURL } }) => {
console.log({ uid, email, displayName, photoURL });
const userPayload: UserData = {
uid: uid,
email: email as string,
displayName: displayName as string,
photoURL: photoURL as string,
};
setCurrentUser(userPayload);
console.log({ userPayload });
console.log({ storeState });
})
.catch((err) => {
const errorCode = err.code;
const errorMessage = err.message;
const email = err.email;
const credential = GithubAuthProvider.credentialFromError(err);
// todo - display errors
console.log(err);
console.log(errorCode, errorMessage, email, credential);
});
};
return (
<button className="gh-button button is-primary" onClick={signinHandler}>
<i className="fab fa-github" />
<strong style={{ marginLeft: '10px' }}>{loginButtonText}</strong>
</button>
);
};
export default UserAuth;
这是useTypedSelector
钩子
import { useSelector, TypedUseSelectorHook } from 'react-redux';
import { RootState } from '../state';
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;
这是我userReducer
的上下文
import produce from 'immer';
import { ActionType } from '../action-types';
import { Action } from '../actions';
interface UserState {
uid: string;
email: string;
displayName: string;
photoURL: string;
}
const initialState: UserState = {
uid: '',
email: '',
displayName: '',
photoURL: '',
};
const reducer = produce((state: UserState = initialState, action: Action) => {
switch (action.type) {
case ActionType.SET_USER:
const { uid, email, displayName, photoURL } = action.payload;
state.uid = uid;
state.email = email;
state.displayName = displayName;
state.photoURL = photoURL;
return state;
default:
return state;
}
});
export default reducer;
笔记
我正在使用 Immer,所以我可以轻松修改不可变状态
如果我可以将我的组件连接到 Redux 存储,以便我可以直接从我的组件调度操作 - 那也很棒。
我正准备切换到上下文 API,因为 Redux 远不如 Vuex 直观。
解决方案
这甚至不是 Redux 问题。正如您所提到的,这是一个 React “钩子规则”问题。 所有钩子调用都必须位于函数组件或另一个自定义钩子的顶层,并且永远不能嵌套在条件语句中。
从当前代码中实际上并不清楚您在这里尝试做什么。如果我们忽略钩子规则方面,选择器的使用本身似乎并不正确:
useEffect(() => {
if (currentUser !== null) {
useTypedSelector(({ user }) => currentUser);
}
}, [currentUser]);
即使这是合法用法,它也没有做任何有用的事情:
- 选择器忽略
user
从状态中解构的字段,并返回currentUser
已经在范围内的变量 useSelector
总是返回你的选择器从 Redux 状态对象中提取的值,但是这里的返回值被完全忽略了
我认为您的意思是“当我从 Firebase 获得新的用户对象时,我想更新 Redux 存储以保存该用户数据”。在这种情况下,您真正需要的是调度一个动作,这与从状态中选择数据相反。
假设是这种情况,那么这里的正确方法可能涉及useState<UserData>
完全删除,并使用 React-ReduxuseDispatch
钩子让您从组件内调度操作:
// The reducer file should export an action creator for this case
import { userLoaded } from './userSlice';
const UserAuth = () => {
const [loginButtonText, setLoginButtonText] = useState<string>('Log In');
// get access to the Redux store `dispatch` method
const dispatch = useAppDispatch();
const signinHandler = () => {
const provider: GithubAuthProvider = new GithubAuthProvider();
const auth: Auth = getAuth();
signInWithPopup(auth, provider)
.then(({ user: { uid, email, displayName, photoURL } }) => {
console.log({ uid, email, displayName, photoURL });
const userPayload: UserData = {
uid: uid,
email: email as string,
displayName: displayName as string,
photoURL: photoURL as string,
};
// Dispatch a Redux action containing this data
dispatch(userLoaded(userPayload))
})
.catch((err) => {
const errorCode = err.code;
const errorMessage = err.message;
const email = err.email;
const credential = GithubAuthProvider.credentialFromError(err);
// todo - display errors
console.log(err);
console.log(errorCode, errorMessage, email, credential);
});
};
return (
<button className="gh-button button is-primary" onClick={signinHandler}>
<i className="fab fa-github" />
<strong style={{ marginLeft: '10px' }}>{loginButtonText}</strong>
</button>
);
};
我认为您对 Redux 的工作原理以及如何正确使用它缺少一些关键的理解。我强烈建议阅读我们的 Redux 核心文档中的“Redux Essentials”教程,该教程教授“如何以正确的方式使用 Redux”。
关于代码的其他几点说明:
- 您不应该在渲染逻辑中实例化像 Github auth 提供者那样的“实例”。在效果中执行此操作,或者直接在单击处理程序中执行此操作
- 您还应该使用我们的官方 Redux Toolkit 包来编写您的 Redux 逻辑(reducers 等)。RTK 已经内置了 Immer,旨在提供良好的 TS 使用体验。特别是,
createSlice
API 将根据您定义的 reducer 为您自动生成操作创建者和操作类型。 - 通常建议不要使用该
React.FC
类型。相反,只需props
在组件中声明类型,如果有的话,让 TS 推断返回类型。
推荐阅读
- php - 简单的 html dom - 在查找中查找
- c - 将数学方程插入C中的二叉树
- java - 将数组中的新值添加到其正确的索引 java
- hyperledger-sawtooth - 链头尚未设置。全部允许
- docker - 如何使用自签名证书通过 TLS 将 Docker 应用程序包推送到私有注册表
- ios - 找不到 tableViewCell 不可编辑且未显示默认文本的原因
- sql - 我应该如何重构子查询
- c# - 一次单击按钮后未显示来自 Ajax 加载的部分视图的模态
- codeigniter - 带有 Visual Studio 代码的 codeigniter - 如何在编码时查找翻译字符串
- r - 将列转换为数字,给定列列表