前提・実現したいこと
vue-routerのpathに外部ファイル化したコンポーネントを設定したい。
発生している問題・エラーメッセージ
設定したいコンポーネントが画面に表示されない。
該当ソース
html
1<div id="router"> 2 <router-link to="/top" >トップページ</router-link> 3 <router-view></router-view> 4</div>
js
1//top.js 2Vue.component('top', { 3 template: `<div>トップページです。</div>` 4}); 5 6new Vue({ 7 el: '#app' 8})
js
1//router.js 2var router = new VueRouter({ 3 routes: [ 4 { 5 path: '/top', 6 component:{ 7 template: '<top></top>' 8 } 9 }, 10}) 11 12new Vue({ 13 router: router 14}).$mount('#router') 15
試したこと
router.jsとtop.jsのマウント先IDを同一にする等してみましたが、どうしてもtop.jsのtemplateが非表示となってしまいます。
参考書を使って学習しているのですが、そこではx-templateを使って実施していました。
その方法ならばできそうなのですが、htmlファイルが冗長になってしまうのでできれば避けたいです。
また、node.jsやvue cliに詳しくないので単一コンポーネントもできれば避けたいと考えています。
補足情報(FW/ツールのバージョンなど)
php :7.4.4
vue :2.5.17
vue-router :3.0.1
私自身がvueやvue/cliやその係累に詳しくないので、何を目的に何がしたいのかイマイチ分かりません。
多分もう少し具体的に書いた方がいいと思います。
「外部ファイル化したコンポーネント」が何を意味しているのか分かりません。vueファイルを動的に生成したいとかですか?
「htmlファイルが冗長になってしまう」のはどうしてですか?
「単一コンポーネント」とはどういうのもを指してますか?
top.jsで記述したtemplateとscriptの処理のセットを、router.jsのpathにアクセスした際に表示したいです。
このtop.jsが「外部ファイル化したコンポーネント」です。
今後はrouter.js内のroutesの中身を追加していき、pathと「外部ファイル化したコンポーネント」の組み合わせを増やしてSPAを実装しようとしています。
pathに対応したコンポーネント(またはテンプレート)が<router-view></router-view>に動的に反映されるのがvuerouterの仕組みです。
単一コンポーネントはvue拡張子のファイルです。今回は使用するつもりがありません。
htmlファイルが冗長になるのは、x-templateを使用した場合、ページを追加した分だけ、同一HTML内にページ毎のhtml文を羅列しなければいけないためです。
「Webpack や Browserify のビルドツールにより実現された .vue 拡張子の 単一ファイルコンポーネント」を使用しないということは、つまり、vue-cliやwebpackも使わずに、自前でhtmlとコンポーネントごとなどに分かれた複数の.jsファイルを使って複数のコンポーネントを使ったUIを実現したいってことですか?
で、ルーティングにはvue-routerを単品で使いたい・・・と。
その通りです。
「外部ファイル化したコンポーネント」の中のtemplate要素だけをHTMLファイルにxtemplateとして記述する方法は見たことがあるので模倣できるのですが、質問内容以外にもコンポーネントは作成してしまっている事と、折角なら今回目指している方法での記述方法も知りたいので質問しています。
xtemplateも外部ファイル化してrequireする方法でもできるような気がしてきたので、HTMLファイルが冗長になる現象は避けられるかもしれませんが、今回のパターンも知っておきたいです。
# とりあえず、commonjs以前のグローバル汚染で書いてみました。Unix系なら空のディレクトリ上でコピペ実行するとファイルになると思います。
sed 's/^\xc2\xa0/ /;s/$/$/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >index.html
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="router">
<div id="nav">
<router-link to="/top" >トップページ</router-link>|<router-link to="/sample">サンプル</router-link>
</div>
<router-view></router-view>
</div>
<script src="top.js"></script>
<script src="sample.js"></script>
<script src="router.js"></script>
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >router.js
//router.js
var router = new VueRouter({
routes: [
{
path: '/top',
component: Top
},
{
path: '/sample',
component: Sample
},
]
})
new Vue({
router: router
}).$mount('#router')
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >top.js
const Top = {
template: '<div>トップページです。</div>'
};
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >sample.js
Sample = {
template: '<div>サンプルページです。</div>'
};
__END_OF_TERMINAL_CODE__
#ES6の静的import版です。ルートモジュールだけ使えます。
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >index.html
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="router">
<div id="nav">
<router-link to="/top" >トップページ</router-link>|<router-link to="/sample">サンプル</router-link>
</div>
<router-view></router-view>
</div>
<script src="./router.js" type="module"></script>
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >router.js
//router.js
import Top from './top.js';
import Sample from './sample.js';
var router = new VueRouter({
routes: [
{
path: '/top',
component: Top
},
{
path: '/sample',
component: Sample
},
]
})
new Vue({
router: router
}).$mount('#router')
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >sample.js
export default {
template: '<div>サンプルページです。</div>'
}
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >top.js
export default {
template: '<div>トップページです。</div>'
}
__END_OF_TERMINAL_CODE__
# 本当にトップモジュールだけなのかなぁと調べてみたら出来たのでよく分からなくなりました。
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >index.html
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="router">
<div id="nav">
<router-link to="/top" >トップページ</router-link>|<router-link to="/sample">サンプル</router-link>
</div>
<router-view></router-view>
</div>
<script src="./router.js" type="module"></script>
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >router.js
//router.js
import Top from './top.js';
import Sample from './sample.js';
var router = new VueRouter({
routes: [
{
path: '/top',
component: Top
},
{
path: '/sample',
component: Sample
},
]
})
new Vue({
router: router
}).$mount('#router')
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >sample.js
import SampleSub from './samplesub.js';
export default {
template: '<div>サンプルページです。<sample-sub></sample-sub></div>',
components: {
'sample-sub': SampleSub
},
}
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >samplesub.js
export default {
template: '<div>サンプルサブコンポーネントです。</div>',
}
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >top.js
export default {
template: '<div>トップページです。</div>'
}
__END_OF_TERMINAL_CODE__
動的は静的が出来ればそれほど要らないと思ったので、とりあえずここまでで。
他のパターン(requireとかexport.moduleとか)はご自分で調べてください。テンプレート部分を外に出すのはまた別の話かと思うので、こちらは除外します。
丁寧な回答ありがとうございます。
色々試してみたいと思います。
自分自身のソースは一旦x-templateで作成したのですが、propsが使えないため、phpからデータを送れない問題が発生してしまいました。
また別枠で質問してみます。
何がどういうことなんだかよく分かりませんが...
結局テンプレート部分をjsと分けたかったってことなんでしょうか?
propsが使えないって起こり得ない気がするし、使えない場合困るのはリアクティブな反応=プロパティ変化によるUIの変更ができなくなることで、APIをどうするのか知りませんが、PHPで作成したHTMLに埋め込む際にdata経由でデータを送ることができるのでPHPから送れないこともないと思うのですが、、、
API側(ajax)もPHPならそれはpropsがないとそもそもvueとして機能しないとは思います。
多分諸々含めて、PHP側の実装がどういう仮定なのか分からないので、なんとも言えません。
これで終わりもアレなのでphpのAPI部分も付けてみました。
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >api.php
<?php
// https://www.indetail.co.jp/blog/6053/ より拝借、変更
$daysInJapanese = ["日", "月", "火", "水", "木", "金", "土"];
parse_str($_SERVER['QUERY_STRING'], $queryString);
$targetDate = strtotime($queryString['date'], 0);
$dayInJapanese = $daysInJapanese[date("w", $targetDate)] . "曜日";
$response = array(
'target_date' => $targetDate,
'day_in_japanese' => $dayInJapanese
);
echo json_encode($response);
?>
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >index.html
<html lang='ja'>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="router">
<div id="nav">
<router-link to="/top" >トップページ</router-link>|<router-link to="/sample">サンプル</router-link>
</div>
<router-view></router-view>
</div>
<script src="./router.js" type="module"></script>
</html>
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >router.js
//router.js
import Top from './top.js';
import Sample from './sample.js';
var router = new VueRouter({
routes: [
{
path: '/top',
component: Top
},
{
path: '/sample',
component: Sample
},
]
})
new Vue({
router: router
}).$mount('#router')
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >sample.js
import SampleSub from './samplesub.js';
export default {
template: '<div>サンプルページです。<sample-sub></sample-sub></div>',
components: {
'sample-sub': SampleSub
},
}
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >samplesub.js
export default {
template:
`<div>
<div>サンプルサブコンポーネントです。</div>
<div>{{date}}はサーバーで{{weekday}}と認識されています。</div>
</div>`,
props: ['date', 'weekday'],
created: function() {
const self = this;
const today = new Date();
self.date = new Date().toLocaleDateString();
fetch('./api.php?date=' + today.toISOString())
.then(response => response.json())
.then(data => {self.weekday = data.day_in_japanese;});
}
}
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >top.js
export default {
template: '<div>トップページです。</div>'
}
__END_OF_TERMINAL_CODE__
色々割愛しすぎていましたが、PHPのslim3フレームワークを使用していて、index.htmlにアクセスした時点で、htmlファイルはDBから引っ張て来た連想配列を取得済の状態です。
Vue.componentを使ったコンポーネント作成の場合、コンポーネント呼び出し時に以下のような記述をするだけでphpからvueコンポーネントへ値を渡すことができます。vue.jsの教科書でprops(親から子への値引き渡し。)を使う時の記述はこれしか載っていませんでした。
<top post-data = <?php echo $response ?>></top>
topコンポーネント内でpost-dataをpropsに登録することで、$responseの値を使用可能になります。
なのでこれに乗っ取って記述しようとしたのですが、template等を変数に入れ込んで扱う場合、<top></top>のようなタグを使用しないので、値の受け渡し方がわからなくなってしまいました。
ajaxで叩くしかないなら仕方ないと思いますが、フレームワークを使っている以上、vue.jsもslimも活用したいと思った次第です。
この質問をした経緯として、vuerouterのcomponentの値に<top></top>といったタグを設定できないため、上記の方法でphpの値を受け渡せない事がそもそもの問題でした。
しかし、今ソースをいじっていて気づいたのですが、<top></top>といったタグはvue-routerに設定可能でした。
なぜ、いままで上手くいかなかったかというと、<top post-data = <?php echo $response ?>></top>のような形でphpの値を受け渡す記述をしていましたが、この記述がエラーになっているようです。
この値の受け渡しをrouter.jsを通して実施できればすべてが解決します。
う~ん、根本的にPHPを使ってHTMLを返そうとしている限り、繋ぎの部分はフレームが何だろうと、*.vueを使おう使うまいと、そこは全く関係ないのではないでしょうか?
初期化はともかくコンポーネント同士でやり取りするような処理は個人的に好ましくないと思うので、ややこしいのはお名前から類推されるアレと同じ分類のVuexなどで状態管理するのが望ましいようですよ。
いずれにせよ、要件の整理はあなたにとってもいいことでしょうし、質問は変えた方がいいかもですね。
改めて質問してみます。
たくさんソースを記述していただいてありがとうございました。勉強になります。
新しい質問見ました。
https://teratail.com/questions/284250
私が何か書くと多分他の人が書かなくなっちゃうかもなので、私は引き続きこちらに記述しますね。
vue-router経由で渡すにしても、結局SPAという性質上HTMLでの取得は原則1回です。
つまり初期値であり、routingして遷移してるように見せてるだけで、ajaxを使わなければ初期値から変わることはありません。しかし、そのアプリはルーティングをした際に毎回HTML取得時の初期値を必要とするのでしょうか?設計上は遷移したときに結局必要な情報をajaxで取りに行くのが普通かと思います。
vue-router側も遷移と割り切ってrouter→component間でさほどデータを授受できるリッチなI/Fを持っていないようです。
という前置きを置いた上で、それでも初期値はなんか必要だよね、というところから考えた結果、結局phpの中でグローバルデータを置いてしまうのが一番ラクというのが私の見解です。コンポーネントのプロパティに設定する方法としては、routerのオプションで、routeの設定をする部分で、propsを定義し、そこにfunctionを設定してしまうのがいいようです。
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >api.php
<?php
// https://www.indetail.co.jp/blog/6053/ より拝借、変更
$daysInJapanese = ["日", "月", "火", "水", "木", "金", "土"];
parse_str($_SERVER['QUERY_STRING'], $queryString);
$targetDate = strtotime($queryString['date'], 0);
$dayInJapanese = $daysInJapanese[date("w", $targetDate)] . "曜日";
$response = array(
'target_date' => $targetDate,
'day_in_japanese' => $dayInJapanese
);
echo json_encode($response);
?>
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >index.php
<html lang='ja'>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="router">
<div id="nav">
<router-link to="/top" >トップページ</router-link>|<router-link to="/sample">サンプル</router-link>
</div>
<router-view></router-view>
</div>
<script>
const json = <?php echo '{"data":[{"id": "id1", "value": "value1"}, {"id": "id2", "value": "value2"}]}'; ?>;
const initialData = json.data;
</script>
<script src="./router.js" type="module"></script>
</html>
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >router.js
//router.js
import Top from './top.js';
import Sample from './sample.js';
var router = new VueRouter({
routes: [
{
path: '/top',
component: Top,
props: (route)=>({dbdata: initialData}),
},
{
path: '/sample',
component: Sample
},
]
})
window.vue=new Vue({
router: router
}).$mount('#router')
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >sample.js
import SampleSub from './samplesub.js';
export default {
template: '<div>サンプルページです。<sample-sub></sample-sub></div>',
components: {
'sample-sub': SampleSub
},
}
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >samplesub.js
export default {
template:
`<div>
<div>サンプルサブコンポーネントです。</div>
<div>{{date}}はサーバーで{{weekday}}と認識されています。</div>
</div>`,
props: ['date', 'weekday'],
created: function() {
const self = this;
const today = new Date();
self.date = new Date().toLocaleDateString();
fetch('./api.php?date=' + today.toISOString())
.then(response => response.json())
.then(data => {self.weekday = data.day_in_japanese;});
}
}
__END_OF_TERMINAL_CODE__
sed 's/^\xc2\xa0/ /;s/$/$/g;s/`/`/g;s/ /\x20\x20/g' <<__END_OF_TERMINAL_CODE__ >top.js
export default {
template: `<div>トップページです。
<table>
<tr v-for="row, i in dbdata" :key="row.id">
<td>{{row.id}}</td><td>{{row.value}}</td>
</tr>
</table>
</div>`,
props: ['dbdata'],
}
__END_OF_TERMINAL_CODE__
回答ありがとうございます。
Vue.componentで同じことをしようとしたのですが、上手くいかず、
module構文で記述したら再現できました。
これで製作がはかどります。ありがとうございます。
Vue.componentはグローバルに登録する形になる気がしたので、考えたことなかったです。
余談ですがmodule構文使うと外から本当に見えないので、vueオブジェクト見えずにデバッグ時に泣きそうでした。上ではwindow.vueとかに放り込んでます。
あなたの回答
tips
プレビュー