送给React开发者十九条性能优化建议

送给React开发者十九条性能优化建议

笔者是一个 react 重度爱好者,在工作之余,也看了不少的 react 文章, 写了很多 react 项目 ,接下来笔者讨论一下 React 性能优化的主要方向和一些工作中的小技巧。送人玫瑰,手留余香,阅读的朋友可以给笔者点赞,关注一波 。陆续更新前端文章。

1. 组件卸载前进行清理操作

在组件中为 window 注册的全局事件, 以及定时器, 在组件卸载前要清理掉, 防止组件卸载后继续执行影响应用性能.

需求:使用 useState 保存一个数值,然后开启定时器改变数值,卸载组件查看定时器是否还在运行。

import React, { useState, useEffect } from "react"
import ReactDOM from "react-dom"

const App = () => {
  let [index, setIndex] = useState(0)
  useEffect(() => {
    let timer = setInterval(() => {
      console.log(index)
      setIndex(++index)
    }, 1000)
    return () => clearInterval(timer)
  }, [])
  return (
    <button onClick={() => ReactDOM.unmountComponentAtNode(document.getElementById("root"))}>
      {index}
    </button>
  )
}

export default App

2. 使用纯组件降低组件重新渲染的频率

什么是纯组件
相同的输入 (state、props) 呈现相同的输出. 在输入内容相同的情况下纯组件不会被重新渲染.

如何实现纯组件
React 提供了 PureComponent 类, 类组件在继承它以后, 类组件就变成了纯组件. 纯组件会对 props 和 state 进行浅层比较, 如果上一次的 props、state 和下一次的 props、state 相同, 则不会重新渲染组件.

什么是浅层比较
浅比较指的是比较基本数据类型是否具有相同的值, 比如1是否等于1, true是否等于true. 比较复杂数据类型的第一层值是否相同。

浅层比较难道没有性能消耗吗
和进行 diff 比较操作相比,浅层比较将消耗更少的性能。diff 操作会重新遍历整颗 virtualDOM 树, 而浅层比较值操作当前组件的 state 和 props。

需求:在状态对象中存储 name 值为张三,组件挂载完成后将 name 属性的值再次更改为张三,然后分别将 name 传递给纯组件和非纯组件,查看结果。纯组件只渲染一次,非纯组件会一秒打印一次

import React from "react"
export default class App extends React.Component {
  constructor() {
    super()
    this.state = {name: "张三"}
  }
  updateName() {
    setInterval(() => this.setState({name: "张三"}), 1000)
  }
  componentDidMount() {
    this.updateName()
  }
  render() {
    return (
      <div>
        <RegularComponent name={this.state.name} />
        <PureChildComponent name={this.state.name} />
      </div>
    )
  }
}

class RegularComponent extends React.Component {
  render() {
    console.log("RegularComponent")
    return <div>{this.props.name}</div>
  }
}

class PureChildComponent extends React.PureComponent {
  render() {
    console.log("PureChildComponent")
    return <div>{this.props.name}</div>
  }
}

3. 使用 React.memo 进行组件缓存

React.memo 是一个高阶组件.

什么是高阶组件 Higher Order Component ( HOC )
高阶组件是 React 应用中共享代码, 增加逻辑复用的一种方式. 比如 A 组件和 B 组件都需要使用一个相同的逻辑,如何将逻辑抽取到一个公共的地方呢?答案就是使用高阶组件。
高阶组件的核心思想就是在组件的外层再包裹一层执行逻辑的组件,在外层组件中执行逻辑,再将逻辑执行的结果传递到内层组件。
高阶组件形式是一个函数,接收组件作为参数, 返回一个新的组件. 参数组件就是需要复用逻辑的组件,函数内部返回的新组件就是执行逻辑的组件,在新组件内部执行完逻辑以后再调用参数组件并将逻辑结果传递给参数组件。Image
函数名通常以 with 开头, 接收的组件形参名称为 WrappedComponent, 返回的组件名称和函数名称一样, 只不过 with 中的 w 要大写.

import React from "react"

export class A extends React.Component {
  render() {
    return <div>{this.props.sizes.join("-")}</div>
  }
}
export class B extends React.Component {
  render() {
    return <div>{this.props.sizes.join("-")}</div>
  }
}

const WrappedA = withResizable(A)
const WrappedB = withResizable(B)
function withResizable(WrappedComponent) {
  class WithResizable extends React.Component {
    constructor() {
      super()
      this.state = {
        sizes: [window.innerWidth, window.innerHeight]
      }
      this.updateSizes = this.updateSizes.bind(this)
    }
    updateSizes() {
      this.setState({
        sizes: [window.innerWidth, window.innerHeight]
      })
    }
    componentDidMount() {
      window.addEventListener("resize", this.updateSizes)
    }
    render() {
      return <WrappedComponent sizes={this.state.sizes} />
    }
  }
  return WithResizable
}
import React from "react"

export default class App extends React.Component {
  render() {
    return (
      <div>
        <WrappedA />
        <WrappedB />
      </div>
    )
  }
}

React.memo 介绍
与 PureComponent 相似, 如果上一次输入的 props 和下一次输入的 props 相同, 则组件不会重新渲染, 从而使组件更高效.
PureComponent 应用于类组件, React.memo 应用于函数组件.
React.memo 使用的也是使用浅层比较.
React.memo 使用

import React, { useState, useEffect, memo } from "react"

export default function App() {
  let [index, setIndex] = useState(0)
  let [name] = useState("张三")
  useEffect(() => {
    let timer = setInterval(() => {
      setIndex(++index)
    }, 1000)
    return () => clearInterval(timer)
  }, [])
  return (
    <>
      {index}
      <ShowNameMemo name={name} />
    </>
  )
}

function ShowName(props) {
  console.log("ShowName")
  return <div>{props.name}</div>
}

const ShowNameMemo = memo(ShowName)
为 memo 传递比较逻辑
memo 方法可以自定义比较逻辑,如果为组件传递的数据涉及嵌套层次,就可以自定义比较逻辑。memo 方法的第二个参数即为比较函数.比较函数的第一个参数为上一次的 props, 比较函数的第二个参数为下一次的 props, 比较函数返回 true, 不进行渲染, 比较函数返回 false, 组件重新渲染.

import React, { useState, useEffect } from "react"

const Home = () => {
  let [index, setIndex] = useState(0)
  useEffect(() => {
    const timer = setInterval(() => {
      setIndex(++index)
    }, 1000)
    return () => clearInterval(timer)
  })

  return (
    <>
      <div>{index}</div>
      <ShowPersonMemoComponent person={{ name: "张三", age: 20 }} />
    </>
  )
}

export default Home
// props => {person: {name: "张三", age: 20}}
const ShowPersonComponent = ({ name, age }) => {
  console.log("ShowNameComponent Rendering")
  return <div>{name} {age}</div>
}

const ShowPersonMemoComponent = React.memo(ShowPersonComponent, comparePerson)

function comparePerson(prevProps, nextProps) {
  if (prevProps.name === nextProps.name && prevProps.age === nextProps.age) {
    return true
  }
  return false
}

4. shouldComponentUpdate 减少组件渲染频率

shouldComponentUpdate 是类组件的生命周期函数, 在组件 props 或者 state 发生改变后调用. 函数默认返回 true, 重新渲染组件, 返回 false, 阻止组件重新渲染.

函数的第一个参数为 nextProps, 第二个参数为 nextState.

需求: 在页面中展示员工信息, 员工信息包括, 姓名, 年龄, 薪水, 职位等等. 但是在页面中只想展示姓名和年龄. 也就是说只有姓名和年龄发生变化时才有必要重新渲染组件, 如果员工的其他信息发生了变化没必要重新渲染组件.

import React from "react"

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {name: "张三", age: 20, job: "waiter"}
  }
  componentDidMount() {
    setTimeout(() => this.setState({ job: "chef" }), 1000)
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.state.name !== nextState.name || this.state.age !== nextState.age) {
      return true
    }
    return false
  }

  render() {
    console.log("rendering")
    let { name, age } = this.state
    return <div>{name} {age}</div>
  }
}

5. 使用组件懒加载

使用组件懒加载可以减少 bundle 文件大小, 加快组件呈递速度.

路由组件懒加载

import {BrowserRouter, Link, Redirect, Switch, Route } from "react-router-dom"
import { lazy, Suspense } from "react"
import Home from "./pages/Home"
// import About from "./pages/About"

const About = lazy(() => import(/* webpackChunkName: "about" */ "./pages/About"))

function App() {
  return (
    <BrowserRouter>
      <div>
        <Link to="/home">Home</Link> - <Link to="/about">About</Link>
      </div>
      <Switch>
        <Route path="/home">
          <Home />
        </Route>
        <Route path="/about">
          <Suspense fallback={<div>loading....</div>}>
            <About />
          </Suspense>
        </Route>
        <Route path="/">
          <Redirect to="/home" />
        </Route>
      </Switch>
    </BrowserRouter>
  )
}

export default App

根据条件进行组件懒加载
适用于组件不会随条件频繁切换。好处是可以将两个组件全部从 bundle 中分离出来,减少 bundle 文件的体积。

import { lazy, Suspense } from "react"

export default function About() {
  let LazyComponent = null
  if (true) {
    LazyComponent = lazy(() => import(/* webpackChunkName: "list" */ "../components/List"))
  } else {
    LazyComponent = lazy(() => import(/* webpackChunkName: "news" */ "../components/News"))
  }
  return (
    <div>
      about page works
      <Suspense fallback={<div>loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  )
}

6. 使用 Fragment 以避免额外的标记

React 组件中返回的 jsx 如果有多个同级元素, 多个同级元素必须要有一个共同的父级.

function App() {
  return (
    <div>
      <div>message a</div>
      <div>message b</div>
    </div>
  )
}

为了满足这个条件我们通常都会在最外层添加一个div, 但是这样的话就会多出一个无意义的标记, 如果每个组件都多出这样的一个无意义标记的话, 浏览器渲染引擎的负担就会加剧.

为了解决这个问题, React 推出了 fragment 占位符标记. 使用占位符标记既满足了拥有共同父级的要求又不会多出额外的无意义标记.

import { Fragment } from "react"

function App() {
  return (
    <Fragment>
      <div>message a</div>
      <div>message b</div>
    </Fragment>
  )
}
function App() {
  return (
    <>
      <div>message a</div>
      <div>message b</div>
    </>
  )
}

7. 不要使用内联函数定义

在使用内联函数后, render 方法每次运行时都会创建该函数的新实例, 导致 React 在进行 Virtual DOM 比对时, 新旧函数比对不相等,导致React 总是为元素绑定新的函数实例, 而旧的函数实例又要交给垃圾回收器处理.

import React from "react"

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {
      inputValue: ""
    }
  }
  render() {
    return (
      <input
        value={this.state.inputValue}
        onChange={e => this.setState({ inputValue: e.target.value })}
        />
    )
  }
}

正确的做法是在组件中单独定义函数, 将函数绑定给事件.

import React from "react"

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {
      inputValue: ""
    }
  }
  setInputValue = e => {
    this.setState({ inputValue: e.target.value })
  }
  render() {
    return (
      <input value={this.state.inputValue} onChange={this.setInputValue} />
    )
  }
}

8. 在构造函数中进行函数this绑定

在类组件中如果使用 fn() {} 这种方式定义事件函数, 事件函数 this 默认指向 undefined. 也就是说函数内部的 this 指向需要被更正.

可以在构造函数中对函数的 this 进行更正, 也可以在行内进行更正, 两者看起来没有太大区别, 但是对性能的影响是不同的.

export default class App extends React.Component {
handleClick() {
console.log(this)
}
render() {
return 按钮
}
}
上面代码的问题是, render 方法每次执行时都会调用 bind 方法生成新的函数实例.

export default class App extends React.Component {
  constructor() {
    super()
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick() {
    console.log(this)
  }
  render() {
    return <button onClick={this.handleClick}>按钮</button>
  }
}

上面代码优化了在每次调用 render 方法时都生成新函数实例的问题, 构造函数只执行一次, 所以函数 this 指向更正的代码也只执行一次.

9. 类组件中的箭头函数

在类组件中使用箭头函数不会存在 this 指向问题, 因为箭头函数本身并不绑定 this.

export default class App extends React.Component {
  handleClick = () => console.log(this)
  render() {
    return <button onClick={this.handleClick}>按钮</button>
  }
}

箭头函数在 this 指向问题上占据优势, 但是同时也有不利的一面.

当使用箭头函数时, 该函数被添加为类的实例对象属性, 而不是原型对象属性. 如果组件被多次重用, 每个组件实例对象中都将会有一个相同的函数实例, 降低了函数实例的可重用性造成了资源浪费.

综上所述, 更正函数内部 this 指向的最佳做法仍是在构造函数中使用 bind 方法进行绑定

10. 避免使用内联样式属性

当使用内联 style 为元素添加样式时, 内联 style 会被编译为 JavaScript 代码, 通过 JavaScript 代码将样式规则映射到元素的身上, 浏览器就会花费更多的时间执行脚本和渲染 UI, 从而增加了组件的渲染时间.

function App() {
  return <div style={{ backgroundColor: "skyblue" }}>App works</div>
}

在上面的组件中, 为元素附加了内联样式, 添加的内联样式为 JavaScript 对象, backgroundColor 需要被转换为等效的 CSS 样式规则, 然后将其应用到元素, 这样涉及到脚本的执行.

更好的办法是将 CSS 文件导入样式组件.

11. 优化条件渲染

频繁的挂载和卸载组件是一项耗性能的操作, 为了确保应用程序的性能, 应该减少组件挂载和卸载的次数.

在 React 中我们经常会根据条件渲染不同的组件. 条件渲染是一项必做的优化操作.

function App() {
  if (true) {
    return (
      <>
        <AdminHeader />
        <Header />
        <Content />
      </>
    )
  } else {
    return (
      <>
        <Header />
        <Content />
      </>
    )
  }
}

在上面的代码中, 当渲染条件发生变化时, React 内部在做 Virtual DOM 比对时发现, 刚刚第一个组件是 AdminHeader, 现在第一个组件是 Header, 刚刚第二个组件是 Header, 现在第二个组件是 Content, 组件发生了变化, React 就会卸载 AdminHeader、Header、Content, 重新挂载 Header 和 Content, 这种挂载和卸载就是没有必要的.

function App() {
  return (
    <>
      {true && <AdminHeader />}
      <Header />
      <Content />
    </>
  )
}

12.不要在 render 方法中更改应用状态

当应用程序状态发生更改时, React 会调用 render 方法, 如果在 render 方法中继续更改应用程序状态, 就会发生 render 方法递归调用导致应用报错.

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {name: "张三"}
  }
  render() {
    this.setState({name: "李四"})
    return <div>{this.state.name}</div>
  }
}

与其他生命周期函数不同, render 方法应该被作为纯函数. 这意味着, 在 render 方法中不要做以下事情, 比如不要调用 setState 方法, 不要使用其他手段查询更改原生 DOM 元素, 以及其他更改应用程序的任何操作. render 方法的执行要根据状态的改变, 这样可以保持组件的行为和渲染方式一致.

13. 为组件创建错误边界

默认情况下, 组件渲染错误会导致整个应用程序中断, 创建错误边界可确保在特定组件发生错误时应用程序不会中断.

错误边界是一个 React 组件, 可以捕获子级组件在渲染时发生的错误, 当错误发生时, 可以将错误记录下来, 可以显示备用 UI 界面.

错误边界涉及到两个生命周期函数, 分别为 getDerivedStateFromError 和 componentDidCatch.

getDerivedStateFromError 为静态方法, 方法中需要返回一个对象, 该对象会和state对象进行合并, 用于更改应用程序状态.

componentDidCatch 方法用于记录应用程序错误信息. 该方法的参数就是错误对象.

// ErrorBoundaries.js
import React from "react"
import App from "./App"

export default class ErrorBoundaries extends React.Component {
  constructor() {
    super()
    this.state = {
      hasError: false
    }
  }
  componentDidCatch(error) {
    console.log("componentDidCatch")
  }
  static getDerivedStateFromError() {
    console.log("getDerivedStateFromError")
    return {
      hasError: true
    }
  }
  render() {
    if (this.state.hasError) {
      return <div>发生了错误</div>
    }
    return <App />
  }
}
// App.js
import React from "react"

export default class App extends React.Component {
  render() {
    // throw new Error("lalala")
    return <div>App works</div>
  }
}
// index.js
import React from "react"
import ReactDOM from "react-dom"
import ErrorBoundaries from "./ErrorBoundaries"

ReactDOM.render(, document.getElementById(“root”))
注意: 错误边界不能捕获异步错误, 比如点击按钮时发生的错误.

14. 避免数据结构突变

组件中 props 和 state 的数据结构应该保持一致, 数据结构突变会导致输出不一致.

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {
      employee: {
        name: "张三",
        age: 20
      }
    }
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick() {
    this.setState({
      employee: {
        name: "李四"
      }
    })
  }
  render() {
    const { name, age } = this.state.employee
    return (
      <div>
        <div>{name} {age}</div>
        <button onClick={this.handleClick}>更改</button>
      </div>
    )
  }
}

15. 为列表数据添加唯一标识

当需要渲染列表数据时, 我们应该为每一个列表项添加 key 属性, key 属性的值必须是唯一的.

key 属性可以让 React 直接了当的知道哪些列表项发生了变化, 从而避免了 React 内部逐一遍历 Virtual DOM 查找变化所带来的性能消耗. 避免了元素因为位置变化而导致的重新创建.

当列表数据没有唯一标识时, 可以临时使用索引作为 key 属性的值, 但是仅限于列表项是静态的, 不会被动态改变. 比如不会对列表项进行排序或者过滤, 不会从顶部或者中间添加或者删除项目. 当发生以上行为是, 索引会被更改, 并不可靠.

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {
      inputValue: "",
      arrayData: []
    }
    this.handleChange = this.handleChange.bind(this)
    this.handleClick = this.handleClick.bind(this)
  }
  handleChange(event) {
    this.setState({
      inputValue: event.target.value
    })
  }
  handleClick() {
    this.setState({
      arrayData: [this.state.inputValue, ...this.state.arrayData]
    })
  }
  render() {
    const { inputValue, arrayData } = this.state
    return (
      <div>
        <input type="text" value={inputValue} onChange={this.handleChange} />
        <button onClick={this.handleClick}>添加</button>
        <ul>
          {arrayData.map((item, index) => (
            <li key={index}>{item}</li>
          ))}
        </ul>
      </div>
    )
  }
}

16. 节流和防抖

节流和防抖可以用于控制在指定的时间内调用的事件处理程序的次数。

节流 throttle

在高频触发事件时,可以以指定的间隔调用事件处理函数。比如当页面滚动到底部要进行获取数据时, 需要使用节流进行限制, 如果不进行限制, 页面滚动到底部时将多次触发事件并向服务器端发送请求导致性能问题.

function throttle(fn, time) {
  let canRun = true
  return function () {
    if (!canRun) return
    canRun = false
    setTimeout(() => {
      fn.call(this, ...arguments)
      canRun = true
    }, time)
  }
}

function getScrollTop(event) {
  console.log(this)
  console.log(event)
  console.log(document.documentElement.scrollTop)
}

window.addEventListener("scroll", throttle(getScrollTop, 1000))

防抖 debounce

在触发高频事件时,只在最后一次触发事件后进行响应。比如用户在搜索框中输入内容后, 在搜索框的下面有一个悬浮框, 框内要显示和用户输入相关的数据. 当用户在框内容输入内容时, 每输入一次就会触发一次网络请求, 导致性能问题.

此处就需要使用防抖进行限制以提升性能. 通过防抖让网络请求进行延迟发送, 每次用户触发事件都会重新计算延迟时间, 当用户输入内容结束以后, 由于事件不再触发, 延迟时间不再重新计算, 时间到了就可以发送请求了.

function debounce(fn, time) {
  let timer = null
  return function () {
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.call(this, ...arguments)
    }, time)
  }
}

function getScrollTop(event) {
  console.log(this)
  console.log(event)
  console.log(document.documentElement.scrollTop)
}

window.addEventListener("scroll", debounce(getScrollTop, 1000))

可以通过第三方库throttle-debounce实现节流与防抖.

17. 外部资源使用 CDN 加载

什么是 CDN (Content Delivery Network) ?
内容交付网络(CDN)指的是地理上分散的服务器组, 它们一起工作以提供网络内容的快速交付.

使用 CDN 有什么好处 ?
浏览器对于同一个域下的并发请求有数量上的限制. 主流浏览器的同一域下的并发请求数量是 6 或 10. 意味着超过并发数量的其他资源需要等待, 这就增加了应用呈递的时间. 我们可以将不同的资源放在不同的 CDN 中, 这样就可以突破浏览器的并发限制, 加入应用的呈递速度.

CDN 通常由大型公司托管, 在全球都分布了数据中心, 当你向 CDN 发送请求时, 它会通过离你最近的数据中心进行响应, 减少网络延迟, 增加程序性能.

缓存使访问速度更加快速.

18. 依赖优化

在应用程序中经常会依赖第三方包, 但我们不想引用包中的所有代码, 我们只想用到哪些代码就包含哪些代码. 此时可以使用插件对依赖项进行优化. 优化资源

当前我们就使用 lodash 举例子. 应用基于 create-react-app 脚手架创建。

下载依赖

npm install react-app-rewired customize-cra lodash babel-plugin-lodash
  1. react-app-rewired: 覆盖 crate-react-app 的默认配置
module.exports = function (oldConfig) {
  return newConfig
}
// 参数中的 config 就是默认的 webpack config
  1. customize-cra: 导出了一些辅助方法, 可以让以上写法更加简洁
const { override, useBabelRc } = require("customize-cra")

module.exports = override(
  (oldConfig) => newConfig,
  (oldConfig) => newConfig
)

override:可以接收多个参数, 每个参数都是一个配置函数, 函数接收 oldConfig, 返回 newConfig

useBabelRc: 允许使用 .babelrc 文件进行 babel配置
  1. babel-plugin-lodash: 对应用中的 lodash 进行精简

在项目的根目录下新建 config-overrides.js 并加入配置代码

const { override, useBabelRc } = require("customize-cra")

module.exports = override(useBabelRc())

修改 package.json 文件中的构建命令

"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test": "react-app-rewired test --env=jsdom",
  "eject": "react-scripts eject"
}

创建 .babelrc 文件并加入配置

{
  "plugins": ["lodash"]
}

生产环境下的三种 JS 文件

  • main.[hash].chunk.js: 这是你的应用程序代码, App.js 等.
  • [hash].chunk.js: 这是第三方库的代码, 包含你在 node_modules 中导入的模块
  • runtime~main.[hash].js webpack运行时代码

19. 长列表优化 (虚拟列表)

在工作中, 我们经常需要以列表的形式展示数据, 如果列表项过多一次加载一次全部展示就会出现页面卡顿从而产生性能问题. 可以使用分页或者上拉加载解决问题. 除分页和上拉加载以外, 还可以使用虚拟列表技术解决性能问题.

虚拟列表就是按需显示, 不必一次渲染所有列表项, 只显示用户可视区域内的元素, 再根据用户的滚动位置逐步渲染其余数据. 上拉加载是随着滚动请求数据和渲染数据, 虚拟列表是一次请求所有数据, 随着滚动按需渲染.

react-virtualized 可以实现虚拟列表. 它会在页面中生成一块滚动区域, 在区域内进行列表内容的显示. 它会根据可视区域大小生成数据, 比如可视区域内正好可以放置10条数据, 它就会渲染10条数据, 然后再根据用户的滚动位置, 不断的渲染数据并替换区域内数据. 再通过定位的方式设置列表项的位置, 形成滚动的视觉效果.

使用 faker 生成假数据 npm i faker

import faker from "faker"

function createRecord(count) {
  let records = []
  for (let i = 0; i < count; i++) {
    records.push({
      username: faker.internet.userName(),
      email: faker.internet.email()
    })
  }
  return records
}

使用 react-virtualized 实现虚拟列表

下载

react-virtualized npm install react-virtualized

通过 List 组件显示列表数据

import { List } from "react-virtualized"

<List width={400} height={600} rowCount={records.length} rowHeight={44} rowRenderer={rowRenderer} />

// width: 指定窗口宽度
// height: 指定窗口高度
// rowHeight: 指定每一行的高度
// rowCount: 指定数据条数, 用于计算列表的总高度
// rowRenderer: 指定渲染函数, 内部会根据 rowCount 进行遍历, 遍历过程中会不断调用渲染函数并将关键信息传递给渲染函数, 比如索引, 渲染函数内部返回每一行要渲染的UI和数据.
指定渲染函数
// 渲染单行
function rowRenderer({ index, key, style }) {
  return (
    <div key={key} style={style}>
      <div>
        <b>username: </b>
        {records[index].username}
      </div>
      <div>
        <b>email: </b>
        {records[index].email}
      </div>
    </div>
  )
}
// index: 数据索引
// key: 每条数据的唯一标识
// style: 当前行样式, 包含当前行的位置信息

最后

最后, 送人玫瑰,手留余香,觉得有收获的朋友可以点赞,关注一波

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/905864.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

检索引擎Elasticsearch

一.为什么要用Elasticsearch 由于我们在运行我们的项目的时候通常都是将数据存到mysql或者sql serve等数据库中&#xff0c;在进行数据搜索时使用sql 语句 like进行模糊匹配查询&#xff0c;其一&#xff1a;虽然可以查到数据&#xff0c;但是它模糊匹配查询速度较慢&#xff0…

vue前端使用pdfjs与pdfdist-mergeofd 实现预览pdf并翻页,同时解决预览pdf显示模糊的问题

vue前端使用pdfjs与pdfdist-mergeofd 实现预览pdf并翻页&#xff0c;同时解决预览pdf显示模糊的问题 插件介绍 pdfdist-mergeofd插件的作用可查看这篇文章&#xff0c;同时使用ofdjs和pdfjs遇到的问题&#xff0c;和解决方法——懒加载 该插件主要是为了解决pdfjs和ofdjs同时…

深度学习:yolov3的使用--建立模型

使用argparse模块来定义和解析命令行参数 创建一个ArgumentParser对象 parser argparse.ArgumentParser() 训练的轮数&#xff0c;每批图像的大小&#xff0c;更新模型参数之前累积梯度的次数,模型定义文件的路径。 parser.add_argument("--epochs", typeint, d…

38.第二阶段x86游戏实战2-HOOK窗口消息机制(解决多开窗口句柄问题)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要…

详解汉明纠错码原理以及FPGA实现

文章目录 一、汉明纠错码简介二、汉明码编码原理以及步骤三、汉明码纠错原理以及步骤四、FPGA实现74汉明编码器五、FPGA实现74汉明解码器 一、汉明纠错码简介 汉明纠错码&#xff08;Hamming Code&#xff09;是一种用于检测和纠正数据传输中错误的编码方法。它由理查德汉明&am…

无人机光电识别跟踪算法!

一、算法概述 无人机光电识别跟踪算法结合了可见光和红外成像技术&#xff0c;通过光学系统收集目标的光学信息&#xff0c;并将其转换为电信号进行处理和分析。该算法能够实现对目标的快速、准确识别与追踪&#xff0c;极大提升了无人机在复杂环境下的作业能力和效率。 二、…

Ethernet 系列(6)-- 基础学习::OSI Model

&#xff08;写在前面&#xff1a;最近在学习车载以太网的知识&#xff0c;顺便记录一下知识点。&#xff09; OSI&#xff08;Open System Interconnect &#xff09;模型是一种网络通信框架&#xff0c;由国际标准化组织&#xff08;‌ISO&#xff09;在1985年提出&#xff0…

day15:shell基础

一&#xff0c;编程语法分类&#xff08;了解&#xff09; 编程范式&#xff1a; 面向过程&#xff1a;程序通过按步骤执行函数或过程完成任务&#xff0c;强调流程控制和函数调用&#xff0c;适合流程明确的任务&#xff0c;如 C。面向对象&#xff1a;通过“类”和“对象”封…

无人机测绘遥感技术算法概述!

一、数据采集算法 航线规划算法 根据测绘任务需求&#xff0c;利用地理信息系统&#xff08;GIS&#xff09;和遥感技术&#xff0c;对无人机进行航线规划。 考虑地形、气候、障碍物等因素&#xff0c;优化飞行路径&#xff0c;确保数据采集的完整性和准确性。 传感器控制算…

Pytest-Bdd-Playwright 系列教程(6):在测试步骤函数中设置别名数据共享

Pytest-Bdd-Playwright 系列教程&#xff08;6&#xff09;&#xff1a;在测试步骤函数中设置别名&数据共享 前言一、步骤别名二、特性文件三、测试脚本四、运行测试五、小测验总结 前言 有的时候&#xff0c;为了提高可读性&#xff0c;我们需要使用不同的名称来声明相同的…

HTML 分组标签与语义化应用:合理使用 <div>、<span> 和基础语义容器

文章目录 1. `<div>` 标签特点用途示例2. `<span>` 标签特点用途示例3. `<fieldset>` 标签特点用途示例4. `<section>` 标签特点用途示例5. `<article>` 标签特点用途示例总结HTML中的分组(容器)标签用于结构化内容,将页面元素组织成逻辑区域…

关于武汉芯景科技有限公司的马达驱动芯片AT6237开发指南(兼容DRV8837)

一、芯片引脚介绍 1.芯片引脚 二、系统结构图 三、功能描述 逻辑功能

Android View

前面我们了解了Android四大组件的工作流程&#xff0c;Android中还存在一个和四大组件地位相同的概念&#xff1a;View&#xff0c;用于向用户页面展示内容。我们经常使用的TextView、Button、ImageView控件等都继承于它&#xff0c;也会自定义View实现自定义效果。View类源码内…

谷歌插件开发学习指南

什么是谷歌插件 谷歌插件&#xff08;Chrome Extension&#xff09;是为谷歌浏览器&#xff08;Chrome&#xff09;开发的小程序&#xff0c;旨在增强浏览器的功能或用户体验。它们可以通过添加工具栏按钮、修改网页内容、集成其他服务等方式&#xff0c;实现各种功能&#xf…

linux命令行的艺术

文章目录 前言基础日常使用文件及数据处理系统调试单行脚本冷门但有用仅限 OS X 系统仅限 Windows 系统在 Windows 下获取 Unix 工具实用 Windows 命令行工具Cygwin 技巧 更多资源免责声明 熟练使用命令行是一种常常被忽视&#xff0c;或被认为难以掌握的技能&#xff0c;但实际…

Puppeteer 与浏览器版本兼容性:自动化测试的最佳实践

Puppeteer 支持的浏览器版本映射&#xff1a;从 v20.0.0 到 v23.6.0 自 Puppeteer v20.0.0 起&#xff0c;这个强大的自动化库开始支持与 Chrome 浏览器的无头模式和有头模式共享相同代码路径&#xff0c;为自动化测试带来了更多便利。从 v23.0.0 开始&#xff0c;Puppeteer 进…

知识管理新选择!本地大模型助手“知我AI”全功能解析

抖知书老师推荐&#xff1a; 随着人工智能技术的飞速发展&#xff0c;本地大模型知识管理工具逐渐成为提高工作效率的利器。今天&#xff0c;我要向大家介绍一款名为**“知我AI”**的本地知识管理助手&#xff0c;它以其独特的功能和优势&#xff0c;正在成为众多专业人士的新…

Banana Pi BPI-R3路由器开发板运行 OrayOS物联网系统

近日&#xff0c;Banana PI开发板宣布与贝锐达成战略合作&#xff0c;贝锐OrayOS现已成功适配Banana PI的BPI-R3型号&#xff0c;并计划进一步扩展硬件支持&#xff0c;包括目前Banana PI热销的BPI-R4、BPI-R3 Mini等更多型号。这一合作为用户提供了更广泛的开发板选择&#xf…

No.24 笔记 | WEB安全 - 任意文件包含漏洞 part 6

在 Web 安全领域中&#xff0c;任意文件包含漏洞是一种较为常见且具有潜在危险性的漏洞类型。本文将详细介绍任意文件包含漏洞的概念、原理、分类、利用方法以及防护措施&#xff0c;帮助新手小白更好地理解和防范这一漏洞。&#x1f603; 一、概念 包含的定义 开发人员为了提…

森利威尔SL2516D 耐压60V内置5V功率MOS 支持PWM LED恒流驱动器芯片

一、基本特性 型号&#xff1a;SL2516D封装&#xff1a;ESOP8工作频率&#xff1a;140kHz驱动MOS管&#xff1a;内置 二、电气特性 输入电压范围&#xff1a;8V~100V&#xff08;注意&#xff0c;虽然问题中提到耐压60V&#xff0c;但根据官方信息&#xff0c;其实际耐压范围…