当 Redux 状态改变时,React 不会重新渲染

React won't rerender when Redux state changes

在 React 应用程序中,我将从 Redux 存储中获取的一些数据映射到某个组件

{this.props.team && this.props.team.map((value: User, index: number) =>
                                    (<Card key={index} className="team-card">
                                        <CardMedia style={{
                                            backgroundImage: `url(${value.photoURL})`
                                        }} />
                                        <Typography use={"headline4"}>{value.displayName}</Typography>
                                        <Typography use={"body1"}>{value.description}</Typography>
                                        <CardActions>
                                            <CardActionButtons>
                                                {/* TODO: Add the ability to go to About to a specific team member card */}
                                                <CardActionButton>Vezi profilul</CardActionButton>
                                            </CardActionButtons>
                                        </CardActions>
                                    </Card>)
                                )}

这里 team 是从 redux Store 映射的 prop。当用户打开应用程序时,Redux 存储中的数据将从数据库中获取。这是有效的,因为我已经记录了 team 道具的更改并且它实际上按预期更新。

问题在于,即使在道具更新后(可能在初始渲染后一秒钟发生),应用程序也不会重新渲染以反映此道具更改。但是,如果之后这个组件被卸载并重新安装,它就会正确呈现。同样在卸载和重新安装之间,redux 存储没有得到更新,并且在安装生命周期中没有任何反应。

有谁知道是什么导致了这种行为?提前致谢!

更新:

这是完整的组件(它使用 Typescript)

import React from "react"
import { Article } from "../../models/Article";
import Carousel, { CarouselItem } from "../Carousel/Carousel";


import "./Homescreen.scss";
import { connect } from "react-redux";
import AppState from "../../store/AppState";
import { Typography, Card, CardMedia, CardActionButton, CardActions, CardActionButtons } from "rmwc"
import User from "../../models/User";

import ArticleCompact from "../Article/ArticleCompact/ArticleCompact";
import Navbar from "../Navbar/Navbar";

class Homescreen extends React.Component<HomescreenProps, {}>{

    constructor(props: Readonly<HomescreenProps>) {
        super(props);

    }

    render() {
        return (
            <main>
                <Navbar></Navbar>
                <div id="container">
                    <div id="content">
                        <Carousel className="homescreen-carousel" items={this.props.carouselItems} speed={5}></Carousel>
                        {this.props.recentArticles.length !== 0 && (<section id="homescreen-recent-articles">
                            <Typography use={"headline2"} className="homescreen-head">Recente</Typography>
                            <hr className="homescreen-hr" />
                            {this.props.recentArticles[0] && (
                                <ArticleCompact URL={"/article/" + this.props.recentArticles[0].url} image={this.props.recentArticles[0].coverURL}
                                    text={this.props.recentArticles[0].shortVersion} title={this.props.recentArticles[0].title}
                                    type={"left-to-right"}
                                />)}
                            {this.props.recentArticles[1] && (<ArticleCompact URL={"/article/" + this.props.recentArticles[1].url} image={this.props.recentArticles[1].coverURL}
                                text={this.props.recentArticles[1].shortVersion} title={this.props.recentArticles[1].title}
                                type={"right-to-left"}
                            />)}
                        </section>)}
                        <section id="homescreen-team">
                            <Typography use={"headline2"} className="homescreen-head">Echipa</Typography>
                            <hr className="homescreen-hr" />
                            <div id="team-cards">
                                {this.props.team && this.props.team.map((value: User, index: number) =>
                                    (<Card key={index} className="team-card">
                                        <CardMedia style={{
                                            backgroundImage: `url(${value.photoURL})`
                                        }} />
                                        <Typography use={"headline4"}>{value.displayName}</Typography>
                                        <Typography use={"body1"}>{value.description}</Typography>
                                        <CardActions>
                                            <CardActionButtons>
                                                {/* TODO: Add the ability to go to About to a specific team member card */}
                                                <CardActionButton>Vezi profilul</CardActionButton>
                                            </CardActionButtons>
                                        </CardActions>
                                    </Card>)
                                )}
                            </div>
                        </section>
                    </div>
                </div>
            </main>
        )
    }
}



function mapStateToProps(state: Readonly<AppState>) {
    const items: CarouselItem[] = [] as CarouselItem[];
    const articles: Article[] = [];
    if (state.articles.featured.length !== 0)
        state.articles.featured.map((item: Article) => {
            return {
                image: item.coverURL,
                title: item.title,
                path: "/article/"+item.url
            }
        }
        ).forEach((value: CarouselItem) => {
            items.push(value);
        })

    //Map the first 4 recent articles to CarouselItems and push them to an array
    state.articles.recent.map(async (item: Article) => (
        {
            image: URL.createObjectURL(await fetch(item.coverURL).then(res => res.blob())),
            title: item.title,
            path: "/article/"+item.url
        })
    ).forEach(async (value, index) => {
        if (index < 4)
            items.push(await value);
    });

    //Map the last 2 recent articles to props
    for (let [index, article] of state.articles.recent.entries()) {
        if (index >= 4)
            articles.push(article)
    }

    return {
        carouselItems: items,
        recentArticles: articles,
        team: state.metadata.team
    }
}

export default connect(mapStateToProps)(Homescreen);

这里还有负责更新该商店的 reducer 属性

export default function userReducer(state: Readonly<MetadataState> | undefined = initialAppState.metadata, action: MetadataActions): MetadataState {
    switch (action.type) {
        case 'TEAM_RECIEVED': return { ...state, team: action.payload };
        default: return state;
    }
}

更新#2:

下面是让人心烦意乱的动作TEAM_RECIEVED

export function retrieveTeam() {
    return async (dispatch: Dispatch) => {

        const team = await getTeam_firestore();
        const mappedTeam: User[] = [];
        team.forEach(async (val: User, index: number) => mappedTeam.push({
            ...val,
            photoURL: val.photoURL !== null ? URL.createObjectURL(await fetch(val.photoURL!!).then(res => res.blob())) : null
        }));
        console.log('Got team')

        return dispatch({
            type: 'TEAM_RECIEVED',
            payload: mappedTeam
        })
    }
}

您的异步操作有问题。特别是这段代码:

team.forEach(async (val: User, index: number) => mappedTeam.push({
        ...val,
        photoURL: val.photoURL !== null ? URL.createObjectURL(await 
           fetch(val.photoURL!!).then(res => res.blob())) : null
    }));

将在未来某个时间异步 改变 任何操作之外的存储状态。这是不允许的。试试这个版本。

export function retrieveTeam() {
    return async (dispatch: Dispatch) => {

        const team = await getTeam_firestore();
        const mappedTeam: User[] = await Promise.all(team.map(
            async (val: User, index: number) => {
              const result = {...val};
              if (result.photoURL !== null) {
                const response = await fetch(result.photoURL);
                const blob = await response.blob();
                result.photoURL = URL.createObjectURL(blob);
              }
              return result;
        }));

        console.log('Got team')

        return dispatch({
            type: 'TEAM_RECIEVED',
            payload: mappedTeam
        })
    }
}

此版本在调度 TEAM_RECIEVED 操作之前等待异步获取。

更多解释:

array.foreach(异步函数)只会对一堆异步工作进行排队,但 foreach 会立即 return。您需要等待所有异步工作。所以你不能使用 array.foreach()。解决方案是以下两种模式之一:

假设你有这个方法:

async function getValWithPhoto(val) {
  const result = {...val};
  if (result.photoURL !== null) {
     const response = await fetch(result.photoURL);
     const blob = await response.blob();
     result.photoURL = URL.createObjectURL(blob);
  }
  return result;
}

模式 1 - 运行 串行顺序的每个异步提取(一次一个):

const mappedTeam = [];
for (const val of team) {
  const mappedVal = await getValWithPhoto(val);
  mappedTeam.push(mappedVal);
}

return dispatch(...);

模式 2 - 运行 所有并行(同时)的获取作业(我在上面的回答中所做的):

const arrayOfPromises = team.map(val => getValWithPhoto(val));
// Use Promise.all() to turn the array of promises into a single
// promise: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
const promise = Promise.all(arrayOfPromises);
// now await that promise, which will return array of results
const mappedTeam = await promise;
return dispatch(...);