特定のキーを除く型(Omitをネストさせる)

type NestedOmit<T, K extends keyof T> = {
    [P in keyof Omit<T, K>]: T[P] extends Array<infer R>
        ? K extends keyof R 
            ? Array<NestedOmit<R, K>>
            : Array<R>
        : K extends keyof T[P]
            ? NestedOmit<T[P], K>
            : T[P]
};

例えばレシピとその材料を表す以下のinterfaceがあったとき

interface Recipe {
  id: number;
  name: string;
  ingredients: Ingredient[];
}

interface Ingredient {
  id: number;
  name: string;
}

作成apiにpostするデータとしては、idは不要。そのデータにつけるための型を用意する場合、色々やり方はあるが、例えば以下のようにしなければいけない

interface PostData {
  name: string;
  ingredients: Omit<Ingredient, "id">[];
}

idが共通して不要な場合、これはめんどくさいし、元のRecipeの型が変わったとき変更についていくのがだるそう

ので、冒頭に書いたようなねすとするOmitを定義してみた。

type PostData = NestedOmit<Recipe, "id">;

楽でいいな〜と思いつつ、こういうのってみなさんどうしてるんですかねと思った

GAEでcronを使って定時実行処理をする

定期的に処理を実行したいときがありますね。GAEはcronの設定ができます。

cron設定

アプリケーションのルートディレクトリにcron.yamlファイルを作成します。中身はこんな感じ

// cron.yaml
cron:
- description: "ゴミ出し通知"
  url: /cron
  schedule: every day 22:30
  timezone: Asia/Tokyo

descriptionは省略できます。

以下、各プロパティの説明

url

cron設定をしておくと、このurlに対してscheduleした時間にGETリクエストを送ってくれます。定期的に実行したい処理を、GETリクエスト受けたときに実行できるようにアプリを書いておきます。

schedule

いつ実行するかを記述します。上のyaml設定例だと、毎日22:30に実行されます。 構文はここを参考に→ https://cloud.google.com/appengine/docs/flexible/go/scheduling-jobs-with-cron-yaml?hl=ja#Go_cron_yaml_The_schedule_format

timezone

scheduleタイムゾーン。省略できます。省略した場合はUTC

デプロイ

あとはデプロイすればOKです。

$ gcloud app deploy cron.yaml

ここハマりました。普段どおり、gcloud app deployでアプリのデプロイすればcron反映されると思っていました。が、上のように、cron.yaml指定してデプロイしないとだめでした。

参考

cloud.google.com

React useContextとemotion-themingを使ってテーマ切り替え機能を実装する🌔

emotion-themingを利用して、テーマ切り替え機能を実装します。Twitterのと一緒です。ボタン押して、toggleできるものを目指します。

Install

とりあえず

npx create-react-app dark-mode --typescript

emotion

yarn add @emotion/core @emotion/styled emotion-theming

モード切り替え

React.Contextに今どのモードなのかを保存、useContextを使って、コンポーネントからモード切り替えをします。

まずはContextを用意

// themeContext.ts

import { createContext, useContext } from 'react';

interface ThemeContextType {
  colorMode: ColorMode;
  setColorMode: () => void;
}

const defaultContext: ThemeContextType = {
  colorMode: 'light', // 現在のモードを管理
  setColorMode: () => {}, // colorMode書き換え用の関数を渡す
};

export const ThemeContext = createContext<ThemeContextType>(defaultContext);
export const useTheme = () => useContext(ThemeContext);

setColorModecolorMode を書き換えるための関数を渡します。(Providerのvalue propsで)

Theme用意

この辺はなんでもいいです。

import { ColorMode, Theme } from './types';

const lightTheme: Theme = {
  background: '#ffffff',
  color: '#000000',
};

const darkTheme: Theme = {
  background: '#222639',
  color: '#f0f5fa',
};

export function getTheme(colorMode: ColorMode): Theme { // mode受けてテーマ返す
  switch (colorMode) {
    case 'light':
      return lightTheme;
    case 'dark':
      return darkTheme;
    default:
      return lightTheme;
  }
}

Providerの用意

上で定義した、getTheme()を使って、テーマを切り替えます

// ThemeProvider.tsx

import React, { useState } from 'react';
import { ThemeProvider as EmotionProvider } from 'emotion-theming';
import { ThemeContext } from '../themeContext';
import { getTheme } from '../theme';

type ColorMode = 'light' | 'dark';

const ThemeProvider: React.FC = ({ children }) => {
  const [colorMode, setColorMode] = useState<ColorMode>('light');

  function toggleColorMode() { // colorMode切り替え用関数
    setColorMode(colorMode === 'light' ? 'dark' : 'light');
  }
  return (
    <EmotionProvider theme={getTheme(colorMode)}>
      <ThemeContext.Provider
        value={{
          colorMode,
          setColorMode: toggleColorMode,
        }}
      >
        {children}
      </ThemeContext.Provider>
    </EmotionProvider>
  );
};

export default ThemeProvider;

あとは、コンポーネントから、useTheme() を使ってテーマを切り替えるだけ!

// App.tsx

import React from 'react';
import './App.css';
import { useTheme } from './themeContext';
import styled from './components/styled';

const App: React.FC = () => {
  const { colorMode, setColorMode } = useTheme();
  return (
    <Container>
      <p>current color mode: {colorMode}</p>
      <button onClick={setColorMode}>toggle color mode</button>
    </Container>
  );
};

export default App;

const Container = styled.div`
  height: 100%;
  background: ${props => props.theme.background};
  color: ${props => props.theme.color};
`;

Typescriptを使っていると、emotionからimportしたstyled を使うとtheme内にProviderで渡したプロパティの型情報が含まれていないため、使えないです。

styledに型情報をもたせて、それを替わりに使います。

参考:https://emotion.sh/docs/typescript#define-a-theme

// styled.tsx

import styled, { CreateStyled } from '@emotion/styled';
import { Theme } from '../types';

export default styled as CreateStyled<Theme>; // 今回のthemeの型

やったね!

やったね。

f:id:morimo7:20190713154542g:plain

おわり

以上です。Githubにあげてあります。

github.com

ExpressをTypescriptで書く

インストール

Express, Typescript, webpackをインストール

yarn add express
yarn add -D @types/express typescript ts-loader webpack webpack-cli

ファイルの変更を検知してnode再起動してくれる nodemon をインストール

yarn add -D nodemon

Webpackの設定

webpack.config.js

const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/server.ts',
  target: 'node',
  devtool: 'inline-source-map',
  module: {
    rules: [
      {
        loader: 'ts-loader',
        test: /\.ts$/,
        exclude: [/node_modules/],
        options: {
          configFile: 'tsconfig.json',
        },
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.js'],
  },
  output: {
    filename: 'server.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

tsconfig

なんでもいいと思います。

{
  "compilerOptions": {
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "jsx": "react",
    "lib": ["es2018", "dom"],
    "moduleResolution": "node",
    "removeComments": true,
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "strictFunctionTypes": false,
    "strictNullChecks": true
  }
}

express

動作確認程度。 src/server.ts

import * as Express from 'express';

const app = Express();

app.get('/', (req: Express.Request, res: Express.Response) => {
  return res.send('Hello world.');
});

app.listen(3000, () => {
  console.log('Example app listening on port 3000!');
});

export default app;

起動

package.json に起動用のコマンドを追加。

webpack —watch してnodemon でoutput先を見張ります。

{
  "scripts": {
    "dev": "webpack --config webpack.config.dev.js --watch & nodemon ./dist/server.js"
  },
  "dependencies": {
    "express": "^4.17.1"
  },
  "devDependencies": {
    "@types/express": "^4.17.0",
    "nodemon": "^1.19.1",
    "ts-loader": "^6.0.4",
    "typescript": "^3.5.2",
    "webpack": "^4.35.2",
    "webpack-cli": "^3.3.5"
  }
}

以上です。

Githubに公開しておきました↓

github.com

Webpackとは(今更)

いつもなんとなくでググって出てくるものをコピペしてたので、自分で構成できるよう、調べた。

Webpack?

webpack is a static module bundler for modern JavaScript applications.

https://webpack.js.org/concepts/

module bundler?

モジュールをまとめてくれるツール

モジュールを組み合わせてアプリケーションを開発すれば、それぞれのモジュールは小さく、デバッグやテストをしやすい。 しかし、ファイル数が増えると、それだけリクエスト数は多くなってしまう。(遅延につながる) module bundlerを使えば、各ファイルを1つのファイルにまとめてくれる!(設定による)

設定については別の記事で書く予定です(多分)


morimo7.hatenablog.com

今日から書くぞと意気込んでから76日も経ってるとは、、

今日から。

いままで技術系のアウトプットを(公開されているところで)したことがなかったので、今日から始める。

 

毎日は難しいかもしれないが、技術的につまずいたこと、調べたことはどんどん残していく。