【问题标题】:Instruct vue-router to re-render correct page when refresh指示 vue-router 在刷新时重新渲染正确的页面
【发布时间】:2021-11-05 18:31:27
【问题描述】:

我的应用是由 vuejs + rails 构建的,它有这些路由组件:

 {
    path: '/',
    name: 'home',
    component: Home,
    children: [
      {
        path: '/',
        name: 'welcome',
        component: Welcome,
      },
      {
        path: '/classes/:id',
        name: 'class',
        component: Class,
      },
      {
        path: '/classes/:id/members',
        name: 'member',
        component: Member,
      },
      {
        path: '/account',
        name: 'account',
        component: Account,
      },
      ...
    ]
  },

当我浏览链接时,路由器链接部分工作正常,但是当我站在 /classes/1 path 并刷新 url 时,会导致未找到错误:

No route matches [GET] "/classes/1"

我知道当我刷新时,我向服务器发出了一个新请求,所以我添加了这个 在 Rails 路由器中路由:

get '/*path', to: 'home#index'

因此,它不会引发错误,而是使主页成功,但这不是我想要的。 我希望它仍然保留或重新加载我所在的当前页面。 如果我在/classes/1 并点击刷新,它应该保持/刷新/classes/1 页面。 我怎样才能做到这一点?

【问题讨论】:

  • Rails 应该只路由到主页。 Vue-router 将从那里处理事情。您是说客户端导航到 /classes/1 -> 刷新,将您重定向回 /?
  • 是的,是这样的,那我该如何指导vue-router正确渲染页面呢?
  • 最后澄清:是将 URL 更新为 / 还是保持为 /classes/1 但在 / 上显示内容?
  • 如果我使用路由器 rails 将所有内容重定向到 home#index 页面,它会将 URL 更新为 /,如果我不这样做(删除 get '/*path', to: 'home#index'),它将保持为 /classes/1并导致路由错误No route matches [GET] "/classes/1"。我希望它保持为 /classes/1 并呈现 Class 组件。

标签: vue.js vue-router vuejs3


【解决方案1】:

这是我在工作中所做的应用程序关键部分的修改版本。

# config/routes.rb
# frozen_string_literal: true

Rails.application.routes.draw do
  root('application#index')

  # I use a scope because in the real app I also have other routes such as
  # browserconfig.xml and site.webmanifest which are both procedurally
  # generated.
  scope('/', format: false) do
    # Catch all for HTML 5 history routing. This must be the last route.
    get('*path', to: 'application#index')
  end
  # equivalent, if scope is unnecessary
  # get('/*path', to: 'application#index', format: false)
end
# app/controllers/application_controller.rb
# frozen_string_literal: true

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  def index
    # Avoid having an empty view file.
    render(inline: '', layout: 'application')
  end
end
/ app/views/layouts/application.html.slim
doctype html
html
  head
    / ...

  body
    main

    / Webpacker 4.3.0
    = javascript_packs_with_chunks_tag('main', defer: true)

在我的 JS 代码中,我在 main 元素上挂载了一个 Vue 实例。所有客户端/Vue 路由都通过我的 Vue 路由器处理。我有一个JS目录树结构如下:

project_root/
|-- src/
    |-- api/ <- external REST API handling code
    |-- channels/ <- Action Cable stuff
    |-- components/ <- Vue SFC's and functional components
    |-- images/ <- image files
    |-- lib/ <- various local library code
    |-- mixins/ <- Vue component mixins
    |-- packs/ <- all files in here are picked up by Webpacker; these are the entry-points
        |-- main.mjs <- imports pages/main.mjs (my app has multiple packs and pages, and there's "some" logic behind this structure)
    |-- pages/
        |-- main.mjs <- does the heavy lifting of building the Vue instance
    |-- plugins/ <- Vue plugins
    |-- scss/ <- SCSS files
    |-- store/ <- Vuex stuff
    |-- vendor/ <- third-party vendor stuff, like poly-fills
    |-- main.mjs
// src/pages/main.mjs
import '@/scss/main.scss';

// this should be first to ensure `vendors` loads first
import build from '@/main';

import VuexRouterSync from 'vuex-router-sync';
import App from '@/components/app.vue';
import router from '@/lib/router';
import store from '@/store';

build(
  App,
  {
    router,
    store,
  },
  (Vue) => {
    Vue.use(BootstrapVueUtils);

    VuexRouterSync.sync(store, router);
  },
);
// src/main.mjs
import '@/vendor';
import isFunction from 'lodash-es/isFunction';
import Vue from 'vue';

export default function build(appComponent, config = {}, preInit = null) {
  // call Vue.use here for any plugins that are common to all packs/pages
  // Vue.use(Vuelidate);

  if (preInit == null && isFunction(config)) {
    preInit = config;
    config = {};
  }

  if (isFunction(preInit)) {
    preInit(Vue);
  }

  function init() {
    new Vue({
      beforeCreate() {
        Vue.$rootVm = this;
      },

      render(h) {
        return h(appComponent);
      },

      ...config,
    }).$mount('main');
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
}
// src/lib/router.mjs
import Vue from 'vue';
import VueRouter from 'vue-router';
import Folder from '@/components/folders/show.vue';
import Folders from '@/components/folders'; // index.vue
import Home from '@/components/home.vue';
import LogIn from '@/components/log-in.vue';
import NotFound from '@/components/not-found.vue';
import Settings from '@/components/settings'; // index.vue

Vue.use(VueRouter);

const router = new VueRouter({
  mode: 'history',
  saveScrollPosition: true,
  routes: [
    {
      path: '/',
      redirect: '/folders',
      component: Home,
      children: [
        {
          name: 'folders',
          path: 'folders',
          component: Folders,
        },
        {
          name: 'folder',
          path: 'folders/:id',
          component: Folder,
        },
        {
          name: 'settings',
          path: 'settings',
          component: Settings,
        },
      ],
    },
    {
      name: 'log-in',
      path: '/log-in',
      component: LogIn,
    },
    // This isn't necessary, and I question it now. Has no effect on the functioning of the router.
    {
      name: 'not-found',
      path: '*',
      component: NotFound,
    }
  ],
});

// There's some additional code after this to setup event handling on the
// `router`, specifically `router.beforeEach` to check the logged-in status
// and if not logged in redirect to the log-in route.

这是很多代码,但我想尝试涵盖从 Rails 路由器到 Vue 路由器的所有内容,还包括一些可能有帮助的额外内容。

我确实注意到您在 'home' Vue 路由的子数组中包含了前导 /。这是不必要的,因为它是基于父级隐含的。

来自Vue Router docs

请注意,以 / 开头的嵌套路径将被视为根路径。这允许您利用组件嵌套,而无需使用嵌套 URL。

因此,虽然没有必要,但我认为这不是您问题的原因。

我唯一能想到的就是绝对确定 Rails 包罗万象的路线是最后一条路线。 Rails 路由从上到下按照config/routes.rb 文件中指定路由的顺序进行匹配。

【讨论】:

  • 我还是不知道我的设置有什么问题,和你差不多。我把 rails router 放在最后一行 get '/*path', to: 'home#index', format: false ,除了领先的 / 。我删除了它,但应用程序仍然无法重新加载当前页面。它总是呈现欢迎页面。
  • 对于您的示例,此设置 get('*path', to: 'application#index') 将始终重定向到您安装 vue 根应用程序的 application#index 页面,因此每当我刷新时,我认为它会呈现您的 vue 根应用程序组件,它是Home 组件,我想知道vue 是如何路由到正确的先前路径的,例如:/settings/folders/1
  • Vue 路由器在浏览器中查看 URL。如果是example.com/,那么它会渲染Home 组件。如果是example.com/account,那么它将渲染Account 组件。如果是example.com/classes/1,那么它将渲染Class 组件。这就是为什么get('/*path', to: 'home#index') 很重要,因为它可以防止 Rails 中出现任何未知路由,并允许 URL 保持不变。
  • 如果您的 Vue 应用程序不在域/子域的根目录下,那么您需要更新您的 Vue 路由器路径;例如,如果应用根目录位于example.com/my-app/,则home Vue 路由路径应为/my-app/class 应为/my-app/classes/:id
  • allows the URL to remain intact是什么意思,所以刷新时你的URL不会改变?在我的情况下,URL 总是变成/ root,而不是保持刷新链接 (/classes/1)
【解决方案2】:

您必须将所有请求重定向到您的 index.html 以便 vue-router 可以处理请求。

如果你使用 apache => https://gist.github.com/Prezens/f99fd28124b5557eb16816229391afee

使用 nginx => https://*.com/a/47656569/8613189

【讨论】:

  • 你能再澄清一下吗?
  • 这与 Rails 应用程序无关,因为一切都应该通过 Rails 路由器,假设您希望能够使用 Rails 会话 cookie。