【JavaScript】ドットみっつのヤツってなに?

const obj = { abcd: "abcd", nu: 1 }
// 何かしらのコンポーネントのプロパティに渡す
<ABCD obj={ ...obj } />

React関係を調べているとこういうのを死ぬほど見ます。なにコレ?となっているまま続けると、メンテ時に本当に死んじゃいます。それは厄介なので理解しておきましょう。

名前

スプレッド構文というらしいです。イメージ的には、内包物を展開させるといったものみたい。

使い方

スプレッド構文のつかいかた
こういったイメージで、JSONや配列の内容を展開できます。

ちょっと応用

オブジェクトや配列のディープコピーがしたい場合

const arr = [ { abced: "abcde", fg: "fg" } ];
const copied = arr.map((obj) => { return { ...obj } }); // 新しい配列かつオブジェクトが作れる。

参照が同じになってしまう場合に試してみましょう。
私は直近でハマりました。

ではまた。

【TypeScript】型の定義:type

Javaでいうところのinterfaceのようなもの。

type ABCD = {
  val1: string,
  val2: number
}

使うとき

const abcde: ABCD = {
  val1: "値1".
  val2: 1234
}

特徴

  • 基本的に型そのものに拡張はできない

ABCDtypeval3というプロパティを追加することはできない

  • 継承して拡張することはできる
type EFGH = ABCD & {
  val3: string;
};

const efgh : EFGH = {
  val1: "efgh",
  val2: 5678,
  val3: "jklm"
}; // コンパイルエラーが起きない

下記の方が可読性はよい

type EFGH = {
  val3: string
};

type ABCDEFGH = ABCD & EFGH;

const abcdefgh : ABCDEFGH= {
  val1: "efgh",
  val2: 5678,
  val3: "jklm"
}

他、interfaceやclassも存在するが、typeが一番型定義しやすい気がする。

【JavaScript】たまに見る、ダブルクエスチョンマークはなんなんだろう

const atai = undefined ?? "excd";

こういうやつ。 Null合体演算子っていうらしい。かっこいい。 ??の左辺がnull、undefined、0でなければ、右辺を使用するというもの。
下記と等価。

let atai;
if (!atai) {
  atai = "excd";
}

関数でも使える。

const XXX = getXXX ?? getXXX();

Javaにはないので、めっちゃスタイリッシュに見える。

【Nextjs】Layoutとは

レイアウトファイル

import Navbar from './navbar'
import Footer from './footer'
 
export default function Layout({ children }) {
  return (
    <>
      <Navbar />
      <main>{children}</main>
      <Footer />
    </>
  )
}

_app.tsx

import type { ReactElement, ReactNode } from 'react'
import type { NextPage } from 'next'
import type { AppProps } from 'next/app'
 
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
  getLayout?: (page: ReactElement) => ReactNode
}
 
type AppPropsWithLayout = AppProps & {
  Component: NextPageWithLayout
}
 
export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
  // Use the layout defined at the page level, if available
  const getLayout = Component.getLayout ?? ((page) => page)
 
  return getLayout(<Component {...pageProps} />)
}

ページごとのindex.tsx

import type { ReactElement } from 'react'
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'
import type { NextPageWithLayout } from './_app'
 
const Page: NextPageWithLayout = () => {
  return <p>hello world</p>
}
 
Page.getLayout = function getLayout(page: ReactElement) {
  return (
    <Layout>
      <NestedLayout>{page}</NestedLayout>
    </Layout>
  )
}
 
export default Page

注意点

  • getLayoutで囲うのはだけ。
  • 他にProviderなどがある場合は、getLayoutを囲う。
    <Provider>{getLayout(<Components />)}</Provider>のような感じ。

【Next.js】ENVの使い方

ファイルの場所

ルートフォルダ

ファイルの種類

全体共通の.envファイルと、環境別の.env.production.env.localがある。それぞれの違い。

  • .env

全体共通の定数を持てるファイル。

  • .env.production

npm run buildで生成した本番用ソースで用いられるenvファイル。

  • .env.local

npm run devで走らせる開発環境で用いられるenvファイル。

APIのContextを環境で切り替えるシチュエーションが一番多そう。

定数の使い方

process.env.定数名

定数宣言の注意点

  • 定数の命名法はアッパースネークケース

ABCD_EFGのような命名をする。

  • prefixでNEXT_PUBLIC_を付けないと、クライアント側では使えない。

特に、SPAでビルドする場合はNEXT_PUBLIC_を定数名の頭に付けないと使えない。
SSRの場合は、付けない場合はサーバーサイドでのみ使用可能な定数となる。

【React】Contextを使おう

  • Contextとは

値を子に渡せる仕組みの一つ。
プロパティと違い、Contextが提供されているコンポーネントであれば
どのコンポーネントからでも使用可能。

※例はTypeScriptを用いた例です。

  • createContext

コンテキストを作成する。 <>の中には型。引数には

export const ABContext = createContext<string>("");
  • Provider

プロバイダを用いて、子要素にContextを渡す。

<ABCContext.Provider value={"子要素に渡す値"}>
  // Contextを使う子要素
  {children}
</ABContext.Provider>
  • useContext

コンテキストの値を使う。

const value = useContext(ABContext);
console.log(value); // 子要素に渡す値
  • 使いどころ

useStateをContextで子要素に渡して状態管理したりするととても便利。

AWS SDK Java v2 を使用して、大容量なファイルをMultipartで非同期にアップロードできなかった話。

以前紹介した以下のコード

AWS SDK Java で S3 に少しずつ来たデータをバッファリングしつつ一つのファイルとしてアップロードするサンプルコード

©okkez氏

 /**
     * Multipartアップロード
     * 
     * @param logger
         * @param executorService
     * @param bucketName
     * @param dest
     * @param is
     * @throws AppException
     */
    public static void multipartUpload(LambdaLogger logger, ExecutorService executorService, String bucketName, String dest, InputStream is)
            throws IOException, ExecutionException, InterruptedException {
        final S3Client s3 = getS3Client();
        final CreateMultipartUploadResponse response = s3
                .createMultipartUpload(builder -> builder.bucket(bucketName).key(dest));
        final String uploadId = response.uploadId();
        final ByteBuffer buffer = ByteBuffer.allocate(PART_BUFFER);

        try {
            final ReadableByteChannel channel = Channels.newChannel(is);
            List<Future<CompletedPart>> futures = new ArrayList<>();
            int partNumber = 0;
            while (channel.read(buffer) != -1) {
                if (buffer.position() > PART_LIMIT) {
                    long contentLength = buffer.position();
                    buffer.flip();
                    partNumber++;
                    Future<CompletedPart> future = uploadMultipartUploadPart(executorService, s3, bucketName, dest,
                            uploadId, partNumber, contentLength, buffer.array());
                    futures.add(future);
                    buffer.clear();
                }
            }
            if (buffer.position() > 0) {
                long contentLength = buffer.position();
                buffer.flip();
                partNumber++;
                Future<CompletedPart> future = uploadMultipartUploadPart(executorService, s3, bucketName, dest,
                        uploadId, partNumber, contentLength, buffer.array());
                futures.add(future);
                buffer.clear();
            }

            // 全部のパートをアップロードするまで待つ
            while (!futures.stream().allMatch(Future::isDone)) {
                try {
                    TimeUnit.MILLISECONDS.sleep((100));
                } catch (InterruptedException ignore) {
                    // NOP
                    logger.log("スレッドが途中で終了:MultipartUploadの待機時");
                }
            }

            // CompletedPart を集める
            List<CompletedPart> parts = new ArrayList<>();
            for (Future<CompletedPart> future : futures) {
                try {
                    parts.add(future.get());
                } catch (ExecutionException | InterruptedException e) {
                    // NOP
                    logger.log("スレッドが途中で終了:CompletedPart集約時");
                }
            }

            // マルチパートアップロードの完了処理を行う
            final var completedMultipartUpload = CompletedMultipartUpload.builder().parts(parts).build();
            s3.completeMultipartUpload(
                    builder -> builder
                            .bucket(bucketName)
                            .key(dest)
                            .uploadId(uploadId)
                            .multipartUpload(completedMultipartUpload));
        } catch (IOException ioe) {
            ioe.printStackTrace();
            logger.log("大容量ファイルのアップロードに失敗");
            throw ioe;
        } finally {
            executorService.shutdown();
        }
    }

    /**
     * MultipartUploadを実行
     * 
     * @param executorService
     * @param s3
     * @param bucket
     * @param key
     * @param uploadId
     * @param partNumber
     * @param contentLength
     * @param bytes
     * @return
     */
    private static Future<CompletedPart> uploadMultipartUploadPart(
            ExecutorService executorService,
            S3Client s3,
            final String bucket,
            final String key,
            final String uploadId,
            final int partNumber,
            final long contentLength,
            final byte[] bytes) {
        return executorService.submit(
                () -> {
                    UploadPartRequest request = UploadPartRequest.builder()
                            .bucket(bucket)
                            .key(key)
                            .uploadId(uploadId)
                            .contentLength(contentLength)
                            .partNumber(partNumber)
                            .build();
                    UploadPartResponse response = s3.uploadPart(request, RequestBody.fromBytes(bytes));
                    CompletedPart part = CompletedPart.builder().partNumber(partNumber).eTag(response.eTag()).build();
                    return part;
                });
    }

で、ZIPファイルをアップロードすると、一部解凍できない場合が発生した。
スレッドの消失まで待つように変更したりしてみたが、改善はしたものの完全ではなかった。
リクエストを追っかけて、ログを出せば原因解明ができるとは思うが一旦は実現性重視として、同期処理に変更した。

©okkez氏

 /**
     * 大容量ファイルのアップロード
     * 
     * @param context
     * @param bucketName
     * @param dest
     * @param is
     * @throws AppException
     */
    public static void multipartUpload(Context context, String bucketName, String dest, InputStream is)
            throws IOException {
        /** S3のクライアント */
        final S3Client s3 = getS3Client();
        final CreateMultipartUploadResponse response = s3
                .createMultipartUpload(
                        builder -> builder.bucket(bucketName).key(dest));
        final String uploadId = response.uploadId();
        final ByteBuffer buffer = ByteBuffer.allocate(PART_BUFFER);

        try {
            final ReadableByteChannel channel = Channels.newChannel(is);
            List<CompletedPart> parts = new ArrayList<>();
            int partNumber = 0;
            while (channel.read(buffer) != -1) {
                if (buffer.position() > PART_LIMIT) {
                    long contentLength = buffer.position();
                    buffer.flip();
                    partNumber++;
                    CompletedPart part = uploadMultipartUploadPart(s3, bucketName, dest, uploadId, partNumber,
                            contentLength, buffer.array());
                    parts.add(part);
                    buffer.clear();
                }
            }
            if (buffer.position() > 0) {
                long contentLength = buffer.position();
                buffer.flip();
                partNumber++;
                CompletedPart part = uploadMultipartUploadPart(s3, bucketName, dest, uploadId, partNumber,
                        contentLength, buffer.array());
                parts.add(part);
                buffer.clear();
            }
            // マルチパートアップロードの完了処理を行う
            final var completedMultipartUpload = CompletedMultipartUpload.builder().parts(parts).build();
            s3.completeMultipartUpload(
                    builder -> builder
                            .bucket(bucketName)
                            .key(dest)
                            .uploadId(uploadId)
                            .multipartUpload(completedMultipartUpload));
        } catch (IOException ioe) {
            ioe.printStackTrace();
            throw ioe;
        }

    }
/**
     * MultipartUploadを実行
     * 
     * @param s3
     * @param bucket
     * @param key
     * @param uploadId
     * @param partNumber
     * @param contentLength
     * @param bytes
     * @return
     */
    private static CompletedPart uploadMultipartUploadPart(
            S3Client s3,
            final String bucket,
            final String key,
            final String uploadId,
            final int partNumber,
            final long contentLength,
            final byte[] bytes) {

        UploadPartRequest request = UploadPartRequest.builder()
                .bucket(bucket)
                .key(key)
                .uploadId(uploadId)
                .contentLength(contentLength)
                .partNumber(partNumber)
                .build();
        UploadPartResponse response = s3.uploadPart(request, RequestBody.fromBytes(bytes));
        CompletedPart part = CompletedPart.builder().partNumber(partNumber).eTag(response.eTag()).build();
        return part;
    }

ServletのPartから取れるInputStreamからアップロードする、という目的はいったん達成したため、許容しました。
くやしい。