列表子项未正确更新? (反应/预反应)
List childs are not beeing updated correctly? (React / Preact)
我有以下组件
import {h, Component} from 'preact'
import {getPersons} from '../../lib/datalayer'
import Person from '../person'
import {SearchInput} from '../search'
export default class Persons extends Component {
state = {
allPersons: [],
persons: [],
search: ''
}
async fetchData () {
try {
const allPersons = await getPersons()
this.setState({allPersons: allPersons.slice(), persons: allPersons.slice()})
} catch (error) {
....
}
}
constructor (props) {
super(props)
this.state = {
allPersons: [],
persons: [],
search: ''
}
this.fetchData()
}
onSearchInput = (search) => {
if (search === '') {
this.setState({search: search, persons: this.state.allPersons.slice()})
} else {
const persons = this.state.allPersons.filter(p => p.name.toLowerCase().includes(search.toLowerCase())).slice()
this.setState({search: search, persons: persons)})
}
}
render () {
const {persons} = this.state
return (
<div>
<SearchInput onInputChange={this.onSearchInput} placeHolder={'filter: name'} />
{persons.map(p => <Person person={p} />)}
</div>
)
}
}
页面呈现人员列表,顶部有一个过滤器。过滤器似乎工作正常,我通过console.log测试它的结果很好
问题是,如果我的列表包含以下对象:
[{name: 'thomas'}, {name: 'john'}, {name: 'marcus'}, {name: 'usa'}]
然后我在搜索输入中写:'us'
过滤器工作正常,结果是:
[{name: 'marcus'}, {name: 'usa'}] \ (the expected result)
在页面中呈现此对象
[{name: 'thomas'}, {name: 'john'}] \ (wrong, this are the two first elements of the list)
如果我搜索:'joh'
过滤器的结果是
[{name: 'john'}] \ (this is fine)
并且页面只呈现
[{name: 'thomas'}] \ (the first element in the list)
看起来渲染的元素数量没问题,但是列表的子元素的内容没有被重新渲染。
我的代码有什么问题?
React 在列表的子项上使用 keys
来确定哪些项目发生了变化,哪些保持不变。由于您没有在 person 上指定 key
,因此需要 index
作为键。
当索引是关键时,您可以看到如何将列表缩短为两个项目,显示列表中的前两个项目(其他索引现在丢失)。为了解决这个问题,你必须给这个人一个唯一的标识符作为关键。
根据您的对象,假设 name
是唯一的(通常不是):
{persons.map(p => <Person person={p} key={p.name} />)}
我无法用 React 重现错误,确实删除了一些不需要的切片并为每个元素添加了唯一的 ID(如果你不给每个元素一个唯一的键,React 会抱怨,也许会预反应)。
const Person = React.memo(props => (
<pre>{JSON.stringify(props, undefined, 2)}</pre>
));
class Persons extends React.Component {
state = {
allPersons: [
{ name: 'aaa', id: 1 },
{ name: 'aab', id: 2 },
{ name: 'abb', id: 3 },
{ name: 'bbb', id: 4 },
{ name: 'bbc', id: 5 },
],
persons: [
{ name: 'aaa', id: 1 },
{ name: 'aab', id: 2 },
{ name: 'abb', id: 3 },
{ name: 'bbb', id: 4 },
{ name: 'bbc', id: 5 },
],
search: '',
};
onSearchInput = search => {
if (search === '') {
//slice not needed here
this.setState({
search: search,
persons: this.state.allPersons,
});
} else {
//filter already copies allPersons
const persons = this.state.allPersons.filter(p =>
p.name.toLowerCase().includes(search.toLowerCase())
);
this.setState({ search: search, persons: persons });
}
};
render() {
const { persons } = this.state;
return (
<div>
<input
type="text"
value={this.state.search}
onChange={e => this.onSearchInput(e.target.value)}
placeHolder={'filter: name'}
/>
{persons.map(p => (
<Person person={p} key={p.id} />
))}
</div>
);
}
}
ReactDOM.render(
<Persons />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
我有以下组件
import {h, Component} from 'preact'
import {getPersons} from '../../lib/datalayer'
import Person from '../person'
import {SearchInput} from '../search'
export default class Persons extends Component {
state = {
allPersons: [],
persons: [],
search: ''
}
async fetchData () {
try {
const allPersons = await getPersons()
this.setState({allPersons: allPersons.slice(), persons: allPersons.slice()})
} catch (error) {
....
}
}
constructor (props) {
super(props)
this.state = {
allPersons: [],
persons: [],
search: ''
}
this.fetchData()
}
onSearchInput = (search) => {
if (search === '') {
this.setState({search: search, persons: this.state.allPersons.slice()})
} else {
const persons = this.state.allPersons.filter(p => p.name.toLowerCase().includes(search.toLowerCase())).slice()
this.setState({search: search, persons: persons)})
}
}
render () {
const {persons} = this.state
return (
<div>
<SearchInput onInputChange={this.onSearchInput} placeHolder={'filter: name'} />
{persons.map(p => <Person person={p} />)}
</div>
)
}
}
页面呈现人员列表,顶部有一个过滤器。过滤器似乎工作正常,我通过console.log测试它的结果很好
问题是,如果我的列表包含以下对象:
[{name: 'thomas'}, {name: 'john'}, {name: 'marcus'}, {name: 'usa'}]
然后我在搜索输入中写:'us'
过滤器工作正常,结果是:
[{name: 'marcus'}, {name: 'usa'}] \ (the expected result)
在页面中呈现此对象
[{name: 'thomas'}, {name: 'john'}] \ (wrong, this are the two first elements of the list)
如果我搜索:'joh'
过滤器的结果是
[{name: 'john'}] \ (this is fine)
并且页面只呈现
[{name: 'thomas'}] \ (the first element in the list)
看起来渲染的元素数量没问题,但是列表的子元素的内容没有被重新渲染。
我的代码有什么问题?
React 在列表的子项上使用 keys
来确定哪些项目发生了变化,哪些保持不变。由于您没有在 person 上指定 key
,因此需要 index
作为键。
当索引是关键时,您可以看到如何将列表缩短为两个项目,显示列表中的前两个项目(其他索引现在丢失)。为了解决这个问题,你必须给这个人一个唯一的标识符作为关键。
根据您的对象,假设 name
是唯一的(通常不是):
{persons.map(p => <Person person={p} key={p.name} />)}
我无法用 React 重现错误,确实删除了一些不需要的切片并为每个元素添加了唯一的 ID(如果你不给每个元素一个唯一的键,React 会抱怨,也许会预反应)。
const Person = React.memo(props => (
<pre>{JSON.stringify(props, undefined, 2)}</pre>
));
class Persons extends React.Component {
state = {
allPersons: [
{ name: 'aaa', id: 1 },
{ name: 'aab', id: 2 },
{ name: 'abb', id: 3 },
{ name: 'bbb', id: 4 },
{ name: 'bbc', id: 5 },
],
persons: [
{ name: 'aaa', id: 1 },
{ name: 'aab', id: 2 },
{ name: 'abb', id: 3 },
{ name: 'bbb', id: 4 },
{ name: 'bbc', id: 5 },
],
search: '',
};
onSearchInput = search => {
if (search === '') {
//slice not needed here
this.setState({
search: search,
persons: this.state.allPersons,
});
} else {
//filter already copies allPersons
const persons = this.state.allPersons.filter(p =>
p.name.toLowerCase().includes(search.toLowerCase())
);
this.setState({ search: search, persons: persons });
}
};
render() {
const { persons } = this.state;
return (
<div>
<input
type="text"
value={this.state.search}
onChange={e => this.onSearchInput(e.target.value)}
placeHolder={'filter: name'}
/>
{persons.map(p => (
<Person person={p} key={p.id} />
))}
</div>
);
}
}
ReactDOM.render(
<Persons />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>