【问题标题】:Re-render React component when prop changes当 prop 改变时重新渲染 React 组件
【发布时间】:2016-08-28 19:19:41
【问题描述】:

我正在尝试将展示组件与容器组件分开。我有一个SitesTable 和一个SitesTableContainer。容器负责触发 redux 操作以根据当前用户获取适当的站点。

问题是在容器组件最初呈现之后,当前用户是异步获取的。这意味着容器组件不知道它需要重新执行其componentDidMount 函数中的代码,该函数将更新要发送到SitesTable 的数据。我认为当容器组件之一的道具(用户)发生更改时,我需要重新渲染容器组件。我该如何正确地做到这一点?

class SitesTableContainer extends React.Component {
    static get propTypes() {
      return {
        sites: React.PropTypes.object,
        user: React.PropTypes.object,
        isManager: React.PropTypes.boolean
      }
     }

    componentDidMount() {
      if (this.props.isManager) {
        this.props.dispatch(actions.fetchAllSites())
      } else {
        const currentUserId = this.props.user.get('id')
        this.props.dispatch(actions.fetchUsersSites(currentUserId))
      }  
    }

    render() {
      return <SitesTable sites={this.props.sites}/>
    }
}

function mapStateToProps(state) {
  const user = userUtils.getCurrentUser(state)

  return {
    sites: state.get('sites'),
    user,
    isManager: userUtils.isManager(user)
  }
}

export default connect(mapStateToProps)(SitesTableContainer);

【问题讨论】:

  • 如果你想在 props 改变时触发某些东西,你确实有一些其他可用的功能,比如 componentDidUpdate,或者可能是你正在寻找的那个,componentWillReceiveProps(nextProps)
  • 如果 SitesTable 没有改变它的 props,为什么你需要重新渲染它?
  • @QoP 在componentDidMount 中调度的操作将更改应用程序状态下的sites 节点,该节点被传递到SitesTable。 SitesStable 的 sites 节点将发生变化。
  • 哦,我明白了,我要写答案了。
  • 如何在一个功能组件中实现这一点

标签: reactjs redux react-redux


【解决方案1】:

您必须在 componentDidUpdate 方法中添加条件。

示例是使用fast-deep-equal 来比较对象。

import equal from 'fast-deep-equal'

...

constructor(){
  this.updateUser = this.updateUser.bind(this);
}  

componentDidMount() {
  this.updateUser();
}

componentDidUpdate(prevProps) {
  if(!equal(this.props.user, prevProps.user)) // Check if it's a new user, you can also use some unique property, like the ID  (this.props.user.id !== prevProps.user.id)
  {
    this.updateUser();
  }
} 

updateUser() {
  if (this.props.isManager) {
    this.props.dispatch(actions.fetchAllSites())
  } else {
    const currentUserId = this.props.user.get('id')
    this.props.dispatch(actions.fetchUsersSites(currentUserId))
  }  
}

使用 Hooks(React 16.8.0+)

import React, { useEffect } from 'react';

const SitesTableContainer = ({
  user,
  isManager,
  dispatch,
  sites,
}) => {
  useEffect(() => {
    if(isManager) {
      dispatch(actions.fetchAllSites())
    } else {
      const currentUserId = user.get('id')
      dispatch(actions.fetchUsersSites(currentUserId))
    }
  }, [user]); 

  return (
    return <SitesTable sites={sites}/>
  )

}

如果您要比较的道具是对象或数组,则应使用useDeepCompareEffect 而不是useEffect

【讨论】:

  • 请注意 JSON.stringify 只能用于这种比较,如果它是稳定的(根据规范它不是),所以它为相同的输入产生相同的输出。我建议比较用户对象的 id 属性,或者在 props 中传递 userId-s,并比较它们,以避免不必要的重新加载。
  • 请注意,componentWillReceiveProps 生命周期方法已被弃用,并且可能会在 React 17 中被删除。结合使用 componentDidUpdate 和新的 getDerivedStateFromProps 方法是 React 开发团队的建议策略。更多内容请参阅他们的博文:reactjs.org/blog/2018/03/27/update-on-async-rendering.html
  • @QoP 第二个例子,使用 React Hooks,它会在user 改变时卸载和重新安装吗?这有多贵?
【解决方案2】:

ComponentWillReceiveProps() 将在未来因错误和不一致而被弃用。在 props 更改时重新渲染组件的另一种解决方案是使用 ComponentDidUpdate()ShouldComponentUpdate()

每当组件更新时调用ComponentDidUpdate(),如果ShouldComponentUpdate()返回true(如果ShouldComponentUpdate()未定义,则默认返回true)。

shouldComponentUpdate(nextProps){
    return nextProps.changedProp !== this.state.changedProp;
}

componentDidUpdate(props){
    // Desired operations: ex setting state
}

仅使用 ComponentDidUpdate() 方法可以通过在其中包含条件语句来实现相同的行为。

componentDidUpdate(prevProps){
    if(prevProps.changedProp !== this.props.changedProp){
        this.setState({          
            changedProp: this.props.changedProp
        });
    }
}

如果尝试在没有条件或没有定义ShouldComponentUpdate() 的情况下设置状态,组件将无限重新渲染

【讨论】:

  • 这个答案需要投票(至少现在),因为componentWillReceiveProps 即将被弃用,建议不要使用。
  • 第二种形式(componentDidUpdate 中的条件语句)对我有用,因为我希望其他状态更改仍然发生,例如关闭一条消息。
  • 有人在我的代码中使用了 shouldComponentUpdate,我不明白是什么原因导致了这个问题。
【解决方案3】:

您可以使用 KEY 唯一键(数据的组合)随着道具的变化而变化,并且该组件将使用更新的道具重新呈现。

【讨论】:

    【解决方案4】:
    componentWillReceiveProps(nextProps) { // your code here}
    

    我认为这是您需要的活动。 componentWillReceiveProps 每当你的组件通过 props 接收到东西时触发。从那里您可以进行检查,然后做任何您想做的事情。

    【讨论】:

    • componentWillReceiveProps 已弃用*
    【解决方案5】:

    我建议您看看我的answer,看看它是否与您正在做的事情相关。如果我理解你真正的问题,那就是你没有正确使用你的异步操作并更新了 redux“商店”,它会自动用它的新道具更新你的组件。

    这部分代码:

    componentDidMount() {
          if (this.props.isManager) {
            this.props.dispatch(actions.fetchAllSites())
          } else {
            const currentUserId = this.props.user.get('id')
            this.props.dispatch(actions.fetchUsersSites(currentUserId))
          }  
        }
    

    不应在组件中触发,应在执行第一个请求后处理。

    看看这个来自redux-thunk的例子:

    function makeASandwichWithSecretSauce(forPerson) {
    
      // Invert control!
      // Return a function that accepts `dispatch` so we can dispatch later.
      // Thunk middleware knows how to turn thunk async actions into actions.
    
      return function (dispatch) {
        return fetchSecretSauce().then(
          sauce => dispatch(makeASandwich(forPerson, sauce)),
          error => dispatch(apologize('The Sandwich Shop', forPerson, error))
        );
      };
    }
    

    您不一定必须使用 redux-thunk,但它会帮助您推理此类场景并编写匹配的代码。

    【讨论】:

    • 对,我明白了。但是,您究竟在哪里分派 makeASandwichWithSecretSauce 在您的组件中?
    • 我会将您链接到带有相关示例的仓库,您是否在您的应用中使用 react-router ?
    • @David 也会感谢该示例的链接,我也有基本相同的问题。
    【解决方案6】:

    一个友好的使用方法如下,一旦 prop 更新它会自动重新渲染组件:

    render {
    
    let textWhenComponentUpdate = this.props.text 
    
    return (
    <View>
      <Text>{textWhenComponentUpdate}</Text>
    </View>
    )
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-28
      • 2021-01-14
      • 2021-12-15
      • 1970-01-01
      • 1970-01-01
      • 2020-12-16
      • 2020-09-30
      • 2020-08-29
      相关资源
      最近更新 更多