JSFiddle - React, Tailwind, and code Playground

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
  <table>
    <thead>
      <tr>
        <th
          v-for="col in columns"
          :class="col === sort.column && [ 'sorted-by', `sorted-${sort.reverse ? 'desc' : 'asc'}` ]"
          @click="sortBy(col)"
        >{{ col.title }}</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="item in sortedItems">
        <td v-for="col in columns">
          {{ col.output?.(item[col.name]) ?? item[col.name] }}
        </td>
      </tr>
    </tbody>
  </table>
</div>

CSS

th, td {
  border: 1px solid silver;
  padding: 10px 15px;
}

th {
  cursor: pointer;
  position: relative;
}
th::after {
  position: absolute;
  right: 2px;
  font: 24px bold monospace;
  top: 50%;
  transform: translateY(-50%);
}

th.sorted-by {
  background: #ddd;
}
th.sorted-asc::after { content: "\2191"; }
th.sorted-desc::after { content: "\2193"; }

JavaScript

new Vue({
  el: '#app',
  data: {
    columns: [
      { name:   'id', title:    '#', type: 'number' },
      { name: 'name', title: 'Name', type: 'string' },
      { name: 'city', title: 'City', type: 'string' },
      { name: 'date', title: 'Date', type: 'number', output: v => v.toLocaleDateString('ru-RU') },
    ],
    items: [
      { name: 'Jack',    city: 'Paris',       date: new Date(2019,  1,  2) },
      { name: 'Tom',     city: 'Los Angeles', date: new Date(2017,  4, 14) },
      { name: 'Kate',    city: 'Berlin',      date: new Date(2018,  3,  8) },
      { name: 'Sam',     city: 'Rome',        date: new Date(2020,  9, 21) },
      { name: 'Jane',    city: 'Hong Kong',   date: new Date(2019, 10, 15) },
      { name: 'Ann',     city: 'Tokyo',       date: new Date(2018,  3, 12) },
      { name: 'Nick',    city: 'Cairo',       date: new Date(2020,  6, 10) },
      { name: 'Michael', city: 'Adelaide',    date: new Date(2019,  7, 26) },
    ].map((n, i) => ({ id: i + 1, ...n })),
    sort: {
      column: null,
      reverse: false,
    },
    sortFuncs: {
      number: (a, b) => a - b,
      string: (a, b) => a.toLowerCase().localeCompare(b.toLowerCase()),
    },
  },
  computed: {
    sortedItems() {
      const { items, sort: { column, reverse } } = this;
      const key = column?.name;
      const sort = this.sortFuncs[column?.type];

      return sort
        ? [...items].sort((a, b) => sort(a[key], b[key]) * (reverse ? -1 : 1))
        : items;
    },
  },
  methods: {
    sortBy(column) {
      const { sort } = this;
      this.sort = { column, reverse: (sort.column === column) ^ sort.reverse };
    },
  },
  created() {
    this.sortBy(this.columns[this.columns.length - 1]);
  },
});