1、问题背景

在用hooks改写之前的项目时,AlbumList组件以props的形式传入了数据(歌曲数组),并且实现了一个删除事件的响应函数。为了实现删除某个数据之后更新整个组件,将setAlbums方法用函数封装,并将这个函数传入到子组件中。

子组件代码:

// AlbumList.tsx
import { IAlbum } from "../utils/types";

// 引入组件
import Album from "./Album";

interface IAlbumListProps {
  albums: IAlbum[];
  currentArea: number;

  fun: (i: number) => void;
}

const AlbumList = (props: IAlbumListProps) => {
  const currentAlbums = () => {
    return props.albums.filter((album) => album.area === props.currentArea);
  };

  const deleteAlbum = (album: IAlbum) => {
    let index = props.albums.indexOf(album);
    props.fun(index);
  };

  return (
    <section className="list">
      {currentAlbums().map((album) => (
        <Album
          key={album.id}
          album={album}
          onDelete={() => deleteAlbum(album)}
        />
      ))}
    </section>
  );
};

export default AlbumList;

父组件代码:

// App.tsx
import React, { useEffect, useState } from "react";

import * as api from "./services/api";
import { IArea, IAlbum } from "./utils/types";

// 引入组件
import Header from "./components/Header";
import Areas from "./components/Areas";
import AlbumList from "./components/AlbumList";

import "./styles/style.css";

const App = () => {
  let [areas, setAreas] = useState<IArea[]>();
  let [currentArea, setCurrentArea] = useState<number>(0);
  let [albums, setAlbums] = useState<IAlbum[]>();

  const loadData = async () => {
    // 导入areas
    let areas = await api.getAreas();
    console.log(areas);
    setAreas(areas);
    setCurrentArea(areas[0].id);

    // 导入albums
    let albums = await api.getAlbums();
    console.log(albums);
    setAlbums(albums);
  };

  const changeTab = (currentArea: number) => {
    setCurrentArea(currentArea);
  };

  const fun = (i: number) => {
    albums?.splice(i, 1);
    setAlbums(albums);
    return;
  };

  useEffect(() => {
    loadData();
  }, []);

  return (
    <div className="app">
      <Header />
      <main>
        <Areas
          areas={areas || []}
          currentArea={currentArea}
          changeTab={changeTab}
        />
        <AlbumList albums={albums || []} fun={fun} currentArea={currentArea} />
      </main>
    </div>
  );
};

export default App;

但是通过以上代码实现的删除有以下问题:在控制台调试发现删除成功,但是页面没有重新渲染。

2、问题原因及解决方案

这是由于state的更新机制导致的,setState的更新机制如下:

  • 简单类型的值:新旧值一样时不会引起渲染,新旧值不一样才会引起渲染
  • 复杂类型的值:当新旧值指向的是同一个对象(引用)时即使值不一样也不会 引起渲染

由此可知由于上面代码中的albums数组还是之前的引用,react判断是同一个对象,于是没有重新渲染,这里的解决方案有以下两种:

  • 使用扩展运算符,创建一个新数组,更改内存引用
  • 触发展示组件的re-render,这样即使不改变数组的引用,依然可以正确显示变动

下面演示一下第一种解决方案 - 更改内存引用:

const fun = (i: number) => {
  albums?.splice(i, 1);
  // 下面的JSON.parse(JSON.stringify())相当于创建了新数组(修改引用)
  setAlbums(JSON.parse(JSON.stringify(albums)));
  return;
};

上面的代码在fun函数中使用JSON.parse(JSON.stringify())的方式创建新数组(修改引用)