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())的方式创建新数组(修改引用)