【问题标题】:Most efficient way of rendering JSX elements when iterating on array of data in React在 React 中迭代数据数组时呈现 JSX 元素的最有效方法
【发布时间】:2019-02-10 02:24:00
【问题描述】:

我有一个包含对象的数组。我正在创建此数组的映射以使用 span 组件呈现名称。

let data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];

我一直在使用以下两个不同的功能来迭代该对象数组,并使用 map 来呈现 JSX 元素。

功能1:

import React, { Component } from 'react';
class App extends Component {

  render() {
    let data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];
    const items = data.map((key, i) => {
      return <span key={key.id}>{key.name}</span>;
    });
    return (
      <div>
        {items}
      </div>
    );
  }
}

export default App;

功能2:

import React, { Component } from 'react';
class App extends Component {

  render() {
    let data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];
    let rows = [];
    data.map((key, i) => {
      rows.push(<span key={key.id}>{key.name}</span>);
    });
    return (
      <div>
        {rows}
      </div>
    );
  }
}


export default App;

我知道上述两种使用map 和渲染JSX 元素的不同方式。除了这两种之外,还有其他方法可以做到这一点吗?如果有,推荐哪个?

【问题讨论】:

  • "Functionality2" 在调用 .map 时创建一个从未使用过的数组。切换到data.forEach,这样就不会创建不必要的数组了。
  • 但这仅在分配给参考权时?比如 const array = data.map(如果我错了请纠正我
  • 不,无论您是否将其分配给变量,都会创建数组。在上面的代码中,创建了 Array,然后立即有资格进行垃圾收集,因为没有引用指向它。如果切换到.forEach,则不会创建额外的Array。
  • 好的,明白了。谢谢。当我们在迭代中渲染 jsx 元素时,您还可以帮助我使用其他渲染方式吗?除了我提到的和下面提到的其他内容
  • 对您的两种方法以及一些变体进行快速的JsPerf 比较。功能 1 是您的最佳选择。

标签: javascript reactjs jsx array.prototype.map


【解决方案1】:

第一种方法更好。

  1. Array.prototype.map 在后台创建一个数组,并在对每个元素应用修改后返回它。 Functionality-1 创建两个数组,而 Functionality-2 创建三个。

  2. Functionality-1 读起来更好。这就是通常编写 React 代码的方式。对于像这样的简单元素,我会保存项目的 const 定义,并将 map 语句放在 JSX 中以便直接返回。

【讨论】:

    【解决方案2】:

    我会这样做

    const data = [{id: 1, name: 'a'}, {id: 2, name: 'b'}];
    
    export default class App extends PureComponent {
      render() {
        return (
          <div>
            {data.map(({ id, name }) => <span key={id}>{name}</span>)}
          </div>
        );
      }
    }
    

    现在,您的 data 不会在每次渲染时都重新实例化,并且您不必垃圾收集任何不必要的变量声明。

    【讨论】:

    • 赞成。我要补充一点,如果您想避免将 &lt;span&gt; 元素包装在 &lt;div&gt; 中,您可以使用 React Fragments:reactjs.org/docs/fragments.html
    【解决方案3】:

    您可以使用它来更好地理解

     const data = [{id: 1, name: 'a'}, {id: 2, name: 'b'}];
    
    export default class App extends React.Component {
      render() {
        return (
          <div>
            {data.map((data, dataIndex ) => <span key={dataIndex}>{data.name}</span>)}
          </div>
        );
      }
    }
    

    这里可以理解为name属于with属性。

    【讨论】:

    • map-method提供的索引不推荐作为元素键值使用。
    【解决方案4】:

    大多数情况下,我遵循以下规则:

    创建一个渲染项目的组件

    // in some file
    export const RenderItems = ({data}) => {
      return data && data.map((d, i) => <span key={d.id}>{d.name}</span>) || null
    }
    

    挂钩 RenderItems

    import { RenderItems } from 'some-file'
    
    class App extends Component {
      render() {
        // you may also define data here instead of getting data from props
        const { data } = this.props
        return (
          <div>
            <RenderItems data={data} />
          </div>
        )
      }
    }
    

    在组件中附加数据

    const data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}]
    <App data={data} />
    

    即使使用第二个代码示例,遵循此规则也不会影响性能。推送数组中的项目并渲染项目。因为,您不是直接在渲染钩子中工作。始终注意渲染钩子不会直接在其中实现任何逻辑。


    此外,我不会仅仅为了使用密钥而创建 id

    const data = [{"name": "Hi"}, {"name": "Hello"}]
    //... and when using index as key
    .map((d, i) => <span key={'item-'+i}>
    // or,
    .map((d, i) => <span key={'item-'+i+'-'+d.name}>
    

    请参阅 this post 为什么我在使用索引作为键时遵循此语法。


    更新:

    如果要避免使用不必要的html标签,可以使用React.Fragment

    export const RenderItems = ({data}) => {
      return data && 
        data.map(
          (d, i) => <React.Fragment key={d.id}>{d.name}</React.Fragment>
        ) || null
    }
    // and when rendering, you just return the component
    return <RenderItems data={data} />
    

    注意:

    1. 仅当您没有任何其他属性时,您才能使用 &lt;&gt;&lt;/&gt; 作为 &lt;React.Fragment&gt;&lt;/React.Fragment&gt; 的别名。由于我们在其上使用 key 属性,而不是使用它。
    2. 查看this 以支持React.Fragment 的短符号。

    使用&lt;&gt;&lt;/&gt;的示例:

    <>{d.name}</>
    

    这将在不带任何标记的 html 中呈现d.name 的值。当我们专门将现有设计转换为响应应用程序时,这被认为是最好的。或者,可能还有其他情况。比如,我们将显示一个定义列表:

    <dl>
      <dt></dt>
      <dd></dd>
      <dt></dt>
      <dd></dd>
      <dt></dd>
    </dl>
    

    而且我们不想附加不必要的html标签,那么使用Fragment会让我们的生活更轻松:

    例子:

    <>
      <dt>{d.term}</dt>
      <dd>{d.definition}</dd>
    </>
    

    最重要的情况是在tr(TR 组件)中渲染td 元素。如果我们不这样做,那么我们就违反了 HTML 的规则。该组件将无法正确呈现。在反应中,它会抛出一个错误。

    更新2:

    另外,如果你有long list of props,如下所示:

    const {
      items,
      id,
      imdbID,
      title,
      poster,
      getMovieInfo,
      addToFavorites,
      isOpen,
      toggleModal,
      closeModal,
      modalData,
    } = props
    

    你可以考虑像这样解构:

    const { items, ...other } = props
    // and in your component you can use like:
    <div modalData={other.modalData}>
    

    但是,我个人更喜欢使用第一个示例代码。这是因为在开发过程中,我不需要每次都回顾其他组件或寻找控制台。在给定的示例中,有像 modalData={} 这样的键,所以我们很容易维护 modalData={other.modalData}。但是如果需要像&lt;div&gt;{modalData}&lt;/div&gt; 这样的代码呢?那么,你也可以同意我的偏好。

    【讨论】:

    • 谢谢。在第一次看到之前,我从未尝试过这样的方式。
    • 很高兴为您提供帮助。
    • 索引适合用作此处的键,但通常被认为是不好的做法。答案没有提到这一点。
    • @estus 与某个常数值一起使用时,它不会被认为是坏的。
    【解决方案5】:

    通常,forwhile 语句是迭代数组的最有效方式。在非关键位置处理小阵列的方式可以认为是微优化。

    map 的使用在 React 组件中是惯用的,因为它足够快并且可以将值作为表达式的一部分返回。

    虽然这是一个反模式:

    let rows = [];
    data.map((key, i) => {
      rows.push(<span key={key.id}>{key.name}</span>);
    });
    

    map 应该将数组元素映射到其他值(因此得名),而不是迭代数组而不是 forEach 或其他循环。这个问题可以用 ESLint array-callback-return 规则跟踪。

    组件是无状态的,不需要是Component 类。它可以是功能组件或PureComponent 类。由于data 是常量,所以不需要在每次渲染时都赋值:

    const data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];
    
    const App = props => <div>
      {data.map(({ id, name }) => <span key={id}>{name}</span>)}
    </div>;
    

    【讨论】:

    • 谢谢。如果我想在迭代中生成带有事件处理函数的 jsx 元素怎么办。我还能为此使用无状态组件吗?
    • @Think-Twice 可能是的,这取决于这些处理程序应该做什么。你能用另一个例子来更新这个问题吗?
    【解决方案6】:

    第一种方法是正确的。使用 map 函数遍历数组。

    export default class App extends React.Component{
        constructor(props){
            super(props);
            this.state = {
              data: [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];
            };
     }
        render(){
            return(
                <div>           
                    {this.state.data.map((data,index)=>
                          <span key={data.id}>{data.name}</span>
                    )}             
            );
        }
    }

    【讨论】:

      【解决方案7】:

      我会在 return(...) 中使用 map,因为 map 返回一个数组。它更干净、更易读,这是我个人努力的目标。

      为了补充答案,如果数组 data 可能会改变方向,我会创建一个新的无状态转储组件:

      const Spanner = props => { return <span key={ props.data.id }>{ props.data.name }</span> };
      
      class App extends Component {
        render() {
          let data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];
            return (
              <div>
                { 
                  data.map( (item, ind) => {
                    return (<Spanner key={item.id} data={item.name} />);
                  })
                }
              </div>
            );
          }
        }
      }
      

      这样我们就可以把这个 Spanner 组件当作一个公共组件在不同组件之间共享。如果data 随时间变化(大多数情况下会这样),您可以将包装函数写入 Spanner 组件并在需要时调用新函数。 data=[{"id": "01", "name": "Hi", "userType": "Admin"}, {"id": "02", "name": "Hello", "userType": "Client"}];

      const UserWidget = (props) => {
        return (
          <div>
            <h4>{ props.type }</h4>
            <Spanner key={ props.key } data={ props.name }/>
          </div>
        );
      }
      // now both the UserWidget and the Spanner Components can be used wherever required
      // say a dashboard Component wants to use the UserWidget
      class Dashboard extends Component {
        render() {
          let data = [{"id": "01", "name": "Hi", "userType": "Admin"}, {"id": "02", "name": "Hello", "userType": "Client"}];
            return (
              <div>
                { 
                  data.map( (item, ind) => {
                    return (<UserWidget type={ item.userType } key={item.id} data={item.name} />);
                  })
                }
              </div>
            );
          }
        }
      }
      

      这样您就不会重复自己,即使现在新的 data 数组具有新的键 userType,您的应用程序组件仍然具有预期的行为。 这也是我的做法。

      【讨论】:

        【解决方案8】:

        函数toSpan可以复用。

        class App extends React.Component {
          render() {
            const data = [{id: `01`, name: `Hi`}, {id: `02`, name: `Hello`}]
            const toSpan = ({id, name}) => <span key={id}>{name}</span>
            return (
              <div>{data.map(toSpan)}</div>
            )
          }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-06-03
          • 2012-02-03
          • 2019-04-21
          • 1970-01-01
          • 2019-04-03
          • 2015-06-12
          • 2020-06-09
          • 2020-04-08
          相关资源
          最近更新 更多