如何在设置新对象之前从对象中删除键?



新键是这样分配的,但旧键需要删除,我如何才能在不干扰分配的情况下以紧凑的方式做到这一点?

{Object.entries(environments).map(([k, v]) => (
<input
type="text"
className="input-text border border-radius lightgray-background"
value={k}
onChange={(e) => {
setEnvironments({
...environments,
[e.target.value]: v,
});
}}
/>
)}

任何时候在React中呈现列表,都必须为每个项目的key属性提供一个稳定的(确定性的)值。不这样做是一个bug,因为-否则- React不知道哪些数据属于系列中的哪个项目。

键帮助React识别哪些项已经更改、添加或删除。为了给数组中的元素提供一个稳定的标识,应该给数组中的元素赋键。

这个行为在名为列表和键的文档小节中有详细的介绍。

如果您需要管理具有独立编辑键和值的项的集合-以及,特别是如果集合的键可能包含重复项(如您的问题代码中所建议的)-那么集合中的键不一定是唯一标识符,并且不适合用作React键在呈现的节点列表中。

您可以使用唯一字符串(例如,从Crypto.randomUUID()生成的uidv4)作为每个键-值条目的实际,内部和唯一键。

在上述信息的上下文中,Map可能是您的环境数据的更好的数据结构:您不仅可以在每个条目中以元组的形式存储键和值-Map还为您提供了对其条目顺序的更强控制:不像对象,其键是有一些限制的确定性排序(参见JavaScript保证对象属性顺序吗?),Map的条目总是根据插入排序。

下面我创建了一个自包含的示例,使用Map作为状态来存储一组可编辑的键值条目,包括一个用于可视化状态的组件。

选择"整页";在运行代码片段以获得演示的扩展视图后。

* { box-sizing: border-box; } body { font-family: sans-serif; } button, input[type="text"] { font-size: 1rem; padding: 0.5rem; } .entry-list { list-style: none; padding: 0; } .entry-row { display: flex; gap: 0.5rem; } pre.visual { background-color: hsla(0, 0%, 50%, 0.1); font-size: 1rem; padding: 1rem; } pre.visual > code { font-family: monospace; line-height: 1.5; }
<div id="root"></div><script src="https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.development.js"></script><script src="https://cdn.jsdelivr.net/npm/react-dom@18.2.0/umd/react-dom.development.js"></script><script src="https://cdn.jsdelivr.net/npm/@babel/standalone@7.20.6/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">
const {useCallback, useState} = React;
function VisualizeState ({serializable}) {
return (
<pre className="visual">
<code>{JSON.stringify(serializable, null, 2)}</code>
</pre>
);
}
function TextInput ({placeholder, value, setValue}) {
return (
<input
type="text"
onChange={ev => setValue(ev.target.value)}
{...{placeholder, value}}
/>
);
}
// The default values for a new key-value entry in the environment:
const createDefaultEnvEntry = () => ['', ''];
function App () {
const [environmentMap, setEnvironmentMap] = useState(new Map());
const setEnvKey = useCallback(
(uuid, key) => setEnvironmentMap(m => {
const map = new Map([...m.entries()]);
const entry = map.get(uuid) ?? createDefaultEnvEntry();
entry[0] = key;
map.set(uuid, entry);
return map;
}),
[setEnvironmentMap],
);
const setEnvValue = useCallback(
(uuid, value) => setEnvironmentMap(m => {
const map = new Map([...m.entries()]);
const entry = map.get(uuid) ?? createDefaultEnvEntry();
entry[1] = value;
map.set(uuid, entry);
return map;
}),
[setEnvironmentMap],
);
const createEnvEntry = useCallback(
() => setEnvironmentMap(m => new Map([
...m.entries(),
[crypto.randomUUID(), createDefaultEnvEntry()],
])),
[setEnvironmentMap],
);
const deleteEnvEntry = useCallback(
(uuid) => setEnvironmentMap(m => {
const map = new Map([...m.entries()]);
map.delete(uuid);
return map;
}),
[setEnvironmentMap],
);
return (
<div>
<ul className="entry-list">
{
[...environmentMap.entries()].map(([uuid, [key, value]]) => (
// Note the use of the "key" attribute with the mapped node child:
<li className="entry-row" key={uuid}>
<TextInput
placeholder="key"
value={key}
setValue={key => setEnvKey(uuid, key)}
/>
<TextInput
placeholder="value"
value={value}
setValue={value => setEnvValue(uuid, value)}
/>
<button onClick={() => deleteEnvEntry(uuid)}>Delete entry</button>
</li>
))
}
</ul>
<button onClick={() => createEnvEntry()}>Create new entry</button>
<div>
<p>Visulization of state:</p>
<VisualizeState
serializable={Object.fromEntries([...environmentMap.entries()])}
/>
</div>
</div>
);
}
const reactRoot = ReactDOM.createRoot(document.getElementById('root'));
reactRoot.render(<App />);
</script>

最新更新