我有一个react hook组件,它需要注册到一个外部事件,如下所示:
const DefaultRateSection: React.FC<{
registerOnSaveEvent: (fn: () => void) => void;
}> = ({registerOnSaveEvent}) => {
const [serviceName, setServiceName] = useState('the name');
const serviceNameInput = useRef<input>(null);
useEffect(
() => {
return registerOnSaveEvent(() => {
if (!serviceName) {
serviceNameInput.current?.focus();
}
});
},
[registerOnSaveEvent]
);
return (
<input
ref={serviceNameInput}
value={serviceName}
onChange={(event) => {
const newValue = event.target.value;
setServiceName(newValue)
}}
/>
);
};
registerOnSaveEvent
是一个我不能改变的API,我没有退订方法,因此我只需要注册一次。但是,当它(从组件外部)触发时,我正在接收serviceName
的初始值,而不是更新的值。我知道它的发生,因为我没有调用useEffect
后的变化,但我需要避免多次注册。
我怎样才能做到这一点?
TL-TR:简短的回答是使用另一个ref,以便箭头函数可以访问服务名称的最新呈现值。
const serviceNameRef = useRef();
serviceNameRef.current = serviceName;
// use serviceNameRef.current in the arrow function
这段代码不能工作
// Get a hook function - only needed for this Stack Snippet
const {useState, useRef, useEffect} = React;
const DefaultRateSection = ({ registerOnSaveEvent }) => {
const [serviceName, setServiceName] = useState("the name");
const serviceNameInput = useRef();
useEffect(() => {
return registerOnSaveEvent(() => {
console.log(serviceName)
});
}, [registerOnSaveEvent]);
return (
<input
ref={serviceNameInput}
value={serviceName}
onChange={(event) => {
const newValue = event.target.value;
setServiceName(newValue);
}}
/>
);
};
const App = () => {
const callbackRef = useRef();
function registerOnSaveEvent(callback) {
callbackRef.current=callback
}
function execCallback() {
callbackRef.current();
}
return <div>
<h2>This will not work</h2>
<p>Try to change the input field and click 'RegisterOnSaveEvent'.</p>
<p>The callback will not see the new value of the input</p>
<DefaultRateSection registerOnSaveEvent={registerOnSaveEvent}/>
<button onClick={execCallback}>RegisterOnSaveEvent</button>
</div>;
};
ReactDOM.render(
<App/>,
document.getElementById("react")
);
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
这段代码可以工作
// Get a hook function - only needed for this Stack Snippet
const {useState, useRef, useEffect} = React;
const DefaultRateSection = ({ registerOnSaveEvent }) => {
const [serviceName, setServiceName] = useState("the name");
const serviceNameInput = useRef();
// use a ref for the service name and
// update it with the serviceName state on every render
const serviceNameRef = useRef();
serviceNameRef.current = serviceName;
useEffect(() => {
return registerOnSaveEvent(() => {
console.log(serviceNameRef.current)
});
}, [registerOnSaveEvent]);
return (
<input
ref={serviceNameInput}
value={serviceName}
onChange={(event) => {
const newValue = event.target.value;
setServiceName(newValue);
}}
/>
);
};
const App = () => {
const callbackRef = useRef();
function registerOnSaveEvent(callback) {
callbackRef.current=callback
}
function execCallback() {
callbackRef.current();
}
return <div>
<h2>This will work</h2>
<p>Try to change the input field and click 'RegisterOnSaveEvent'.</p>
<p>The callback will not see the new value of the input</p>
<DefaultRateSection registerOnSaveEvent={registerOnSaveEvent}/>
<button onClick={execCallback}>RegisterOnSaveEvent</button>
</div>;
};
ReactDOM.render(
<App/>,
document.getElementById("react")
);
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
<标题>当useEffect
被执行时,它创建一个箭头函数。这个箭头函数引用constserviceName
,这是初始值。这是箭头函数看到的值。当你在输入字段中输入一些东西时,你调用setServiceName
,它会改变状态并触发渲染。渲染本身只不过是一个函数调用。因此,当组件被重新呈现时,useState
返回状态,并将其分配给一个名为serviceName
的全新本地const。这与箭头函数引用的不一样。因此,箭头函数在创建时将始终看到serviceName
的值。
为了解决这个问题,我为serviceName
使用另一个ref,称为serviceNameRef
,并在每次渲染时用serviceName
状态更新该ref。由于useRef
在每次调用时都返回serviceRefName
的相同实例,因此它与箭头函数使用的实例是相同的。这就是它的工作原理。