React:当prop更改时,为什么子组件不会更新

Tuo*_*nen 90 html javascript reactjs

为什么在以下伪代码示例中,当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>
  }
}
Run Code Online (Sandbox Code Playgroud)

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

Fra*_*ard 69

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

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

它会通过props将数据从父级传递给子级,但是那里没有重新构造逻辑.

您需要将某个状态设置为父级,然后在父级更改状态下重新呈现子级.这可能有所帮助. https://facebook.github.io/react/tips/expose-component-functions.html

  • 为什么setState会导致重新渲染但forceUpdate不会?也很少偏离主题,但是当从redux传递道具并且它的状态通过动作更新时,组件是如何更新的? (5认同)
  • state!=道具你需要阅读更多相关信息.您不使用道具进行更新 (3认同)

pol*_*a-c 50

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

Child {
  render() {
    return <div key={this.props.bar}>{this.props.bar}</div>
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 但为什么?这是一个黑客行为。如果孩子的道具里有三四件东西我该怎么办?将它们串成一个可笑的 hack 来解决 React 不按照广告宣传的那样工作? (10认同)
  • 这个答案可能有效,但它缺乏关于为什么组件在其 props 更改时不更新的正确解释,请参阅下面的 @François Richard 以获得正确的解释(组件仅渲染有两个原因:1.初始渲染和 2.组件的或者它的祖先状态之一已被更新。此外,这是一种相当混乱的黑客行为,而不是一个优雅的解决方案。 (5认同)
  • 这应该是公认的答案 (3认同)
  • 我使用索引作为键,但它无法正常工作。现在我正在使用 uuid,它很完美:) 感谢@Maiya (3认同)
  • 谢谢你 我使用的是redux存储,直到添加了密钥,我才能重新渲染组件。(我使用uuid库生成随机密钥,并且它起作用了)。 (2认同)
  • 这也是我所需要的。我很困惑,什么时候需要`key`而不是`setState`?我有一些孩子依靠父母陈述,但他们并没有触发重新渲染... (2认同)
  • @dcsan我认为原因是react使用引擎盖下的关键属性来计算通过地图动态渲染的项目是否已被移动等。 (2认同)

小智 45

遵守不变性

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

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

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

这意味着如果我们自己的托管状态发生变化或者通过 props 传入的信息发生变化,React 就会重新渲染。这是自然行为,您不必将 props 数据传输到您自己的状态中。现在非常重要的一点来了:React 如何知道信息何时发生了变化?简单:就是做个比较!每当你设置一些状态或给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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)
这次的不同之处在于,对象实际上是指向内存区域的指针,并且通过断言 b=a,您将 b 设置为与 a 相同的指针,从而指向完全相同的对象。无论您在 a 中做什么,都可以通过 a 指针或 b 指针访问。您的线路:

this.props.foo.bar = 123
Run Code Online (Sandbox Code Playgroud)

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

this.props.foo.bar = 123
Run Code Online (Sandbox Code Playgroud)
扩展运算符(带有三个点的运算符)或映射函数是将数据复制到新对象或数组的常见方法。

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

  • 每个新的 React 开发人员都应该阅读这个答案,直到他们完全理解为止。换句话说:如果父级仅更改一个对象属性并且需要传播到子级,则在子级 prop 对象上使用展开。更好的是,使每个对象属性成为单独的父状态/子属性。 (3认同)

pet*_*hor 19

我有同样的问题。这是我的解决方案,我不确定这是否是个好习惯,如果不是,请告诉我:

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

componentDidUpdate(prevProps) {
  if(prevProps.value !== this.props.value) {
    this.setState({value: this.props.value});
  }
}
Run Code Online (Sandbox Code Playgroud)

UPD:现在您可以使用React Hooks执行相同的操作:(仅当component是一个函数时)

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

  • 您不需要也不应该将 props 转换为子组件中的 state。直接使用道具就可以了。在 `FunctionComponents` 中,如果 props 发生变化,它会自动更新子组件。 (10认同)
  • 这绝对是正确的答案,更不用说最简单的了 (5认同)
  • 我也在使用这种方法。 (2认同)
  • @oemera 这正是我所做的。当从父级发送到子级的道具更新时,子级并没有相应地改变其状态。我想知道为什么状态不遵循道具,如果它是基于它们设置的。 (2认同)

tjr*_*226 15

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

React 希望在创建子组件时高效。如果它与另一个子组件相同,则不会呈现新组件,这会使页面加载速度更快。

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

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


Nel*_*les 14

当从functions和创建 React 组件时useState

const [drawerState, setDrawerState] = useState(false);

const toggleDrawer = () => {
      // attempting to trigger re-render
      setDrawerState(!drawerState);
};
Run Code Online (Sandbox Code Playgroud)

这确实工作

         <Drawer
            drawerState={drawerState}
            toggleDrawer={toggleDrawer}
         />
Run Code Online (Sandbox Code Playgroud)

确实有效(添加密钥)

         <Drawer
            drawerState={drawerState}
            key={drawerState}
            toggleDrawer={toggleDrawer}
         />
Run Code Online (Sandbox Code Playgroud)


Suj*_*are 10

根据React哲学组件无法改变其道具.它们应该从父母那里收到,并且应该是不可改变的.只有父母可以改变其子女的道具.

状态与道具的好解释

另外,阅读这个帖子为什么我不能更新react.js中的道具?

  • Container不直接从商店接收道具,它们是通过读取redux状态树的一部分来提供的(令人困惑的??).!! 混淆是因为我们不知道反应还原的神奇功能.如果你看到connect方法的源代码,基本上它会创建一个更高阶的组件,它具有属性storeState的状态并且订阅了redux存储.当storeState更改时,整个重新呈现发生,并且通过读取更改的状态将修改的props提供给容器.希望这能回答这个问题 (3认同)

Jam*_*Yin 6

你应该使用setState功能.

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>
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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


小智 6

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

<Child
    key={JSON.stringify(props)}
/>
Run Code Online (Sandbox Code Playgroud)

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

希望对某人有所帮助。


小智 6

您一定使用过动态组件。

在此代码片段中,我们多次渲染子组件并传递key

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

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

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>
        );
    }
}
Run Code Online (Sandbox Code Playgroud)