
Oh, my gosh...
Vue.jsのPropsって親コンポーネントから子コンポーネントにデータを渡すものだよね?
Vue.jsのPropsと言えば、親コンポーネントから子コンポーネントにデータを渡すものだよね?
Usually, when we need to pass data from the parent to a child component, we use props. However, imagine the case where we have a large component tree, and a deeply nested component needs something from a distant ancestor component. With only props, we would have to pass the same prop across the entire parent chain:

https://vuejs.org/guide/components/provide-inject.html#prop-drilling
⇧ 一般的に、我々は、Propsを使う必要があるって言ってますね。
Vue.jsのPropsは自分自身に渡せるらしい
そんな定説を覆すコードがあったとさ。
<template>
{{my_array}}
<my-component :my_counter="compA()">
</template>
<script>
module.exports = {
props: ['my_counter'],
name: 'my-component',
computed: {
compA() {
const my_counter = this.my_counter ? this.my_counter : [];
let my_array = [name: "static name", counter: my_counter];
return my_array;
}
}
}
</script>
https://stackoverflow.com/questions/66277762/vue-js-recursive-components-data-including-props
⇧ う~む、何度見ても、自コンポーネント内でPropsを定義して、呼び出してらっしゃるように見える...
と言うのも、
⇧ 上記のコードを見てたら、同じく自コンポーネント内でPropsを呼んでらっしゃる気配だったもので。
■vue-admin/vue-typescript-admin-template/src/layout/components/Sidebar/SidebarItem.vue
<template>
<div
v-if="!item.meta || !item.meta.hidden"
:class="[isCollapse ? 'simple-mode' : 'full-mode', {'first-level': isFirstLevel}]"
>
<template v-if="!alwaysShowRootMenu && theOnlyOneChild && !theOnlyOneChild.children">
<sidebar-item-link
v-if="theOnlyOneChild.meta"
:to="resolvePath(theOnlyOneChild.path)"
>
<el-menu-item
:index="resolvePath(theOnlyOneChild.path)"
:class="{'submenu-title-noDropdown': isFirstLevel}"
>
<svg-icon
v-if="theOnlyOneChild.meta.icon"
:name="theOnlyOneChild.meta.icon"
/>
<span
v-if="theOnlyOneChild.meta.title"
slot="title"
>{{ $t('route.' + theOnlyOneChild.meta.title) }}</span>
</el-menu-item>
</sidebar-item-link>
</template>
<el-submenu
v-else
:index="resolvePath(item.path)"
popper-append-to-body
>
<template slot="title">
<svg-icon
v-if="item.meta && item.meta.icon"
:name="item.meta.icon"
/>
<span
v-if="item.meta && item.meta.title"
slot="title"
>{{ $t('route.' + item.meta.title) }}</span>
</template>
<template v-if="item.children">
<sidebar-item
v-for="child in item.children"
:key="child.path"
:item="child"
:is-collapse="isCollapse"
:is-first-level="false"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</template>
</el-submenu>
</div>
</template>
<script lang="ts">
import path from 'path'
import { Component, Prop, Vue } from 'vue-property-decorator'
import { RouteConfig } from 'vue-router'
import { isExternal } from '@/utils/validate'
import SidebarItemLink from './SidebarItemLink.vue'
@Component({
// Set 'name' here to prevent uglifyjs from causing recursive component not work
// See https://medium.com/haiiro-io/element-component-name-with-vue-class-component-f3b435656561 for detail
name: 'SidebarItem',
components: {
SidebarItemLink
}
})
export default class extends Vue {
@Prop({ required: true }) private item!: RouteConfig
@Prop({ default: false }) private isCollapse!: boolean
@Prop({ default: true }) private isFirstLevel!: boolean
@Prop({ default: '' }) private basePath!: string
get alwaysShowRootMenu() {
if (this.item.meta && this.item.meta.alwaysShow) {
return true
}
return false
}
get showingChildNumber() {
if (this.item.children) {
const showingChildren = this.item.children.filter((item) => {
if (item.meta && item.meta.hidden) {
return false
} else {
return true
}
})
return showingChildren.length
}
return 0
}
get theOnlyOneChild() {
if (this.showingChildNumber > 1) {
return null
}
if (this.item.children) {
for (const child of this.item.children) {
if (!child.meta || !child.meta.hidden) {
return child
}
}
}
// If there is no children, return itself with path removed,
// because this.basePath already conatins item's path information
return { ...this.item, path: '' }
}
private resolvePath(routePath: string) {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(this.basePath)) {
return this.basePath
}
return path.resolve(this.basePath, routePath)
}
}
</script>
<style lang="scss">
.el-submenu.is-active > .el-submenu__title {
color: $subMenuActiveText !important;
}
.full-mode {
.nest-menu .el-submenu>.el-submenu__title,
.el-submenu .el-menu-item {
min-width: $sideBarWidth !important;
background-color: $subMenuBg !important;
&:hover {
background-color: $subMenuHover !important;
}
}
}
.simple-mode {
&.first-level {
.submenu-title-noDropdown {
padding: 0 !important;
position: relative;
.el-tooltip {
padding: 0 !important;
}
}
.el-submenu {
overflow: hidden;
&>.el-submenu__title {
padding: 0px !important;
.el-submenu__icon-arrow {
display: none;
}
&>span {
visibility: hidden;
}
}
}
}
}
</style>
<style lang="scss" scoped>
.svg-icon {
margin-right: 16px;
}
.simple-mode {
.svg-icon {
margin-left: 20px;
}
}
</style>
■vue-admin/vue-typescript-admin-template/src/layout/components/Sidebar/index.vue
<template>
<div :class="{'has-logo': showLogo}">
<sidebar-logo
v-if="showLogo"
:collapse="isCollapse"
/>
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:active-text-color="menuActiveTextColor"
:unique-opened="false"
:collapse-transition="false"
mode="vertical"
>
<sidebar-item
v-for="route in routes"
:key="route.path"
:item="route"
:base-path="route.path"
:is-collapse="isCollapse"
/>
</el-menu>
</el-scrollbar>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import { AppModule } from '@/store/modules/app'
import { PermissionModule } from '@/store/modules/permission'
import { SettingsModule } from '@/store/modules/settings'
import SidebarItem from './SidebarItem.vue'
import SidebarLogo from './SidebarLogo.vue'
import variables from '@/styles/_variables.scss'
@Component({
name: 'SideBar',
components: {
SidebarItem,
SidebarLogo
}
})
export default class extends Vue {
get sidebar() {
return AppModule.sidebar
}
get routes() {
return PermissionModule.routes
}
get showLogo() {
return SettingsModule.showSidebarLogo
}
get menuActiveTextColor() {
if (SettingsModule.sidebarTextTheme) {
return SettingsModule.theme
} else {
return variables.menuActiveText
}
}
get variables() {
return variables
}
get activeMenu() {
const route = this.$route
const { meta, path } = route
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu
}
return path
}
get isCollapse() {
return !this.sidebar.opened
}
}
</script>
<style lang="scss">
.sidebar-container {
// reset element-ui css
.horizontal-collapse-transition {
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
}
.scrollbar-wrapper {
overflow-x: hidden !important;
}
.el-scrollbar__view {
height: 100%
}
.el-scrollbar__bar {
&.is-vertical {
right: 0px;
}
&.is-horizontal {
display: none;
}
}
}
</style>
<style lang="scss" scoped>
.el-scrollbar {
height: 100%
}
.has-logo {
.el-scrollbar {
height: calc(100% - 50px);
}
}
.el-menu {
border: none;
height: 100%;
width: 100% !important;
}
</style>
⇧ 親コンポーネントの方でも呼んでるっぽいけど、子コンポーネント自身でもPropsを呼んでるっぽいのよね...
う~む、Vue.js、相変わらずカオスな気がする...
ドキュメント見たけども、
⇧ 親コンポーネント→子コンポーネント、以外の使い方もあるってことなんかな?
毎度モヤモヤ感が半端ない...
今回はこのへんで。