英文:
Directly modifying a global variable in a React component leads to unexpected render results
问题
I am currently learning react and have stumbled upon a section in their 'tutorial' section called 'Describing the UI'. I can't grasp my mind around understanding a subsection called 'Keeping components pure'.
Given this code:
let guest = 0;
function Cup() {
// Bad: changing a preexisting variable!
guest = guest + 1;
return <h2>Tea cup for guest #{guest}</h2>
}
export default function TeaSet() {
return (
<>
<Cup />
<Cup />
<Cup />
</>
);
}
Why does the output look like this:
Tea cup for guest #2
Tea cup for guest #4
Tea cup for guest #6
And not like this:
Tea cup for guest #1
Tea cup for guest #2
Tea cup for guest #3
I tried to figure it out myself but I can't.
英文:
I am currently learning react and have stumbled upon a section in their 'tutorial' section called 'Describing the UI'. I can't grasp my mind around understanding a subsection called 'Keeping components pure'.
The thing I don't understand is this:
Given this code:
let guest = 0;
function Cup() {
// Bad: changing a preexisting variable!
guest = guest + 1;
return <h2>Tea cup for guest #{guest}</h2>;
}
export default function TeaSet() {
return (
<>
<Cup />
<Cup />
<Cup />
</>
);
}
Why does the output look like this:
Tea cup for guest #2
Tea cup for guest #4
Tea cup for guest #6
And not like this:
Tea cup for guest #1
Tea cup for guest #2
Tea cup for guest #3
I tried to figure it out myself but I can't.
答案1
得分: 2
这可能是因为在开发模式下,React 在挂载时会两次渲染所有内容。您可以通过将以下内容添加到 Cup
来观察到这一点:
useEffect(() => {
console.log('cup');
}, []);
尽管 useEffect
没有依赖项并且应该只运行一次,但您会看到每个 Cup
打印两次 "cup"。
考虑到这一点,每个 Cup
组件都被渲染了两次。您有三个,因此最终渲染会在每个组件的第二次渲染之后显示结果,分别为 2、4 和 6。
然而,像这样将状态保存在全局变量中不是一个好做法,会导致意外的行为。鉴于您提到 Cup
应该是一个 "纯" 组件,它对全局变量 guest
的依赖是不直观的。
更好的方法是告诉 Cup
接受一些输入,并始终显示该输入。这被称为 props
(缩写为 properties
),它允许您基于在其他地方管理的状态纯粹地渲染杯子。一个建议的示例:
function Cup({ guest }) {
// 我们从props中解构guest
return <h2>Tea cup for guest #{guest}</h2>;
}
export default function TeaSet() {
return (
<>
<Cup guest={1} />
<Cup guest={2} />
<Cup guest={3} />
</>
);
}
这打开了在 TesSet
中保持客人状态并对其进行循环处理的可能性。更高级的示例:
function TeaSet() {
const [guests, setGuests] = useState([1, 2, 3]);
return (
<>
{guests.map(guest => <Cup key={guest} guest={guest} />)}
</>
);
}
这使您可以在 TeaSet
中从内部操纵状态,而不会影响现有客人的个别杯子,并且不依赖于任何全局变量,这可能以意外的方式处理。
英文:
This likely happens becuase, in development mode, React renders everything twice on mount. You can witness this by adding the following into Cup
:
useEffect(() => {
console.log('cup');
}, []);
Even though the useEffect
has no dependencies and should only run once, you would see "cup" printed twice per Cup
.
With that in mind, each Cup
component is being rendered twice. You have three, and therefore the final render displays the result after the second render for each, settling on 2, 4, and 6.
However, keeping state in a global variable like this is not the move, and leads to unexpected behaviour. Given that, as you mention, Cup
should be a "pure" component, its dependency on the global variable guest
is counter-intuitive.
A better approach would be to tell Cup
to take some input, and always display that input. This is called props
(short for properties
), and allows you to render your cups purely based on a state, which is managed elsewhere. A serving suggestion:
function Cup({ guest }) {
// we destructure the guest from the props
return <h2>Tea cup for guest #{guest}</h2>;
}
export default function TeaSet() {
return (
<>
<Cup guest={1} />
<Cup guest={2} />
<Cup guest={3} />
</>
);
}
This opens up the possibility of keeping a state of guests in TesSet
then looping over it. A more advanced example:
function TeaSet() {
const [guests, setGuests] = useState([1, 2, 3]);
return (
<>
{guests.map(guest => <Cup key={guest} guest={guest} />)}
</>
);
}
This allows you to manipulate the state from within TeaSet
without it affecting individual cups for existing guests, and without relying on any global variable, which may be handled in unexpected ways.
答案2
得分: 0
在开发模式下,React会将您的组件渲染两次,这对于调试非常有用(使查找不希望的行为变得更容易,最明显的用例可能是useEffect()
)。
我怀疑您在index.js中有一个<React.StrictMode/>
元素,这是导致这种行为的组件,但我建议不要将其移除。
如果一个渲染是一个步骤,那么所有的计算都必须与步骤相关,我强烈建议不要在一个组件中使用外部变量,"正确"的方法是创建另一个负责渲染<Cup/>
并将值作为属性传递的组件。
英文:
In development mode React renders your component twice, this is usefull for debugging (makes it easier to find unwanted behaviors, most obvious use case would arguably be useEffect()
).
I suspect you have a <React.StrictMode/>
element in your index.js, that's the component responsible for this behavior, but i would advise against removing it.
If a render is a step then all calculation must be step-relevant, i would definitely advise against having an outside variable used in one of your components, the "right" way to do that would be to have another component responsible for rendering <Cup/>
and passing the value as a prop.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论