我无法在条件渲染上正确使用 useEffect 读取输入
I can't make useEffect read input properly on conditional render
我正在尝试创建具有 'static' 模式的输入组件。在这种模式下,输入被替换为包含输入值的范围。在这个问题中,我使用了范围输入。
import React from 'react';
import ReactDOM from 'react-dom';
const { useState, useEffect, } = React;
const RangeInput = ({
inputID, min, max, defaultValue, children,
}) => {
const [valueAfterStaticChange, setValueAfterStaticChange] = useState(
defaultValue
);
const [isStatic, setIsStatic] = useState(false);
useEffect(
() => () => {
// do this before component unmounts and when 'isStatic' changes,
// but only when 'isStatic' is true
isStatic
|| setValueAfterStaticChange(document.getElementById(inputID).value);
},
[isStatic]
);
return (
<div>
<label htmlFor={inputID}>{children}</label>
<span style={{ display: isStatic || 'none', }}>
{valueAfterStaticChange}
</span>
<input
type="range"
min={min}
max={max}
defaultValue={valueAfterStaticChange}
id={inputID}
style={{ display: isStatic && 'none', }}
/>
<button
onClick={() => {
setIsStatic(!isStatic);
}}
>
Toggle Static
</button>
</div>
);
};
ReactDOM.render(
<RangeInput inputID="myID" min={0} max={100} defaultValue={50}>
My Range Input
</RangeInput>,
document.getElementById('root')
);
以上代码有效。但是它用display: none
来模拟一个'no render'。我认为我可以使用以下逻辑完成实际卸载:
// all unchanged until...
return (
<div>
<label htmlFor={inputID}>{children}</label>
{isStatic ? (
<span>{valueAfterStaticChange}</span>
) : (
<input
type="range"
min={min}
max={max}
defaultValue={valueAfterStaticChange}
id={inputID}
/>
)}
<button
onClick={() => {
setIsStatic(!isStatic);
}}
>
Toggle Static
</button>
</div>
);
};
我的 useEffect
return 函数在实际卸载后无法正确读取输入。我认为在 useEffect
中使用 return 函数会导致在组件卸载之前 运行 的函数。我做错了什么?
如果未安装 <input>
元素,您自然无法访问 document.getElementById(inputID).value
- 它不存在!即便如此,您也不会希望以这种方式读取其值。这是一种非常 un-React 的做事方式。 React 组件应该在 state 和 props 中拥有他们需要的一切——如果你发现自己直接查询或操作 DOM,你(几乎总是)走在黑暗的道路上。
相反,您想要的是 controlled component。
受控组件不依赖 DOM 来存储 "value" 之类的任何内容。相反,该组件仅显示 React 在状态中具有的任何值并提供方便的事件挂钩。
我强烈建议您阅读我链接的文档,看看您是否可以自己想出一个实现。这毕竟是最好的学习方式!但是,如果您仍然无法弄清楚,我会回来并用一个基本的工作示例来编辑我的答案。
编辑:这是一个带有一些注释的简单工作演示。
https://stackblitz.com/edit/controlled-input-demo?file=index.js
注意其实很简单! <App>
跟踪状态中的滑块值,而 <RangeInput>
只是将该值作为道具使用并提供事件处理程序以将其更新回 <App>
。 <RangeSlider>
可以跟踪它自己的 "editing mode" 值,因为其他组件可能不需要访问它(但如果你愿意,你可以 "lift" 声明一个级别!)你甚至不需要useEffect()
这里!
除非有理由在卸载时获取值,否则您似乎可以在值发生变化时跟踪它。这样你就根本不需要 useEffect
:
const RangeInput = ({ inputID, min, max, defaultValue, children }) => {
const [value, setValue] = useState(defaultValue); // <-- keep track of value
const [isStatic, setIsStatic] = useState(false);
return (
<div>
<label htmlFor={inputID}>{children}</label>
{isStatic ? (
<span>{value}</span>
) : (
<input
type="range"
min={min}
max={max}
defaultValue={value}
onChange={e => setValue(e.target.value)} // <-- Set value onChange
id={inputID}
/>
)}
<button
onClick={() => {
setIsStatic(!isStatic);
}}
>
Toggle Static
</button>
</div>
);
};
我正在尝试创建具有 'static' 模式的输入组件。在这种模式下,输入被替换为包含输入值的范围。在这个问题中,我使用了范围输入。
import React from 'react';
import ReactDOM from 'react-dom';
const { useState, useEffect, } = React;
const RangeInput = ({
inputID, min, max, defaultValue, children,
}) => {
const [valueAfterStaticChange, setValueAfterStaticChange] = useState(
defaultValue
);
const [isStatic, setIsStatic] = useState(false);
useEffect(
() => () => {
// do this before component unmounts and when 'isStatic' changes,
// but only when 'isStatic' is true
isStatic
|| setValueAfterStaticChange(document.getElementById(inputID).value);
},
[isStatic]
);
return (
<div>
<label htmlFor={inputID}>{children}</label>
<span style={{ display: isStatic || 'none', }}>
{valueAfterStaticChange}
</span>
<input
type="range"
min={min}
max={max}
defaultValue={valueAfterStaticChange}
id={inputID}
style={{ display: isStatic && 'none', }}
/>
<button
onClick={() => {
setIsStatic(!isStatic);
}}
>
Toggle Static
</button>
</div>
);
};
ReactDOM.render(
<RangeInput inputID="myID" min={0} max={100} defaultValue={50}>
My Range Input
</RangeInput>,
document.getElementById('root')
);
以上代码有效。但是它用display: none
来模拟一个'no render'。我认为我可以使用以下逻辑完成实际卸载:
// all unchanged until...
return (
<div>
<label htmlFor={inputID}>{children}</label>
{isStatic ? (
<span>{valueAfterStaticChange}</span>
) : (
<input
type="range"
min={min}
max={max}
defaultValue={valueAfterStaticChange}
id={inputID}
/>
)}
<button
onClick={() => {
setIsStatic(!isStatic);
}}
>
Toggle Static
</button>
</div>
);
};
我的 useEffect
return 函数在实际卸载后无法正确读取输入。我认为在 useEffect
中使用 return 函数会导致在组件卸载之前 运行 的函数。我做错了什么?
如果未安装 <input>
元素,您自然无法访问 document.getElementById(inputID).value
- 它不存在!即便如此,您也不会希望以这种方式读取其值。这是一种非常 un-React 的做事方式。 React 组件应该在 state 和 props 中拥有他们需要的一切——如果你发现自己直接查询或操作 DOM,你(几乎总是)走在黑暗的道路上。
相反,您想要的是 controlled component。
受控组件不依赖 DOM 来存储 "value" 之类的任何内容。相反,该组件仅显示 React 在状态中具有的任何值并提供方便的事件挂钩。
我强烈建议您阅读我链接的文档,看看您是否可以自己想出一个实现。这毕竟是最好的学习方式!但是,如果您仍然无法弄清楚,我会回来并用一个基本的工作示例来编辑我的答案。
编辑:这是一个带有一些注释的简单工作演示。
https://stackblitz.com/edit/controlled-input-demo?file=index.js
注意其实很简单! <App>
跟踪状态中的滑块值,而 <RangeInput>
只是将该值作为道具使用并提供事件处理程序以将其更新回 <App>
。 <RangeSlider>
可以跟踪它自己的 "editing mode" 值,因为其他组件可能不需要访问它(但如果你愿意,你可以 "lift" 声明一个级别!)你甚至不需要useEffect()
这里!
除非有理由在卸载时获取值,否则您似乎可以在值发生变化时跟踪它。这样你就根本不需要 useEffect
:
const RangeInput = ({ inputID, min, max, defaultValue, children }) => {
const [value, setValue] = useState(defaultValue); // <-- keep track of value
const [isStatic, setIsStatic] = useState(false);
return (
<div>
<label htmlFor={inputID}>{children}</label>
{isStatic ? (
<span>{value}</span>
) : (
<input
type="range"
min={min}
max={max}
defaultValue={value}
onChange={e => setValue(e.target.value)} // <-- Set value onChange
id={inputID}
/>
)}
<button
onClick={() => {
setIsStatic(!isStatic);
}}
>
Toggle Static
</button>
</div>
);
};