如何更新使用两个上下文消费者的组件的状态

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>

&lt;User /&gt; 仍然会在每次道具(用户、文本)更改时重新渲染。

【讨论】:

【参考方案2】:

你不能更新渲染函数中的状态。

这样,您将处于渲染的无限循环中。每当您更改触发渲染功能的状态时,您都会再次更改状态,依此类推。

不管怎样,你不需要将这个状态存储在本地状态中来使用它,你可以直接从上下文中使用它。

【讨论】:

【参考方案3】:

首先 - 你确定你真的需要在状态中存储上下文吗?我看不出有任何理由将上下文(始终可用)复制到状态。只使用上下文中的值,而不是状态中的值。

但是如果你真的需要它,你不能在render函数中更新状态,因为它会导致无限更新循环。有一些选项可以这样做:

提取组件:
return (
  <TextContext.Consumer>
    ( text ) => (
      <UserContext.Consumer>
        (user) => <ExtractedComponent text=text user=user />
      </UserContext.Consumer>
    )
  </TextContext.Consumer>
);

那么你只需要覆盖getDerrivedStateFromProps()ExtractedComponent 以在道具更改时获得新状态。

【丑陋的方式】在render函数中进行条件更新,防止死循环:
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 ]);

【讨论】:

以上是关于如何更新使用两个上下文消费者的组件的状态的主要内容,如果未能解决你的问题,请参考以下文章

使用上下文和钩子更新未安装组件的状态 - 反应原生

通过反应上下文 api 将子状态传递给父组件

两个组件可以具有完全相同的状态,异步更新吗?

React 上下文需要 2 次状态更新才能让消费者重新渲染

使用Hook更新上下文状态值

当提供者组件父状态发生变化时,为啥不能更新子组件中的值(带有反应上下文)?