【问题标题】:React: why child component doesn't update when prop changesReact:为什么子组件在道具更改时不更新
【发布时间】:2016-12-17 23:23:55
【问题描述】:

为什么在下面的伪代码示例中,当 Container 更改 foo.bar 时 Child 不重新渲染?

Container {
  handleEvent() {
    this.props.foo.bar = 123
  },

  render() {
    return <Child bar={this.props.foo.bar} />
}

Child {
  render() {
    return <div>{this.props.bar}</div>
  }
}

即使我在修改Container中的值后调用forceUpdate(),Child仍然显示旧值。

【问题讨论】:

  • 这是你的代码吗?似乎它不是有效的 React 代码
  • 我认为容器组件中的 props 值不应该改变,而是应该通过 setState 在父组件中更改,并且该状态应该映射到容器 props
  • 像这样使用扩展运算符
  • @AdrianWydmanski 和另外 5 位点赞的人:en.wikipedia.org/wiki/Pseudocode
  • @PiyushPatel 属性会在组件重新就地重新渲染时更新,如伪代码示例所示。另一个例子是使用&lt;Route exact path="/user/:email" component={ListUserMessagePage} /&gt;,同一页面上的链接将更新道具,而无需创建新实例并运行通常的生命周期事件。

标签: javascript html reactjs


【解决方案1】:

更新孩子,使其属性“key”等于名称。每次 key 改变时组件都会重新渲染。

Child {
  render() {
    return <div key={this.props.bar}>{this.props.bar}</div>
  }
}

【讨论】:

  • 谢谢。我正在使用 redux 商店,在添加密钥之前无法让我的组件重新渲染。 (我使用了 uuid 库来生成随机密钥,并且成功了)。
  • 这也是我需要的。我很困惑,什么时候需要key,而不是setState?我有一些依赖父母状态的孩子,但他们没有触发重新渲染......
  • 我使用索引作为键,但它不能正常工作。现在我正在使用 uuid,它很完美 :) 感谢@Maiya
  • @dcsan 我认为原因是 react 在底层使用 key 属性来计算通过 map 动态呈现的项目是否已被移动等。
  • 在我的情况下,仅将密钥作为道具发送到父级也进行了重新渲染
【解决方案2】:

因为如果父级的道具发生变化,子级不会重新渲染,但如果它的状态发生变化:)

你展示的是这样的: https://facebook.github.io/react/tips/communicate-between-components.html

它将通过 props 将数据从父级传递给子级,但那里没有重新渲染逻辑。

您需要为父级设置一些状态,然后在父级更改状态时重新渲染子级。 这可能会有所帮助。 https://facebook.github.io/react/tips/expose-component-functions.html

【讨论】:

  • 为什么 setState 会导致重新渲染而 forceUpdate 不会?也很少离题,但是当 props 从 redux 传递并且它的状态通过 action 更新时,组件是如何更新的?
  • props 不会更新,即使你更新了你传递的 props 仍然存在的组件。通量是另一个主题是的:)
  • state != 你需要了解更多相关的道具。您不会使用道具进行更新
  • 源码里可以直接看,根本不是同一个过程
  • 这是不正确的:reactjs.org/docs/react-component.html 如果 Child 和 Container 是组件,则会发生重新渲染。
【解决方案3】:

我遇到了同样的问题。 这是我的解决方案,我不确定这是不是好的做法,如果不是,请告诉我:

state = {
  value: this.props.value
};

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

UPD:现在你可以使用 React Hooks 做同样的事情: (仅当组件是函数时)

const [value, setValue] = useState(propName);
// This will launch only if propName value has chaged.
useEffect(() => { setValue(propName) }, [propName]);

【讨论】:

  • 这绝对是正确答案,更不用说最简单了
  • 我也在用这种方法。
  • @vancy-pants 在这种情况下,componentDidUpdate 位于子组件中。
  • 您不需要也不应该将道具转换为子组件中的状态。直接使用道具即可。在FunctionComponents 中,如果 props 发生变化,它会自动更新子组件。
  • 当您在从父状态派生的子组件中有道具时,此方法也适用
【解决方案4】:

已确认,添加 Key 有效。我浏览了文档以尝试了解原因。

React 希望在创建子组件时高效。如果它与另一个子组件相同,则不会渲染新组件,从而使页面加载更快。

添加一个 Key 会强制 React 渲染一个新组件,从而重置该新组件的状态。

https://reactjs.org/docs/reconciliation.html#recursing-on-children

【讨论】:

    【解决方案5】:

    当从 functionsuseState 创建 React 组件时。

    const [drawerState, setDrawerState] = useState(false);
    
    const toggleDrawer = () => {
          // attempting to trigger re-render
          setDrawerState(!drawerState);
    };
    

    工作

             <Drawer
                drawerState={drawerState}
                toggleDrawer={toggleDrawer}
             />
    

    确实有效(添加密钥)

             <Drawer
                drawerState={drawerState}
                key={drawerState}
                toggleDrawer={toggleDrawer}
             />
    

    【讨论】:

    • 谢谢你
    【解决方案6】:

    根据 React 哲学,组件不能改变它的 props。它们应该从父级接收并且应该是不可变的。只有父母可以更改其孩子的道具。

    关于state vs props的很好解释

    另外,请阅读此主题Why can't I update props in react.js?

    【讨论】:

    • 这不是也意味着容器不能修改它从redux store接收到的props吗?
    • Container 不直接从 store 接收 props,它们是通过读取 redux 状态树的一部分来提供的(令人困惑??)。!!困惑是因为我们不知道 react-redux 的神奇功能 connect。如果您看到 connect 方法的源代码,基本上它会创建一个高阶组件,该组件具有具有属性 storeState 的状态并订阅了 redux 存储。随着 storeState 的变化,整个重新渲染都会发生,并且通过读取更改后的状态将修改后的 props 提供给容器。希望这能回答问题
    • 很好的解释,考虑高阶组件有助于我理解发生了什么
    • 家长也不能更换孩子的道具。
    • 取决于您要达到的目标。父母不能在孩子道具周围戳来改变它。父母需要重新渲染自己以将更新的道具传递给孩子。从这个意义上说,父母可以改变孩子的道具。
    【解决方案7】:

    您应该使用setState 函数。否则,无论您如何使用 forceUpdate,state 都不会保存您的更改。

    Container {
        handleEvent= () => { // use arrow function
            //this.props.foo.bar = 123
            //You should use setState to set value like this:
            this.setState({foo: {bar: 123}});
        };
    
        render() {
            return <Child bar={this.state.foo.bar} />
        }
        Child {
            render() {
                return <div>{this.props.bar}</div>
            }
        }
    }
    

    您的代码似乎无效。我无法测试此代码。

    【讨论】:

    • 不应该是&lt;Child bar={this.state.foo.bar} /&gt;吗?
    • 对。我更新答案。但正如我所说,这可能不是一个有效的代码。只需简单地从问题中复制即可。
    【解决方案8】:

    你一定用过动态组件。

    在这段代码 sn-p 中,我们多次渲染子组件并传递 key

    • 如果我们多次动态渲染一个组件,那么 React 不会渲染该组件,直到它的键被更改。

    如果我们使用 setState 方法更改 checked。在我们改变它的key之前,它不会反映在子组件中。我们必须将其保存在孩子的状态中,然后将其更改为相应地渲染孩子。

    class Parent extends Component {
        state = {
            checked: true
        }
        render() {
            return (
                <div className="parent">
                    {
                        [1, 2, 3].map(
                            n => <div key={n}>
                                <Child isChecked={this.state.checked} />
                            </div>
                        )
                    }
                </div>
            );
        }
    }

    【讨论】:

      【解决方案9】:

      我的案例涉及在 props 对象上有多个属性,并且需要在更改其中任何一个时重新渲染 Child。 上面提供的解决方案是有效的,但是为每个解决方案添加一个密钥变得乏味和肮脏(想象一下有 15 个......)。如果有人遇到这个问题 - 您可能会发现字符串化 props 对象很有用:

      <Child
          key={JSON.stringify(props)}
      />
      

      这样,props 上每个属性的每次更改都会触发 Child 组件的重新渲染。

      希望对某人有所帮助。

      【讨论】:

        【解决方案10】:

        遵守不变性

        这是一个很老的问题,但它是一个长期存在的问题,如果只有错误的答案和解决方法,它并不会变得更好。 子对象不更新的原因不是缺少key或缺少状态,原因是你不遵守不变性原则。

        react 的目的是让应用程序更快、响应更快、更容易维护等等,但你必须遵循一些原则。只有在必要时才会重新渲染 React 节点,即如果它们已更新。 react 如何知道组件是否已更新?因为它的状态已经改变。现在不要将它与 setState 挂钩混为一谈。状态是一个概念,每个组件都有它的状态。状态是组件在给定时间点的外观或行为。如果您有一个静态组件,那么您始终只有一个状态,而不必照顾它。如果组件必须以某种方式改变,它的状态就会改变。

        现在 react 非常具有描述性。组件的状态可以从一些变化的信息中得出,这些信息可以存储在组件的外部或内部。如果信息存储在内部,则这是组件必须自行跟踪的一些信息,我们通常使用 setState 之类的钩子来管理这些信息。如果此信息存储在我们的组件之外,那么它会存储在不同的组件内部,并且必须跟踪它,即它们的状态。现在其他组件可以通过 props 将它们的状态传递给我们。

        这意味着如果我们自己的托管状态发生更改或通过 props 传入的信息发生更改,则 react 会重新渲染。这是自然行为,您不必将道具数据传输到您自己的状态。 现在到了非常重要的一点:react 如何知道信息何时发生变化? 简单:就是进行比较!每当你设置一些状态或给反应一些它必须考虑的信息时,它会将新给定的信息与实际存储的信息进行比较,如果它们不相同,反应将重新呈现该信息的所有依赖关系。 在这方面不一样意味着 javascript === 运算符。 也许你已经明白了。 让我们看看这个:

        let a = 42;
        let b = a;
        console.log('is a the same as b?',a === b); // a and b are the same, right? --> true
        
        a += 5; // now let's change a
        console.log('is a still the same as b?',a === b); // --> false

        我们正在创建一个值的实例,然后创建另一个实例,将第一个实例的值分配给第二个实例,然后更改第一个实例。 现在让我们看一下对象的相同流程:

        let a = { num: 42}; 
        let b = a; 
        console.log('is a the same as b?',a === b); // a and b are the same, right? --> true
        a.num += 5; // now let's change a
        console.log('is a still the same as b?',a === b); // --> true
        这次的不同之处在于,一个对象实际上是一个指向内存区域的指针,并且在断言 b=a 时,您将 b 设置为与指向完全相同对象的 a 相同的指针。 您在 a 中所做的任何事情都可以通过您的 a 指针或 b 指针访问。 您的线路:
        this.props.foo.bar = 123
        

        实际上更新了“this”指向的内存中的一个值。 React 根本无法通过比较对象引用来识别此类更改。您可以将对象的内容更改一千次,并且引用将始终保持不变,并且 react 不会重新渲染相关组件。 这就是为什么您必须将反应中的所有变量视为不可变的。要进行可检测的更改,您需要不同的参考,并且只能通过新对象获得。因此,您不必更改对象,而是必须将其复制到新对象,然后您可以更改其中的一些值,然后再将其移交给做出反应。 看:

        let a = {num: 42};
        console.log('a looks like', a);
        let b = {...a};
        console.log('b looks like', b);
        console.log('is a the same as b?', a === b); // --> false
        扩展运算符(带有三个点的运算符)或 map 函数是将数据复制到新对象或数组的常用方法。

        如果您遵守不变性,则所有子节点都会使用新的道具数据进行更新。

        【讨论】:

          【解决方案11】:

          如果 Child 不维护任何状态并且只是渲染 props 然后从父级调用它,您可能应该将 Child 作为功能组件。替代方案是您可以将钩子与功能组件(useState)一起使用,这将导致无状态组件重新呈现。

          此外,您不应该更改 propas,因为它们是不可变的。维护组件的状态。

          Child = ({bar}) => (bar);
          

          【讨论】:

            【解决方案12】:
            export default function DataTable({ col, row }) {
              const [datatable, setDatatable] = React.useState({});
              useEffect(() => {
                setDatatable({
                  columns: col,
                  rows: row,
                });
              /// do any thing else 
              }, [row]);
            
              return (
                <MDBDataTableV5
                  hover
                  entriesOptions={[5, 20, 25]}
                  entries={5}
                  pagesAmount={4}
                  data={datatable}
                />
              );
            }
            

            此示例使用useEffectprops 更改时更改状态。

            【讨论】:

              【解决方案13】:

              你可以使用componentWillReceiveProps:

              componentWillReceiveProps({bar}) {
                  this.setState({...this.state, bar})
              }
              

              感谢Josh Lunsford

              【讨论】:

              • 子组件或父组件在哪里?
              • 那么你将所有的道具复制到你的状态中?
              【解决方案14】:

              我遇到了同样的问题。 我有一个Tooltip 组件正在接收showTooltip 道具,我正在基于if 条件更新Parent 组件,它在Parent 组件中得到更新,但Tooltip 组件没有呈现。

              const Parent = () => {
                 let showTooltip = false;
                 if(....){ showTooltip = true; }
                 return(
                    <Tooltip showTooltip={showTooltip}></Tooltip>
                 )
              }
              

              我犯的错误是将showTooltip 声明为let。

              我意识到我做错了我违反了渲染工作原理,用钩子替换它就可以了。

              const [showTooltip, setShowTooltip] =  React.useState<boolean>(false);
              

              【讨论】:

                【解决方案15】:

                在子组件的connect方法的mapStateToProps中定义改变的props。

                function mapStateToProps(state) {
                  return {
                    chanelList: state.messaging.chanelList,
                  };
                }
                
                export default connect(mapStateToProps)(ChannelItem);
                

                就我而言,channelList 的频道已更新,因此我在 mapStateToProps 中添加了 chanelList

                【讨论】:

                  【解决方案16】:

                  在我的例子中,我正在更新传递给组件的loading 状态。在 Button 中,props.loading 按预期通过(从 false 切换到 true),但显示微调器的三元组没有更新。

                  我尝试添加一个键,添加一个使用 useEffect() 等更新的状态,但其他答案均无效。

                  对我有用的是改变这个:

                  setLoading(true);
                  handleOtherCPUHeavyCode();
                  

                  到这里:

                  setLoading(true);
                  setTimeout(() => { handleOtherCPUHeavyCode() }, 1)
                  

                  我认为这是因为handleOtherCPUHeavyCode 中的过程非常繁重和密集,因此应用程序冻结了一秒钟左右。添加 1ms 超时允许加载布尔值更新,然后繁重的代码函数可以完成它的工作。

                  【讨论】:

                    【解决方案17】:

                    安装包 npm install react-native-uuid 要么 yarn add react-native-uuid

                    import ...
                    import uuid from 'react-native-uuid';
                    

                    将此方法用于 uuid for child 将解决重新渲染问题 uuid.v4();

                    【讨论】:

                      猜你喜欢
                      • 2019-03-03
                      • 2018-08-09
                      • 2021-08-20
                      • 2020-01-19
                      • 2021-02-25
                      • 2017-05-10
                      • 2019-05-25
                      • 2021-09-01
                      相关资源
                      最近更新 更多