CSS 排序列表时的转换

CSS transition when sorting a list

我正在尝试在 table 的行上创建一个简单的 css 背景过渡,因为项目从非活动状态变为活动状态。

过渡应该从蓝色(active === false)淡化到粉红色(active === true)。

当从蓝色变为粉红色并且顺序发生变化时,过渡按预期工作,但当从粉色变为蓝色并且顺序发生变化时则没有。

我已经为我的问题做了一个简单的例子https://codesandbox.io/s/simple-react-8pf4s

在此先感谢您的帮助!

这是一个奇怪的行为,但我假设当 React 正在控制将 dom 节点插入其新位置时,class 已经添加,因此会立即呈现。为了抵消这一点,我创建了一个关键帧动画和一堆正在动画以将此动画应用到的 id。我可以通过在创建引用时记录引用来确认相同的元素正在被重用(而不是被破坏和重新创建)。

https://codesandbox.io/s/objective-dijkstra-ifren

const App = () => {
  const [animating, setAnimating] = useState([]);
  const removeAnimation = id => {
    setAnimating(animating.filter(x => x !== id));
  };

  const [data, setData] = useState(sourceData);
  const toggle = id => {
    let data2 = [...data];
    const index = data.findIndex(x => x.id === id);
    data2[index] = { ...data2[index], active: !data2[index].active };
    setAnimating([...animating, data2[index].id]);
    data2 = [...data2.filter(x => x.active), ...data2.filter(x => !x.active)];
    setData(data2);
  };

  return (
    <div className="App">
      {data.map(d => (
        <div
          key={d.id}
          className={cl(
            "row",
            d.active && "row_active",
            !d.active && animating.includes(d.id) && "row_hasAnimation",
            d.active && animating.includes(d.id) && "row_active_hasAnimation"
          )}
          onAnimationEnd={() => removeAnimation(d.id)}
        >
          <div className="cell">{d[1]}</div>
          <div className="cell">{d[2]}</div>
          <div className="cell">{d[3]}</div>
          <div className="cell">
            <button onClick={() => toggle(d.id)}>Toggle</button>
          </div>
        </div>
      ))}
    </div>
  );
};

这可能就是 React 处理列表协调的方式。

当顶部项目变为非活动状态时,它首先从 DOM 中删除,然后作为新的子项附加。这就是为什么没有过渡。当您将底部项目变为活动状态时,过渡会起作用,因为它再次是要删除的顶部项目,因此过渡项目会保留在 DOM.

在这种特定情况下,在 table 行上设置键没有帮助,因为据我所知 this,它们仅用于优化,实际上并不能保证将重复使用相同的 DOM 个元素。

您可能会遇到提到的相同问题 here

您可以查看一些用于转换列表的库以找到潜在的解决方案。例如。 react-flip-move

我的一部分希望我是错的,因为它有点糟透了,让一件简单的事情变得非常复杂。

我能够为您解决排序问题,但真正难倒的是 CSS 问题..

问题与 keys 和创建项目映射有关。据我所知,React 会将转换应用到特定键。切换到非活动状态后,React 就像将其视为一个全新的节点,并且由于某种原因它没有正确应用转换..

我不确定为什么这会以一种方式起作用而不是另一种方式,这对我来说真的很奇怪......我无法在网上找到解决这个问题的方法,我测试了一堆东西......你应该看看使用 React 和键进入 CSS 转换..

话虽如此,我确实想出了一些 "hacky" 方法来完成此任务..

CSS 作为 JS 对象:

const { useState, useEffect } = React;
const { render } = ReactDOM;

const styles = {
  fontFamily: "sans-serif",
  textAlign: "center"
};

const sourceData = [
  {
    id: "es",
    1: "uno",
    2: "dos",
    3: "tres",
    active: true
  },
  {
    id: "de",
    1: "eine",
    2: "zwei",
    3: "drei",
    active: false
  },
  {
    id: "abc",
    1: "a",
    2: "b",
    3: "c",
    active: false
  }
];

const activeClass = {
  background: "pink",
  color: "blue",
  transition: "all 1s"
};

const inactiveClass = {
  background: "blue",
  color: "pink",
  transition: "all 1s"
};

const App = () => {
  const [data, setData] = useState(sourceData);

  useEffect(() => {
    setClassNames();
  }, [data]);

  const sortData = d => d.sort((a, b) => (a[1] < b[1] ? -1 : 1));

  const sortAllData = d => [
    ...sortData(d.filter(i => i.active)),
    ...sortData(d.filter(i => !i.active))
  ];

  const handleToggle = index => event => {
    let clone = [...data];
    clone[index].active = !clone[index].active;
    setData(sortAllData(clone));
  };

  const setClassNames = () => {
    let actives = document.querySelectorAll(`[dataactive=${true}]`);
    let inactives = document.querySelectorAll(`[dataactive=${false}]`);
    setTimeout(() => {
      actives.forEach(a => {
        Object.keys(activeClass).forEach(k => a.style[k] = activeClass[k])
      });
      inactives.forEach(ina => {
        Object.keys(inactiveClass).forEach(k => ina.style[k] = inactiveClass[k])
      });
    }, 10);
  };

  return (
    <div style={styles}>
      <table>
        <thead>
          <tr>
            <td>Active?</td>
            <td>1</td>
            <td>2</td>
            <td>3</td>
          </tr>
        </thead>
        <tbody>
          {data.map((d, index) => {
            return (
              <tr dataactive={d.active.toString()} key={d.id} id={d.id}>
                <td>{d.active.toString()}</td>
                <td>{d[1]}</td>
                <td>{d[2]}</td>
                <td>{d[3]}</td>
                <td>
                  <button onClick={handleToggle(index)}>TOGGLE</button>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
};

render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>

使用 .CSS 文件:

const { useState, useEffect } = React;
const { render } = ReactDOM;

const styles = {
  fontFamily: "sans-serif",
  textAlign: "center"
};

const sourceData = [
  {
    id: "es",
    1: "uno",
    2: "dos",
    3: "tres",
    active: true
  },
  {
    id: "de",
    1: "eine",
    2: "zwei",
    3: "drei",
    active: false
  },
  {
    id: "abc",
    1: "a",
    2: "b",
    3: "c",
    active: false
  }
];


const App = () => {
  const [data, setData] = useState(sourceData);

  useEffect(() => {
    setClassNames();
  }, [data]);

  const sortData = d => d.sort((a, b) => (a[1] < b[1] ? -1 : 1));

  const sortAllData = d => [
    ...sortData(d.filter(i => i.active)),
    ...sortData(d.filter(i => !i.active))
  ];

  const handleToggle = index => event => {
    let clone = [...data];
    clone[index].active = !clone[index].active;
    setData(sortAllData(clone));
  };

  const setClassNames = () => {
    let actives = document.querySelectorAll(`[dataactive=${true}]`);
    let inactives = document.querySelectorAll(`[dataactive=${false}]`);
    setTimeout(() => {
      actives.forEach(a => a.className = "active");
      inactives.forEach(ina => ina.className = "inactive");
    }, 10);
  };

  return (
    <div style={styles}>
      <table>
        <thead>
          <tr>
            <td>Active?</td>
            <td>1</td>
            <td>2</td>
            <td>3</td>
          </tr>
        </thead>
        <tbody>
          {data.map((d, index) => {
            return (
              <tr dataactive={d.active.toString()} key={d.id} id={d.id}>
                <td>{d.active.toString()}</td>
                <td>{d[1]}</td>
                <td>{d[2]}</td>
                <td>{d[3]}</td>
                <td>
                  <button onClick={handleToggle(index)}>TOGGLE</button>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
};

render(<App />, document.body);
.active {
  background: pink;
  color: blue;
  transition: all 1s;
}
.inactive {
  background: blue;
  color: pink;
  transition: all 1s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>