Vue 3 和 Vue Router 4 仅对特定组件使用路由器视图时如何传递道具

Vue 3 and Vue Router 4 How to pass prop when using router-view only for specific component

我有基本的路由和道具工作,但我有它的方式,msg 道具将被发送到路由器加载的每个组件?有解决办法吗?

App.vue 是我的基础组件

<template>
    <div>
        <img alt="Vue logo" src="./assets/logo.png" />
        <router-view msg="Hello Foo"></router-view>
    </div>
</template>

<script>
export default {
    name: 'App',
};
</script>

这是我的 HomePage.vue 组件

<template>
    <div class="hello">
        <h1>{{ msg }}</h1>
    </div>
</template>

<script>
export default {
    name: 'HomePage',
    props: {
        msg: String,
    },
};
</script>

这是我的 routes.js

import * as VueRouter from 'vue-router';
import HomePage from './components/HomePage';

const routes = [
    {
        name: 'HomePage',
        path: '/',
        component: HomePage,
    },
];

const router = new VueRouter.createRouter({
    history: VueRouter.createWebHistory(),
    routes,
});

export default router;

我想知道有没有一种方法可以根据加载的 route/component 动态发送不同的道具?

您可以将 'key' 属性 添加到 router-view 这样每次路由更改都会强制更新 msg 属性,您必须根据路由为 msg 属性设置动态值。

但您可以做得更好:使用提供的 beforeEach 钩子 vue-router 并根据路由名称添加动态参数。

您可以为特定路由配置 route meta field

// router.js
import { createRouter } from 'vue-router'
import HomePage from '@/views/HomePage.vue'

export default createRouter({
  routes: [
    {
      name: 'HomePage',
      path: '/',
      component: HomePage,

       
      meta: {
        msg: 'Hello Foo',
      },
    },
    ⋮
  ],
})

并直接在模板中通过 $route.meta 在您的组件中使用它:

<!-- MyComponent.vue -->
<template>
  <h2>Home</h2>
  <h3>{{ $route.meta.msg }}</h3>
</template>

demo 1

或通过useRoute().meta:

<script setup>
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()
const msg = ref('')

onMounted(() => {
  msg.value = route.meta.msg
})
</script>

demo 2

这里的用例看起来有点傻,但它适用于任何需要作为 prop 或参数传递给组件的数据,无论是静态的还是动态设置的,所以我认为这是一个很好的信息.

一些选项:

1。 Route Meta Fields

@tony19 的回答根据文档还有其他目的,例如与 beforeEach Navigation Guard 一起用于身份验证检查,但它适用于简单地向特定路由添加 field/value可以从组件访问。

我也可以在路线中添加一个道具,只要我不需要对其进行任何动态处理即可。 Props 可以在每个路由中传递,但不能像 to.meta 和 to.params 那样在 beforeEach 钩子中访问。

2。 routes.js 文件

中的参数和动态路由

Navigation Guards

Dynamic Route Matching

Optional Route Parameters

Router Views v-slot

正如@Witold 所建议的,我们可以使用 beforeEach 挂钩并动态添加参数。这是我根据一些研究得出的结论。 setParams() 只是设置参数的一种真正简单的方法,可能会做得更好,但它适用于演示。

对于 App.vue 中的 <router-view> 我正在使用新的 v-slot 方式。文档使它看起来只是转换和 v-for 所必需的,即使没有 key 属性,旧方法也适用于我在这里所做的事情。事实上,我的 App.vue 中显示的所有 3 种方法都适用于此用例。但是从这个 和我在一些 github 问题中看到的一些小信息,我认为 v-slot 的新方法是首选方法,它涵盖了更复杂情况的所有基础。不过还不能 100% 确定。

在 routes.js 中,/ 路由获取由我的 setParams 方法设置的 2 个参数。 props: true 将参数复制到道具。但是在 HomePage.vue 中我只声明了 msg2 和 msg3(在 App.vue 中设置)所以 msg1 必须作为参数访问,而 msg2 是一个道具。如果您尝试将它作为道具访问,msg1 将在控制台中显示警告,因为它未在组件的道具中声明:{} 属性 of the component.

/:foo? 路由是常规路由参数,因此我确保不要在我的 setParams 方法中覆盖它,因此它也可以在 HomePage.vue 中作为参数使用

3。 App.vue

中的动态设置道具

我不确定旧版本,但当前版本的 Vue Router 是异步工作的,因此生命周期挂钩将无法访问 $route.name。我在文档中找不到这个,但在已关闭的 github 问题中多次发现相同的重复情况。我们可以像选项 2 一样在路由文件中使用类似 beforeEach 的东西。或者我们可以创建一个观察者来观察 $route.name 中的变化,这就是我为 msg3.

所做的

msg3 作为数据存储在App.vue 中,当它改变时将触发消息的re-render。观察者调用 setMsg(),这是另一个适用于演示的简单 non-realistic js 方法。 changeMsg() 只是 HomePage.vue 中按钮的一种方法,可以在任何时候更改 msg3 时查看它是否有效。

您可以在主 Vue 实例中使用 router.isReady() 而不是观察者。

应用或main.js

//...imports and stuff
const app = Vue.createApp(App);

app.use(router);

router.isReady().then(() => app.mount('#app'));
//app.mount('#app');

我认为等待路由器准备就绪会导致白屏闪烁,尽管我在这个快速简单的演示中没有看到它。加载页面并观察 route.name 变化可以避免这种情况。

一些其他想法

我们可以在 window 或更好的情况下全局设置数组、对象或对象数组,作为所有组件都可以访问的 Vue 全局变量。

在app.js

app.config.globalProperties.$pageMessages = {};

然后在App.vue中访问它并设置动态消息。然后访问页面组件上的全局以检索和显示消息,而不将其作为 prop 或参数传递。

我们还可以在根应用程序或页面组件中使用 axios 或 fetch API 来检索消息,就像从数据库中检索任何其他数据一样。

routes.js

import * as VueRouter from 'vue-router';
import HomePage from './components/HomePage';

const routes = [
    {
        name: 'HomePage',
        path: '/',
        component: HomePage,
        props: true,
    },
    {
        name: 'HomePage',
        path: '/:foo?',
        component: HomePage,
    },
];

const router = new VueRouter.createRouter({
    history: VueRouter.createWebHistory(),
    routes,
});

const setParams = name => {
    // if matches name, set params
    if (name === 'HomePage')
        return {
            msg1: 'Hello I am a param',
            msg2: 'Hello I am a param/prop',
        };

    return {}; // empty object if no match
};

router.beforeEach(to => {
    // copy original params if they exist so I don't overwrite
    const params = { ...to.params, ...setParams(to.name) };
    to.params = params;
});

export default router;

HomePage.vue

<template>
    <div class="hello">
        <!--msg1 accessible as param but not as prop-->
        <p>{{ this.$route.params.msg1 }}</p>
        <!--as a prop, msg1 warning in console because not set in props:{}-->
        <p>{{ msg1 }}</p>
        <!--msg2 prop copied from params-->
        <p>{{ msg2 }}</p>
        <!--msg3 prop dynamic set in App.vue-->
        <p>{{ msg3 }}</p>
        <!--foo is a regular route param-->
        <p>{{ this.$route.params.foo }}</p>
    </div>
</template>

<script>
    export default {
        name: 'HomePage',
        props: {
            msg2: String,
            msg3: String,
        },
    };
</script>

App.vue

<template>
    <div class="container">
        <!-- <router-view :msg3="this.msg3"></router-view> -->
        <!-- <router-view :key="$route.path" :msg3="this.msg3"></router-view> -->

        <router-view v-slot="{ Component, route }">
            <component :is="Component" :key="route.path" :msg3="this.msg3" />
        </router-view>

        <button @click="changeMsg">Change msg3</button>
    </div>
</template>

<script>
    export default {
        name: 'App',

        data() {
            return {
                msg3: '',
            };
        },

        watch: {
            '$route.name'(name) {
                this.msg3 = this.setMsg(name);
            },
        },

        methods: {
            setMsg(name) {
                if (name === 'HomePage')
                    return 'Hello I am a dynamically set prop for the home page';
                if (name === 'AboutPage')
                    return 'Hello I am a dynamically set prop for the about page';

                return '';
            },

            changeMsg() {
                this.msg3 = 'foobar';
            },
        },
    };
</script>