错误地列出更新
List Updates Incorrectly
一个非常简单的列表,其中包含应该可以添加和更新的输入。
问题发生在我添加一个或多个输入然后尝试在其中一个输入中键入时 - 输入后的所有输入都消失了。
它与记录 Item 组件有关,我想了解那里到底发生了什么(valueChanged 被缓存了吗?)。脑袋转不过来了。
如果没有备忘录功能,代码将按预期工作,但当然,所有输入都会在每次更改时更新。
这是正在发生的事情的 gif:https://streamable.com/gsgvi
要复制,请将下面的代码粘贴到 HTML 文件中或查看此处:https://codesandbox.io/s/81y3wnl142
<style>
ul {
list-style-type:none;
padding:0;
}
input[type=text] {
margin:0 10px;
}
</style>
<div id="app"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script type="text/babel">
const randomStr = () => Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10);
const { useState, memo, Fragment } = React
const Item = memo(({ value, i, valueChanged }) => {
console.log('item rendering...');
return <li>
<input type='text' value={value} onChange={e => valueChanged(i, e.target.value)}/>
</li>
}, (o, n) => o.value === n.value)
const ItemList = ({ items, valueChanged }) => {
return <ul>{
items.map(({ key, title }, i) => (
<Item
key={key}
i={i}
value={title}
valueChanged={valueChanged}
/>
))
}</ul>
}
const App = () => {
const [items, setItems] = useState([
{ key: randomStr(), title: 'abc' },
{ key: randomStr(), title: 'def' },
])
const valueChanged = (i, newVal) => {
let updatedItems = [...items]
updatedItems[i].title = newVal
setItems(updatedItems)
}
const addNew = () => {
let newItems = [...items]
newItems.push({ key: randomStr(), title:'' })
setItems(newItems)
}
return <Fragment>
<ItemList items={items} valueChanged={valueChanged}/>
<button onClick={addNew}>Add new</button>
</Fragment>
}
ReactDOM.render(<App/>, document.querySelector('#app'))
</script>
valueChanged
是一个闭包,每次渲染都会得到一个新的 items
。但是您的 memo
不会触发更新,因为它只检查 (o, n) => o.value === n.value
。 Item
中的事件处理程序使用旧的 items
值。
可以用functional updates修复:
const valueChanged = (i, newVal) => {
setItems(oldItems => {
let updatedItems = [...oldItems];
updatedItems[i].title = newVal;
return updatedItems;
});
}
所以valueChanged
不依赖于items
,也不需要memo
检查。
您的代码可能与其他处理程序存在类似问题。当新值基于旧值时,最好使用功能更新。
你可以试试这样初始化状态,我在codesandbox上试过了,可能就是你要找的行为。
https://codesandbox.io/s/q8l6m2lj64
const [items, setItems] = useState(() => [
{ key: randomStr(), title: 'abc' },
{ key: randomStr(), title: 'def' },
]);
一个非常简单的列表,其中包含应该可以添加和更新的输入。 问题发生在我添加一个或多个输入然后尝试在其中一个输入中键入时 - 输入后的所有输入都消失了。
它与记录 Item 组件有关,我想了解那里到底发生了什么(valueChanged 被缓存了吗?)。脑袋转不过来了。
如果没有备忘录功能,代码将按预期工作,但当然,所有输入都会在每次更改时更新。
这是正在发生的事情的 gif:https://streamable.com/gsgvi
要复制,请将下面的代码粘贴到 HTML 文件中或查看此处:https://codesandbox.io/s/81y3wnl142
<style>
ul {
list-style-type:none;
padding:0;
}
input[type=text] {
margin:0 10px;
}
</style>
<div id="app"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script type="text/babel">
const randomStr = () => Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10);
const { useState, memo, Fragment } = React
const Item = memo(({ value, i, valueChanged }) => {
console.log('item rendering...');
return <li>
<input type='text' value={value} onChange={e => valueChanged(i, e.target.value)}/>
</li>
}, (o, n) => o.value === n.value)
const ItemList = ({ items, valueChanged }) => {
return <ul>{
items.map(({ key, title }, i) => (
<Item
key={key}
i={i}
value={title}
valueChanged={valueChanged}
/>
))
}</ul>
}
const App = () => {
const [items, setItems] = useState([
{ key: randomStr(), title: 'abc' },
{ key: randomStr(), title: 'def' },
])
const valueChanged = (i, newVal) => {
let updatedItems = [...items]
updatedItems[i].title = newVal
setItems(updatedItems)
}
const addNew = () => {
let newItems = [...items]
newItems.push({ key: randomStr(), title:'' })
setItems(newItems)
}
return <Fragment>
<ItemList items={items} valueChanged={valueChanged}/>
<button onClick={addNew}>Add new</button>
</Fragment>
}
ReactDOM.render(<App/>, document.querySelector('#app'))
</script>
valueChanged
是一个闭包,每次渲染都会得到一个新的 items
。但是您的 memo
不会触发更新,因为它只检查 (o, n) => o.value === n.value
。 Item
中的事件处理程序使用旧的 items
值。
可以用functional updates修复:
const valueChanged = (i, newVal) => {
setItems(oldItems => {
let updatedItems = [...oldItems];
updatedItems[i].title = newVal;
return updatedItems;
});
}
所以valueChanged
不依赖于items
,也不需要memo
检查。
您的代码可能与其他处理程序存在类似问题。当新值基于旧值时,最好使用功能更新。
你可以试试这样初始化状态,我在codesandbox上试过了,可能就是你要找的行为。
https://codesandbox.io/s/q8l6m2lj64
const [items, setItems] = useState(() => [
{ key: randomStr(), title: 'abc' },
{ key: randomStr(), title: 'def' },
]);