Closed3

ユーザーインターフェースの作成記録

masterakmasterak

ローディング画面による非同期処理

React Suspenseとは?
データをローディング中の状態

React
<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】技術面接を振り返る。〜非同期処理の順番問題〜

参考書籍

インターフェースデザインの心理学

masterakmasterak

テストフレームワーク

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でテストを書こう」

app/test/isZero.ts
export function isZero(value: number): boolean {
    return value === 0;
}

引数のnumber型が、0かどうかを厳密に比較する関数

app/test/isZero.test.ts
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
ユニットテストの参考例

jest.config.ts
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);
jest.setup.ts
import '@testing-library/jest-dom';
package.json
{
  "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"
  }
}
masterakmasterak

セキュリティ設定

昨今のセキュリティ事情を鑑みて、下記を参考にWEBサーバのセキュリティを見直し

多言語対応

型指定が全然理解できない問題

const dictionary = await getDictionary(lang);で、型推論がうまくいかずにdictionaryの型がanyになってしまう問題を解決するために、ChatGPTに作らせたは良いがブラックボックス化してしまった。
エラープログラム:app-dir-i18n-routing

ChatGPTに生成させたプログラム

get-dictionary.ts
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;
}
app/page.tsx
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<{}>>がくっついているのが問題だったようだ。ただ元々のプログラムだと、型の判定ができないので、そこは要改善

このスクラップは4ヶ月前にクローズされました