ユーザーインターフェースの作成記録
ローディング画面による非同期処理
React Suspenseとは?
データをローディング中の状態
<Suspense fallback={<div>loading...</div>}>
<Componnet />
</Suspense>
Loading UI and Streaming
Next.js のスケルトンスクリーンUIの実装例 - Streaming with Suspense
React ドキュメントの実装例 - <Suspense>
SVG Animation
ローディングアニメーション - Tailwind CSS Spinner - Flowbite
TODO: pneding
にローディングアニメーションを組み込む
Promise
サバイバルTypescript - Promise<T>
Javascript はもともと、非同期的に実行する。
Promise型は、非同期処理の実行順番を文字通り約束する。
実行順番に関する記事 - 【JavaScript】技術面接を振り返る。〜非同期処理の順番問題〜
参考書籍
テストフレームワーク
Jest
Javascript ユニットテストフレームワーク
Next.js テスト - Testing Jest
npm install -D jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom @types/jest ts-jest ts-node
簡単なテスト例 - サバイバルTypescript 「Jestでテストを書こう」
export function isZero(value: number): boolean {
return value === 0;
}
引数のnumber型が、0かどうかを厳密に比較する関数
import { isZero } from "./isZero";
test("0を渡したらtrueになること", () => {
const result = isZero(0);
export(result).toBe(true);
}
)
const result = isZero(0);
で関数を実行
export(result).toBe(true)
で検査実行と検査結果をコンソールに出力
export(検査対象).toBe(期待される出力)
Next.js with-jest example
ユニットテストの参考例
import type { Config } from 'jest';
import nextJest from 'next/jest.js';
const createJestConfig = nextJest({
dir: './',
});
const config: Config = {
coverageProvider: 'v8',
testEnvironment: 'jest-environment-jsdom',
// Add more setup options before each test is run
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
moduleNameMapper: {
'@/(.*)$': '<rootDir>/src/$1',
},
};
export default createJestConfig(config);
import '@testing-library/jest-dom';
{
"name": "jest-test",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "jest",
"test:watch": "jest --watch"
},
"dependencies": {
"next": "14.2.4",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/react": "^16.0.0",
"@types/jest": "^29.5.12",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.4",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"ts-jest": "^29.1.5",
"ts-node": "^10.9.2",
"typescript": "^5"
}
}
セキュリティ設定
昨今のセキュリティ事情を鑑みて、下記を参考にWEBサーバのセキュリティを見直し
多言語対応
型指定が全然理解できない問題
const dictionary = await getDictionary(lang);
で、型推論がうまくいかずにdictionaryの型がanyになってしまう問題を解決するために、ChatGPTに作らせたは良いがブラックボックス化してしまった。
エラープログラム:app-dir-i18n-routing
ChatGPTに生成させたプログラム
import "server-only";
import type { Locale } from "./i18n-config";
interface Dictionary {
[key: string]: Record<string, string | boolean | number | URL>;
}
const dictionaries: Record<Locale, () => Promise<Dictionary>> = {
en: () => import("./dictionaries/en.json").then((module) => module.default as Dictionary),
ja: () => import("./dictionaries/ja.json").then((module) => module.default as Dictionary),
};
export const getDictionary = async (locale: Locale): Promise<Dictionary> => {
const data = await (dictionaries[locale]?.() ?? dictionaries.ja());
for (const key in data) {
const nestedDict = data[key];
for (const nestedKey in nestedDict) {
if (typeof nestedDict[nestedKey] === 'string' && nestedDict[nestedKey].startsWith('http')) {
nestedDict[nestedKey] = new URL(nestedDict[nestedKey]);
}
}
}
return data;
}
import LocaleSwitcher from "@/components/Locale-Switcher";
import { getDictionary } from "@/get-dictionary";
import { Locale } from "@/i18n-config";
type DictionaryValue = string | boolean | number | URL;
export default async function Home({ params: { lang } }:{ params: { lang: Locale }; }) {
const dictionary = await getDictionary(lang);
const renderValue = (value: DictionaryValue) => {
if (typeof value === 'string') {
return value;
} else if (typeof value === 'boolean') {
return value ? 'True' : 'False';
} else if (typeof value === 'number') {
return value.toString();
} else if (value instanceof URL) {
return <a href={value.toString()}>{value.toString()}</a>;
} else {
return null;
}
};
return (
<section className="w-[1024px] mx-auto mt-32 px-8 mb-8">
<div>
<LocaleSwitcher />
<p>Current locale: {lang}</p>
<p>Welcome Message: {renderValue(dictionary["server-component"]?.a)}</p>
<p>Text: {renderValue(dictionary["server-component"]?.b)}</p>
<p>language: {renderValue(dictionary["server-component"]?.c)}</p>
<p>Rust language Document page: {renderValue(dictionary["server-component"]?.d)}</p>
</div>
</section>
);
}
<!-- en.json -->
{
"server-component": {
"a": "Welcome",
"b": "This tests the language display.",
"c": "English",
"d": "https://doc.rust-lang.org/stable/book/"
}
}
<!-- ja.json -->
{
"server-component": {
"a": "ようこそ",
"b": "これは言語表示をテストしています。",
"c": "日本語",
"d": "https://doc.rust-jp.rs/book-ja/"
}
}
元々のプログラムと比較してみたところ、dictionaries:Record<Locale, () => Promise<{}>>
がくっついているのが問題だったようだ。ただ元々のプログラムだと、型の判定ができないので、そこは要改善