如何更新使用两个上下文消费者的组件的状态
Posted
技术标签:
【中文标题】如何更新使用两个上下文消费者的组件的状态【英文标题】:How to update state of a component which uses two context consumers 【发布时间】:2020-01-25 10:34:50 【问题描述】:我有一个类组件,它使用两个上下文以及从 API 调用到 REST API 生成的值。
我想要做的是获取上下文值并使用它们来更新我的组件状态。
我像这样传递上下文值
<TextContext.Consumer>
(textContext) => (
<UserContext.Consumer>
(userConsumer) =>
const text = textContext.text;
const user = userConsumer.user;
if(text != null && user != null)
return (
<div className="md:flex max-w-2xl">
<div className="flex flex-col flex-1 md:pr-32">
<FuseAnimateGroup
enter=
animation: "transition.slideUpBigIn"
>
<div style=paddingRight:"8px">
<Typography variant="h4" >text.TITLE_PAGE_PROFILE</Typography>
<TextField
id="outlined-full-width"
style= margin: 8
placeholder=text.PROFILE_EMAIL_PLACEHOLDER
value = user.email
disabled
fullWidth
margin="normal"
variant="outlined"
InputProps=
endAdornment: (
<InputAdornment>
<IconButton>
<EmailIcon/>
</IconButton>
</InputAdornment>
)
/>
</div>
<div style=paddingRight:"8px">
<form className=classes.container noValidate autoComplete="off">
<TextField
id="outlined-full-width"
style= margin: 8
placeholder=text.PROFILE_NAME
value=user.name_user
fullWidth
margin="normal"
variant="outlined"
InputProps=
endAdornment: (
<InputAdornment position="start">
<AccountCircle />
</InputAdornment>
)
/>
</form>
</div>
<div style=paddingRight:"8px">
<TextField
id="outlined-full-width"
style= margin: 8
value=user.address_user
placeholder=text.PROFILE_ADDRESS_PLACEHOLDER
fullWidth
margin="normal"
variant="outlined"
InputLabelProps=
shrink: true,
/>
</div>
<div style=paddingRight:"8px">
<form className=classes.container noValidate autoComplete="off">
<TextField
id="outlined-full-width"
style= margin: 8
value=user.city_user
label=text.PROFILE_CITY_PLACEHOLDER
className=classes.textField
fullWidth
margin="normal"
variant="outlined"
InputProps=
endAdornment: (
<InputAdornment position="start">
<LocationCityIcon/>
</InputAdornment>
)
/>
</form>
</div>
<div>
<TextField
id="outlined-select-currency"
select
value=user.country_user
label=text.PROFILE_COUNTRY_PLACEHOLDER
InputProps=
endAdornment: (
<InputAdornment>
<IconButton>
<FlagIcon/>
</IconButton>
</InputAdornment>
)
fullWidth
style= margin: 8, paddingRight: 8
SelectProps=
MenuProps:
className: classes.menu,
,
margin="normal"
variant="outlined"
/>
</div>
<div style=padding:"10px">
<Fab variant="contained" aria-label="delete" className=classes.fab>
text.PROFILE_CHANGE_PASSWORD_BUTTON_PLACEHOLDER
</Fab>
</div>
<div style=paddingRight:"8px">
<Typography variant="h4" > text.COMPANY_INFORMATION_TITLE</Typography>
<TextField
id="outlined-full-width"
style= margin: 8
placeholder=text.COMPANY_NAME_PLACEHOLDER
value=user.name_company
fullWidth
margin="normal"
variant="outlined"
InputLabelProps=
shrink: true,
/>
</div>
<div style=paddingLeft:"10px">
<form className=classes.container noValidate autoComplete="off">
<TextField
style=divStyle
id="outlined"
label=text.COMPANY_EU_VAT_PLACEHOLDER
value=user.vat_company
className=classes.textField
margin="normal"
variant="outlined"
/>
<TextField
style=div2Style
id="outlined"
label=text.COMPANY_NUMBER_PLACEHOLDER
value=user.registration_number_company
className=classes.textField
margin="normal"
variant="outlined"
/>
</form>
</div>
<div style=paddingRight:"8px">
<TextField
id="outlined-full-width"
style= margin: 8
value=user.address_company
placeholder=text.COMPANY_ADDRESS_PLACEHOLDER
fullWidth
margin="normal"
variant="outlined"
InputLabelProps=
shrink: true,
/>
</div>
<div style=paddingRight:"8px">
<form className=classes.container noValidate autoComplete="off">
<TextField
id="outlined-full-width"
style= margin: 8
label=text.COMPANY_CITY_PLACEHOLDER
value=user.city_company
className=classes.textField
fullWidth
margin="normal"
variant="outlined"
InputProps=
endAdornment: (
<InputAdornment position="start">
<LocationCityIcon/>
</InputAdornment>
)
/>
</form>
</div>
<div>
<TextField
id="outlined-select-currency"
select
label=text.COMPANY_COUNTRY_PLACEHOLDER
fullWidth
style= margin: 8, paddingRight: 8
SelectProps=
MenuProps:
className: classes.menu,
,
InputProps=
endAdornment: (
<InputAdornment>
<IconButton>
<FlagIcon/>
</IconButton>
</InputAdornment>
)
margin="normal"
variant="outlined"
/>
</div>
</FuseAnimateGroup>
</div>
<div className="flex flex-col md:w-320">
<FuseAnimateGroup
enter=
animation: "transition.slideUpBigIn"
>
<Button variant="contained" size="large" color="default" className=classes.button>
text.UPDATE_BUTTON_TEXT
</Button>
</FuseAnimateGroup>
</div>
</div>
);
else return <div>Loading...</div>
</UserContext.Consumer>
)
</TextContext.Consumer>
我尝试通过执行类似的操作来更新渲染内部的状态
<TextContext.Consumer>
(textContext) => (
<UserContext.Consumer>
(userConsumer) =>
const text = textContext.text;
const user = userConsumer.user;
this.setState(
user:user,
text: text,
)
</UserContext.Consumer>
)
</TextContext.Consumer>
这种方法的问题在于它会抛出“超出最大更新深度”。错误。
我该怎么办?
【问题讨论】:
解决这个问题的方法是编写一个组件,传递您感兴趣的两个上下文值,然后更新其状态。您不能在渲染方法中调用setState
,因为这会触发另一个渲染,这将触发另一个渲染,从而导致无限更新循环。但是,如果您已经通过上下文获得了它,为什么还要将它存储在本地状态中呢?组件没有任何状态并从外部获取数据是完全可以的。
因为我希望能够使用另一个 API 调用来更新后端的用户字段。
那么您想将它们用作表单的初始值来更新用户数据吗?然后,您应该提取一个组件,该组件以初始数据作为道具呈现该表单。
【参考方案1】:
“超过最大更新深度。”错误。
不要将setState()
放在render()
内。
我该怎么办?
只需 extract a component 即可。
const User = (props) =>
return (
<>
<span>props.user</span>
<span>props.text</span>
</>
);
// in render
<TextContext.Consumer>
(textContext) => (
<UserContext.Consumer>
(userConsumer) => (
<User
text=textContext.text
user=userConsumer.user
/>
))
</UserContext.Consumer>
)
</TextContext.Consumer>
<User />
仍然会在每次道具(用户、文本)更改时重新渲染。
【讨论】:
【参考方案2】:你不能更新渲染函数中的状态。
这样,您将处于渲染的无限循环中。每当您更改触发渲染功能的状态时,您都会再次更改状态,依此类推。
不管怎样,你不需要将这个状态存储在本地状态中来使用它,你可以直接从上下文中使用它。
【讨论】:
【参考方案3】:首先 - 你确定你真的需要在状态中存储上下文吗?我看不出有任何理由将上下文(始终可用)复制到状态。只使用上下文中的值,而不是状态中的值。
但是如果你真的需要它,你不能在render函数中更新状态,因为它会导致无限更新循环。有一些选项可以这样做:
提取组件:return (
<TextContext.Consumer>
( text ) => (
<UserContext.Consumer>
(user) => <ExtractedComponent text=text user=user />
</UserContext.Consumer>
)
</TextContext.Consumer>
);
那么你只需要覆盖getDerrivedStateFromProps()
为ExtractedComponent
以在道具更改时获得新状态。
if (state.user !== user || state.text !== text)
this.setState( user, text );
或许你可以切换到带有钩子的功能组件:
const YourComponent = () =>
const user = useContext(UserContext);
const text = useContext(TextContext);
const [ state, setState ] = useState();
useEffect(() =>
setState( user, text );
, [ user, text ]);
【讨论】:
以上是关于如何更新使用两个上下文消费者的组件的状态的主要内容,如果未能解决你的问题,请参考以下文章