在 useEffect 中使用 useRef current 的问题
Posted
技术标签:
【中文标题】在 useEffect 中使用 useRef current 的问题【英文标题】:Problem with using useRef current in useEffect 【发布时间】:2021-07-03 01:40:43 【问题描述】:我有以下问题:目前我正在尝试编写一个 customHook,我可以使用它进行所有后端操作。这样做的目的是让我的代码看起来更干净、更容易理解并且更少写。
要理解这个问题,我必须先解释几件事。首先,我正在使用 JWT 令牌并执行一些操作,您需要使用身份验证标头中的令牌进行身份验证。如果我在尝试发布内容时遇到错误,我的函数应该刷新 JWT 令牌并再次调用该函数,如果第一次发生身份验证错误。因为我正在使用功能组件,而 customHook(我认为)只能以功能组件的方式使用钩子,所以在将状态设置为当前错误值后,我必须再次调用前一个函数。因此,我将以前的函数及其参数保存在 useRefs 中,但是当触发 useEffect 时,ref.current 值没有它们应该具有的值。
在下面的代码中只包含了这个问题需要的操作:
使用后端:
import axios from "axios";
import useEffect, useRef, useState from "react";
const BASE_URL = 'https://localhost:44372/api/';
const R = 'Requests/'; const M = 'Movies/'; const U = 'Users/';
const defaultOperations = (controller) =>
return
post: (object) => buildParams((`$controller/Post$controller.substring(0, controller.length-1)`), 'post', true, object),
put: (object, id) => buildParams((`$controller/Put$controller.substring(0, controller.length-1)/$id`), 'put', true, object),
delete: (id) => buildParams((`$controller/Delete$controller.substring(0, controller.length-1)/$id`), 'delete', true),
getAll: () => buildParams((`$controller/Get$controller`), 'get', false),
getByID: (id) => buildParams((`$controller/Get$controller.substring(0, controller.length-1)/$id`), 'get', false),
const buildParams = (url, type, header, param) =>
return url: url, type: type, header: header, param: param;
export const CONTROLLERS =
REQUESTS:
post: (object) => defaultOperations('Requests').post(object),
put: (object, id) => defaultOperations('Requests').put(object, id),
delete: (id) => defaultOperations('Requests').delete(id),
getAll: () => defaultOperations('Requests').getAll(),
getByID: (id) => defaultOperations('Requests').getByID(id),
getRequestsByUser: () => buildParams(`$RGetRequestsByUser`, 'post', true, accessToken: localStorage.accessToken),
,
USERS:
refreshToken: () => buildParams(`$URefreshToken`, 'post', false, accessToken: localStorage.accessToken, refreshToken: localStorage.refreshToken),
login: (mail, password) => buildParams(`$ULogin`, 'post', false, mail: mail, password: password),
register: (object) => buildParams(`$URegister`, 'post', false, object),
getUserByAccessToken: () => buildParams(`$UGetUserByAccessToken`, 'post', true, accessToken: localStorage.accessToken),
authentificate: () => buildParams('authentificate', 'special', true, undefined),
getAll: () => defaultOperations('Users').getAll(),
getByID: (id) => defaultOperations('Users').getByID(id),
post: (object) => defaultOperations('Users').post(object),
put: (object, id) => defaultOperations('Users').put(object, id),
delete: (id) => defaultOperations('Users').delete(id),
,
export const useBackend = (error, setError, initValue) =>
const [values, setValues] = useState([]);
const lastFunction = useRef();
const lastParameter = useRef();
function selectFunction(objc)
switch(objc.type)
case 'get': buildGetAndFetch(objc.url, objc.param, objc.header); break;
case 'post': buildPostAndFetch(objc.url, objc.param, objc.header);break;
case 'put': buildPutAndFetch(objc.url, objc.param, objc.header);break;
case 'delete': buildDeleteAndFetch(objc.url, objc.param, objc.header);break;
case 'special': authentificate();break;
default: console.log("Error in Switch");
if(initValue!==undefined) setValues(initValue);
function buildPostAndFetch(url, param, header)
let _param = param;
if(param?.accessToken !== undefined)
_param = accessToken: localStorage.accessToken;
if(param?.refreshToken)
_param = ...param, refreshToken: localStorage.refreshToken
'GetUserByAccessToken'));
const finalurl = `$BASE_URL$url`;
if(header)
axios.post(finalurl, _param, headers:
'Authorization': `Bearer $(localStorage.accessToken)`
)
.then(res =>
response(res);
)
.catch(err =>
authError(buildPostAndFetch, url, param, header);
)
else
axios.post(finalurl, param)
.then(res =>
response(res);
)
.catch(err =>
setError(true);
)
useEffect(() =>
, [values])
function buildDeleteAndFetch(url, param, header)
const finalurl = `$BASE_URL$url`;
if(header)
axios.delete(finalurl, param, headers: 'Authorization': `Bearer $(localStorage.accessToken)`)
.then(res =>
setValues(values.filter(item => item.requestID !== param));
setError(false);
)
.catch(err =>
authError(buildDeleteAndFetch, url, param, header);
)
else
axios.delete(finalurl, param)
.then(res =>
setValues(values.filter(item => item.requestID !== param));
setError(false);
)
.catch(err =>
setError(true);
)
function response(res)
setValues(res.data)
setError(false);
function refreshToken(err)
const finalurl= `$BASE_URL$URefreshToken`;
axios.post(finalurl, accessToken: localStorage.accessToken, refreshToken: localStorage.refreshToken)
.then(res =>
localStorage.accessToken = res.data.accessToken;
setError(err);
)
.catch(err =>
localStorage.accessToken = undefined;
localStorage.refreshToken = undefined;
setError(err);
);
function authError(funktion223, url, param, header)
if(error!==true)
lastFunction.current = funktion223;
lastParameter.current = [url, param, header];
refreshToken(true);
useEffect(() =>
if(error===true)
if(localStorage.accessToken !== undefined && localStorage.refreshToken !== undefined)
const lastfunc = lastFunction.current;
const lastparams = lastParameter.current;
if(lastfunc !== undefined && lastparams.length > 0 )
console.log(lastfunc);
console.log(lastparams[0]);
lastfunc(lastparams[0], lastparams[1], lastparams[2]);
, [error])
return [values,
(objc) => selectFunction(objc)];
组件说明: 我是 userSettings 组件,当 componentsDidMount 调用钩子以检查用户是否登录时,如果没有,尝试从后端获取其他数据没有任何意义。如果他已登录或至少他的令牌已过期,它将被刷新。在 UserRequests 组件中,当没有错误时,将获取用户的请求。
(我不知道你是否需要这些信息,也许你认为自定义钩子是正确的,而我只是在使用它们的组件中犯了错误)
用户设置:
import React, useEffect, useState from 'react';
import Row, Col from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import '../../App.css';
import ProfileSettings from './profileSettings';
import SettingsChooser from './settingsChooser';
// import SettingRoutings from '../settingRoutings';
import BrowserRouter as Router, useLocation, useParams from 'react-router-dom';
// import Routings from '../Routings.js';
import UserRequests from './userRequests';
import useAuth from '../../API/useAuthentification';
import CONTROLLERS, useBackend from '../../hooks/useBackend';
function UserSettings(user)
const title: path = useParams();
const [authError, setAuthError] = useState(false);
const [userValues, authentificate] = useBackend(authError, setAuthError, user);
let component;
useEffect(() =>
console.log('render');
authentificate(CONTROLLERS.USERS.getUserByAccessToken());
, [])
useEffect(() =>
console.log(userValues);
, [userValues])
if(path ==='logout')
// localStorage.accessToken = undefined;
// localStorage.refreshToken = undefined
else if(path === 'deleteaccount')
//TODO
//Implement
component = <UserRequests user=userValues setAuthError=setAuthError authError=authError/>
return (
<div classname="">
<Row className="">
<Col className="formsettings2" md= span: 3, offset: 1>
<SettingsChooser/>
</Col>
<Col className="ml-5 formsettings2"md= span: 6>
authError ? <p>No Access, please Login first</p> : component
</Col>
</Row>
</div>
);
export default UserSettings;
请求:
import React, useEffect, useState from 'react';
import Table from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import createAPIEndpoint from '../../API/callBackendAPI';
import toast from 'react-toastify';
import Loading from '../Alerts/loading';
import notify from '../Alerts/toasts';
import ToastProvider from 'react-toast-notifications';
import useAuth from '../../API/useAuthentification';
import useHistory from 'react-router';
import CONTROLLERS, useBackend from '../../hooks/useBackend';
toast.configure(
);
function UserRequests(user, authError, setAuthError)
const headers = ['ID', 'Title', 'Release Date', 'Producer', 'Director', 'Status', 'UTC Time', '#', '#'];
const history = useHistory();
const [requests, requestActions] = useBackend(authError, setAuthError);
const [loaded, setLoaded] = useState(false);
useEffect(() =>
if(requests.length > 0) setLoaded(true);
, [requests])
useEffect(() =>
if(authError === false) requestActions(CONTROLLERS.REQUESTS.getRequestsByUser());
, []);
const handleDelete = (e) =>
requestActions(CONTROLLERS.REQUESTS.delete(e));
const handleUpdate = (e) =>
console.log(e);
sessionStorage.movie = JSON.stringify(e);
history.push('/updateRequest');
if(loaded===false) return <Loading/>
return(
<ToastProvider>
<Table bordered hover responsive="md">
<thead>
<tr>
headers.map((item, index) =>
return( <th className="text-center" key=index>item</th> );
)
</tr>
</thead>
<tbody>
requests.map((item, index) =>
return(
<tr>
<td>index + 1</td>
<td>item.movie.movieTitle</td>
<td>item.movie.movieReleaseDate</td>
<td>item.movie.movieProducer</td>
<td>item.movie.movieDirector</td>
<td>(item.requestStatus === 1 ? 'Success' : item.requestStatus ===2 ? 'Pending' : 'Denied')</td>
<td className="col-md-3">item.requestDate</td>
<td><span onClick=() => handleDelete(item.requestID)><i className="fas fa-times"></i></span></td>
<td><span onClick=() => handleUpdate(item.movie)><i className="fa fa-pencil-alt"></i></span></td>
</tr>);
)
</tbody>
</Table>
</ToastProvider>
);
export default UserRequests
【问题讨论】:
【参考方案1】:useEffect
运行 onmount
,
当您使用 customHook useBackend
和 error === true
并且您在 localStorage
中有令牌时,它将尝试使用 lastFunction
和 lastParameter
引用,但您尚未启动它们。
useEffect(() =>
if(error===true) //could use if(error) if error is boolean
if(localStorage.accessToken !== undefined && localStorage.refreshToken !== undefined)
const lastfunc = lastFunction.current;
const lastparams = lastParameter.current;
if(lastfunc !== undefined && lastparams.length > 0 )
console.log(lastfunc);
console.log(lastparams[0]);
lastfunc(lastparams[0], lastparams[1], lastparams[2]);
, [error])
如果您不想让useEffect
运行onmount
,您可以使用isMounted
标志,此处已回答:
Stop useEffect from running on mount
【讨论】:
非常感谢您的回答,但我真的不明白为什么它们还没有初始化。我知道 useEffects 在挂载时运行,但是这个显式操作只在我的错误为真并且在设置 lastfunction 和 lastparams 后才设置为 true 时运行。 我试过这个解决方案,但还是不行。在 authError 中,ref 属性已正确更改,但是正如您提到的,当调用 useEffect 时,refs 没有初始化,但正如我所提到的,我不知道为什么。在我看来,isMounted 标志仅提供 useEffect 中的代码不会在初始渲染时调用,但我真的不知道它应该如何避免 useEffect 在 refs 初始化并正确设置后使用这些值。可能我只是理解不正确,思维有误,但如果您能更详细地向我解释,我将不胜感激 在您发送请求之前,请验证您的令牌是否有效,如果它已过期,您可以通过刷新令牌来处理它,然后再发送您的请求。你可以做类似这样的事情:***.com/questions/44982412/… 那么你就不需要存储你的最后一个请求,在发送请求之前处理令牌。以上是关于在 useEffect 中使用 useRef current 的问题的主要内容,如果未能解决你的问题,请参考以下文章
在 Div 中添加子元素时,虽然使用 UseEffect 触发,但到达 useRef() 当前属性返回未知
如何在 UseEffect 中将事件侦听器添加到 UseRefs
使用 useeffect 和 useref TypeError 时出错:无法读取 null 的属性“getBoundingClientRect”