当 prop 改变时重新渲染 React 组件

IT技术 reactjs redux react-redux
2021-04-20 10:55:12

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

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

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);
6个回答

您必须在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))
  }  
}

使用钩子 (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}/>
  )

}

如果您要比较的props是对象或数组,则应使用useDeepCompareEffect代替useEffect

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

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()组件的情况下设置状态将无限重新渲染

有人在我的代码中使用了 shouldComponentUpdate ,我不明白是什么导致了这个问题。
2021-05-24 10:55:12
第二种形式(componentDidUpdate 内的条件语句)对我有用,因为我希望其他状态更改仍会发生,例如关闭 flash 消息。
2021-05-25 10:55:12
这个答案需要被赞成(至少现在是这样),因为componentWillReceiveProps它即将被弃用并建议不要使用。
2021-06-07 10:55:12

您可以使用KEY随props更改的唯一键(数据的组合),并且该组件将使用更新的props重新呈现。

componentWillReceiveProps(nextProps) { // your code here}

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

componentWillReceiveProps 已弃用*
2021-06-04 10:55:12

我建议看看我的这个答案,看看它是否与你正在做的事情有关。如果我理解你真正的问题,那就是你没有正确使用你的异步操作并更新 redux “store”,它会自动用它的新props更新你的组件。

代码的这一部分:

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,但它会帮助您推理这样的场景并编写匹配的代码。

@David 也会感谢该示例的链接,我有基本相同的问题。
2021-06-01 10:55:12
对,我明白了。但是你到底makeASandwichWithSecretSauce 在你的组件中在哪里调度
2021-06-11 10:55:12
我会用一个相关的例子将你链接到一个 repo,你是否在你的应用中使用 react-router ?
2021-06-18 10:55:12