数组作为属性传递不反映更改。

huangapple go评论82阅读模式
英文:

Array passed as prop doesn't reflect changes

问题

这个问题更多是为了理解 React 如何处理和响应变化,而不是实现方面,因此我暂时放弃了不可变的 props 方法。

我正在尝试获取数组的第一个元素并从原始数组中移除它,该数组作为 prop 传递给组件:

const ChildComponent = (
  arrayToChange: Array<any>[]
) => {
  const elementFromArray = arrayToChange.shift();
}

根据shift() 方法的定义:

shift() 方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。

尽管 elementFromArray 变量现在包含来自数组的元素,但数组保持完整,没有受到任何影响,仍然包含所有元素。

但这是怎么可能的呢?React 应该通过引用传递 props,因此应该影响原始数组。如果 React 有一些保护措施并且更改不会反映在父组件中,我能理解,然而,我仍然期望在子组件中反映出变化。

我找不到任何有用的东西来解释这种行为,大多数资源只提及了对 props 的不可变方法以及如何绕过它,而没有说明背后的原因或逻辑。

尽管 elementFromArray 变量现在包含来自数组的元素,但数组保持完整,没有受到任何影响,仍然包含所有元素。然而,如果我使用 push() 方法,那么变化会反映出来,arrayToChange 会包含一个额外的元素。

那么我的问题是 - 为什么 arrayToChange 对这些方法有不同的反应?如果 shift() 不改变内容,我也会期望 push() 也不会改变。

英文:

This question is more for understanding more how react handles and reacts to changes, than implementation, therefore I'm letting immutable-props-apprach go for a little bit.

I'm trying to get the first element of an array and remove it from the original array, which was passed to component as a prop:

const ChildComponent = (
  arrayToChange: Array&lt;any&gt;[]
) =&gt; {
  const elementFromArray = arrayToChange.shift();
}

From the definition of the shift() method::
> The shift() method removes the first element from an array and returns that removed element. This method changes the length of the array.

Even though the elementFromArray variable now contains the element from array, the array is intact, it was not affected in any way and still contains all the elements.

But how is that possible? React should pass the props by reference, therefore the original array should be affected. I'd understand if React had some protective measures in place and the changes wouldn't be reflected in the parent, however, I'd still expect changes being reflected in the child.
I cannot find anything useful which would explaing this behaviour, majority of the resources only mention the immutable approach to props and how to find a way around it, not the reasons or logic behind it.

Even though the elementFromArray variable now contains the element from array, the array is intact, it was not affected in any way and still contains all the elements. However, if I use push() method, then the changes are reflected, arrayToChange contains one more element.

My question then is - why does the arrayToChange react differently to these methods? If shift() doesn't change the contents, I'd expect push() wouldn't either.

答案1

得分: 3

以下是翻译好的内容:

我不确定这个问题是什么,但我猜一下,然后再评论,如果不对,请在评论之前告诉我。

我认为你应该在你的子组件中尝试这样做:

const [data, setData] = React.useState(arrayToChange);

React.useEffect(() => setData(arrayToChange), [arrayToChange.length]);

然后在你的jsx中使用"data"来映射输出。

然后在你的父组件中,在"arrayToChange"上执行shift操作。你可以将useEffect想象成一个"监听器",当数组的长度发生变化时会触发。

英文:

I am not entirely sure what this question is but I am going on a limb here to take a guess at it so please comment here and I will change before downvoting.

I think you should try this, in your child component:

const [data, setData] = React.useState(arrayToChange);

React.useEffect(() =&gt; setData(arrayToChange), [arrayToChange.length]);

Then use "data" to map through to output in your jsx

Then in your parent component, do the shift on the arrayToChange. You can think of the useEffect as a "watcher" which will fire when the length of the array changes.

答案2

得分: 0

以下是代码的中文翻译部分:

function Parent() {
  const forceUpdate = useForceUpdate();
  const [letters] = React.useState(["a", "b", "c"]);

  return (
    <div className="bg-yellow">
      A: {JSON.stringify(letters)}
      <ChildShowArray array={letters} />
      B: {JSON.stringify(letters)}
      <ChildChangeArray arrayToChange={letters} />
      C: {JSON.stringify(letters)}
      <ChildShowArray array={letters} />
      D: {JSON.stringify(letters)}

      <hr />
      <button type="button" onClick={forceUpdate}>re-render</button>
    </div>
  );
}

function ChildChangeArray({ arrayToChange }) {
  const elementFromArray = arrayToChange.shift();
  
  return (
    <div className="bg-red">
      elementFromArray = {JSON.stringify(elementFromArray)}
    </div>
  );
}

function ChildShowArray({ array }) {
  return (
    <div className="bg-green">
      array = {JSON.stringify(array)}
    </div>
  );
}

// 辅助钩子
function useForceUpdate() {
  const [_state, setState] = React.useState({});
  return React.useCallback(() => { setState({}) }, []);
}

ReactDOM.createRoot(document.querySelector("#root")).render(<Parent />)
.bg-yellow { background: yellow }
.bg-red { background: red }
.bg-green { background: green }
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="root"></div>

希望这个翻译对您有帮助。如果您需要进一步的信息,请随时提问。

英文:

<!-- begin snippet: js hide: false console: true babel: true -->

<!-- language: lang-js -->

function Parent() {
const forceUpdate = useForceUpdate();
const [letters] = React.useState([&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]);
return (
&lt;div className=&quot;bg-yellow&quot;&gt;
A: {JSON.stringify(letters)}
&lt;ChildShowArray array={letters} /&gt;
B: {JSON.stringify(letters)}
&lt;ChildChangeArray arrayToChange={letters} /&gt;
C: {JSON.stringify(letters)}
&lt;ChildShowArray array={letters} /&gt;
D: {JSON.stringify(letters)}
&lt;hr /&gt;
&lt;button type=&quot;button&quot; onClick={forceUpdate}&gt;re-render&lt;/button&gt;
&lt;/div&gt;
);
}
function ChildChangeArray({ arrayToChange }) {
const elementFromArray = arrayToChange.shift();
return (
&lt;div className=&quot;bg-red&quot;&gt;
elementFromArray = {JSON.stringify(elementFromArray)}
&lt;/div&gt;
);
}
function ChildShowArray({ array }) {
return (
&lt;div className=&quot;bg-green&quot;&gt;
array = {JSON.stringify(array)}
&lt;/div&gt;
);
}
// helper hook
function useForceUpdate() {
const [_state, setState] = React.useState({});
return React.useCallback(() =&gt; { setState({}) }, []);
}
ReactDOM.createRoot(document.querySelector(&quot;#root&quot;)).render(&lt;Parent /&gt;)

<!-- language: lang-css -->

.bg-yellow { background: yellow }
.bg-red { background: red }
.bg-green { background: green }

<!-- language: lang-html -->

&lt;script crossorigin src=&quot;https://unpkg.com/react@18/umd/react.development.js&quot;&gt;&lt;/script&gt;
&lt;script crossorigin src=&quot;https://unpkg.com/react-dom@18/umd/react-dom.development.js&quot;&gt;&lt;/script&gt;
&lt;div id=&quot;root&quot;&gt;&lt;/div&gt;

<!-- end snippet -->

The behaviour in the snippet can be explained if you see the rendering process as a breadth-first algorithm.

JSX will convert:

&lt;div className=&quot;bg-yellow&quot;&gt;
  A: {JSON.stringify(letters)}
  &lt;ChildShowArray array={letters} /&gt;
  B: {JSON.stringify(letters)}
  &lt;ChildChangeArray arrayToChange={letters} /&gt;
  C: {JSON.stringify(letters)}
  &lt;ChildShowArray array={letters} /&gt;
  D: {JSON.stringify(letters)}
&lt;/div&gt;

Into the following JavaScript:

React.createElement(&quot;div&quot;, { className: &quot;bg-yellow&quot; },
  &quot;A: &quot;, JSON.stringify(letters),
  React.createElement(ChildShowArray, { array: letters }),
  &quot;B: &quot;, JSON.stringify(letters),
  React.createElement(ChildChangeArray, { arrayToChange: letters }),
  &quot;C: &quot;, JSON.stringify(letters),
  React.createElement(ChildShowArray, { array: letters }),
  &quot;D: &quot;, JSON.stringify(letters),
)

React.createElement(ChildShowArray, { array: letters }) creates a structure that does not immediately invoke the ChildShowArray component. It will create some sort of intermediate structure/object that only runs whenever the renderer asks it to run.

JavaScript placed within {...} (JSX context) are passed directly as arguments, and therefore are resolved directly. Meaning that all {JSON.stringify(letters)} inside Parent runs before any of the code in child components runs.

When building the parent structure is complete, the renderer will visit each intermediate structure/object and asks it to render. This is done from top to bottom, which is why the first ChildShowArray render does still show the full array. Then ChildChangeArray is rendered which removes the first element. The second ChildShowArray render reflects this change, and is rendered without the first element.

Note that shift() does change the contents of letters, but when it's called the content of Parent is already rendered and no longer changes. This change does impact Parent on the next render (click "re-render" button in snippet). It also impacts renders of other child components below it that use the same array reference.

huangapple
  • 本文由 发表于 2023年6月27日 21:26:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/76565368.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定