我正在尝试使用受控表单组件内的useEffect
钩子,以便在用户更改表单内容时通知父组件并返回表单内容的 DTO。这是我目前的尝试
const useFormInput = initialValue => {
const [value, setValue] = useState(initialValue)
const onChange = ({target}) => {
console.log("onChange")
setValue(target.value)
}
return { value, setValue, binding: { value, onChange }}
}
useFormInput.propTypes = {
initialValue: PropTypes.any
}
const DummyForm = ({dummy, onChange}) => {
const {value: foo, binding: fooBinding} = useFormInput(dummy.value)
const {value: bar, binding: barBinding} = useFormInput(dummy.value)
// This should run only after the initial render when user edits inputs
useEffect(() => {
console.log("onChange callback")
onChange({foo, bar})
}, [foo, bar])
return (
<div>
<input type="text" {...fooBinding} />
<div>{foo}</div>
<input type="text" {...barBinding} />
<div>{bar}</div>
</div>
)
}
function App() {
return (
<div className="App">
<header className="App-header">
<DummyForm dummy={{value: "Initial"}} onChange={(dummy) => console.log(dummy)} />
</header>
</div>
);
}
但是,现在在挂载期间设置初始值时,效果将在第一次渲染上运行。我该如何避免这种情况?
以下是加载页面并随后编辑这两个字段的当前日志。我也想知道为什么我会收到缺少依赖的警告。
onChange callback
App.js:136 {foo: "Initial", bar: "Initial"}
backend.js:1 ./src/App.js
Line 118: React Hook useEffect has a missing dependency: 'onChange'. Either include it or remove the dependency array. If 'onChange' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps
r @ backend.js:1
printWarnings @ webpackHotDevClient.js:120
handleWarnings @ webpackHotDevClient.js:125
push../node_modules/react-dev-utils/webpackHotDevClient.js.connection.onmessage @ webpackHotDevClient.js:190
push../node_modules/sockjs-client/lib/event/eventtarget.js.EventTarget.dispatchEvent @ eventtarget.js:56
(anonymous) @ main.js:282
push../node_modules/sockjs-client/lib/main.js.SockJS._transportMessage @ main.js:280
push../node_modules/sockjs-client/lib/event/emitter.js.EventEmitter.emit @ emitter.js:53
WebSocketTransport.ws.onmessage @ websocket.js:36
App.js:99 onChange
App.js:116 onChange callback
App.js:136 {foo: "Initial1", bar: "Initial"}
App.js:99 onChange
App.js:116 onChange callback
App.js:136 {foo: "Initial1", bar: "Initial2"}
您可以看到有关如何忽略初始渲染的方法的答案。此方法使用useRef
来跟踪第一个渲染。
const firstUpdate = useRef(true);
useLayoutEffect(() => {
if (firstUpdate.current) {
firstUpdate.current = false;
} else {
// do things after first render
}
});
至于你得到的警告:
React Hook useEffect缺少一个依赖项:"onChange">
钩子调用中的尾随数组(useEffect(() => {}, [foo]
(列出了钩子的依赖关系。这意味着,如果您在钩子范围内使用一个变量,该变量可以根据组件的更改(例如组件的属性(进行更改,则需要在此处列出。
如果你正在寻找类似componentDidUpdate((的东西而不通过componentDidMount((,你可以写一个钩子,比如:
export const useComponentDidMount = () => {
const ref = useRef();
useEffect(() => {
ref.current = true;
}, []);
return ref.current;
};
在您的组件中,您可以像这样使用它:
const isComponentMounted = useComponentDidMount();
useEffect(() => {
if(isComponentMounted) {
// Do something
}
}, [someValue])
在您的情况下,它将是:
const DummyForm = ({dummy, onChange}) => {
const isComponentMounted = useComponentDidMount();
const {value: foo, binding: fooBinding} = useFormInput(dummy.value)
const {value: bar, binding: barBinding} = useFormInput(dummy.value)
// This should run only after the initial render when user edits inputs
useEffect(() => {
if(isComponentMounted) {
console.log("onChange callback")
onChange({foo, bar})
}
}, [foo, bar])
return (
// code
)
}
让我知道它是否有帮助。
我为此创建了一个简单的钩子
https://stackblitz.com/edit/react-skip-first-render?file=index.js
它基于 paruchuri-p
const useSkipFirstRender = (fn, args) => {
const isMounted = useRef(false);
useEffect(() => {
if (isMounted.current) {
console.log('running')
return fn();
}
}, args)
useEffect(() => {
isMounted.current = true
}, [])
}
第一个效果是主要效果,就像您在组件中使用它一样。它将运行,发现isMount没有true
,并且会跳过任何操作。
然后在运行底部useEffect
后,它将isMounted
更改为true
- 因此当组件被强制重新渲染时。它将允许第一个使用效果正常渲染。
它只是做了一个很好的自我封装的可重复使用的钩子。显然你可以改变名字,这取决于你。
挂载后可以使用自定义钩子运行使用效果。
const useEffectAfterMount = (cb, dependencies) => {
const mounted = useRef(true);
useEffect(() => {
if (!mounted.current) {
return cb();
}
mounted.current = false;
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
这是typescript
版本:
const useEffectAfterMount = (cb: EffectCallback, dependencies: DependencyList | undefined) => {
const mounted = useRef(true);
useEffect(() => {
if (!mounted.current) {
return cb();
}
mounted.current = false;
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
例:
useEffectAfterMount(() => {
console.log("onChange callback")
onChange({foo, bar})
}, [count])
我不明白为什么你首先需要useEffect
。您的表单输入几乎肯定应该是受控的输入组件,其中表单的当前值作为道具提供,表单仅提供onChange
处理程序。表单的当前值应存储在<App>
中,否则您将如何从应用程序中的其他地方访问表单的值?
const DummyForm = ({valueOne, updateOne, valueTwo, updateTwo}) => {
return (
<div>
<input type="text" value={valueOne} onChange={updateOne} />
<div>{valueOne}</div>
<input type="text" value={valueTwo} onChange={updateTwo} />
<div>{valueTwo}</div>
</div>
)
}
function App() {
const [inputOne, setInputOne] = useState("");
const [inputTwo, setInputTwo] = useState("");
return (
<div className="App">
<header className="App-header">
<DummyForm
valueOne={inputOne}
updateOne={(e) => {
setInputOne(e.target.value);
}}
valueTwo={inputTwo}
updateTwo={(e) => {
setInputTwo(e.target.value);
}}
/>
</header>
</div>
);
}
更干净,更简单,更灵活,利用标准的React模式,不需要useEffect
。