So sánh tốc độ giữa VueJS vs ReactJS

Môi trường

Macbook Pro 2017 15′ – 2.8 GHz Intel Core i7 – 16 GB

macOS Sierra 10.16.6

Google Chrome 66

React v16.3.2

VueJS v2.5.3

Thiết lập

 

Tạo project mới hoàn toàn từ create-react-app và vue-cli

npx create-react-app benchmark

vue init webpack benchmark

Tất cả benchmark logic viết trong 1 file component duy nhất

Library hỗ trợ

  • Bootstrap 4.1.0
  • Lodash 4.17.5
  • Faker 4.1.0

Các mục so sánh

  • Create n row sau khi load page
  • Replace toàn bộ row sau khi tạo
  • Select 1 row để highlight (5 lần chuẩn bị)
  • Update nội dung tất cả các row thứ 10, 20, 30 … (5 lần chuẩn bị)
  • Delete 1 row (5 lần chuẩn bị)
  • Swap 2 row (5 lần chuẩn bị)
  • Append 1000 row vào cuối bảng
  • Clear all

Thực hiện benchmark với cài đặt có sử dụng key và không sử dụng key khi render bảng

Kết quả

VueJS – ReactJS Benchmark – Results

Nhận xét

==> React nhìn chung chậm hơn VueJS

  • Hầu hết các hạng mục VueJS đều nhanh hơn React chỉ trừ clear all
  • VueJS đặc biệt nhanh hơn ở 2 mục selectswap, có thể lên đến 400%
  • Tại mốc 100,000 row, React mất hơn 90s để Create còn VueJS chỉ mất dưới 20s
  • Giữa keyednon-keyed của React nhìn chung có sự khác biệt
    • keyed nhanh hơn ở các tác vụ appenddelete
    • non-keyed nhanh hơn ở các tác vụ khác
    • tuy nhiên khi số lượng row nhiều lên thì keyed chậm đi nhiều hơn
  • keyednon-keyed của VueJS nhìn chung là tương đương

Source code

React

import React, { Component } from 'react';
import _ from 'lodash'
import faker from 'faker'
import './App.css';

let startTime
let lastMeasure
const startMeasure = (name) => {
  startTime = performance.now()
  lastMeasure = name
}
const stopMeasure = () => {
  const last = lastMeasure
  if (lastMeasure) {
    setTimeout(() => {
      lastMeasure = null
      const stop = performance.now()
      console.log(last + " took " + (stop - startTime))
    }, 0);
  }
}

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      items: [],
      amount: 100000,
      selectedId: '',
    }
    this.create = this.create.bind(this)
    this.removeItem = this.removeItem.bind(this)
    this.clear = this.clear.bind(this)
    this.append = this.append.bind(this)
    this.update = this.update.bind(this)
    this.swap = this.swap.bind(this)
    this.createCustom = this.createCustom.bind(this)
    this.handleInputChange = this.handleInputChange.bind(this)
  }
  printDuration() {
    stopMeasure();
  }
  componentDidUpdate() {
      this.printDuration();
  }
  componentDidMount() {
      this.printDuration();
  }
  generateData(amount = 1000, lastIndex = 0) {
    return _.times(amount, (index) => (
      {
        id: (lastIndex + index + 1),
        name: faker.commerce.productName(),
      }
    ))
  }
  create(amount = 1000) {
    startMeasure(`create ${amount}`)
    this.setState({ selectedId: '', items: this.generateData(amount) })
  }
  createCustom() {
    const amount = this.state.amount
    if (!isNaN(amount) && amount > 0) {
      this.create(amount)
    }
  }
  removeItem(index) {
    startMeasure('remove one')
    const items = _.concat(this.state.items.slice(0, index), this.state.items.slice(index+1, this.state.items.length))
    this.setState({ items })
  }
  clear() {
    startMeasure('clear all')
    this.setState({ items: [] })
  }
  append(amount = 1000) {
    startMeasure(`append ${amount}`)
    const items = _.concat(this.state.items, this.generateData(amount, _.isEmpty(this.state.items) ? 0 : _.last(this.state.items).id))
    this.setState({ items })
  }
  update(pos) {
    startMeasure(`update`)
    const items = _.map(this.state.items, (item) => {
      if (item.id % pos === 0) {
        return {id: item.id, name: item.name + ' !!!'}
      }
      return item
    })
    this.setState({ items })
  }
  swap() {
    startMeasure('swap')
    const items = _.map(this.state.items, (item, index) => {
      if (index === 4) {
        return this.state.items[9]
      } else if (index === 9) {
        return this.state.items[4]
      }
      return item
    })
    this.setState({ items })
  }
  select(id) {
    startMeasure('select')
    this.setState({ selectedId: id })
  }
  listItems() {
    const { items, selectedId } = this.state
    return items.map((item, index) => (
      <tr className={selectedId === item.id ? 'selected' : ''}>
        <td>{ item.id }</td>
        <td onClick={() => this.select(item.id)}>{ item.name }</td>
        <td><button type="button" className="btn btn-outline-danger btn-small" onClick={() => this.removeItem(index)}>x</button></td>
      </tr>
    ))
  }
  handleInputChange(event) {
    event.preventDefault()
    const value = event.target.value
    this.setState({ amount: value })
  }
  render() {
    return (
      <div className="container">
        <div className="jumbotron">
          <div className="row">
            <div className="col-md-6">
              <h1>React 16.3.2</h1>
            </div>
            <div className="col-md-6">
              <div className="row">
                <div className="col-md-6 smallpad">
                  <button type="button" className="btn btn-info btn-block" onClick={() => this.create(1000)}>Create 1000 rows</button>
                </div>
                <div className="col-md-6 smallpad">
                  <button type="button" className="btn btn-info btn-block" onClick={() => this.create(10000)}>Create 10000 rows</button>
                </div>
                <div className="col-md-6 smallpad">
                  <button type="button" className="btn btn-info btn-block" onClick={() => this.append(1000)}>Append 1000 rows</button>
                </div>
                <div className="col-md-6 smallpad">
                  <button type="button" className="btn btn-info btn-block" onClick={() => this.update(10)}>Update every 10th rows</button>
                </div>
                <div className="col-md-6 smallpad">
                  <button type="button" className="btn btn-info btn-block" onClick={this.clear}>Clear</button>
                </div>
                <div className="col-md-6 smallpad">
                  <button type="button" className="btn btn-info btn-block" onClick={this.swap}>Swap rows</button>
                </div>
                <div className="col-md-6 smallpad">
                  <input type="number" className="form-control" value={this.state.amount} onChange={this.handleInputChange} onBlur={this.handleInputChange} />
                </div>
                <div className="col-md-6 smallpad">
                  <button type="button" className="btn btn-info btn-block" onClick={this.createCustom}>{`Create { ${this.state.amount} } rows`}</button>
                </div>
              </div>
            </div>
          </div>
        </div>
        <br />
        <table className="table">
          <tbody>
            {this.listItems()}
          </tbody>
        </table>
      </div>
    );
  }
}

export default App;

Vue

<template>
  <div class="container">
    <div class="jumbotron">
      <div class="row">
        <div class="col-md-6">
          <h1>VueJS 2.5.2</h1>
        </div>
        <div class="col-md-6">
          <div class="row">
            <div class="col-md-6 smallpad">
              <button type="button" class="btn btn-info btn-block" @click="create(1000)">Create 1000 rows</button>
            </div>
            <div class="col-md-6 smallpad">
              <button type="button" class="btn btn-info btn-block" @click="create(10000)">Create 10000 rows</button>
            </div>
            <div class="col-md-6 smallpad">
              <button type="button" class="btn btn-info btn-block" @click="append(1000)">Append 1000 rows</button>
            </div>
            <div class="col-md-6 smallpad">
              <button type="button" class="btn btn-info btn-block" @click="update(10)">Update every 10th rows</button>
            </div>
            <div class="col-md-6 smallpad">
              <button type="button" class="btn btn-info btn-block" @click="clear()">Clear</button>
            </div>
            <div class="col-md-6 smallpad">
              <button type="button" class="btn btn-info btn-block" @click="swap()">Swap rows</button>
            </div>
            <div class="col-md-6 smallpad">
              <input type="number" class="form-control" v-model="amount">
            </div>
            <div class="col-md-6 smallpad">
              <button type="button" class="btn btn-info btn-block" @click="createCustom()">Create { {{amount}} } rows</button>
            </div>
          </div>
        </div>
      </div>
    </div>
    <br>
    <table class="table">
      <tbody>
        <tr v-for="(item, index) in items" :key="item.id" :class="item.id === selectedId ? 'selected' : ''">
          <td>{{ item.id }}</td>
          <td @click="select(item.id)">{{ item.name }}</td>
          <td><button type="button" class="btn btn-outline-danger btn-small" @click="removeItem(index)">x</button></td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
import faker from 'faker'
import _ from 'lodash'

let startTime
let lastMeasure
const startMeasure = (name) => {
  startTime = performance.now()
  lastMeasure = name
}
const stopMeasure = () => {
  const last = lastMeasure
  if (lastMeasure) {
    setTimeout(() => {
      lastMeasure = null
      const stop = performance.now()
      console.log(last + " took " + (stop - startTime))
    }, 0);
  }
}

export default {
  name: 'HelloWorld',
  data () {
    return {
      items: [],
      selectedId: '',
      amount: 100000,
    }
  },
  methods: {
    generateData(amount = 1000, lastIndex = 0) {
      return _.times(amount, (index) => (
        {
          id: (lastIndex + index + 1),
          name: faker.commerce.productName(),
        }
      ))
    },
    select(id) {
      startMeasure('select')
      this.selectedId = id
      stopMeasure()
    },
    create(amount = 1000) {
      startMeasure(`creat ${amount}`)
      this.selectedId = ''
      this.items = this.generateData(amount)
      stopMeasure()
    },
    createCustom() {
      this.create(this.amount)
    },
    removeItem(index) {
      startMeasure('remove row')
      this.items = _.concat(this.items.slice(0, index), this.items.slice(index+1, this.items.length))
      stopMeasure()
    },
    clear() {
      startMeasure('clear all')
      this.items = []
      stopMeasure()
    },
    append(amount = 1000) {
      startMeasure(`append ${amount}`)
      this.items = _.concat(this.items, this.generateData(amount, _.isEmpty(this.items) ? 0 : _.last(this.items).id))
      stopMeasure()
    },
    update(pos) {
      startMeasure('update')
      this.items = _.map(this.items, (item) => {
        if (item.id % pos === 0) {
          return {id: item.id, name: item.name + ' !!!'}
        }
        return item
      })
      stopMeasure()
    },
    swap() {
      startMeasure('swap')
      this.items = _.map(this.items, (item, index) => {
        if (index === 4) {
          return this.items[9]
        } else if (index === 9) {
          return this.items[4]
        }
        return item
      })
      stopMeasure()
    }
  }
}
</script>

<style scoped>
.smallpad {
  margin: 5px 0;
}
.jumbotron {
  padding: 2rem !important;
}
tr.selected {
  background-color: lightpink;
}
</style>

Tham khảo

http://www.stefankrause.net/js-frameworks-benchmark6/webdriver-ts-results/table.html

Add a Comment