首页 > 解决方案 > 通过 ReactJS 中的一个共享字段(整数)对多个不同对象的数组进行排序?

问题描述

我有一个对象(页面),其中包含多个不同的嵌套对象(问题、图像、文本、视频等),它们都具有相同的属性(数字)作为来自 Ruby on Rails 的 JSON

render :json => @page, :include => [:videos, :images, :rtfs, questions: { :include => :answers }]   

如何显示按 number 属性排序的所有这些内容?我可以轻松地通过page.imagespage.videos等进行映射,并以正确的相应顺序显示它们,因为它们已经按 RoR 中的数字字段排序,但这只能让我到目前为止。假设该页面有 2 个问题(编号 2 和 4)、1 个视频(编号 3)、1 个图像(编号 1)并且没有 rtf。现在我只是先映射视频并渲染它​​们,然后是图像,然后是问题,然后是 rtf。但是在这种特殊情况下我需要做的是首先显示图像(因为它的数字属性是 1),然后是一个问题(数字属性是 2),然后是视频(因为它是数字 3),最后,另一个问题。我觉得我需要为所有嵌套对象类型创建单独的数组(videos, images, questions等),然后将它们(最有可能使用 splat 运算符)合并到一个名为 的数组page_data中,按数字字段对其进行排序,然后进行渲染。由于我对编码还是很陌生,所以我很难弄清楚,希望能得到任何帮助!以下是 React 组件的相关部分

class LessonCreatorPage extends Component {constructor(props) {
super(props);
this.state = {
  page_preview: {}, //this is the main object that has different types of nested objects
}}
showPage(id){
//returns the response from backend and passes it to page_preview object 
}
  };
render(){
   {this.state.page_preview && (
            React.Fragment>

对于 this.state.page_preview.images、questions、rtf,重复以下代码段。它们都以不同的方式显示

 {this.state.page_preview.videos && (
                <React.Fragment>
                  {this.state.page_preview.videos.map((video) =>{
                    const video_id = `${video.vimeo_id}`;
                    return (
                      <React.Fragment key={video.id}>
                        <Label pointing="below">{video.title}</Label>

                        <Embed id={video_id} source="vimeo" />
                        <Label pointing>{video.description}</Label>
                      </React.Fragment>
                    );
                  })}
                </React.Fragment>
              )}
            </React.Fragment>
          )}

标签: javascriptruby-on-railsreactjs

解决方案


我觉得我需要为所有嵌套对象类型(videos, images,questions等)创建单独的数组,然后将它们(最有可能使用 splat 运算符)合并到一个名为 的数组page_data中,按数字字段对其进行排序,然后渲染它.

你在正确的道路上。不过,没有理由在服务器上执行此操作。您正在请求 JSON,并且您标记的问题,所以我假设数据将在那里传递。

您可以在客户端组合不同的项目,根据 排序number,然后显示它们。无需更改服务器代码。下面的代码片段是如何以所需方式显示数据的示例。

在 JavaScript 中,您可以使用扩展...运算符(类似于 Ruby 中的 splat 运算符)组合 2 个数组。

const a = [1, 2];
const b = [3, 4];
[...a, ...b]; //=> [1, 2, 3, 4]

然后,您可以使用.sort()按所需顺序对组合数组进行排序。

<script type="text/babel">
class Demo extends React.Component {
  constructor(props) {
    super(props);
    this.state = { page: null, pageData: null };
  }
  async componentDidMount() {
    // You might want to wrap the code in a try/catch block to handle any errors
    // that might occur (like no network connection).
    const page = await fetchRailsPage();
    
    // Concatenate the different items into a single array, but add a new type
    // property first to differentiate the different types of items later.
    const pageData = [
      ...page.videos.map((video) => ({ ...video, type: "video" })),
      ...page.images.map((image) => ({ ...image, type: "image" })),
      ...page.rtfs.map((rtf) => ({ ...rtf, type: "rtf" })),
      ...page.questions.map((question) => ({ ...question, type: "question" })),
    ].sort((a, b) => a.number - b.number);
    
    this.setState({ page, pageData });
  }
  render() {
    if (!this.state.page) return <>loading...</>;
    return (
      <>
        <h1>{this.state.page.title}</h1>
        {this.state.pageData.map((item) => (
          <p key={JSON.stringify([item.type, item.id])}>
            {item.number} - {item.title}
          </p>
        ))}
      </>
    );
  }
}

// mock fetching and parsing the JSON data
async function fetchRailsPage() {
  await sleep(2000);
  
  return {
    id: 1,
    title: "page title",
    videos: [
      { id: 1, number: 3, title: "video title" },
    ],
    images: [
      { id: 1, number: 1, title: "image title" },
    ],
    rtfs: [],
    questions: [
      { id: 1, number: 2, title: "question title #1", answers: [] },
      { id: 2, number: 4, title: "question title #2", answers: [] },
    ],
  };
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

ReactDOM.render(<Demo />, document.querySelector("#demo"));
</script>

<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<div id="demo"></div>

在上面的代码片段.map((video) => ({ ...video, type: "video" }))中是可选的,但type为每个项目添加了一个属性。这可以省略,但可能很有用,因为您可能希望渲染与“问题”对象不同的“视频”对象。

type添加属性用法的一个示例是将map()回调更改为类似的内容(您也可以使用switch语句):

{this.state.pageData.map((item) => {
  if (item.type == "video") {
    const video = item;
    const video_id = `${video.vimeo_id}`;
    return (
      <React.Fragment key={video.id}>
        <Label pointing="below">{video.title}</Label>

        <Embed id={video_id} source="vimeo" />
        <Label pointing>{video.description}</Label>
      </React.Fragment>
    );
  }
  if (item.type == "image") {
    // ...
  }
})}

另一种选择是传递渲染组件或函数而不是类型。

// in your class component
renderVideo(video) {
  const video_id = `${video.vimeo_id}`;
  return (
    <React.Fragment key={video.id}>
      <Label pointing="below">{video.title}</Label>

      <Embed id={video_id} source="vimeo" />
      <Label pointing>{video.description}</Label>
    </React.Fragment>
  );
}

然后代替类型,传递渲染方法的名称:

...page.videos.map((video) => ({ ...video, render: "renderVideo" })),

最后,更新方法map()中的回调render

{this.state.pageData.map((item) => this[item.render](item)}

推荐阅读