如何在react useEffect中只注册一次,并获得useState的更改



我有一个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的相同实例,因此它与箭头函数使用的实例是相同的。这就是它的工作原理。

相关内容

  • 没有找到相关文章