道具更改时重新渲染React组件


90

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

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

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);

您确实还有其他可用的功能,例如componentDidUpdate,或者如果您想在道具更改时触发一些操作,则可能是您想要的组件componentWillReceiveProps(nextProps)
thsorens

如果未更改道具,为什么需要重新渲染SitesTable?
QoP

@QoP,正在分派的动作componentDidMount将更改sites处于应用程序状态的节点,该节点将传递到中SitesTable。SitesStable的sites节点将更改。
大卫

哦,知道了,我要写答案。
QoP

1
如何在功能组件中实现这一目标
yaswanthkoneri,

Answers:


115

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

}

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


请注意,如果JSON.stringify是稳定的(根据规范不稳定),则只能用于这种比较,因此它会为相同的输入产生相同的输出。我建议比较用户对象的id属性,或在道具中传递userId-s并进行比较,以避免不必要的重载。
拉斯洛KARDINAL

4
请注意,componentWillReceiveProps生命周期方法已被弃用,并有可能在React 17中删除。React开发团队建议使用componentDidUpdate和新的getDerivedStateFromProps方法的组合。他们博客中的更多内容:reactjs.org/blog/2018/03/27/update-on-async-rendering.html
michaelpoltorak

@QoP第二个示例,带有React Hooks,是否会在user更改时卸载并重新安装?这多少钱?
Robotron

30

ComponentWillReceiveProps()由于错误和不一致,将来将不推荐使用。在props更改时重新渲染组件的另一种解决方案是使用ComponentDidUpdate()ShouldComponentUpdate()

ComponentDidUpdate()每当组件更新时调用,并且ifShouldComponentUpdate()返回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()组件的情况下设置状态,则会无限地重新渲染


2
由于componentWillReceiveProps即将弃用此建议的答案(目前至少),建议不要使用。
AnBisw '18

第二种形式(componentDidUpdate内部的条件语句)对我有用,因为我希望其他状态更改仍然发生,例如,关闭Flash消息。
Little Brain

11

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


4
componentWillReceiveProps(nextProps) { // your code here}

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


11
componentWillReceiveProps已弃用*
Maihan Nijat

2

我建议看看我的这个答案,看看它是否与您的工作有关。如果我了解您的真正问题,那就是您只是没有正确使用异步操作而更新了redux“ store”,这将自动使用新道具来更新您的组件。

您的代码的这一部分:

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?
TameBadger '16

@David也将感谢该示例的链接,我基本上有同样的问题。
SamYoungNY

0

以下是一种易于使用的方法,道具更新后,它将自动重新渲染组件:

render {

let textWhenComponentUpdate = this.props.text 

return (
<View>
  <Text>{textWhenComponentUpdate}</Text>
</View>
)

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.