質問編集履歴

1

ミス修正

2020/01/11 14:04

投稿

kumakuma112
kumakuma112

スコア21

test CHANGED
@@ -1 +1 @@
1
- SPAでディング時のスクロル位置が変わってしまう問題の解決と待機時間の改善をしたい
1
+ SPAでAPIの読み込みやデありなし処理をもっとトに書きたい
test CHANGED
@@ -1,18 +1,34 @@
1
1
  **タイトルで、SPAと書きましたが、
2
2
 
3
- 今作ってるものはポータルサイトで開発の都合で
3
+ 今作ってるものはポータルサイトで開発の都合で
4
4
 
5
5
  動的ページはフレームワークのHyperappを使い、静的ページはhtmlとcssで通常のコーディングをしてます。
6
6
 
7
- そのため、ページ間の移動はSPAではなくリンクで遷移してます。**
7
+ 工数削減という理由もあり、ページ間の移動はSPAではなくリンクで遷移してます。**
8
8
 
9
9
 
10
10
 
11
11
  # お聞きしたい課題
12
12
 
13
+ 待機時間改善のためにFacebookのようなデータがないときのスケルトンスクリーンの実装をしてます。
14
+
13
- 一つはAPIを複数回呼び出さないようにるための記述の改善
15
+ その結果色々改善しないといけない点が出てしまい、苦戦してます。
16
+
17
+
18
+
14
-
19
+ 1. **api読み込みの書き方もっと良い方法はないか?**
20
+
21
+ 2. **複数のAPIのデータを使い何かactionを実行したい場合、データありの場合でif文書くと無限ループに陥る**
22
+
23
+ 3. **スケルトンスクリーンのもっとスマートな書き方がないか**
24
+
15
- もう一つは、リロード時ページ位置の制御の仕方。
25
+ 4. **リロード時に少しでもスクロール中だと高確率でページの最上部に位置が変わってしまう**
26
+
27
+
28
+
29
+ ![イメージ説明](ee5913b95ea1b3015ef7b518110185bb.png)
30
+
31
+
16
32
 
17
33
 
18
34
 
@@ -30,6 +46,8 @@
30
46
 
31
47
  - GET / top(トップページのデータ)
32
48
 
49
+ - GET / news(ニュースのデータ)
50
+
33
51
 
34
52
 
35
53
 
@@ -44,18 +62,20 @@
44
62
 
45
63
  │   ├─ stores/
46
64
 
65
+ │   │  └─ modules/
66
+
47
67
  │   │  ├─ Store.js
48
68
 
49
69
  │   ├─ views/
50
70
 
51
- │   │  └─ Component/
71
+ │   │  └─ component/
52
-
72
+
53
- │   │  └─ Layout/
73
+ │   │  └─ layout/
74
+
75
+ │   │  └─ project/
54
76
 
55
77
  │   │  └─ Page/
56
78
 
57
- │   │  └─ Page/
58
-
59
79
  │   │     └─ Top.js
60
80
 
61
81
  │   │  └─ App.js
@@ -80,7 +100,7 @@
80
100
 
81
101
 
82
102
 
83
- src/index.js
103
+ #### src/index.js
84
104
 
85
105
  ```jacascript
86
106
 
@@ -90,28 +110,610 @@
90
110
 
91
111
  import App from 'src/views/App.js'
92
112
 
93
-
94
-
95
- app(State, Actions, App, document.body).init();
96
-
97
- ```
98
-
99
-
100
-
101
- ## APIを複数回呼び出したくない
102
-
103
- 待機時間の向上のため、データがなくてもダミーでFacebookアプリのような「スケルトンスクリーン」を実装しました。
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
- APIからデータを受け取るまでレンダリングしない作りにしていたら、
116
-
117
- ページの位置がリロードするたびにトップに戻ってしまい不便になりました。
113
+ import route from 'src/route.js';
114
+
115
+
116
+
117
+ let routeInfo = route(); // urlから {type,params..} を取得
118
+
119
+ app(State, Actions, App, document.body).init(routeInfo);
120
+
121
+ ```
122
+
123
+
124
+
125
+ #### src/Store/store.js
126
+
127
+ ```javascript
128
+
129
+ import axios from 'axios';
130
+
131
+ import StoreTop from 'src/stores/modules/top/StoreTop.js';
132
+
133
+ import StoreNews from 'src/stores/modules/news/StoreNews.js';
134
+
135
+
136
+
137
+ export default {
138
+
139
+ state: {
140
+
141
+ route: null,
142
+
143
+ master: null,
144
+
145
+ top: StoreTop.state,
146
+
147
+ news: StoreNews.state,
148
+
149
+ },
150
+
151
+ actions: {
152
+
153
+ top: StoreTop.actions,
154
+
155
+ news: StoreNews.actions,
156
+
157
+ // 初期設定
158
+
159
+ init: routeInfo => (state, actions) => {
160
+
161
+ actions.getMaterData(); // 共通データを先に取得
162
+
163
+ if (routeInfo) {
164
+
165
+ // url情報をrouteに反映
166
+
167
+ return {
168
+
169
+ route: routeInfo
170
+
171
+ };
172
+
173
+ }
174
+
175
+ },
176
+
177
+ //--------------------------------------------------
178
+
179
+ // 【API】共通
180
+
181
+ //--------------------------------------------------
182
+
183
+ // ローカルストレージに保存する処理も必要だがまだ実装しきれてない
184
+
185
+ getData: ({ key, apiPath, params, fn }) => (state, actions) => {
186
+
187
+ let url = null;
188
+
189
+ // ローカルのJSONデータ読み込みの名残でゲットパラメータをテキストでつなげてます
190
+
191
+ let paramsText = null;
192
+
193
+ if (params) {
194
+
195
+ for (let key in params) {
196
+
197
+ if (params[key]) {
198
+
199
+ paramsArray.push(key + '=' + encodeURIComponent(params[key]));
200
+
201
+ }
202
+
203
+ }
204
+
205
+ if (paramsArray.length > 0) {
206
+
207
+ paramsText = '?' + paramsArray.join('&');
208
+
209
+ }
210
+
211
+ }
212
+
213
+ if (paramsText) {
214
+
215
+ url = path + apiPath + paramsText;
216
+
217
+ } else {
218
+
219
+ url = path + apiPath;
220
+
221
+ }
222
+
223
+
224
+
225
+ //actions.loading.open();
226
+
227
+ actions.loadApi({
228
+
229
+ key: key,
230
+
231
+ url: url,
232
+
233
+ fn: result => {
234
+
235
+ actions.getResult({ key: key, result: result, fn: fn });
236
+
237
+ }
238
+
239
+ });
240
+
241
+ },
242
+
243
+ loadApi: ({ key, url, fn }) => (state, actions) => {
244
+
245
+ axios.get(url).then(res => {
246
+
247
+ if (!res.error) {
248
+
249
+ const data = res.data;
250
+
251
+ fn &&
252
+
253
+ fn({
254
+
255
+ success: true,
256
+
257
+ message: `API-${key} Success`,
258
+
259
+ data: data
260
+
261
+ });
262
+
263
+ } else {
264
+
265
+ fn &&
266
+
267
+ fn({
268
+
269
+ success: false,
270
+
271
+ error: res.error,
272
+
273
+ message: res.errorMessage,
274
+
275
+ data: null
276
+
277
+ });
278
+
279
+ }
280
+
281
+ });
282
+
283
+ },
284
+
285
+ getResult: ({ key, result, fn }) => (state, actions) => {
286
+
287
+ if (result.success) {
288
+
289
+ switch (key) {
290
+
291
+ case 'master':
292
+
293
+ actions.setMasterData(result.data);
294
+
295
+ break;
296
+
297
+ case 'top':
298
+
299
+ actions.top.setTopData(result.data);
300
+
301
+ break;
302
+
303
+ case 'news':
304
+
305
+ actions.news.setNewsData(result.data);
306
+
307
+ break;
308
+
309
+ }
310
+
311
+ //actions.loading.close();
312
+
313
+ fn && fn();
314
+
315
+ } else {
316
+
317
+ //actions.loading.close();
318
+
319
+ console.log(result.message);
320
+
321
+ }
322
+
323
+ },
324
+
325
+ //--------------------------------------------------
326
+
327
+ // 【API】master
328
+
329
+ //--------------------------------------------------
330
+
331
+ getMaterData: ({ fn }) => (state, actions) => {
332
+
333
+ actions.getData({
334
+
335
+ key: 'master',
336
+
337
+ apiPath: 'https://xxxx.com/api/master',
338
+
339
+ params: null,
340
+
341
+ fn: () => {
342
+
343
+ fn && fn();
344
+
345
+ }
346
+
347
+ });
348
+
349
+ },
350
+
351
+ setMasterData: data => (state, actions) => {
352
+
353
+ return {
354
+
355
+ master: data
356
+
357
+ };
358
+
359
+ },
360
+
361
+ //--------------------------------------------------
362
+
363
+ // 【API】トップ
364
+
365
+ //--------------------------------------------------
366
+
367
+ getTopData: ({ fn }) => (state, actions) => {
368
+
369
+ actions.getData({
370
+
371
+ key: 'top',
372
+
373
+ apiPath: 'https://xxxx.com/api/top',
374
+
375
+ params: null,
376
+
377
+ fn: () => {
378
+
379
+ fn && fn();
380
+
381
+ }
382
+
383
+ });
384
+
385
+ },
386
+
387
+ //--------------------------------------------------
388
+
389
+ // 【API】ニュース
390
+
391
+ //--------------------------------------------------
392
+
393
+ getNewsData: ({ year, category, limit, fn }) => (state, actions) => {
394
+
395
+ actions.getData({
396
+
397
+ key: 'news',
398
+
399
+ apiPath: 'https://xxxx.com/api/news',
400
+
401
+ params: {
402
+
403
+ year: year,
404
+
405
+ category: category,
406
+
407
+ limit: limit
408
+
409
+ },
410
+
411
+ fn: () => {
412
+
413
+ fn && fn();
414
+
415
+ }
416
+
417
+ });
418
+
419
+ },
420
+
421
+ }
422
+
423
+ };
424
+
425
+ ```
426
+
427
+
428
+
429
+ #### src/App.js
430
+
431
+ ```javascript
432
+
433
+ import { h } from 'hyperapp';
434
+
435
+ import Top from 'src/views/page/Top.js';
436
+
437
+ export default (state, actions) => {
438
+
439
+ console.log('State : ', state);
440
+
441
+ return (
442
+
443
+ <div>
444
+
445
+ <Top />
446
+
447
+ </div>
448
+
449
+ );
450
+
451
+ };
452
+
453
+ ```
454
+
455
+
456
+
457
+ #### src/Page/Top.js
458
+
459
+ ```javascript
460
+
461
+ import { h } from 'hyperapp';
462
+
463
+ import styles from 'src/views/page/top/top.scss';
464
+
465
+ export default (props, children) => (state, actions) => {
466
+
467
+ if (state.route.type === 'top') {
468
+
469
+ // この書き方で同じAPIを複数回実行してしまわないか不安
470
+
471
+ if (state.master && state.top.data == null) {
472
+
473
+ actions.getTopData({
474
+
475
+ fn: () => {
476
+
477
+ actions.getNewsData({
478
+
479
+ year: null,
480
+
481
+ category: null,
482
+
483
+ limit: 10,
484
+
485
+ fn: null
486
+
487
+ });
488
+
489
+ }
490
+
491
+ });
492
+
493
+ }
494
+
495
+ if (state.master && state.top.data) {
496
+
497
+ // masterとtopデータを使いたい場合、この書き方だとstate更新の無限ループ陥る
498
+
499
+ actions.top.setYear(state.master.year|| state.top.data.year);
500
+
501
+ }
502
+
503
+ return (
504
+
505
+ <div key="top" class={styles['top']}>
506
+
507
+ <PTopMainvisual
508
+
509
+ data={state.top.data && state.top.data.mainvisualItems[0]}
510
+
511
+ dummyData={state.top.dummyData.mainvisualItems}
512
+
513
+ isLoading={state.top.data ? 0 : 1}
514
+
515
+ />
516
+
517
+ // nullにすればAPIから来たときの空データ[]と別物扱いしますか?
518
+
519
+ {state.news.data == null ? (
520
+
521
+ <div>
522
+
523
+ {state.news.data.length > 0 && (
524
+
525
+ <PTopSection title="ニュース">
526
+
527
+ <PList type="news" data={state.news.data} isLoading={0} />
528
+
529
+ </PTopSection>
530
+
531
+ )}
532
+
533
+ </div>
534
+
535
+ ) : (
536
+
537
+ <PTopSection title="ニュース">
538
+
539
+ <PList type="news" dummyData={state.news.dummyData} isLoading={1} />
540
+
541
+ </PTopSection>
542
+
543
+ )}
544
+
545
+ </div>
546
+
547
+ );
548
+
549
+ }
550
+
551
+ };
552
+
553
+ ```
554
+
555
+
556
+
557
+ #### src/project/list.js
558
+
559
+ ```javascript
560
+
561
+ import { h } from 'hyperapp';
562
+
563
+ export default (props, children) => (state, actions) => {
564
+
565
+ return (
566
+
567
+ <div class={[styles['p-list'], props.class].join(' ')}>
568
+
569
+ {props.isLoading == 1 ? (
570
+
571
+ // APIからデータ受け取ってなかったら
572
+
573
+ <div>
574
+
575
+ {[...Array(props.dummyData.limit)].map(item => {
576
+
577
+ return (
578
+
579
+ <image src="/images/dummy/text.png">
580
+
581
+ );
582
+
583
+ })}
584
+
585
+ </div>
586
+
587
+ ) : (
588
+
589
+ // APIからデータ受け取っていたら
590
+
591
+ <div>
592
+
593
+ {props.data && (
594
+
595
+ // データが有れば
596
+
597
+ <div>
598
+
599
+ {props.data.map(item => {
600
+
601
+ return (
602
+
603
+ <div>{item.text}</div>
604
+
605
+ );
606
+
607
+ })}
608
+
609
+ </div>
610
+
611
+ )}
612
+
613
+ </div>
614
+
615
+ )}
616
+
617
+ </div>
618
+
619
+ );
620
+
621
+ };
622
+
623
+ ```
624
+
625
+
626
+
627
+
628
+
629
+ ## 1. api読み込みの書き方もっと良い方法はないか?
630
+
631
+ 待機時間の向上のため、データがなくてもダミーで表示できるようにしてます。
632
+
633
+ そのため、APIが揃う前にpageをreturnで表示させてます。
634
+
635
+ `if (state.master && state.top.data == null) {...}`で書いてる箇所は、問題ないか?
636
+
637
+
638
+
639
+ ## 2. 複数のAPIのデータを使い何かactionを実行したい場合、データありの場合でif文書くと無限ループに陥る
640
+
641
+ 1回だけ実行してほしいのに無限に実行してしまい、処理に負担がかかる。
642
+
643
+ またAPIから返ってきたデータがエラーではなく、1件もなしのようなケースも想定できていないので、改善したいが、
644
+
645
+ どうしたら良いのか?
646
+
647
+ ```javascript
648
+
649
+ if (state.master && state.top.data) {
650
+
651
+ actions.top.setYear(state.master.year|| state.top.data.year);
652
+
653
+ }
654
+
655
+ ```
656
+
657
+
658
+
659
+ ## 3. スケルトンスクリーンのもっとスマートな書き方がないか
660
+
661
+ 同じコードを何回も書いてる気がして、もっと良い方法がないのか。
662
+
663
+ src/page/Top.js
664
+
665
+ src/project/List.js
666
+
667
+
668
+
669
+ ```
670
+
671
+ {state.news.data ? (
672
+
673
+ // APIデータ来た場合
674
+
675
+ <div>
676
+
677
+ // APIデータが1件でもあったら表示
678
+
679
+ {state.news.data.length > 0 && (
680
+
681
+ <PTopSection title="ニュース">
682
+
683
+ <PList type="news" data={state.news.data} isLoading={0} />
684
+
685
+ </PTopSection>
686
+
687
+ )}
688
+
689
+ </div>
690
+
691
+ ) : (
692
+
693
+ // APIデータ来てないときの状態
694
+
695
+ <PTopSection title="ニュース">
696
+
697
+ <PList type="news" dummyData={state.news.dummyData} isLoading={1} />
698
+
699
+ </PTopSection>
700
+
701
+ )}
702
+
703
+ ```
704
+
705
+
706
+
707
+ また`state.news.data ?`この書き方で、空配列はどちらの判定なるのか?
708
+
709
+ nullは、true
710
+
711
+ []はfalseだとダミーデータが表示されるので困る
712
+
713
+
714
+
715
+
716
+
717
+ ## 4. リロード時に少しでもスクロール中だと高確率でページの最上部に位置が変わってしまう
718
+
719
+ これは、原因と対策の検討もついてないです。