【问题标题】:Conditional rendering in React if an array is empty如果数组为空,则在 React 中进行条件渲染
【发布时间】:2020-11-15 20:51:17
【问题描述】:

我正在构建一个影院列表项目。有一个区块,其中包含有关每部电影的信息,然后是其下方的电影时间。

我有 2 个下拉菜单 - 一个用于选择电影,一个用于选择日期。我正在使用三元运算符来渲染结果,但无法获得结果,因此如果电影在特定日期没有任何放映,则在选择该日期时隐藏有关电影的信息块。

我将只发布其中一部电影的示例。

这是获取列表信息的json文件-

[
    {   
        "id": "film1",
        "filmTitle": "Knives Out",
        "paragraphText": "Lorem ipsum dolor sit amet",
        "mon": ["12:00", "15:00", "19:00"],
        "tue": ["13:10", "16:30", "19:00", "21:00"]
    }
]

这是 js 文件的一部分,其中包含电影列表 -

    constructor(props){
        super(props);
        this.state = {
            filmListings: [],
            selectedFilm: "allFilms",
            selectedDate: "allDates"
        }
        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(event){
        const {name, value} = event.target 
        this.setState({ 
            [name]: value 
        });
    }


    componentDidMount() {
        const FilmListings = require("./components/booking/filmTimesData.json");
        this.setState({ filmListings: FilmListings })
    }

    render() {
        const filmsArray = require("./components/booking/filmTimesData.json");
        const selectedFilm = this.state.selectedFilm
        const selectedDate = this.state.selectedDate


        return (
            <form id="searchForm">
                <div id="filmDateContainer">
                    <div className="searchOption">
                        <h2>Film:</h2>
                        <img src={FilmSearch} alt="film icon"/>
                        <select 
                            name="selectedFilm" 
                            value={this.state.selectedFilm}
                            onChange={this.handleChange}
                            className="dropdown"
                        >
                            <option value="allFilms">All Films</option>
                            <option value="film1">Knives Out</option>
                            <option value="film2">Judy and Punch</option>
                            <option value="film3">Harriet</option>
                        </select>
                    </div>
                    <h2 id="or">OR</h2>
                    <div className="searchOption">
                        <h2>Date:</h2>
                        <img src={DateSearch} alt="date icon"/>
                        <select 
                            name="selectedDate" 
                            value={this.state.selectedDate}
                            onChange={this.handleChange}
                            className="dropdown" 
                        >
                            <option value="mon">Monday 2nd December</option>
                            <option value="tue">Tuesday 3rd December</option>
                            <option value="wed">Wednesday 4th December</option>
                        </select>
                    </div>
                </div>

                        <div>
                            {(selectedFilm === "film1" || selectedFilm === "allFilms") ?
                            <FilmInfo filmTitle={filmsArray[0].filmTitle} paragraphText={filmsArray[0].paragraphText}/> : " "}

                            {(selectedDate === "mon" || selectedDate === "allDates") 
                                && (selectedFilm === "film1" || selectedFilm === "allFilms") ? 
                                <Mon day={filmsArray[0]}/> : " "
                            }


                            {(selectedDate === "tue" || selectedDate === "allDates") 
                                && (selectedFilm === "film1" || selectedFilm === "allFilms") ?
                                <Tue day={filmsArray[0]}/> : " "
                            }


                            {(selectedDate === "wed" || selectedDate === "allDates") 
                                && (selectedFilm === "film1" || selectedFilm === "allFilms") ?
                                <Wed day={filmsArray[0]}/> : " "
                            }
                        </div>
            </form>
        );
    }
}

在此示例中,星期三没有放映电影,那么当从下拉列表中选择星期三时,如何获取不放映影片的信息块?

【问题讨论】:

  • 我有点难以理解你在这里想要做什么。例如,需要两次相同的数据文件,或者硬编码film1filmsArray[0] 值,或者在一周中的每一天都有单独的组件。 似乎您想要做的是创建一个根据值 selectedFilmselectedDate 过滤的电影列表,然后您可以映射到渲染多个电影项目,但是您的代码是编写的以这样一种方式,它只能呈现一个。
  • 如果可能的话,你可以尝试做一个Minimal, Complete and Reproducible 的例子。我推荐使用 Codesandbox,但这当然取决于你的喜好。
  • 是的,它不止一部电影。我只是在这个例子中使用了一部电影来尝试简化它。我尝试使用过滤器和地图,但遇到了更多问题,因为我基于 2 个下拉菜单

标签: javascript reactjs conditional-operator


【解决方案1】:

我认为尝试将其限制在一个电影项目而不是循环播放多个电影项目不幸地使事情变得更加复杂,而不是更少。似乎您正在从“某些电影有Wed 放映,因此我们需要有条件地渲染Wed 组件”的结论倒退。换句话说,从数据点之间的差异而不是它们的共同点开始。

我们可以编写一些复杂的条件来检查特定属性是否存在,但它会非常脆弱,一旦您继续映射所有内容,您可能最终还是将其丢弃。不知道每个电影对象具有哪些特定属性并允许数据更自然地流经您的组件更有意义。

从您的 JSON 文件开始,将放映数据分组为一组更离散的属性。您现在可以轻松访问和循环播放节目,而不是尝试按名称单独访问每个节目。

[
    {   
        "id": "film1",
        "filmTitle": "Knives Out",
        "paragraphText": "Lorem ipsum dolor sit amet",
        "showings": [
            {"date": "mon", "times": ["12:00", "15:00", "19:00"]},
            {"date": "tue", "times": ["13:10", "16:30", "19:00", "21:00"]}
        ]
    }
]

在组件上。让我们从摆脱不必要的生命周期方法和一些渲染变量开始。由于您的数据来自静态的本地文件,您只需将其导入组件顶部并直接包含在constructor 中。只有在处理无法立即访问的数据时使用componentDidMount 才有意义on 挂载(例如,它来自远程资源,或等待树中的某些其他组件)。我在这里使用 import 语法,因为它应该在样板 React 环境中可供您使用。

import filmData from "./components/booking/filmTimesData.json";

class FilmListings extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            filmListings: filmData,
            selectedFilm: "allFilms",
            selectedDate: "allDates"
        };
        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(event){
        const {name, value} = event.target 
        this.setState({ 
            [name]: value 
        });
    }

    render() {
        const { filmListings, selectedFilm, selectedDate } = this.state;

        return (
            ...
        );
    }
}

现在是渲染功能。我将不理会filmDateContainer,因为它基本上没问题,尽管您当然希望映射filmListings 中的选项而不是硬编码,这在下一点之后应该会变得更清晰。

我们将用这个映射函数替换filmDateContainer 之后的整个div,它首先循环遍历每个电影对象以确保它不与selectedFilm 冲突。然后它会遍历该电影对象的放映,以确保每个放映对象都不会与selectedDate 发生冲突。本质上,我们创建了一个交错过滤器,它按重要性顺序嵌套数据(至关重要的是,您现在可能注意到,按照与 JSON 文件中的结构相同的顺序)。

{filmListings.map(film => 
  selectedFilm === "allFilms" || selectedFilm === film.id ? (
    <div key={film.id}>
      <FilmInfo filmTitle={film.filmTitle} paragraphText={film.paragraphText}/>
      {film.showings.map(showing => 
        selectedDate === "allDates" || selectedDate === showing.date ? (
          <Day showing={showing} key={film.id + showing.date}/>
      ) : null)}
    </div>
) : null)}

记得在每次渲染映射函数时分配适当的唯一键,以便 React 可以跟踪所有内容。嵌套也已经很密集了,所以您可能需要考虑将每个映射分成自己的变量,该变量返回它负责的 JSX 的小 sn-p,特别是如果您有任何额外的条件要考虑或要映射的属性。

如果您对此有任何疑问,请发表评论,我会尽力解释。

编辑:

我认为处理您所追求的更复杂的条件逻辑的最佳方法是编写一些选择器。当您继续进行更复杂的状态管理时,您会更加熟悉这个概念,但在这里可以通过在渲染返回之前添加一些简单的函数来有效地使用它。作为奖励,它还将清理渲染返回,这非常好,因为它已经开始看起来有点难看。通过组合选择器,您可以开始以更合乎逻辑、更易读的方式推理您的状态,并定义产生特定结果的常见操作。

我也摆脱了三元组并进行了纯 &amp;&amp; 评估,因为它更干净一些。

render() {
  const { filmListings, selectedFilm, selectedDate } = this.state;

  const allFilms = selectedFilm === 'allFilms';
  const allDates = selectedDate === 'allDates';
  const validDate = date => allDates || selectedDate === date;
  const filmHasDate = showings => showings.some(showing => validDate(showing.date));
  const validFilm = (id, showings) => (allFilms || selectedFilm === id) && filmHasDate(showings);

  return (
    <form>
      ...
      {filmListings.map(film => validFilm(film.id, film.showings) && (
        <div key={film.id}>
          <FilmInfo filmTitle={film.filmTitle} paragraphText={film.paragraphText}/>
          {film.showings.map(showing => validDate(showing.date) && (
            <Day showing={showing} key={film.id + showing.date}/>
          ))}
        </div>
      ))}
    </form>
  );
}

这种方法的一个缺点是每次组件重新呈现时您都在进行所有这些计算。根据您拥有的应用程序类型、组件重新呈现的频率以及输入数组的大小,这可能会限制性能,但在绝大多数情况下这不会成为问题。

另一种方法是只在调用handleChange 时执行一次这些计算,并将结果存储在一个新的状态变量(例如filteredListings)中,然后您可以直接在渲染函数中映射该变量。但是,当您的数据集变得相当大时,您的手上会出现重复状态,并且在多个位置使用相同的数据进行同步。只是想一想!

【讨论】:

  • 非常感谢,它在让搜索功能正常工作方面起到了很大的作用。不幸的是,我仍然有一个问题,如果有人要搜索特定电影和没有放映该电影的日期,则日期/时间不会显示,但电影的详细信息仍会显示。我正在努力寻找一种方法,如果列表的日期/时间部分没有任何内容,则 FilmInfo 块也将为空。我希望这是有道理的。再次感谢您的帮助
【解决方案2】:

在您的示例中,您可以简单地执行以下操作:

{((selectedDate === "wed" && filmsArray[0][selectedDate]) || selectedDate === "allDates") 
    && (selectedFilm === "film1" || selectedFilm === "allFilms")
    &&
        <Wed day={filmsArray[0]}/>
}

它会检查您的 film 对象是否确实具有“wed”键并有条件地呈现 Wed 组件。

请注意,我放弃了?,因为如果结果是

((selectedDate === "wed" && filmsArray[0][selectedDate]) || selectedDate === "allDates") 
    && (selectedFilm === "film1" || selectedFilm === "allFilms")

为 false,组件不会被渲染。我只是觉得它更干净。

您可以为每个案例都这样做。

您的代码将如下所示:

<div>
    {(selectedFilm === "film1" || selectedFilm === "allFilms") &&
        <FilmInfo filmTitle={filmsArray[0].filmTitle} paragraphText={filmsArray[0].paragraphText}/>
    }

    {((selectedDate === "mon" && filmsArray[0][selectedDate]) || selectedDate === "allDates") 
        && (selectedFilm === "film1" || selectedFilm === "allFilms")
        &&
            <Mon day={filmsArray[0]}/>
    }

    {((selectedDate === "tue" && filmsArray[0][selectedDate]) || selectedDate === "allDates") 
        && (selectedFilm === "film1" || selectedFilm === "allFilms")
        &&
            <Tue day={filmsArray[0]}/>
    }

    {((selectedDate === "wed" && filmsArray[0][selectedDate]) || selectedDate === "allDates") 
        && (selectedFilm === "film1" || selectedFilm === "allFilms")
        &&
            <Wed day={filmsArray[0]}/>
    }
</div>

但我确实认为您应该重新构建您的解决方案,以便以一种更简单、更强大的方式来显示所需的组件。符合@lawrencee-witt 建议的内容。

最终解决方案将受到您对该 JSON 文件的控制权的严重影响。

如果您想了解更多信息,我可以创建一个小的 CodePen 示例。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-08-26
    • 2017-01-25
    • 1970-01-01
    • 2021-06-07
    • 2018-11-02
    • 2020-09-20
    • 1970-01-01
    相关资源
    最近更新 更多