状态改变时不渲染

时间:2021-05-26 17:32:42

标签: javascript arrays reactjs sorting

我正在学习反应。我正在尝试根据名称对列表进行排序。 ShoppingList 组件是

const ShoppingList = () => {
    const [items, setItems] = useState([]);

    const data = [
        {id: 1, name: 'Soda'},
        {id: 2, name: 'ice'},
    ];

    useEffect(() => {
        setItems(data);
    }, []);
    
    const handleSort = () => {}
   return ();

}

单击按钮,我正在尝试对数据进行排序并显示它。

<button onClick={() => handleSort()}>Sort by name</button>

handleSort() 函数内部

const sortItems = items.sort((a, b) => {
    const nameA = a.name.toUpperCase();
    const nameB = b.name.toUpperCase();
    if(nameA < nameB)
        return -1;
    if(nameA > nameB)
        return 1;
    return 0;
});

console.log(sortItems);
setItems(sortItems);

console.log(sortItems) 显示已排序的数组。但不在 DOM 中渲染。 在 return 中,我试图以这种格式显示排序后的数据

<ul>
    {items.map((item) => {
        return (
            <li key={item.id}>
              <span>{item.name}&nbsp;</span>
              <button onClick={() => handleRemove(item.id)}>&times;</button>
            </li>
        );
        })
     }
</ul>

我在这里缺少什么?

3 个答案:

答案 0 :(得分:2)

我建议使用 useMemo 派生项目的排序列表,因此它的“派生状态”取决于项目数组和所需的排序顺序。

  • 不要使用 useEffect 作为初始状态。 useState 接受初始状态的创建者函数。
  • localeCompare 是返回 -1、0、+1 进行比较的更简洁的方法。
  • [...items]items 的浅拷贝)是必需的,因为 .sort() 对数组进行就地排序。
const sortByName = (a, b) => a.name.toUpperCase().localeCompare(b.name.toUpperCase());

const ShoppingList = () => {
  const [items, setItems] = useState(() => [
    { id: 1, name: "Soda" },
    { id: 2, name: "ice" },
  ]);
  const [sortOrder, setSortOrder] = useState("original");
  const sortedItems = React.useMemo(() => {
    switch (sortOrder) {
      case "byName":
        return [...items].sort(sortByName);
      default:
        return items;
    }
  }, [items, sortOrder]);

  return (
    <>
      <button onClick={() => setSortOrder("byName")}>Sort by name</button>
      <button onClick={() => setSortOrder("original")}>Sort in original order</button>
      <ul>
        {sortedItems.map((el, i) => (
          <li key={el.id}>
            <span>{el.name}&nbsp;</span>
            <button>&times;</button>
          </li>
        ))}
      </ul>
    </>
  );
};

答案 1 :(得分:1)

首先你需要停止使用 useEffect 作为初始状态, 如果您想做出反应以注意到您的更改,请使用对象而不是数组。 (这并不总是反应不会注意到您的更改,但由于您没有更改数组并且它只是排序,因此反应会忽略它)。

const ShoppingList = () => {
    const [items, setItems] = useState({
      data: [
        { id: 1, name: 'Soda' },
        { id: 2, name: 'ice' },
      ],
    });
    const handleSort = () => {
      const sortedItems = items.data.sort((a, b) => {
        const nameA = a.name.toUpperCase();
        const nameB = b.name.toUpperCase();
        if (nameA < nameB) return -1;
        if (nameA > nameB) return 1;
        return 0;
      });
      setItems({
        data: sortedItems,
      });
    };
    return (
      <>
        <button onClick={() => handleSort()}>Sort by name</button>
            <ul>
                {items.data.map((el, i) => (
                    <li key={el.id}>
                        <span>{el.name}&nbsp;</span>
                        <button onClick={() => handleRemove(item.id)}>&times;</button>
                    </li>
                 ))}
            </ul>
       </>
    );
}

希望对你有帮助?

答案 2 :(得分:1)

如果您有兴趣更深入地了解为什么数组项已更改(已排序)但 React 不呈现,请注意以下两点:

  1. array.sort 的工作原理
  2. React 如何使用 useState 重新渲染

对于 (1),很简单,array.sort 返回排序后的数组。请注意,数组是就地排序的,并且没有复制。因此 sortItemsitems 仍然指向同一个数组

对于 (2),它有点复杂,因为我们必须通读 React 代码库。

这是React.useState签名

export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

使用 Github 导航工具扫描我们最终会到达 this code:

useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  currentHookNameInDev = 'useState';
  mountHookTypesDev();
  const prevDispatcher = ReactCurrentDispatcher.current;
  ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
  try {
    return mountState(initialState);
  } finally {
    ReactCurrentDispatcher.current = prevDispatcher;
  }
}

接下来我们必须找到mounState:的定义

function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  ...
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}

注意,mountState 的返回类型是一个数组,其中第二个参数是一个函数,就像 const [items, setItems] = useState([]) 这意味着我们快到了。 dispatch 是来自 dispatchAction.bind

的值

扫描我们将在这一行结束的代码:

      if (is(eagerState, currentState)) {
        // Fast path. We can bail out without scheduling React to re-render.
        // It's still possible that we'll need to rebase this update later,
        // if the component re-renders for a different reason and by that
        // time the reducer has changed.
        return;
      }

最后一部分是 function is 的作用:

function is(x: any, y: any) {
  return (
    (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
  );
}

它只是使用 === 运算符检查相等性。

回到排序函数,在我们的例子中 nextState 是 sortItems,prevState 是 items。考虑到 (1),sortItems === items => true 所以 React 将跳过渲染。 这就是为什么你看到大多数教程都说你必须做浅拷贝。 通过这样做,您的 nextState 将与您的 prevState 不同。

TLDR:

  1. React 使用上面的函数 is 来检查使用钩子时的状态变化
  2. 处理数组时总是做浅拷贝,如果你使用钩子,对象