意外行为:功能与功能组件以及动画

时间:2019-11-13 01:29:06

标签: javascript reactjs

我显示一个foos列表,当我单击一些链接更多结果时,我会保留现有的foos,并将它们添加到我的api中,例如波纹管

const [foos, setFoos] = useState([]);
...
// api call with axios
...
success: (data) => {
    setFoos([ ...foos, ...data ])
},

每个<Foo />组件都在上面运行动画

App.js

...
<div className="foos-results">
    { foos.map((foo, index) => <Foo {...{ foo, index }} key={foo.id}/>) }
</div>
...

Foo.js

const Foo = ({ foo, index }) => <div className="circle">...</div>

animation.css

.circle {
   ...
   animation: progress .5s ease-out forwards;
}

问题是当我添加新的动画时,就会为<Foo />的所有行触发动画。

预期的行为是动画仅针对新动画触发,而不是从现有动画重新开始。

更新

我们已经找到问题的根源(与key={foo.id}的唯一性无关)

如果我们更改

const Foo = ({ foo, index }) => <div className="circle">...</div>

const renderFoo = ({ foo, index }) => <div className="circle">...</div>

将App.js和

...
<div className="foos-results">
    { foos.map((foo, index) => renderFoo({ foo, index })) }
</div>
...

有效

那么为什么这样的行为会发生反应呢?

这是一个基于@Jackyef代码的sandbox

3 个答案:

答案 0 :(得分:1)

我认为没有办法禁用此重新渲染动画,但是我认为有一种解决方法可以解决此问题。

我们知道每个div的css每次都会重新加载,因此我能想到的解决方案是创建另一个css类规则(将该类命名为“ circle_without_anim”),并将其css与类“ circle”相同该动画并在添加新div时,紧接在将所有具有类名称“ circle”的div的更改类附加到“ circle_without_anim”之前,这将对以前的div进行更改和css,但没有该动画,并将此新div添加到class “圆”使其成为唯一具有动画效果的div。

正式的算法如下:

  1. 编写另一个具有与“ circle”相同规则但没有动画规则的css类(例如,不同的名称prev_circle)。
  2. 在Java语言中,在将新div附加到“ circle”类之前,将以前所有具有“ circle”类的div的类更改为没有动画规则的新创建的“ prev_circle”类。
  3. 将新的div附加到“ circle”类。

结果:由于先前的div的CSS相同但没有动画,因此会给人一种幻觉,即它不会被重新加载,但是新的div具有要重新加载的CSS规则(动画规则)。

答案 1 :(得分:1)

这很有趣。

让我们看看问题中提供的sandbox

App里面,我们可以看到这个。

  const renderItems = () => (
    <div>
      {items.map((item, index) => (
        <div className="item" key={item.id}>
          <span>
            {index + 1}. {item.value}
          </span>
        </div>
      ))}
    </div>
  );

  const Items = () => renderItems();

  return (
    <div className="App">
      <h1>List of items</h1>
      <button onClick={addItem}>Add new item</button>
      <Items />
    </div>
  );

似乎很无害吧?问题是ItemsApp渲染函数中声明。这意味着在每个渲染器上,Items实际上是一个不同的函数,即使它所做的是相同的。

<Items />被转换为React.createElement,并且在进行差异化时,React考虑每个组件的参照相等性,以决定它是否与先前渲染的组件相同。如果不一样,React会认为它是一个不同的组件,如果不一样,它将只是创建并安装一个新组件。这就是为什么您看到动画再次播放的原因。

如果您在Items之外声明App组件,例如:

const Items = ({ items }) => (
  <div>
    {items.map((item, index) => (
      <div className="item" key={item.id}>
        <span>
          {index + 1}. {item.value}
        </span>
      </div>
    ))}
  </div>
);

function App() { /* App render function */}

您将看到一切正常。 Sandbox here

因此,总结一下:

  1. 区分时对引用平等很重要
  2. 组件(返回JSX的函数或类)应该是稳定的。如果它们在渲染之间改变,由于点号1,React将很难。

答案 2 :(得分:1)

使用以下代码:

const Items = () => renderItems();
...
<Items />

React没有机会知道当前渲染器中的Items与上一个渲染器中的Items相同。

考虑一下:

A = () => renderItems()
B = () => renderItems()

A和B是不同的组件,因此,如果您在当前渲染中具有<B />,而在先前渲染中具有<A />而不是<B />,则React将丢弃{{ 1}},然后再次渲染。

您每次渲染都会调用<A />(因为React.createElement只是<Items />的JSX语法糖),因此React在DOM树中剪贴了旧的React.createElement(Items, ...)并创建了每次都再次。

查看this question了解更多详细信息。

有两种解决方案:

  • 在渲染功能之外创建<Items />组件(如Jackyef建议的那样)
  • 使用渲染功能(用Items代替{ renderItems() }