React:为什么当道具改变时子组件不更新

为什么在下面的伪代码示例中,当容器更改foo.bar时,子不重新呈现?

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仍然显示旧值。

351267 次浏览

因为如果父对象的道具改变了,子对象不会重新渲染,但是如果它的STATE改变了:)

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

它将通过道具将数据从父对象传递到子对象,但没有渲染逻辑。

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

你应该使用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>
}
}
}

你的代码似乎无效。我不能测试这段代码。

根据React的理念,组件不能改变它的道具。它们应该从父进程接收,并且应该是不可变的。只有父节点可以更改其子节点的道具。

国家vs道具的解释很好

同时,读取这个线程为什么我不能在react.js中更新道具?

更新子进程以使属性'key'与名称相等。每次键改变时,组件都会重新呈现。

Child {
render() {
return <div key={this.props.bar}>{this.props.bar}</div>
}
}
我也有同样的问题。 这是我的解决方案,我不确定这是好的做法,如果不是告诉我:

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


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

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

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

如果Child不维护任何状态,而只是呈现道具,然后从父组件调用它,那么您可能应该将它作为功能组件。除此之外,您还可以使用与功能组件(useState)的挂钩,这将导致无状态组件重新呈现。

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

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

确认,添加一个密钥工作。我看了所有的文件想弄清楚原因。

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

添加Key会迫使React呈现新组件,从而重置新组件的状态。

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

我也遇到了同样的问题。 我有一个正在接收showTooltip道具的Tooltip组件,我正在根据if条件在Parent组件上更新它,它正在Parent组件中更新,但Tooltip组件没有呈现。< / p >
const Parent = () => {
let showTooltip = false;
if(....){ showTooltip = true; }
return(
<Tooltip showTooltip={showTooltip}></Tooltip>
)
}

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

我意识到我做错了什么,我违反了渲染工作的原则,用钩子代替它。

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

当从functionsuseState创建React组件时。

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


const toggleDrawer = () => {
// attempting to trigger re-render
setDrawerState(!drawerState);
};

这是不< em > < / em >工作

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

这个< em >工作< / em >(添加键)

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

在子组件connect方法的mapStateToProps中定义更改的道具。

function mapStateToProps(state) {
return {
chanelList: state.messaging.chanelList,
};
}


export default connect(mapStateToProps)(ChannelItem);

在我的例子中,channelList的通道被更新了,所以我在mapStateToProps中添加了chanelList

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改变时改变状态。

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

我尝试添加一个键,添加一个状态,更新useEffect()等,但没有其他答案工作。

对我有效的是改变这一点:

setLoading(true);
handleOtherCPUHeavyCode();

:

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

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

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

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

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

希望这能帮到别人。

你必须使用动态组件。

在这个代码片段中,我们多次渲染子组件,并传递关键

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

如果我们使用设置状态方法改变检查。它不会反映在子组件中,直到我们改变它的关键。我们必须在child的状态上保存它然后相应地将它改为渲染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>
);
}
}

你可以使用componentWillReceiveProps:

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

功劳归杰克柯。

遵守不变性

这是一个很老的问题,但它是一个常青的问题,如果只有错误的答案和变通方法,它不会变得更好。 子对象不更新的原因不是缺少键或缺少状态,而是你没有遵守不变性原则

react的目标是使应用程序更快、更灵敏、更容易维护等等,但你必须遵循一些原则。React节点只有在必要时才会被重新渲染,也就是说,如果它们已经更新了。react如何知道组件是否已更新?因为它的状态改变了。现在不要把它和setState钩子混在一起。状态是一个概念,每个组件都有自己的状态。状态是组件在给定时间点的外观或行为。如果你有一个静态组件,你就一直只有一个状态,不需要照顾它。如果组件必须改变,那么它的状态也在改变。

react非常具有描述性。组件的状态可以从一些变化的信息中获得,这些信息可以存储在组件的外部或内部。如果信息存储在内部,那么组件必须跟踪自己的一些信息,我们通常使用setState之类的钩子来管理这些信息。如果这个信息存储在我们的组件之外,那么它就存储在另一个组件中,那个组件必须跟踪它,它的状态。现在另一个组件可以通过道具传递给我们它们的状态。

这意味着如果我们自己的托管状态发生了变化,或者通过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

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

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指针访问。 你的线:< / p >
this.props.foo.bar = 123

实际上更新了内存中的一个值,其中"this"指向。 React无法通过比较对象引用来识别这样的更改。你可以改变对象的内容一千次,而引用将始终保持不变,react不会对依赖的组件进行重新渲染。 这就是为什么你必须考虑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-函数是将数据复制到新对象或数组的常用方法

如果你遵守不可变性,所有的子节点更新新的道具数据。

我有同样的问题的重新渲染对象道具,如果道具是一个对象JSON.stringify(obj),并将其设置为功能组件的key。在key上仅为react钩子设置id对我来说不起作用。奇怪的是,要更新组件,你必须包括key上的所有对象属性,并将其连接到那里。

function Child(props) {
const [thing, setThing] = useState(props.something)
  

return (
<>
<div>{thing.a}</div>
<div>{thing.b}</div>
</>
)
}


...


function Caller() {
const thing = [{a: 1, b: 2}, {a: 3, b: 4}]
thing.map(t => (
<Child key={JSON.stringify(t)} something={thing} />
))
}

现在,只要thing对象在运行时改变了它的值,Child组件就会正确地重新呈现它。

考虑到道具的渲染限制和状态的增益,如果你使用反应钩子,你可以使用一些技巧。例如,您可以使用useEffect手动将道具转换为状态。这可能不是最好的做法,但在这些情况下是有帮助的。

import { isEqual } from 'lodash';
import { useEffect, useState } from 'react';


export const MyComponent = (props: { users: [] }) => {
const [usersState, setUsersState] = useState([]);


useEffect(() => {
if (!isEqual(props.users, usersState)) {
setUsersState(props.users);
}
}, [props.users]);


<OtherComponent users={usersState} />;
};