Astro関係メモ
まえがき
Astro周りでの備忘録。
制作経験が浅くTypeScriptやJavaScriptの言語もほぼ初学者レベルのため、Astroには関係がない基本的な内容も含まれる。
- 躓いた箇所
- 調べた内容
- 別な場面でも活用していけそうなこと
など。
グローバルのSCSSが反映されていない
起こったこと
astro.configのpreprocessorOptionsで設定したグローバルSCSSがプレビュー画面で反映されていない。
該当ページはほぼ無記述の状態。
対処方法
Astroファイルに<style>要素を記述したところ反映された。
0から作り始めたため不具合かと思ってしまったが、通常は続く作業で必ず記入するものなので本来引っかかる部分ではない。一応メモに残しておく。
グローバルSCSSで定義済みのmixinが使用できない
原因
astro.configのpreprocessorOptionsで設定したグローバルSCSSは、Astroファイルには自動的に読み込まれるが他のSCSSファイルでは読み込まれないため。
対処方法
mixinを読み込む側のSCSSファイルで@useを行った。
コンポーネントデザインとサイトレイアウトの分離
Astroを使用するメリットだと思ったこと。
- コンポーネント用のAstroファイル
- それらを組み合わせるレイアウト用Atroファイル
で記述を分担できる。
CSSでももちろん便利だが、HTML側のメリットが大きいと感じた。
一つの要素にデザイン用のクラス名とレイアウト用のクラス名を同時に付与することがあったが、それを分散できる。
各要素のクラス名をパッと見で読み取りやすくなり、役割や適用されるデザインがわかりやすい。コードの可読性が向上する。
define:vars=""でのCSS変数定義は子コンポーネントで使用できない
理由
define:vars=""のスコープが子コンポーネントに限られてしまうため。
親コンポーネントで読み込みをした時点で定義されていない変数になってしまう。
大量に画像をインポートする
import.meta.globを使用することで特定のフォルダ下の画像を一括でインポート可能。
Viteの機能で使用にあたって特別な設定変更は必要ない。
公式ドキュメントでの該当ページ
実際の形
const Images = import.meta.glob('/src/assets/images/フォルダ名/*.{jpeg,jpg,png,gif,svg}');
型定義を明示する書き方
const Images = import.meta.glob<{ default: ImageMetadata }>('/src/assets/images/フォルダ名/*.{jpeg,jpg,png,gif,svg}');
親コンポーネントからPropsでパスの値を受け取る
問題
親コンポーネントから値を受け取る際Propsにフルパスを記述するようにすると、親コンポーネントの視認性が低下してしまう。
なお、JSONデータでは値としてフルパスを渡しやすいので問題ない。
対処方法
- 子コンポーネント側でパスの必ず共通する部分を変数で定義
- 親コンポーネントではそれ以下を記述しPropsに渡す
- 子コンポーネント側で文字列として結合する
//型を定義
interface Props {
img: string;
}
//Propsの値を受け取る
const { img } = Astro.props;
//共通部分と親から渡された部分を結合
const ImagePath = `/src/assets/images/${img}`;
//imgが渡されていない場合エラーを表示
if (!(ImagePath in Images)) {
throw new Error(`Image not found: ${ImagePath}`);
}
<Child img="cat/22.png" />
map()メソッドで動的にコンテンツを生成
Astroを使用するメリットだと思ったこと。
- 「同一のHTML要素の集合体に異なるデータを流し込む記述の繰り返し」をシンプルに記述できるようになる
- JSONからデータを取り込みHTML要素の任意の部分に流し込む
- HTML+CSSだけでは実現できない部分。個人的にHTML+CSSでサイト作成をする際に最も「欲しい」と思っていた仕組み
- 当初はVue.jsの導入を考えていたが自分の製作範囲ならばAstroで良さそう(Vue.jsではオーバースペック)
map()メソッドの入れ子
動的生成したコンテンツの中で、更に動的生成をしたい場合。
map()メソッドを入れ子にして記述する。
具体例
- データ1セットごとにコンポーネントを生成。
- データ内に配列を値にするものがあり、コンポーネント内にその配列からli要素を生成したい
[
{
"property1": "値",
"property2":["値1","値2"]
},
{
"property1": "値",
"property2":["値1","値2"]
}
]
---
import Data from '@data/data.json';
---
<div>
{
Data.map( (item1) =>(
<div>{item1.property1}</div>
<ul>
//ここでさらにmap()メソッドを入れ子にする。
{item1.property2.map( (item2) => (
<li> {item2} </li>
))}
</ul>
))}
</div>
親のPropsから受け取った値をImageコンポーネントのsrcに設定する
JSONファイルからデータを取り込むときよりも難儀した。
数々のエラー
- .defaultが未定義
- import.meta.globを収納した変数が関数として認識されない(JSONからの読み込みでは認識された)
- import.meta.globの値にstringを使用することができない(JSONでは「data.プロパティ名」なので文字列にはならず問題ない)
最終的な対処方法
下記のようにして解決はした。なぜこれで解決できたかはわかっていない。
- import.meta.globを直接srcの値に使わず変数に代入
- srcの値を「代入した変数.default」とする
---
// 画像を一括で読み込む
const Images = import.meta.glob<{ default: ImageMetadata }>(
'/src/assets/images/*.{jpeg,jpg,png,gif,svg}'
);
// 親から値を受け取る
const { img , alt } = Astro.props;
// 受け取った値からパスを生成
const ImagePath = `/src/assets/images/${img}`;
// 変数に該当するデータを代入
const ImageModule = await Images[ImagePath]();
---
<Image
src={ImageModule.default}
alt={alt}
/>
検証したところ
- srcの値に直接Images[ImagePath]()を設定しても問題なく挙動した。
- どちらのアプローチも試行錯誤の中で何度も試したが?最終的にはどちらも問題なく動く。これ以外のどこかに問題があったということか。
JSONにデータを持っている場合のみHTML要素に特定の属性を渡したい
想定場面
<input>要素のrequired属性やchecked属性。
懸念点
空の値を渡す、required="required"とするアプローチは避けたい。
変数をそのまま置くこともできない。
対処方法
- スプレッド構文と三項演算子で制御する
- 親コンポーネントがPropsのrequiredにtureを渡したか否かで判別
- 渡された場合:スプレッド構文によりrequired属性が追加
- 渡されていない=falseの場合:何も追加されない
---
// 型を定義
export interface Props {
required?: boolean;
}
// Propsを受け取る
const {
// 初期値はfalseに設定
required = false
} = Astro.props;
---
<input
type="text"
{...required ? { required } : {}}
/>
<!-- requied属性が付与される -->
<Child required="ture">項目名</Child>
<!-- requied属性が付与されない -->
<Child>項目名</Child>
類似事例
親コンポーネントからAstroPropsを渡された場合のみ属性を表示させたい
同じく三項演算子を使用する。
真偽の判定ではなく値を渡されている→親コンポーネント側で属性を定義されているかで判定するため、書き方が少し変わる。
---
// 型を定義
export interface Props {
minValue?: number;
}
// Propsを受け取る
const {
minValue
} = Astro.props;
---
<input
type="number"
{...minValue !== undefined ? { min: minValue } : {}}
/>
minValueに値が渡されている場合{ min: minValue }が適用。
AstroはJSX形式と同様の挙動をするため、これはmin={minValue}と処理される。
テキストコンテンツの取り扱い
まえがき
Astroの様々な機能を用いて「こういうことができないか」というアイデアのメモ。
今後挑戦してみたいという段階であり現時点では未作成かつ未検証の内容。
やりたいこと
- テキストコンテンツの取り扱いをより簡単にしたい
- HTML部分とコンテンツ部分を可能な限り切り離したい(HTML/CSSの知識がなくてもJSONファイル、あるいはJSONやCSVへの変換を挟みExcelファイルを編集することで編集ができることが理想)
テキストボックスコンポーネント
テキストコンテンツを持つためのコンポーネント。
再利用性
- コンポーネントとしての再利用は重視しない(段落数や読み込むデータの数が使用場面によって異なるため)
- 場面ごとにコンポーネントを雛形から複製し専用化する
- 煩雑化しないよう改善点を模索していく
テキストの入力方法
- JSONデータを取得
- コンポーネントに直打ちする:更新頻度がごく低い、またはAstroファイルを取り扱える人物が運用保守をする場合。これでもテキストコンテンツが切り分けられ各ページファイルの見通しをよくできるはず
JSONでのテキストの管理方法
- 文章を段落ごとに配列に格納
- 各配列の値ごとに<p>要素を付与
- 呼び出す際は配列名を使用する
- 複数段落で構成される長文については、適宜配列をオブジェクトにまとめmap()メソッドで出力をする
- 1対1の対応関係になるので、JSONは子コンポーネントで読込する
命名
- コンポーネント名とJSON名は紐づけする(親コンポーネントを見たときに、どのJSONファイルにコンテンツが記述されているかわかるようにするため)
- T-からはじめ、textフォルダでコンポーネントとデータを管理する
スロット機能
使ってみて気付いたこと。
- <slot />にclass属性を設定することはできない
親コンポーネントでJSONデータを読み込む
親コンポーネントで読み込んだJSONデータを子コンポーネントに渡す
基本
---
import Child from '@components/Child.astro';
import Data from '@data/data.json';
---
<Child data={Data} />
JSONデータ内の特定のオブジェクトを指定する場合
---
import Child from '@components/Child.astro';
import Data from '@data/data.json';
---
<Child data={Data.objectName} />
型定義
子コンポーネント側で、JSONデータから受け取る値の型定義を行う。
未定義の場合親コンポーネントにも警告メッセージが表示される。
export interface Object {
pro1: string;
pro2: string;
pro3: string;
}
export interface Props {
data: { [key: string]: Object };
//JSONデータと関係しないPropsの型付けも併記可能
}
const { data , ...rest } = Astro.props;
---
JSONの値の使用方法
下記のJSONファイルはinputのcheckboxに使用することを想定に作成している。
親コンポーネントで、各子コンポーネント要素がJSON内のcheck1オブジェクトを読み込む指定しているものとする。
JSONデータの内容
- check1:チェックボックス全体、1つの設問、1つのlabelに囲われるinput群
- item1,2...:各チェックボックス、1つの項目、input1つ
- name,value...:inputに渡される各属性値
{
"check1": {
"item1": { "name": "name", "value": "選択肢1", "formId": "id" },
"item2": { "name": "name", "value": "選択肢2", "formId": "id" }
}
}
<!-- map関数内 -->
{
Object.entries(data).map(([key, item]) => (
<label>
<input
type="checkbox"
id={item.formId}
name={item.name}
value={item.value}
class:list={[className]}
/>
{item.value}
</label>
))
}
Object.entries()
オブジェクトが持つ「プロパティのキーと値のペア」を配列にして返す。
map()関数はオブジェクトをそのまま処理することが出来ない。配列ではなくオブジェクトを読み込んでいる場合は記述が必要。
// 変換前
{ a: 1, b: 2 }
//変換後
[['a', 1], ['b', 2]]
上記のJSONファイルの場合。
[
['item1', { name: 'name', value: '選択肢1', formId: 'id' }],
['item2', { name: 'name', value: '選択肢2', formId: 'id' }]
]
mapの引数
各配列は元々は
- プロパティ
- 値
だったものを持っている。
map関数では2つの引数を指定し、その順番通りに対応する値を使用できる。
上記のJSONファイルの場合、値の部分にオブジェクトが来ている。ここにオブジェクトがある分には問題なくmap関数を使用できる。
グローバルCSSが適用されない
問題点
astro.configのpreprocessorOptionsに記述していたグローバルCSSが反映されていない。
全てではなく、変数を定義したvariable.scssは問題なく機能している。
結論
グローバルCSSはastro.configのpreprocessorOptionsに記述するものではなかった。
ベースとなるレイアウトコンポーネントでimportして使う。
留意点
astro.configのpreprocessorOptionsは変数やmixinを読み込むために使用する。
こちらは問題なく機能していたので気付くのが遅れてしまった。
Object.entriesを使用した場合に型がunknowになる
問題内容
- 親コンポーネントからPropsを通しJSON内の特定のオブジェクトを受け取る
- オブジェクト内のデータから動的にコンテンツを生成する
記述を行った際、map()の引数itemに対して「unknow型である」という警告が出た。
別な場所で具体的な値やHTML部分以外は同一の記述を行っているがそちらにはエラーが出ない。
---
export interface Item {
id: string;
title: string;
}
export interface Props {
data: { [key: string]: Item };
}
const { data, ...rest } = Astro.props;
---
<ul>
{
Object.entries(data).map(([key, item]) => (
<li>{item.title}</li>
))
}
</ul>
解決方法
Astro.propsに型アサーションを記述した。
const { data, ...rest } = Astro.props as Props;
オブジェクトと配列の区別
オブジェクト
型定義
export interface ObjectName {
property1: string;
property2: number;
}
export interface Props {
data: { [key: string]: ObjectName };
}
動的生成
{
Object.entries(data).map(([key, item]) => (
))
}
配列
型定義
export interface ArrayName {
property1: string;
property2: number;
}
export interface Props {
data: ArrayName[];
}
動的生成
{
data.map((item) => (
))
}
JSONデータで"class"プロパティを与えられているときのみclass属性を付与
留意点
"class"プロパティを持たないときにはclass属性自体を生成しない。
解決方法
class:listを利用する。
DOCSではクラスの結合、複数クラス値の受け取り手段として書かれていたが単一でも問題なかった。
渡される値がない(false/null/undefinedに該当する場合を含む)はclass属性自体が生成されない。
<div class:list={[item.class]}>