简体   繁体   English

如果数组为空,则 React 中的条件渲染

[英]Conditional rendering in React if an array is empty

I'm building a cinema listings project.我正在建立一个电影列表项目。 There is a block with information about each film and then the film times below it.有一个包含有关每部电影的信息的块,然后是它下面的电影时间。

I have 2 dropdown menus - one to select a film, one to select a date.我有 2 个下拉菜单 - 一个选择电影,一个选择日期。 I'm using ternary operators to render the results but can't get it so that if a film doesn't have any showings on a particular day, the block of information about the film is hidden when that date is selected.我正在使用三元运算符来呈现结果,但无法获得它,因此如果某部电影在特定日期没有任何放映,则在选择该日期时会隐藏有关该电影的信息块。

I'll just post an example for one of the films.我将仅发布其中一部电影的示例。

Here's the json file that the listing information is taken from -这是列表信息取自的 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"]
    }
]

Here's part of the js file with one of the film listings -这是带有电影列表之一的 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>
        );
    }
}

In this example, there's no showing of the film on a Wednesday so how can I get the info block for the film not to show when Wednesday is selected from the dropdown list?在这个例子中,星期三没有放映电影,所以当从下拉列表中选择星期三时,我怎样才能让电影的信息块不显示?

I think that trying to limit it to one film item as opposed to looping over multiple ones has unfortunately made things more complicated, not less.我认为试图将它限制在一个电影项目而不是循环多个项目不幸地使事情变得更加复杂,而不是更少。 It seems like you're working backwards from the conclusion that, "some films have a Wed showing, therefore we need to conditionally render a Wed component".似乎您正在从“某些电影有Wed放映,因此我们需要有条件地渲染Wed组件”的结论倒退。 In other words, starting with the difference between the data points rather than what they have in common.换句话说,从数据点之间的差异而不是它们的共同点开始。

We could write some convoluted conditional that checks whether a particular property exists, but it will be very brittle and you'll probably end up throwing it out anyway once you move on to mapping everything.我们可以编写一些复杂的条件来检查特定属性是否存在,但它非常脆弱,一旦您继续映射所有内容,您可能最终会丢弃它。 It makes more sense to just be agnostic about which specific properties each film object has and allow the data to flow through your component more naturally.不知道每个胶片对象具有哪些特定属性并允许数据更自然地流过您的组件更有意义。

Starting with your JSON file, group the showings data into a set of more discrete properties.从您的 JSON 文件开始,将放映数据分组为一组更离散的属性。 You'll now be able to easily access and loop over the showings rather than trying to access each one individually by name.您现在可以轻松访问和循环播放放映,而不是尝试按名称单独访问每个放映。

[
    {   
        "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"]}
        ]
    }
]

On to the component.到组件上。 Let's start by getting rid of the unnecessary lifecycle method, and some render variables.让我们从摆脱不必要的生命周期方法和一些渲染变量开始。 Since your data is coming from a static, local file, you can just import it at the top of the component and include it directly in the constructor .由于您的数据来自一个静态的本地文件,您只需将其导入到组件的顶部并直接将其包含在constructor It only makes sense to use componentDidMount when processing data that is not immediately accessible on mount (eg it is coming from a remote resource, or waiting on some other component in the tree).只有在处理无法装载时立即访问的数据(例如,它来自远程资源,或等待树中的其他组件)时,才使用componentDidMount才有意义。 I'm using import syntax here as it should be available to you in a boilerplate React environment.我在这里使用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 (
            ...
        );
    }
}

Now the render function.现在是渲染功能。 I'm going to leave the filmDateContainer alone because it's essentially fine, although of course you'll want to map over the options from the filmListings instead of hardcoding, which should become clearer after this next bit.我将filmDateContainerfilmDateContainer ,因为它本质上很好,尽管当然您会希望映射来自filmListings的选项而不是硬编码,在下一点之后应该会变得更清楚。

We're going to replace the entire div after filmDateContainer with this mapping function, which first loops over each film object to make sure it does not conflict with selectedFilm .我们将用这个映射函数替换filmDateContainer之后的整个div ,它首先遍历每个 film 对象以确保它不与selectedFilm冲突。 It then loops over that film object's showings to make sure each one in turn does not conflit with selectedDate .然后它循环播放该电影对象的放映,以确保每个放映都不会与selectedDate冲突。 Essentially, we've created a staggered filter which nests the data by order of importance (crucially, as you may now notice, by the same order that it is structured in the JSON file).本质上,我们创建了一个交错过滤器,它按重要性顺序嵌套数据(至关重要的是,正如您现在可能注意到的,按照与 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)}

Remember to assign appropriate, unique keys each time you render a mapping function so that React can keep track of everything.记住在每次渲染映射函数时分配适当的唯一键,以便 React 可以跟踪所有内容。 The nesting is also already quite dense so you might want to think about separating each map into its own variable which returns that little snippet of JSX it's responsible for, especially if you have any extra conditions to consider or properties to map.嵌套也已经非常密集,因此您可能需要考虑将每个映射分离到它自己的变量中,该变量返回它负责的那个小 JSX 片段,特别是如果您有任何额外的条件要考虑或要映射的属性。

If you have any questions about any of this drop a comment and I'll try my best to explain.如果您对此有任何疑问,请发表评论,我会尽力解释。

Edit:编辑:

I think the best way to handle the more complex conditional logic you are after is to write some selectors.我认为处理您所追求的更复杂的条件逻辑的最佳方法是编写一些选择器。 This is a concept you'll get more familiar with when you move on to more complicated state management, but it can be used just as effectively here by adding some simple functions before your render return.当您继续进行更复杂的状态管理时,您将更加熟悉这个概念,但通过在渲染返回之前添加一些简单的函数,在这里可以同样有效地使用它。 As a bonus, it's also going to clean up the render return which is quite nice because it was already starting to look a bit ugly.作为奖励,它还将清理渲染返回,这非常好,因为它已经开始看起来有点难看。 By combining selectors you can start to reason about your state in a more logical, readable way, and define common operations which produce specific outcomes.通过组合选择器,您可以开始以更合乎逻辑、更易读的方式推理您的状态,并定义产生特定结果的常见操作。

I also got rid of the ternaries and went for pure && evaluation since it's a bit cleaner.我也摆脱了三元组并进行了纯&&评估,因为它更干净一些。

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>
  );
}

One of the drawbacks to this approach is that you are doing all these calculations every time the component rerenders.这种方法的一个缺点是,每次组件重新渲染时,您都要进行所有这些计算。 Depending on the kind of application you have, how often the component rerenders, and how large the input array is, that could limit performance, but in the vast majority of cases its not going to be an issue.根据您拥有的应用程序类型、组件重新渲染的频率以及输入数组的大小,这可能会限制性能,但在绝大多数情况下,这不会成为问题。

The alternative would be to do these calculations once, only when handleChange is called, and store the result in a new state variable (eg filteredListings ) which you could then map over directly in your render function.另一种方法是一次做这些计算,只有当handleChange被调用,并将结果保存在一个新的状态变量(例如filteredListings ),然后你可以直接映射在你的渲染功能。 However, then you've got duplicate state on your hands with the same data in multiple places, which can be a headache to reason about and synchronise when your data set gets to be any considerable size.但是,如果您的手上有多个相同数据的重复状态,当您的数据集变得相当大时,这可能会令人头疼地推理和同步。 Just something to think about!只是想一想!

In your example, you could simply do:在您的示例中,您可以简单地执行以下操作:

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

It would check that your film object actually has a "wed" key and conditionally render the Wed component.它会检查您的film对象是否确实具有“wed”键并有条件地渲染Wed组件。

Note that I ditched the ?请注意,我放弃了? because if the result of因为如果结果

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

is false, the component would not be rendered.为 false,则不会呈现该组件。 I just find it cleaner.我只是觉得它更干净。

You could do this for each of your cases.您可以为每个案例执行此操作。

Your code would then look like:您的代码将如下所示:

<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>

But I do think that you should re-architecture your solution to have a simpler and more robust way to display the desired component.但我确实认为您应该重新构建您的解决方案,以使用更简单、更健壮的方式来显示所需的组件。 Something in the line of @lawrencee-witt suggestion.符合@lawrencee-witt 建议的内容。

The final solution would be heavily influenced by the control you have over that JSON file.最终的解决方案将受到您对该 JSON 文件的控制的严重影响。

I can create a small CodePen example if you want more info.如果您需要更多信息,我可以创建一个小的 CodePen 示例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM