我正在用 React 做我的前几个实验,在这个组件中,我调用一个外部 API 来获取所有 NBA 球员的列表,通过作为组件道具接收的 teamId 过滤他们,最后呈现标记过滤后的玩家。
一个考虑因素是,由于我调用了 API 并获得了一个大列表,所以我将它保持在组件的状态中,以便对该组件的新调用将使用该状态,而不是再次调用 API。这不是生产代码,我不拥有 API,所以我这样做是因为我收到了“请求太多”的消息,因为我一直在尝试一些东西。
无论如何,当我尝试这段代码时,我得到了已经很有名的:
<块引用>已超出最大更新深度。这可能发生在组件 在 componentWillUpdate 内重复调用 setState 或 组件DidUpdate。 React 将嵌套更新的数量限制为 防止无限循环。
我查看了标记,我认为我没有进行任何会导致渲染方法再次触发的方法调用等等,所以我不知道为什么会发生这种情况。
>在此先感谢您的帮助。
这是有问题的代码:
class Players extends Component {
nbaPlayersUrl = "https://someUrl.com";
state = {
players: null,
selectedTeamPlayers: null
};
render() {
if (this.props.teamId === null) return null;
if (this.state.players !== null) {
var selectedTeamPlayers = this.filterPlayersByTeamId(this.state.players);
var markup = this.getMarkup(selectedTeamPlayers);
this.setState({selectedTeamPlayers: markup});
} else {
this.getPlayersList();
}
return (
this.state.selectedTeamPlayers
);
}
getPlayersList() {
let api = new ExternalApi();
let that = this;
api.get(this.nbaPlayersUrl).then(r => {
r.json().then(result => {
let players = result.data.map(p => ({
id: p.id,
firstName: p.first_name,
lastName: p.last_name,
position: p.position,
heightInches: p.height_inches,
heightFeet: p.height_feet,
weightPounds: p.weight_pounds,
teamId: p.team.id
}));
that.setState({players: players});
var selectedTeamPlayers = that.filterPlayersByTeamId(players);
var markup = that.getMarkup(selectedTeamPlayers);
that.setState({selectedTeamPlayers: markup});
});
});
}
filterPlayersByTeamId(players) {
return players.filter(p => {
return p.teamId === this.props.teamId;
});
}
getMarkup(players) {
var result = players.map(p => {
<li key={p.id}>
<div>
<label htmlFor="firstName">First Name</label> <input type="text" name="firstName" value={p.firstName} readOnly></input>
</div>
<div>
<label htmlFor="lastName">Last Name</label> <input type="text" name="lastName" value={p.lastName} readOnly></input>
</div>
<div>
<label htmlFor="position">Position</label> <input type="text" name="position" value={p.position} readOnly></input>
</div>
</li>
});
return (
<ul>
{result}
</ul>
);
}
}
export default Players;
答案 0 :(得分:2)
@Sergio Romero - 你不能在渲染函数中设置状态,因为设置的状态会调用一个新的渲染,它会再次设置状态并调用一个新的渲染,并产生一个无限循环。您的数据加载处于渲染和设置状态,这也会造成无限循环。您需要完全重写您的渲染,使其仅成为状态和道具的视图(它不应该操作或加载数据)。我想你想要的,更像是这样的:
class Players extends Component {
nbaPlayersUrl = "https://someUrl.com";
static propTypes = {
teamId: PropTypes.number
};
static defaultProps = {
teamId: null
};
constructor(props) {
super(props);
this.state = {
players: null
};
}
componentDidMount() {
this.getPlayerList();
}
filterPlayersByTeamId(players, teamId) {
return players.filter(p => {
return p.teamId === teamId;
});
}
getPlayersList = () => {
let api = new ExternalApi();
api.get(this.nbaPlayersUrl).then(r => {
r.json().then(result => {
let players = result.data.map(p => ({
id: p.id,
firstName: p.first_name,
lastName: p.last_name,
position: p.position,
heightInches: p.height_inches,
heightFeet: p.height_feet,
weightPounds: p.weight_pounds,
teamId: p.team.id
}));
this.setState({players});
});
});
};
render() {
if (!this.props.teamId || !this.state.players) return null;
const selectedTeamPlayers = this.filterPlayersByTeamId(this.state.players, this.props.teamId);
return (
<ul>
{
selectedTeamPlayers.map(player => {
<li key={player.id}>
<div>
<label htmlFor="firstName">First Name</label><input type="text" name="firstName" value={player.firstName} readOnly></input>
</div>
<div>
<label htmlFor="lastName">Last Name</label><input type="text" name="lastName" value={player.lastName} readOnly></input>
</div>
<div>
<label htmlFor="position">Position</label><input type="text" name="position" value={player.position} readOnly></input>
</div>
</li>
})
}
</ul>
);
}
}
export default Players;
答案 1 :(得分:0)
如果 State 和 Props 改变,组件将重新渲染。 在您的 render() 函数中:
if (this.state.players !== null) {
var selectedTeamPlayers = this.filterPlayersByTeamId(this.state.players);
var markup = this.getMarkup(selectedTeamPlayers);
// this.setState({selectedTeamPlayers:
}
尝试更改注释行,每次玩家不为空时,组件状态都会更新,因此组件渲染功能将再次运行。
答案 2 :(得分:0)
由于我们不能在渲染函数内部设置状态,因为它会引起副作用,因此您不能在渲染函数内部调用 getPlayersList()。
@Jason Bellomy 提到的解决方案是通过在 componentDidMount 内部调用 getPlayerList 来解决这个问题的正确方法,因为它在组件插入树后立即调用,因此它是设置渲染页面的初始数据的地方.
class Players extends Component {
nbaPlayersUrl = "https://someUrl.com";
state = {
players: null,
selectedTeamPlayers: null,
};
componentDidMount(){
this.getPlayersList();
}
render() {
if (this.props.teamId === null) return null;
if (this.state.players !== null && this.state.selectedTeamPlayers !== null) {
return this.getMarkup(selectedTeamPlayers);
} else {
return (<span> Loading ... </span>);
}
}
getPlayersList() {
let api = new ExternalApi();
let that = this;
api.get(this.nbaPlayersUrl).then(r => {
r.json().then(result => {
let players = result.data.map(p => ({
id: p.id,
firstName: p.first_name,
lastName: p.last_name,
position: p.position,
heightInches: p.height_inches,
heightFeet: p.height_feet,
weightPounds: p.weight_pounds,
teamId: p.team.id
}));
var selectedTeamPlayers = that.filterPlayersByTeamId(players);
that.setState({
players: players,
selectedTeamPlayers: selectedTeamPlayers,
});
});
});
}
filterPlayersByTeamId(players) {
return players.filter(p => {
return p.teamId === this.props.teamId;
});
}
getMarkup(players) {
var result = players.map(p => {
<li key={p.id}>
<div>
<label htmlFor="firstName">First Name</label> <input type="text" name="firstName" value={p.firstName} readOnly></input>
</div>
<div>
<label htmlFor="lastName">Last Name</label> <input type="text" name="lastName" value={p.lastName} readOnly></input>
</div>
<div>
<label htmlFor="position">Position</label> <input type="text" name="position" value={p.position} readOnly></input>
</div>
</li>
});
return (
<ul>
{result}
</ul>
);
}
}
export default Players;