避免使用带有React + Redux的array.map重新渲染组件

时间:2019-08-02 06:34:45

标签: javascript reactjs redux react-redux reselect

首先,我已经经历了:

虽然学到了很多东西,但仍然不知道如何解决我的情况。

任务

我正在做音乐播放器,并且已经将商店标准化为:

{
    albums : {
        album1 : {
            name  : "...",
            songs : ["s1, s2"]
        },
        album2 : {
            name  : "...",
            songs : ["s3, s4"]
        }
    },
    songs : {
        s1 : {
            id : "s1",
            title : "title",
            artist : "name"
        },
        s2 : { ... },
        s3 : { ... }
    }
}

因此,我有一个视图,该视图可以加载一堆专辑,并以类似的方式显示每个专辑及其对应的歌曲:

<Album id={albumId} />

“相册”组件如下所示:

class Album extends Component {

    componentDidMount() {
        this.props.loadAlbum(this.props.albumId)
    }

    render() {
        <div> ... </div>
    }
}

mapStateToProps = (state, props) => {
    const album = state.albums[props.albumId]
    return {
        album : album,
        songs : album.songs.map( id => state.songs[id] )
    }
}

(我省略了Redux的一些样板以提高可读性,但是如果感觉缺少某些东西,请让我知道添加它)loadAlbumaction,用于加载专辑中的歌曲,我有两个还原器(albumsReducersongsReducer)分别在store.albums中设置专辑的元数据,并将专辑的新歌曲分别“推送”到store.songs中。

这是简化的songsReducer

(state = initialState.songs, action) => {
    if( types.PUT_SONGS_RESULT ) {
        return { ...state, ...payload.songs }
    }
}

问题

当然,当我每次在mapStateToProps中更新商店时都会创建一个新的“歌曲”数组,因此每次其他每张专辑中都会重新渲染“ album1”的组成部分提取其歌曲(因为即使“ album.song.map”的结果与该专辑的值相同,但其数组也不相同,即[{a:1}] !== [{a:1}])。

我尝试使用reselect将映射移动到以下选择器:

const getAlbum = (state, props) => state.albums[props.albumId]

const getSongs = (state, props) => state.songs

export const makeGetSongsOfAlbums = () => {
    return createSelector (
        [ getAlbum, getSongs ],
        (album, songs) => {
            return album ? album.songs.map( id => songs[id] ) : []
        }
    )
}

这样连接的人:

mapStateToProps = (state, ownProps) => {
    const getSongsOfAlbum = makeGetSongsOfAlbums()
    return {
        "album" : state.albums[ownProps.albumId],
        "songs" : getSongsOfAlbum(state, ownProps)
    }
}

但是它具有相同的效果,因为每次选择器的参数之一发生更改时都会计算该值,而每当一张专辑加载其歌曲时state.songs也会发生更改(对吗?)。

那么,有没有人知道如何防止每次其他专辑加载时都重新呈现Album组件?我可以实现shouldComponentUpdate,但我认为可以“自动”实现,而无需手动执行深度比较,尽管我绝对不是React专家,所以如果我有远离{{1} }尽可能是错误的,请让我知道。

旅程

您可能想知道为什么要像这样的存储会更简单,为什么我要像这样标准化数据?

shouldComponentUpdate

或者甚至以每个{ albums : { album1 : { name : "...", songs : [{id : "s1"}, {id : "s2"}] }, album2 : { name : "...", songs : [{id : "s3"}, {id : "s4"}] } } } 的本地状态加载歌曲。

好吧,我还需要加载播放列表,并且需要支持将该歌曲标记为收藏夹的功能,所以当我在同一屏幕上的多个位置显示同一首歌曲时,这开始变得混乱音乐播放器和任何歌曲列表),因为当我将一首歌曲标记为“收藏夹”时,我希望在应用程序中的任何地方都将其标记为标签,因此显示该歌曲的每个组件都说“嘿!这首歌已经是收藏夹了!在此处显示一个小星星,并且不会让用户重新收藏,因为如果我尝试(对此无能为力),API将会失败,”并且必须在商店和本地的许多地方更新歌曲的状态组件内部的状态听起来并不是一件好事。相反,听起来更像Album应该在全球商店中,所以我只需要通过songs中的ID更新每首歌曲,它还可以提高性能(或者就是我的想法)。

例如,当我加载播放列表时,我会遵循与专辑相同的模式:

state.songs

播放列表将其歌曲放入playlists : { playlist1 : { name : "...", songs : ["s1, s2"] }, playlist2 : { name : "...", songs : ["s3, s4"] } } 中。仍然,如果这种方法听起来过于复杂(因为我确实认为它会更简单),并且您不认为这是可行的方法,请告诉我!

EDIT回复Will Jenkins:

商店根目录中的“歌曲”数组(实际上是一个对象。我已经在上面的代码中编辑了示例以使其更清楚),这是当前将要显示的所有歌曲的存储位置是。 例如,如果我加载显示播放列表歌曲的视图,则将清除该对象,然后将来自API的歌曲放入其中。对于专辑而言,如果该视图需要显示5张专辑及其歌曲,那么我将再次清除该对象并将每个专辑中的歌曲放入其中,以便每个专辑都可以过滤自己的歌曲。

“因为即使“ album.song.map”的结果按该专辑的值相同,也不相同的数组”,这意味着state.songs因为它们看起来相同,但它们不同的数组(我已经编辑了原始语句以包含此示例)。即使它们持有相同的对象,它们仍然会不同:

[{a:1}] !== [{a:1}]

关于收藏夹的问题,是的,商店中每首歌曲应该只有一个条目(这实际上是“歌曲”对​​象的目的)。我试图解释在之前归一化数据时遇到的问题,在该数据中我在很多地方有很多相同歌曲的条目。

0 个答案:

没有答案