20201206のReactに関する記事は14件です。

Reactでブラウザから音声を録音してFirebaseにアップロードしてみた(iOS未対応)

経緯

最近は音声を投稿するSNSが増えてきているなぁと感じてきており、私が知っている限りでも10個は直ぐに思い出せます。
そこで、いつか音声サービスを作ってみたくなるかもしれないので、興味本位で調べてみました。

動作確認

WindowsのChrome

開発情報

  • react
  • firebase
  • react-audio-player

デモ動画

YouTubeで確認できます。
https://youtu.be/zHmhcu2CANs

コード

App.js
import React, { useEffect, useState, useRef } from "react";
import firebase from "firebase";
import ReactAudioPlayer from "react-audio-player";

// configは、自分のfirebaseの設定を指定してください。
const config = {
  apiKey: "",
  authDomain: "",
  databaseURL: "",
  storageBucket: "",
};

const App = () => {
  const [file, setFile] = useState([]);
  const [audioState, setAudioState] = useState(true);
  const audioRef = useRef();

  useEffect(() => {
    if (firebase.apps.length === 0) {
      firebase.initializeApp(config);
    }
    // マイクへのアクセス権を取得
    navigator.getUserMedia =
      navigator.getUserMedia || navigator.webkitGetUserMedia;
    //audioのみtrue
    navigator.getUserMedia(
      {
        audio: true,
        video: false,
      },
      handleSuccess,
      hancleError
    );
  }, []);

  const handleSuccess = (stream) => {
    // レコーディングのインスタンスを作成
    audioRef.current = new MediaRecorder(stream, {
      mimeType: "video/webm;codecs=vp9",
    });
    // 音声データを貯める場所
    var chunks = [];
    // 録音が終わった後のデータをまとめる
    audioRef.current.addEventListener("dataavailable", (ele) => {
      if (ele.data.size > 0) {
        chunks.push(ele.data);
      }
      // 音声データをセット
      setFile(chunks);
    });
    // 録音を開始したら状態を変える
    audioRef.current.addEventListener("start", () => setAudioState(false));
    // 録音がストップしたらchunkを空にして、録音状態を更新
    audioRef.current.addEventListener("stop", () => {
      setAudioState(true);
      chunks = [];
    });
  };
  // 録音開始
  const handleStart = () => {
    audioRef.current.start();
  };

  // 録音停止
  const handleStop = () => {
    audioRef.current.stop();
  };
  // firebaseに音声ファイルを送信
  const handleSubmit = () => {
    // firebaseのrefを作成
    var storageRef = firebase.storage().ref();
    var metadata = {
      contentType: "audio/mp3",
    };
    // ファイル名を付けてBlobからファイルを作成して送信
    var mountainsRef = storageRef.child(new Date() + "test.mp3");
    mountainsRef.put(new Blob(file), metadata).then(function () {
      console.log("アップロード完了!");
    });
  };
  const handleRemove = () => {
    setAudioState(true);
    setFile([]);
  };

  const hancleError = () => {
    alert("エラーです。");
  };

  return (
    <div>
      <button onClick={handleStart}>録音</button>
      <button onClick={handleStop} disabled={audioState}>
        ストップ
      </button>
      <button onClick={handleSubmit} disabled={file.length === 0}>
        送信
      </button>
      <button onClick={handleRemove}>削除</button>
      <ReactAudioPlayer src={URL.createObjectURL(new Blob(file))} controls />
    </div>
  );
};

export default App;

所感

ほぼ参考サイトのコピペですが、それをReactで実装してみました。
Webで音声を録音することも案外できるものだなぁと感じました。
また、音声の再生には「react-audio-player」というライブラリが直感的に使えて助かりました。
音声を録音と変換が簡単にできるreactのライブラリがあればよかったのですが...

また、iOSのブラウザではWeb Audio APIを使用する必要があるみたいで、この実装だと動きません。
実装し終えてから気づきました。。。

次はiOSに対応するバージョンと、ReactNativeでの音声の録音と保存にも挑戦してみようと思います。

最後に

※発言は個人の見解で所属組織とは無関係です

参考にした記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

地図アプリ開発で React Hooks を利用して React コンポーネントを作成する。

本エントリーは React Advent Calendar 2020 5日目のエントリーです。

地図アプリの開発では、よく Google Maps API を使用するケースが多いと思いますが、最近は、Mapbox や ArcGIS、オープンソースを利用して開発するケースも増えてきています。今回は、ArcGIS の ArcGIS API for JavaScript で React を使ってみましたので、インストールから簡単な地図データの表示までを紹介したいと思います。

詳細は以下に公開しています。
https://freedom-tech.hatenablog.com/entry/2020/12/06/214824

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HooksのuseStateがどのように実現されているか調べた話

この記事は 2020年 Reactアドベントカレンダー 12日目の記事です。

はじめに

React の Functional Componentで副作用を実現するためのhooksのうち、おそらく最もわかりやすいuseStateがどのように実現されているかを調べた。

調べる

Reactのリリースをcheckout

Reactをgithubからcloneし、執筆時点で最新の v17.0.1 のtagをcheckout

$ git clone git@github.com:facebook/react.git
$ cd react
$ git checkout v17.0.1

useStateがexportされている部分を検索

VSCodeで開き、愚直に useState で検索したところ、 React.js の 95行目 がexportしており、その実装は ReactHooks.js の 80行目 にあることがわかった。

ひたすら実装を探る

const dispatcher = ReactCurrentDispatcher.current

とあるので、とりあえず ReactCurrentDispatcher.current が何かを調べる必要がある。
ReactCurrentDispatcher の実装が↓

const ReactCurrentDispatcher = {
  /**
   * @internal
   * @type {ReactComponent}
   */
  current: (null: null | Dispatcher),
};

export default ReactCurrentDispatcher;

これだけなので、多分currentに値を突っ込んでいる部分があるはず、ということで ReactCurrentDispatcher.current = で検索したところ、 7ファイルの中に112件も引っ掛かっていしまい早々と心が折れそうになる。

とはいえ、多分いろんなところから値がセットされるんだろうからstaticなDispatcherが取れれば良いんだろう、ということで見なかったことにして Dispatcher が何なのかを調べたら、その中に useState のインターフェースがあった。

じゃあDispatcherの実装を調べればそれで済む話になりそう、というわけでDispatcherの実装を調べる。

実装している部分ではDispatcherをimportしているはずなので、 type {Dispatcher で検索したら、 ReactPartialRendererHooks.js がimportしていて、 それが useState やその他のhooksの実装をexportしていた。
やったぜ。

肝心のuseStateの実装を見ると、内部ではuseReducerが使われている ので、今度は useReducer の実装を読む。

useState から useReducer に渡されているreducerが、 basicStateReducer なので、 useStateuseReducer の特別なもの、として実装されている様子。

useReducer の中で isReRender というフラグが参照されていて、おそらく再描画のときにはこのフラグが立っているんだろう。

とりあえず再描画でないほうの実装を読むと、 この中でDispatchの実装が作られ、これが呼ばれると再描画が走るような作りになっているらしい。
再描画の仕組みは今回の調査の対象ではないので気にしない。

次に再描画のほうの実装を読むと、 値を memoizedState にキャッシュし、更新された値とすでに作成済みのDispatchを返している様子。

Hooks自体がやっていることはここまでで、あとは Dispatch で再描画を走らせる部分なのでここでおしまい。

調べてみて

少なくとも useStateuseReducer は状態管理だけで大したことはやっていなかった。
大したことをやっているのは、おそらくDispatchで再描画する部分。
その部分も追々調べてみたい。

使うときは最初に宣言しておしまいだから1度作った参照が使い回されている気になっていたけど、描画されるたびに新しい参照が返されるんだな。そりゃそうか。
Singletonな状態管理がめっちゃたくさんあってバグらないようにするのは大変そうだがそのへんはライブラリあるあるなんだろうきっと。

終わりに

内部実装を見ると新しい気付きがあって面白い。
アドベントカレンダーに参加しなかったら絶対やらなかったことなので、参加してよかった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】Redux Toolkit のチュートリアルのサンプルコードを読んでみる

はじめに

React を習得するまでの軌跡をメモっていく備忘録的な記事です。

Redux Toolkit を使ってみる

2019年にリリースされた React の状態管理のライブラリで従来の Redux をより使いやすくしたものらしい。

Redux Toolkit の公式サイト

公式サイトの Intermediate Tutorial を参考にしました。
サンプルコードのGitHubのリポジトリはこちら

サンプルコードの中身は todo アプリで
仕様としては

  • 一個一個の todo は text(すべきこと) と completed(完了したかどうか) を持つ
  • フッターのボタンで all(全てのTODOを表示), active(完了してないTODOを表示) , completed(完了したTODOを表示) を切り替えることができる

UIは以下のような感じです。

スクリーンショット 2020-12-06 19.55.17.png

実際にソースコードを読む

ファイル構成はこんな感じです。

スクリーンショット 2020-12-06 23.59.53.png

Redux Toolkitの要になりそうな部分だけ取り上げています。

ソースコード中にコメントを入れる形で書いていきます。

index.js
import React from 'react'
import { render } from 'react-dom'
import { configureStore } from '@reduxjs/toolkit'
import { Provider } from 'react-redux'
import App from './components/App'
import rootReducer from './reducers'


const store = configureStore({
  // store を定義する
  // rootReducer は createSliceで作った Reducer 達を combineReducerにより合体させたもの
  // (↑ reducers/index.js で定義している)
  reducer: rootReducer
})

render(
  // 子コンポーネントが store に接続できるように
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

TODOを追加したり、完了状態を変更する部分の実装

features/todos/todoSlice.js
import { createSlice } from '@reduxjs/toolkit' // createSlice を使えるようにする

// slice を作成する Redux Toolkitのメイン部分
// State, Reducer, Action を一気に生成する
const todosSlice = createSlice({
  name: 'todos', // slice の名前を設定
  initialState: [], // state の初期値を設定
  reducers: { // reducer を設定 複数の reducer を設定できる
     // prepare のコールバックを用いた書き方
    addTodo: {
      reducer(state, action) {
        const { id, text } = action.payload // action.payload には reducer に渡したい正味の値が入る
        state.push({ id, text, completed: false }) // state に新規の値を追加している
      },
      //prepare のコールバックを使うとstateを更新する前に色々と事前に編集できて便利
      prepare(text) { 
        return { payload: { text, id: nextTodoId++ } }
      }
    },
    // こっちが通常の書き方
    toggleTodo(state, action) {
      // state の中から id に一致した todo を見つける
      const todo = state.find(todo => todo.id === action.payload)
      if (todo) {
        todo.completed = !todo.completed // 見つけた todo の complete を更新する
      }
    }
  }
})

// addTodo と toggleTodo の Action を生成できる関数を export
export const { addTodo, toggleTodo } = todosSlice.actions

// Reducer を export
export default todosSlice.reducer

connect, mapDispatch, mapStateToProps についてはこちらの記事が大変参考になりました。
mapStateToPropsとmapDispatchToPropsの理解の仕方

features/todos/AddTodo.js
import React, { useState } from 'react'
import { connect } from 'react-redux'
import { addTodo } from './todosSlice'

// AddTodo component に props として addTodo の Action を渡すため
const mapDispatch = { addTodo }

const AddTodo = ({ addTodo }) => {
  const [todoText, setTodoText] = useState('')

  const onChange = e => setTodoText(e.target.value)

  return (
    <div>
      <form
      {
        // 送信ボタンが押された時、form の値がなければそのまま return
        // 値があれば addTodoの Action が Reducer にフォームの値を送り、 state が更新される、 
        // その後、フォームは空になる
      }
        onSubmit={e => {
          e.preventDefault()
          if (!todoText.trim()) {
            return
          }
          addTodo(todoText)
          setTodoText('')
        }}
      >
        <input value={todoText} onChange={onChange} />
        <button type="submit">Add Todo</button>
      </form>
    </div>
  )
}

// connectを使って Action を AddTodo に Propsとして送る
export default connect(
  null,
  mapDispatch
)(AddTodo)

表示するTODOの切り替え部分の実装

features/filters/filterSlice.js
import { createSlice } from '@reduxjs/toolkit' // createSlice を使えるようにする

// フィルタリングの状態(全て、完了、未完了)を定義した定数
export const VisibilityFilters = {
  SHOW_ALL: 'SHOW_ALL',
  SHOW_COMPLETED: 'SHOW_COMPLETED',
  SHOW_ACTIVE: 'SHOW_ACTIVE'
}


const filtersSlice = createSlice({
  name: 'visibilityFilters', // slice の名前を設定
  initialState: VisibilityFilters.SHOW_ALL, // slice の初期値を設定
  reducers: { // コールバックは使わず通常の書き方
    setVisibilityFilter(state, action) {
      return action.payload // 受け取った action の値でそのまま state を更新
    }
  }
})

// Action を export
export const { setVisibilityFilter } = filtersSlice.actions

// Reducer を export
export default filtersSlice.reducer
features/filters/Link.js
import React from 'react'
import PropTypes from 'prop-types'

// filter には VisibilityFilters のいずれかが入る 
const Link = ({ active, children, setVisibilityFilter, filter }) => (
  <button
    // setVisibilityFilter Action で Reducer に filter の値を送信
    onClick={() => setVisibilityFilter(filter)}
    disabled={active} // 現状クリックされているなら disabled に
    style={{
      marginLeft: '4px'
    }}
  >
    {children}
  </button>
)

Link.propTypes = {
  active: PropTypes.bool.isRequired,
  children: PropTypes.node.isRequired,
  setVisibilityFilter: PropTypes.func.isRequired,
  filter: PropTypes.string.isRequired
}

export default Link

Storeから State を受け取り表示する部分の実装

src/features/todos/VisibleTodoList.js
import { connect } from 'react-redux'
import { createSelector } from '@reduxjs/toolkit'
import { toggleTodo } from 'features/todos/todosSlice'
import TodoList from './TodoList'
import { VisibilityFilters } from 'features/filters/filtersSlice'

const selectTodos = state => state.todos // Store から todos の state を select
const selectFilter = state => state.visibilityFilter //  Store から visibilityFilter の state を select

const selectVisibleTodos = createSelector( // selector を作成
  [selectTodos, selectFilter],
  (todos, filter) => {
    switch (filter) {
      case VisibilityFilters.SHOW_ALL:
        return todos
      case VisibilityFilters.SHOW_COMPLETED:
        return todos.filter(t => t.completed)
      case VisibilityFilters.SHOW_ACTIVE:
        return todos.filter(t => !t.completed)
      default:
        throw new Error('Unknown filter: ' + filter)
    }
  }
)

const mapStateToProps = state => ({
  todos: selectVisibleTodos(state)
})

const mapDispatchToProps = { toggleTodo }

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

まとめ

  • createSlice を使うと State, Action, Reducer を一気に作れるので便利。
  • Componentprops として StateAction を渡したい場合は、 connect, mapStateToProps, mapDispatchToPropsを使うと便利。
  • ReducercombineReducer で結合させて rootReducer を作る
  • configureStorestore を定義する。
  • 作った storeProvider 経由で 子コンポーネントに渡す。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

リモート発表の寂しさ問題を解決するNIGIYAKASHI

はじめに

今年も始まりました。IoTLTのアドベントカレンダー!。この投稿のついでに前から作ってみようと思っていたものをつくってみます

つくるもの

だいたいのイメージはこんな感じです。

NIGIYAKASHI.jpg

やりたいことは以下です。

  1. みんなのメッセージをPCやスマホから受け取ります
  2. メッセージをクラウドで集めます
  3. LEDデバイスで表示します

LEDデバイスは@kitazakiさんよりいただいたMINI LED BADGEを使います。
とりあえずこのアプリケーションをNIGIYAKASHIと名付けました。最近はオンラインでの勉強会での発表がメインになってきましたが、やはりオフラインと比べて賑やかし要素が少なく、寂しく感じる場面が多々あったのでこれを作ってみようと思いました。

完成品

とりあえず動くものから見せます。

IMAGE ALT TEXT HERE

机の上若干ごちゃついているのは無視でw

つかったもの

PubNub

詳しくは公式サイトで見てもらった方が良いかと思いますが、端的に説明するとリアルタイムメッセージチャットやMQTT用のブローカーのサーバーをすべて肩代わりしてくれるサービスです。このサービスを使うとメッセージ送信・受信用のコードを書くだけでリアルタイムメッセージのやり取りができるアプリケーションが作成できます。

UIアプリの作成

コードの全体はこちらで公開しています。ReactでPubNubをつかうためのパッケージpubnub-reactを使うと簡単にPubNubを使うことができます。実装したコードは↓のような感じで、公式ドキュメントのサンプルコードをフォークするだけで簡単に実装することができました。

src/App.js
import React, { useCallback, useState } from 'react'
import PubNub from 'pubnub'
import { PubNubProvider, usePubNub } from 'pubnub-react'
import Button from '@material-ui/core/Button'
import Input from '@material-ui/core/Input'
import { makeStyles } from '@material-ui/core/styles'
import logo from './logo.png'

const pubnub = new PubNub({
  publishKey: process.env.REACT_APP_PUB_KEY,
  subscribeKey: process.env.REACT_APP_SUB_KEY,
})

const useStyles = makeStyles({
  container: {
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  preparedMessage: {
    flexDirection: 'row',
  },
  customMessage: {
    flexDirection: 'row',
  },
  logo: {
    width: 400,
  },
})

const simpleMessages = ['わーい!', 'すごーい!', 'たのしー!']

const Messenger = () => {
  const pubnub = usePubNub()
  const [input, setInput] = useState('')
  const classes = useStyles()

  const sendMessage = useCallback(
    async message => {
      await pubnub.publish({
        channel: 'nigiyakashi',
        message: { message },
      })

      setInput('')
    },
    [pubnub, setInput]
  )

  return (
    <div className={classes.container}>
      <img src={logo} alt='logo' className={classes.logo}/>
      <div className={classes.preparedMessage}>
        {simpleMessages.map(simpleMessage =>
          <Button
            onClick={e => {
              e.preventDefault()
              sendMessage(simpleMessage)
            }}
            variant="outlined"
          >{simpleMessage}</Button>
        )}
      </div>
      <div className={classes.customMessage}>
        <Input
          type="text"
          placeholder="自由記入欄"
          value={input}
          onChange={e => setInput(e.target.value)}
        />
        <Button
          onClick={e => {
            e.preventDefault()
            sendMessage(input)
          }}
          variant="contained"
          color="primary"
        >
          送信
      </Button>
      </div>
    </div>
  )
}

const App = () => {
  return (
    <PubNubProvider client={pubnub}>
      <Messenger />
    </PubNubProvider>
  )
}

export default App

受信デバイスの実装

MINI LED BADGEのM5Stack用のサンプルコードをベースに実装しました。PubNubとのやりとりはn0bisukeさんの記事を参考に実装してみました。
またPubNubから送られてくるメッセージはJSONの形式となっているので、それをパースするためにArduino_JSONを使いました。

まずベースのサンプルコードのTestLEDMatrix()をいじって、任意の長さの文字をスクロール表示するための関数を実装しました。

void displayLEDMatrix(char *str){
  uint8_t buf[8];
  for (int x=17; x>= -1 * (int(String(str).length()) / 3 * 8) - 16; x--) {
    char *ptr = str;
    uint16_t n = 0;
    matrix.clear();
    matrix1.clear();
    matrix.setCursor(x,0);
    matrix1.setCursor(x+16,0);
    while(*ptr){
      ptr = getFontData(buf,ptr,true);
      if(!ptr)
        break;
      matrix.drawBitmap(x+n,0,buf,8,8,1);
      matrix1.drawBitmap(x+16+n,0,buf,8,8,1);
      n+=8;
    }
    matrix.writeDisplay();
    matrix1.writeDisplay();
  }
}

ただこれは全角の文字列が前提なので半角には対応してないですwあとはメッセージをSubscribeしたときのコールバック関数でJSON形式のメッセージをパースしてdisplayLEDMatrix()に送るための文字列を取り出します。

void callback(char* topic, byte* payload, unsigned int length){
  String msg = (char*) payload;
  msg = msg.substring(0, length);
  JSONVar msgObj = JSON.parse((char*) msg.c_str());
  displayLEDMatrix((char*) ((const char*) msgObj["message"]));
}

コード全体はこんな感じになりました。

#include <M5Stack.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Arduino_JSON.h>
#include <Adafruit_GFX.h>
#include <Adafruit_LEDBackpack.h>
#include "misakiUTF16.h"

Adafruit_8x16matrix matrix = Adafruit_8x16matrix();
Adafruit_8x16matrix matrix1 = Adafruit_8x16matrix();

const char* ssid = "ssid";
const char* password = "password";
const char* mqttServer = "mqtt.pndsn.com";
const char* pubnubid = "pub-c-xxx/sub-c-xxx/ufoo68";
const int mqttPort = 1883;

WiFiClient espClient;
PubSubClient client(espClient);

void callback(char* topic, byte* payload, unsigned int length){
  Serial.print("Message arrived in topic: ");
  Serial.println(topic);
  Serial.print("Message:");
  String msg = (char*) payload;
  msg = msg.substring(0, length);
  Serial.println(msg);
  JSONVar msgObj = JSON.parse((char*) msg.c_str());
  displayLEDMatrix((char*) ((const char*) msgObj["message"]));
  Serial.println("-----------------------");
}

void setup() {
  Serial.begin(115200);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED){
    delay(500);
    Serial.println("Connecting to WiFi..");
  }
  Serial.println("Connected to the WiFi network");

  client.setServer(mqttServer, mqttPort);
  client.setCallback(callback);
  while (!client.connected()) {
    Serial.println("Connecting to MQTT...");
    if (client.connect(pubnubid)){
      Serial.println("connected");
    }else{
      Serial.print("failed with state ");
      Serial.print(client.state());
      delay(2000);
    }
  }
  client.subscribe("nigiyakashi");

  Wire.begin(21, 22, 1000);  // M5Stack Grove G21: SDA, G22: SCL
  matrix.begin(0x71);
  matrix1.begin(0x70);
  matrix.setBrightness(0);
  matrix1.setBrightness(0);
  matrix.setTextWrap(false);
  matrix1.setTextWrap(false);
  matrix.setTextColor(LED_ON);
  matrix1.setTextColor(LED_ON);
  matrix.setRotation(1);
  matrix1.setRotation(1);
}

void loop() {
  client.loop();
}

void displayLEDMatrix(char *str){
  uint8_t buf[8];
  for (int x=17; x>= -1 * (int(String(str).length()) / 3 * 8) - 16; x--) {
    char *ptr = str;
    uint16_t n = 0;
    matrix.clear();
    matrix1.clear();
    matrix.setCursor(x,0);
    matrix1.setCursor(x+16,0);
    while(*ptr){
      ptr = getFontData(buf,ptr,true);
      if(!ptr)
        break;
      matrix.drawBitmap(x+n,0,buf,8,8,1);
      matrix1.drawBitmap(x+16+n,0,buf,8,8,1);
      n+=8;
    }
    matrix.writeDisplay();
    matrix1.writeDisplay();
  }
}

さいごに

今回は色んなサンプルコードを組み合わせたような実装になりましたが、Arduinoを使ったリアルタイムメッセージのやりとりをPubNubを使って簡単に実装することができました。かなりシンプルなものができましたが、実際に自分のリモート発表に使ってみようかなと思います。

次回は@wicketさんの投稿です。お楽しみに!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerコンテナ上でGatsbyを動かす

確認環境

  • OS: macOS Catalina
  • Docker: version 19.03.13
  • docker-compose: version 1.27.4

Docker Compose

まずはComposeファイルを準備します。

docker-compose.yml
version: '3'
services:
  gatsby:
    build: ./docker/gatsby
    volumes:
      - ./gatsby:/usr/src/app
    ports:
      - "8000:8000"
    tty: true

Dockerfile

次はDockerfileを準備します。
Composeファイル内に記載されている./docker/gatsby内に配置します。
ファイル名はDockerfileです。
※おそらくQiitaの仕様で拡張子のないファイルのファイル名を以下で表示させることができないので補足

FROM node:15.3.0-alpine3.10
WORKDIR /usr/src/app
RUN apk update && \
    apk add git && \
    npm install -g gatsby-cli
EXPOSE 8000

コマンド実行

以下コマンドを実行します。

コンテナ起動

$ docker-compose up -d

スターターダウンロード

$ docker-compose exec gatsby gatsby new . https://github.com/gatsbyjs/gatsby-starter-hello-world

サーバ起動

$ docker-compose exec gatsby gatsby develop --host=0.0.0.0

動作確認

http://localhost:8000/
にアクセスして以下画像のように表示されたら成功です。
starter.png
公式ドキュメントより引用

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#とかphpとか、せいぜいRuby on Railsの人がReact始めて見た

一言でReactって?

その前に昔話

いわゆるレガシーと言われている言語に生きてきたものにとって、ReactとかVueとかって、なんか近寄りがたいんですよね。
というか、JavaScript的なあたりでなんか壁を感じるのです。

VBAとかDelphiとかVisual Basicとかの時代って、画面描画とロジックが完全に分かれて見えるようになっていたのがプログラミングの革命的な所、私とかはそのあたりからプログラムを開始したので、Webプログラムとかの初期にhtmlタグの文字列を生成して返すなんていうことが、えらく不便な世界に感じたものです。

htmlなんて、version3とかの時代にはプレーンテキストの見栄えを良くするだけの印象だったのに。。

今で言うところのmarkdownののりでしたね。

なんか文章を作成するのに毎回一太郎とか開くのは重いし、とは言って「メモ帳」とか「秀丸エディタ」だと文章作成時はよいんだけど後から読むのがつらい。

そんな私によって中間の選択肢をあたえてくれたのが html。はいばーてきすとまーくあっぷらんげーじ でした。

秀丸エディタで作ったメモが ブラウザ(当時は Internet ExplorerかNetscapeか)で見るとキレイに読める!し、リンクも貼れる。
しかもiframeとか使うと、他のメモをそのまま引用できる!

とか思っていたものです。

で、そんな静的コンテンツをBrowserで見る時代からBrowerの中身がインタラクティブになってきて、表示を補完する言語が JavaScriptだったハズなんですよね。。

それが、気付けば node.jsとかいってサーバーサイドで動いているとかって。。

当初は「ロジック」を司るC言語で、スクリーン上にドットを表示する制御をする仕組みがどんどん高度になって、そのうち画面表示とロジックを分離して考えられるようになり、気がつくと画面表示のウェイトが大きくなって、画面表示の観点からロジックを実装しましょう。という、綱引き的な何かを感じるのです。。

で、Reactを理解してみた。。?

  • その前にJSXという言語の理解が必要
  • JSXを細分化し、部品(コンポーネント)わけて、部品毎にロジックを記載する。という仕組み
  • 部品(「子」)を配置する「親」と言う関係で、「親」から「子」に対して、"props"というデータを受け渡して、子を働かせる。(部品の見え方を制御する)→「子」は受け取った”Props"に基づき画面の一部を描画する
  • "props"という"状態"で画面の描画を制御する

引っかかった所

({ actions }) => {
  const { createPage } = actions
}
  • "Ref"とは:https://ja.reactjs.org/docs/refs-and-the-dom.html
    • 「子」要素の制御をクラス内で行う
      • render()でreturn される、Html要素に対して、クラスでnodeを取り出して直接操作ができる
      • render()の returnに含まれる カスタムクラスコンポーネントについて、そのインスタンスを親の方で参照出来る
      • ただし、関数コンポーネントはインスタンスを持たないので、ref属性は渡せない
    • "props"を使わず(データ受渡をせず)に、親から子になんらかの命令を出して子要素の表示を制御する仕組み
    • (React 16.3より) React.createRef() により"Ref"をというオブジェクトを「親」側で作成し「子」に ref属性をつかって受け渡す
 constructor(props) {
  super(props)
  this.myRef = React.createRef()
 }
 render() {
   return <div ref={this.myRef}
 }
  • export :
    • 静的なhtmlの世界だと、htmlに複数の javascriptファイル (scriptタグで呼び出す)について、呼び出されたjavascript間で他のjavascriptファイルで宣言された変数とか自由自在に使える(グローバル変数として)。すなわち、一つのファイルに結合されたかのように振る舞う。phpのrequireとか Cのincludeとかと同じ
    • とはいえ、それぞれのjavascriptファイルがサーバーサイドで動くことを考えると、変数のスコープは基本的にファイルで閉じるという考え方になった。そこで、他のファイルで特定の変数(オブジェクト)の利用を可能にするために export 宣言により明示的にオブジェクトを指定しなければならない (昔のdllみたいに、インターフェイスに当たるエントリーポイントとかを定義するイメージ)
  • import, require:
    • export 宣言されているオブジェクトを取り込む。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】ログ収集のためのReactライブラリを作る方法【閲覧イベント編】

始めに

Creating a React Analytics Logging Libraryこちらの記事を読んだ上で書いている記事になります。記事中ではClassComponentで書かれていたのを本記事ではReactHooksに書き直しているので一見ぐらいの価値はあると思います・・・!!

かなりいいなと思いました!!僕はここにあるようなアンチパターンを実装をしてきたので『これからログを取る方』は是非とも役立ててください。
なお、この記事では『閲覧イベント』を扱っていきます!

イベントロギングしよう!

イベントロギングって大切ですよね。これをもとにプロダクトを改善していったりします。
例えば、『ECサイトにおいて注文数を上げたい!』って思った時にユーザーがどのように行動しているのかをトラッキングするのって重要です。

一般的な調査方法で『なぜ注文しないのか?』を明らかにしていくことを考えると、『ユーザーインタビュー』を行う方法もあったり、『ユーザーテスト』を行うということもあります。

しかし、残念なことにこれらには『結構なコスト』がかかります。また、いろいろ聞かれるものの言いにくいことや覚えていないこととかもありますよね。

ぶっちゃけ、『クッソ微妙な商品だな・・・』と思ったとていても正直に思ったことを言える人もそうそういないと思います。そのため、『ちょっと高いかなぁって?』って無難にやり過ごしたりすることも多いでしょう。はたまた、『使ってみてわからないこととかありました?』なんて急に聞かれても覚えてなかったりすることもあり、『と、・・特にはなかったですね?』なんて答えが得られたりすることもあります(そしてインタビュー終わったあとにそういえばあれあったなぁ・・・まぁいっか!?と思ったりしちゃうことありますよね)。

そういった中、『実際にユーザーがどうやってつかっているか?どこに時間をかけて、何に迷っているのか?』といったことをしるためにイベントのロギングはかなり寄与します。

そして、コストは少なく抑えられますし、取れるデータ数も膨大な数になります。何よりも『真実を継続的を取れる』といった点はアプリケーションの改善には必須になってきます(とはいえユーザーの思考を知ることはできないため定性的な調査ももちろん必要です)。

Creating a React Analytics Logging Libraryこちらの記事では、SlackのデスクトップアプリケーションはReactでかかれており、

  • 開発者にとって簡単にロギングできるようにする
  • ログデータエラーを減らす
  • ユーザーがどうやってみているかのリアルなデータをとる

これらを実現するためにReactではどういう実装方法をするのが良いのか?について書かれています。
今日は『閲覧編』で、この記事はまだ途中で全てのHow to についてかかれてはいませんので更新次第書いていきたいと思ってますー!?

前提条件

このようなコンポーネントを仮定します。

// Home.tsx
function Home() {

  return (
    ....
    <Section id="welcome">
       ....
       <Banner />
    </Section>
    ...
  );
}

ここでは、Sectionコンポーネントの子供としてBannerコンポーネントがいます。
そこで、

  • Sectionコンポーネントが閲覧されたか?
  • Bannerコンポーネントが閲覧されたか?

というログをとることを題材に説明していきます。

一般的なログの実装方法

では、セクションやバナーといったコンポーネントにどうやってログを入れるのがいいのか?と考えると、真っ先に思いつくのは『ロギングするための関数をインポートして、真っ先にlifecycleやイベントハンドラに紐づける方法』ではないかなと思います。もちろん僕もその一員でした・・!!?

// Banner.tsx
import { sendLog } from '../utils/analytics';

function Banner() {
  useVisible(() => sendLog({ page: 'home', action: 'impression', section: 'welcome' ,component: 'banner' }), []);

  return ....
}

例えばこんな感じに。

useVisibleはコンポーネントが閲覧されたというイベントハンドラを実現するための架空のhooksです。あとでちゃんとuseVisibleのようなイベントハンドラの実装方法を書きますのでご安心ください・・!!

この実装自体は悪くないですが、この親コンポーネントであるSectionを見てみましょう。

// Section.tsx
import { sendLog } from '../utils/analytics';

function Section() {
  useVisible(() => sendLog({ page: 'home', action: 'impression', section: 'welcome' }), []);

  return ....
}

同じようにこのようなコードを書きます。
こうすることで元々の要件

  • Sectionコンポーネントが閲覧されたか?
  • Bannerコンポーネントが閲覧されたか?

は達成できそうです。
だけど、これでいいのでしょうか・・・??

気持ちを切り替えて見返すと、おんなじような記述が目立ちますね・・・。わかりやすくするためにpropsを使っていませんが、props設計にしても・・・

<Section log={sectionLog}>
   ...
   <Banner log={bannerLog} />
</Section>
// Banner.tsx
function Banner(props:Props) {
  useVisible(props.log, []);

  return ....
}

このようになります。こうすると見栄えはきれいになりますが結局bannerLogを定義するめんどくささや、使用するコンポーネント全てにuseVisibleの記述を書かなければならない点などは変わりません。

そこでこれを解決するための方法としてslackの記事で書かれているのはContextAPIを使った書き方です!!
この方法はかなり賢いなと思いました!!

ContextAPIを使ってネストした子要素が親要素を知れるようにする

結論からいきますと、ContextAPIを使うとどのようにロギングできるようになるのかというとこのようになります。

<Log section="section">
  <Section>
    ....
    <Log component="banner">
      <Banner/>
    </Log>
  </Section>
</Log>

ログに送信される結果はこのようになります。

"[View]: {"component":"banner","section":"section"}
"[View]: {"section":"section"}

え。。。なんかLog増やしただけじゃない??』って思うかもしれませんが、良い点としては

  『Logで囲うだけでログが取れる』という点と
  『親のLog情報をこのLogが継承する』という点です。

bannerLogsectionLogといった固有のログ関数を用意する必要もありませんし、コンポーネントを作るたびにuseVisibleを追加するだけのお仕事をする必要もありません。
これが産む具体的な実利は後ほど説明します!!。

ではLogの中身がどうなっているのかをみにいきましょう。

import React, {createContext, ReactNode, useContext, useEffect} from 'react';
import {sendLog} from './sendlog';

export const LogContext = createContext({});

interface Props {
  children: ReactNode;
  component?: string;
  section?: string;
}
type Context = Omit<Props, 'children'>

export default function Log(props: Props) {
  const {children, ...directProps} = props;
  const logContext = useContext<Context>(LogContext);
  useVisible(() => {
    sendLog({...directProps, ...logContext});
  }, []);
  return (
    <LogContext.Provider value={{...directProps, ...logContext}}>
      {children}
    </LogContext.Provider>
  );
}

visibleなイベントについてはいったんuseVisibleを使っていますがあとでしっかりとしたものに修正します。
ここではログパラメータの取り回しについて理解いただければと思います。

なぜ親のログが子に引き継がれるのか?というと、LogComponentの配下にLogComponentが出現する場合、親のLogComponentに渡しているpropsを引き継ぐためです。

具体的には下記のコードがその実装例になります。?

    <LogContext.Provider value={{...directProps, ...logContext}}>
      {children}
    </LogContext.Provider>

このProviderがネストする構造がこのギミックの鍵になっています。これにより階層構造に応じたログを取得できるようになります。

これの何が嬉しいのか?

先ほど

  『Logで囲うだけでログが取れる』という点と
  『親のLog情報をこのLogが継承する』という点です。

という良さみを述べましたが、具体的に『何がいいのか?』を具体的に言いますと・・・。

  • 各コンポーネントの実装に依存しない
  • 階層構造に依存しており、propsに依存していない

という点になります。

各コンポーネントの実装に依存しない

これはかなり大きなメリットになります。
Logコンポーネントがロギングの役割を果たしますので、各コンポーネントは下記のようにロギングなど知らんぷりすることができます。

// Banner
export default function() { return ... }

つまり、ロギングするという責務から解放されます。?
各コンポーネントにロギングするための責務を入れ込んでしまうと『実装忘れたー?』とか『実装上ここは難しい?』などというデメリットがでるだけでなく、何よりおんなじコードがでまくるというDRYに反したものが出来上がってしまいます。

階層構造に依存しており、propsに依存していない

二点目です。まずはいったん、Propsで渡す案を具体的に書いてみましょう。

<Section log={() => sendLog({ section: 'section'})}>
   ...
   <Banner log={() => sendLog({ section: 'section', component='component'}))} />
</Section>

となります。ではここで、Bannerの位置を次のセクションに移したとしましょう。

<Section log={() => sendLog({ section: 'section'})}>
   ...
</Section>
...                      ここも修正
   <Banner log={() => sendLog({ section: 'section2', component='component'}))} />

のようになります。すると位置の移動だけでなく、logの修正も必要になってしまいます。
これでは修正のたびに変更する情報が多く存在し、間違えた時に正確なログを取れなくなってしまいます。

では同じようなケースをLogコンポーネントのある世界で行ってみると・・・

<Log section="section">
  <Section>
    ....
  </Section>
    ...
    <Log component="banner">
      <Banner/>
    </Log>
</Log>

コンポーネントの移動だけですみました。
このようにコンポーネントの階層構造に依存しているため、ログの出力内容を気にする必要はないのです。

visibleなイベントの実装

これで最後になります!

先ほどのコードではvisibleなイベントをuseVisibleという架空のhooksを使うことでお茶を濁しました。?
最後にここでは、お茶を濁さずvisibleなイベントをハンドリングしていくようにします。
素のInterSectionObserverAPIを使ってもいいんですが、react-intersection-observerを使った方が楽なのでこちらを紹介します。

ただし、現行バージョンのreact-intersection-observerはこのあとででてくるdisplay: 'contents'を使うと動作しない問題があるため、以下の3つの方法で動かすことができます?。本記事では修正したコードを使って説明します(slackの記事では自前で作ってます)。

  • 修正したソースコードを使う
    • yarn add https://github.com/YutamaKotaro/react-intersection-observer#build こちらで以下のソースコードを使えるようになっています(プルリクを送っているのでマージされれば記事を更新します)
  • 自前でInterSectionObserverAPIをつかった機能を作成する
    • 作る際に必要な機能としては再度発火させない処理になります。react-intersection-observerではこの機能が実装されています。
  • display: 'contents'を諦める
    • かなりしんどいと思っています・・・。?

コードは下記のようになります。

import React, {createContext, ReactNode, useContext, useEffect, useRef} from 'react';
import {sendLog} from './sendlog';
import {useInView} from 'react-intersection-observer';

export const LogContext = createContext({});

interface Props {
  children: ReactNode;
  component?: string;
  section?: string;
}
type Context = Omit<Props, 'children'>

export default function Log(props: Props) {
  const {children, ...directProps} = props;
  const logContext = useContext<Context>(LogContext);
  const {ref,inView} = useInView({
    child: true,
    triggerOnce: true,
  });
  useEffect(() => {
    if (inView) {
      sendLog({...directProps, ...logContext});
    }
  },[inView]);

  return (
    <LogContext.Provider value={{...directProps, ...logContext}}>
      <div style={{ display: 'contents' }} ref={ref}>
        {children}
      </div>

    </LogContext.Provider>
  );
}

一部抜粋してコードを説明します。

  const {ref,inView} = useInView({
    child: true,
    triggerOnce: true,
  });
  useEffect(() => {
    if (inView) {
      sendLog({...directProps, ...logContext});
    }
  },[inView]);

childはプルリクを送っているオプションになります(自前の場合はこの機能は不要になります)。これを入れることによって判定を行う要素を子要素にすることができます。style={{ display: 'contents' }}これがあると永遠に発火しないためこのような処置が必要になります。

triggerOnceは一度のみ発火する機能になります(自前の場合この機能か、このような機能をLogComponentに入れる必要があります)。

また、

<div style={{ display: 'contents' }} ref={ref}>

こうしているのには理由があって、IntersectionObserverAPIで判定を行うためにはrefが必要になるんですが、そのために何らかのタグを挟んでしまうとレイアウトに影響を及ぼす危険性が高いため、display: 'contents'をあてることによって干渉を防いでいます。

まとめ

以上で完了ですー!!
これらによってvisibleなイベントいい感じにロギングできるようになりました!!?

<Log page="order">
    <ItemSection> アイテムに関する情報
        <Log section="item">
            ....
            <Log component="price">
                <Price />アイテムの値段
            </Log>
        </Log>
    </ItemSection>
    <PrecautionsSection> アイテムの注意事項
        <Log section="precaution">
        ・・・
           <Log component="price">
              <Price small /> 送料
           </Log>
        </Log>
    </PrecautionsSection>
    ...
</Log>

そして、こんな感じの階層構造から『どこの何をみて離脱したのか?』といったことをとっていけるようになりました。しかも、各コンポーネントへの実装をなしに。階層構造でログをとるため、同じ名前のLogをとっても問題ありません。

といった感じにかなり良かった記事なんですがいったんここまでになります!!
次の記事がでたら各種イベントハンドリングについて参考に(めっちゃ)しつつまとめていきたいと思います。

追伸:RNだとどうするのか?はちょっと悩みどころです。IntersectionObserverの変わりはあれどdisplay: 'contents'の代わりになりそうなものがなく、ViewとTextの問題があり、ここまで気軽につかえなさそうな気がしています。何かいい案があれば・・・!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MemoizationによるReactのパフォーマンスチューニング

この記事はQiita Advent Calendar2020 React #212/6の記事になります。

今回はmemoizationによるReactのレンダリングパフォーマンスの改善について書いていこうと思います。

memoizationとは?

memoization(メモ化)は関数の呼び出しと実行結果を保持しておくことで処理の高速化を図る手法です。中にはmemoizationではなくて、memo r izationじゃないの?と思う人がいると思います(自分も途中まで間違えていました)。暗記の意味を持った英語としてはmemo r izationで正しいですが、技術用語としてはmemo r izationを元として作られた造語であるmemoizationが使用されます。

実測

Javascriptにはmemoization用のライブラリがいくつか存在します。
汎用ライブラリのlodashもこの機能を備えています。
ReactにもそれぞれReact.memo()React.useMemo()が存在しています。しかし、これらは最新のpropsと前回のpropsのみを比較しているため複数結果の保持はできません。
なので今回は複数保持できる下記のライブラリを使用しました。

moize

複数あるmemoizationライブラリの中でも今回はパフォーマンスに優れているmoizeを使用します。
パフォーマンスに加えReactのコンポーネントに対応しているという点でも優れています。

では、実際にメモ化をした場合としなかった場合の違いを測っていきたいと思います。処理の重さの違いを確かめるためにこのような関数を用意します。

function calc(num1, num2) {
  let result = 1;
  for (let i = 0; i < num1; i++) {
    result *= num2;
  }
  return result;
}

moize()の引数に関数を指定することでメモ化の対象とします。

const moizedCalc = moize(calc);

この関数を100回実行した際の合計時間を測ります。

for (let i = 0; i < 100; i++) {
  moizedCalc(100, 3);
}

結果

実行にかかった時間はhrtimeを使用して5回測定する形で行いました。

メモ化なし (ms) メモ化あり (ms)
0.4223 0.1842
0.2003 0.1263
0.1791 0.1104
0.2257 0.1349
0.3768 0.1243

この程度の重さの関数であればメモ化をした際に、メモ化をしていないものと比べ大きく処理速度の差が出ています。

次は同じ関数の引数を1に変えて試してみます。

for (let i = 0; i < 100; i++) {
  moizedCalc(1, 1);
}

結果

メモ化なし (ms) メモ化あり (ms)
0.0213 0.1784
0.0247 0.1890
0.0215 0.1134
0.0254 0.1973
0.0493 0.1737

ライブラリを挟むことによるメモ化や結果の引き出しによって処理が発生してしまうため、逆にこのような処理の軽い場合ではメモ化の対象としたほうが時間がかかってしまいます。

また極端に引数が多い場合も比較する要素が増え、かえって処理に時間がかかってしまうため注意です。

コンポーネントのメモ化

色々前置きがありましたが、本題のReactのコンポーネントにおけるメモ化について紹介していこうと思います。
moize.react()もしくはisReactオプションを使用することでReactのコンポーネントをメモ化することができます。

今回はtextnum×numで表示するコンポーネントを用意しました。

export const TextRenderer = ({ text, num }) => {
  return (
    <table>
      {[...Array(num)].map(() => {
        return (
          <td>
            {[...Array(num)].map(() => {
              return <tr>{text}</tr>
            })}
          </td>
        );
      })}
    </table>
  );
};

export const MemorizedTextRenderer = moize.react(TextRenderer);

今回は比較のためにメモ化の対象とする前のコンポーネントもexportしています。

このコンポーネントを↓の要領でボタンを1回押すと1秒おきに["a", "b", "c", "d", "e", "f"]の順に表示するように実装しました。

  const texts = ["a", "b", "c", "d", "e", "f"];
  const [text, setText] = useState("");

  const sleep = (ms) => {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  const start = async () => {
    for (let t of texts) {
      setText(t);
      await sleep(1000);
    }
  }

  return (
    <div className="App">
      <button onClick={start}>start</button>
      <TestRender text={text}></TestRender>
      <MemorizedTestRender text={text}></MemorizedTestRender>
    </div>
  );

moizeはコンポーネントに渡されたpropsを元にキャッシュを引き出しています。

レンダリングにかかった時間はデベロッパーツールのProfilerタブを使って調べていきます。
なお、測定に影響を及ぼさないようにReact DevTools以外の拡張機能は無効化しておきます。

結果

上がメモ化なし、下がメモ化ありの結果です。
メモ化ありの方が明らかにレンダリングにかかる時間が短くなっていることがわかります。

メモ化なし
a (ms) b (ms) c (ms) d (ms) e (ms) f (ms)
1.1 0.7 2.0 2.5 2.6 2.5
1.1 1.1 3.0 2.1 2.0 1.7
メモ化あり
a (ms) b (ms) c (ms) d (ms) e (ms) f (ms)
1.5 1.4 0.7 2.2 1.4 1.4
0.1 0.2 0.2 0.2 0.1 0.1

注意点

便利な機能ですが実行結果を保持しているということは、パターンや、対象となる関数が増えるにつれてメモリを消費してしまうということになります。Reactコンポーネントも同様です。測ってないので具体的な数値までわかりませんが、、、

防ぐためには↓のようにオプションで保持しておく数に制限をかけておく必要があります。

moize(calc, { maxSize: 5 });
moize.react(TextRenderer, { maxSize: 5 });

こうすることによって直近5回までの結果を保持しておくように制限をかけることができます。

おわり

今回使用したコンポーネントは親となる要素をメモ化していましたが、例えば複数コンポーネントで使用される子コンポーネントをメモ化することで効果的に活用できると思います。
便利ですが、メモリの使用量や呼び出される頻度や処理の重さなど様々な要素を考慮して使用する必要がありそうですね。

react: https://github.com/facebook/react
moize: https://github.com/planttheidea/moize

リポジトリ: https://github.com/mugi111/memoization-react-demo

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactで表現するUIモーションデザイン【デュレーション & イージング】

近年UIの表現方法の進化によって、単純にUIのレイアウトや配色・大きさが整っていてわかりやすいだけでなく、モーション(アニメーション・マイクロインタラクション)によるユーザーサポートがUX向上に大きな影響を与えるようになってきました。

Webやアプリは単に「閲覧する」だけでなく「使うための道具」として、より物理的に存在する道具と同等に高度な機能を求められているということだと思います。

その一方でUIモーションはユーザーのUXを著しく害する要因にもなっており、「不用意にモーション(アニメーション)はアプリに組み込むべきではない」とApple - Human Interface Guidelinesに記載があるとともに、GoogleのMaterial Designでもベストプラクティスについての記述が事細かにドキュメント化されています。

Apple - Human Interface Guidelines
Google - Material Design Understanding motion

つまり、UIにモーションの付与するということは毒にも薬にもなる諸刃の刃になるといったところでしょうか。
その用法を知らずに乱用してはUXを向上されるどころか、逆効果になってしまいます。
そのため、「モーションデザイン」についての知見を身につける必要があります。

今回は、モーションデザインを学ぶ一環として、モーションをコントロールするための基礎ともいえる、「デュレーション」「イージング」について、Reactのパッケージreact-springd3-easeを活用しながらどういった手法があるのかまとめてみました。

そもそも 「デュレーション」 ・ 「イージング」 とは?

簡単な説明を行います。
詳しくはGoogleのMaterial DesignのSpeedを読んで頂いたほうが深く理解できると思います。

https://material.io/design/motion/speed.html#controlling-speed

デュレーション

モーションの「継続時間」のことです。
すべてのモーションに対して同一のデュレーションを設定するのではなく、ユーザーの行ったアクションにより時間を変更することにより、よりスムーズな使用感を得ることができます。
(例えば、ページを「開く」動作よりページを「閉じる」動作を高速にするなどが該当します。)

イージング

モーションの「変化の緩急」のことです。
直線的な動きは機械を連想させ、ユーザーに不自然な違和感を感じさせてしまいます。
自然界には「重力」「摩擦」「空気抵抗」などが存在し、動きの変化は常に一定になることはありません。
適切にイージングを使い、より自然界の動きに近い動作の緩急を再現することでより自然な使用感を実現できます。

PlayGround

codesandboxにて簡単な位置移動のイージングを試せるデモページを用意しました。

configのdurationeasing(イージング手法については後述します)を変更すると速度・速度の変化を体感することができると思います。
モーションはreact-springの一番基礎的なhooks APIであるuseSpringを使っています。
useSpringだけでも多彩なモーションを実現することが可能です。)

また、後述するイージングデモではx軸方向にしか位置変動させていませんが、
y軸・z軸・スケールを変動させてみると面白いモーションを実現することができます。

デモページ

https://codesandbox.io/s/easing-96y0h?file=/src/App.tsx

コード

App.tsx
import React, { FC } from "react";
import { useSpring, animated } from "react-spring";
import * as easings from "d3-ease";

const App: FC = () => {
  const { xyzs } = useSpring({
    from: { xyzs: [0, 100, 0, 1] },
    xyzs: [300, 100, 0, 1],
    loop: true,
    // configでパラメータを変化させることで動作が変化する
    config: {
      duration: 2000,
      easing: easings.easeLinear
    }
  });

  return (
    <animated.svg
      style={{
        transform: xyzs.to(
          (x, y, z, s) => `translate3d(${x}px, ${y}px, ${z}px) scale(${s})`
        )
      }}
    >
      <circle cx="100" cy="50" r="40" fill="black" />
    </animated.svg>
  );
};

export default App;

react-spring公式ドキュメント

https://www.react-spring.io/

イージング手法

もちろん手動でイージング数値を指定しても良いのですが、react-springd3-ease組み合わせて使うと、予め用意された関数から深く考えることなくイージング手法を試すことができます。

https://www.npmjs.com/package/d3-ease

イージンググラフによる変化量と実際の動きを対比させながら、手法を検証していきます。
(イージンググラフは https://observablehq.com/@d3/easing からの引用です。)

easeLinear(t)

イージングなし。

277f1813d934ba736de90ed291ade5cc.gif

easeLinear.png

easeQuadIn(t)

ezgif.com-gif-maker.gif

easeQuadIn.png

easeQuadOut(t)

VayUVlaUpkUHiYv8YBxM1607212254-1607212461.gif

easeQuadOut.png

easeQuadInOut(t)

iYkv0kPEurToOW3dBXrf1607212684-1607212756.gif

easeQuadInOut(t) .png

easeExpIn(t)

EkhJGJnsNOZj2IH8UPZj1607213008-1607213149.gif

easeExpIn.png

easeExpOut(t)

P2ulK5hk40qtvfNpU1dn1607213402-1607213458.gif

easeExpOut.png

easeExpInOut(t)

IxXEILIrppYcWHU5jW981607213817-1607213873.gif

easeExpInOut.png

easeCircleIn(t)

HCxSGld6PxQnUHVNMQHl1607214043-1607214096.gif

easeCircleIn.png

easeCircleOut(t)

WWkbZQG6NdldlTu0s9Z11607214323-1607214432.gif

easeCircleOut.png

easeCircleInOut(t)

0aQw3HEbz74Iws5L18U11607214557-1607214557.gif

easeCircleInOut.png

easeBackIn.overshoot(2)(t)

wiHXMu4aEWeNvpwhIv2c1607214802-1607214863.gif

easeBackIn.overshoot(2.01).png

easeBackOut.overshoot(2)(t)

a9KMPk1mIN34oxCHUbXd1607215017-1607215082.gif

easeBackOut.overshoot.png

easeBackInOut.overshoot(2)(t)

6UzJxckB0R18evnqVXrd1607215219-1607215292.gif

easeBackInOut.overshoot.png

easeBounceIn(t)

48MhdBeVWqcuohxIOqAh1607215438-1607215546.gif

easeBounceIn.png

easeBounceOut(t)

1j6V7mQFfldNoXrlGptC1607215693-1607215751.gif

easeBounceOut.png

easeBounceInOut(t)

1i02mB81yc3yOf2M68FB1607215892-1607215947.gif

easeBounceInOut.png

モーションデザイン参考サイト

デモページを見ているだけでも様々なインスピレーションが得られるサイトです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】Hooks(関数コンポーネント)の挙動を理解する回

Hooks の挙動をきちんと説明できる自信がない...

みなさん、 useEffect とか useState とか、なんとなく使っていませんか?

stateの更新後に値が反映されるタイミングやuseEffect が再実行されるタイミングを具体的に説明できますか?

自分はこの辺があやふやだったので、調べたり検証したりしてみました。

関数コンポーネントのライフサイクル

関数コンポーネント+Hooksのライフサイクル? は、こちらの サイトに載っている図を見ていただけたらなんとなく理解できると思います。

クラスコンポーネント がわかる人は比較しながら見てみるとよりわかりやすそうです。

コンポーネントの更新と再レンダリングの挙動

※ この記事ではブラウザに描画することをレンダリング、コンポーネントの関数を再実行することをコンポーネントの更新と表現します

コンポーネントの更新が発生するタイミングは、useStateuseReducerによってstateが変化した時とreact-domReactDOM.renderの実行時です。コンポーネント更新時にreturnの仮想DOMの差分が発生した場合に再レンダリングが発生します。

例えば、こんな感じのシンプルなカウンターがあったとすると、

App.jsx
// 記事内ではimport, exportを省略します
function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setInterval(() => setCount((c) => c + 1), 100);
  }, []);
  return (
    <div>
      count: {count}
      <div>count x20: {count * 20}</div>
    </div>
  );
}

Chromeデベロッパーツールで確認するとこうなります。(紫色の部分が再レンダリングが発生している部分)

ezgif-2-2dd3829f9241.gif

これは具体的にどういう挙動かというと、

  1. setInterval 内の setCount が実行される(stateが更新されたので、この時点でコンポーネントの更新が確定する)
  2. setInterval のコールバック関数が終了したタイミングで App コンポーネント のupdateが開始される(App関数が再実行される)
  3. useState部分のcount が新しい値に置き換わる
  4. return でJSXが確定する
  5. JSXの差分をとって、ブラウザーに再レンダー

となっています。実際に処理を追ってみると、かなり分かりやすいと思います。

なので、このようにコンポーネントの更新がかかる前にstateを使ってしまうと思わぬバグを生む可能性があるので注意しましょう。

App.jsx
function App() {
  const [count, setCount] = useState(0);
  const [backupCount, setBackupCount] = useState(count);
  return (
    <div
      onClick={() => {
        setCount((c) => c + 1);
        setBackupCount(count); // まだcountの更新が反映されていない... バグの原因
        console.log(count, backupCount); // 差が生じる
      }}
    >
      count: {count}
    </div>
  );
}

同じ値をセットしたはずが...となります。

スクリーンショット 2020-12-06 7.07.54.png

次は試しに表示部分に不変的な値のみを設定してみるとどうでしょう。

App.jsx
function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setInterval(() => setCount((c) => c + 1), 100);
  }, []);
  return <div>{"不変的な値"}</div>;
}

当然、再レンダリングは行われません。以下、前者がJSXにstateを組み込んだ例と、後者がJSXに不変的な値のみを組み込んだ例です。(Chromeのデベロッパーツールより)

JSXにstateを組み込んだ例
スクリーンショット 2020-12-06 2.55.30.png

JSXに不変的な値のみを組み込んだ例
スクリーンショット 2020-12-06 2.56.14.png

どちらも コンポーネント のupdateは発生していますが、JSXの差分が無いとRenderingが省略されることがわかりました。

コンポーネントの更新時の子コンポーネントの挙動

コンポーネントが更新された際は原則、子のコンポーネントも全て更新が走ります。propsの変化とか関係なしに、無条件で実行されます。

以下は、子コンポーネントが更新されている例です。

App.jsx
function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setInterval(() => setCount((c) => c + 1), 1000);
  }, []);
  return (
    <>
      {/* Appコンポーネント更新時に、全て更新されます。 */}
      <ChildComponent number={1} />
      <ChildComponent number={2} />
      <ChildComponent number={3} />
    </>
  );
}
ChildComponent.jsx
const ChildComponent = ({ number }) => {
  console.log(`Child ${number}`);
  return <div>Component {number}</div>;
};

スクリーンショット 2020-12-06 6.19.48.png

propsが変化している訳でもないのにしっかり更新されているようですが、これは正しい挙動です。ただ、アンマウントされない限りはマウント処理が実行されることはありません。

試しにマウントとアンマウントと繰り返すような動きを見てみましょう。上記の処理を少し書き換えます。

App.jsx
<ChildComponent number={1} />
{count % 2 === 0 && <ChildComponent number={2} />}
<ChildComponent number={3} />
ChildComponent.jsx
const ChildComponent = ({ number }) => {
  useEffect(() => {
    console.log(`Child ${number} Mounted`);
  }, []);
  return <div>Component {number}</div>;
};

期待した通り、アンマウントされました。

ezgif-7-9c929174a9d7.gif

パフォーマンス上、子コンポーネントの更新すら許したくないシーン(例えば高頻度で更新処理が発生するなど)においては、React.memo, useCallback たちの出番です。(今回はやりません)

useEffectの挙動

まず、useEffectの基本動作からおさらいします。

コンポーネント 内で定義された useEffect はいずれもマウント直後のレンダリング後に実行されます。useEffect(func)useEffect(func, [])useEffect(func, [deps, ...]) も、この動作は変わりません。

  1. useEffect(func) は、コンポーネント更新後に毎度実行されます。
  2. useEffect(func, [deps, ...]) は、コンポーネント更新後にdeps(依存変数)の値が更新前と異なっていた場合に実行されます。
  3. useEffect(func, []) は マウント直後以降は呼び出されることなく、アンマウント時にfuncの戻り値(クリーンアップ関数)が実行され、さよならします。2のdepsが指定されないので依存変数が更新されることがなく呼び出されないイメージです。

では、こちらのプログラムはどういう挙動になるでしょうか。(真似しないでください)

App.jsx
function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setCount((c) => c + 1);
  }, [count]);
  return <div>{count}</div>;
}

当然、CPUの許す限りループし続けます。

ezgif-6-bb31337bd79b.gif

これはどういう挙動かというと、

  1. マウント時にuseEffectの第一引数が実行
  2. stateが変更される
  3. stateが変更されたので、コンポーネントが更新される(再実行)
  4. コンポーネントの更新かつ countが変更されているので、またuseEffectの第一引数が実行される
  5. 以下無限ループ

一見すると思わぬところで起こしてしまいそうですが、普通に実装していたらなかなか発生しないので安心してください。挙動を理解するためにあえて扱ってみただけです。

では、こちらはどうでしょうか。

おわりに

あまり時間をかけれなかったので、甘い部分があるかもしれません。ご指摘お願いします。

余裕があったら useRef, useMemo, useCallback もやるかもしれません。(useMemouseCallbackは今回の内容と重複しそうですが)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】Hooks(関数コンポーネント)の挙動を検証しながら確認する回

Hooks の挙動を具体的に説明できる自信がない...

みなさん、 useEffect とか useState とか、なんとなく使っていませんか?

stateの更新後に値が反映されるタイミングやuseEffect が再実行されるタイミングを具体的に説明できますか?

自分はこの辺があやふやだったので、調べたり検証したりしてみました。

関数コンポーネントのライフサイクル

関数コンポーネント+Hooksのライフサイクル は、こちらの サイトに載っている図を見ていただけたらなんとなく理解できると思います。

コンポーネントにはざっくり分けてマウントと更新とアンマウントのライフサイクルがあって、それぞれのタイミングで処理が実行されているってことがわかりますね。

更新やアンマウントが走るタイミングなどについては後ほど触れます。

再レンダリングとDOMの更新の挙動

※ この記事ではコンポーネントの関数が実行されるタイミングを「レンダリング」、レンダリング後にDOMの差分のみをブラウザに反映させることを「DOMの更新」と表現します。

まずReactのレンダリングの基本について確認しておきます。

コンポーネントの再レンダリングが発生するタイミングは、useStateuseReducerによってstateが変化した時とreact-domReactDOM.renderの実行時です。コンポーネント更新時にreturnの仮想DOMの差分が発生した場合にDOMの更新が発生します。

例えば、こんな感じのシンプルなカウンターがあったとすると、

App.jsx
// 記事内ではimport, exportを省略します
function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setInterval(() => setCount((c) => c + 1), 100);
  }, []);
  return (
    <div>
      count: {count}
      <div>count x20: {count * 20}</div>
    </div>
  );
}

Chromeデベロッパーツールで確認するとこうなります。(紫色の部分がDOM更新が発生している部分)

ezgif-2-2dd3829f9241.gif

これは具体的にどういう挙動かというと、

  1. setInterval 内の setCount が実行される(stateが更新されたので、この時点でコンポーネントの再レンダリングが確定する)
  2. setInterval のコールバック関数が終了したタイミングで App コンポーネントの再レンダリングが開始される(App関数が再実行される)
  3. useState部分のcount が新しい値に置き換わる
  4. return でJSXが確定する
  5. JSXの差分をとって、ブラウザにDOMが反映される

となっています。実際に処理を追ってみると、かなり分かりやすいと思います。

なので、このようにコンポーネントの再レンダーがかかる前にstateを使ってしまうと思わぬバグを生む可能性があるので注意しましょう。

App.jsx
function App() {
  const [count, setCount] = useState(0);
  const [backupCount, setBackupCount] = useState(count);
  return (
    <div
      onClick={() => {
        setCount((c) => c + 1);
        setBackupCount(count); // まだcountの更新が反映されていない... バグの原因
        console.log(count, backupCount); // 差が生じる
      }}
    >
      count: {count}
      <br />
      backupCount: {backupCount}
    </div>
  );
}

同じ値をセットしたはずが...となります。

スクリーンショット 2020-12-06 7.12.04.png

次は、レンダリングは走り続けるが表示部が変化しない場合をみてみましょう。

App.jsx
function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setInterval(() => setCount((c) => c + 1), 100);
  }, []);
  return <div>{"固定値"}</div>;
}

当然、DOMは更新されません。以下、JSXにstateを組み込んだ例と、JSXに固定値のみを組み込んだ例です。(Chromeのデベロッパーツールより)

JSXにstateを組み込んだ例(<div>{state}</div>
スクリーンショット 2020-12-06 2.55.30.png

JSXに固定値のみを組み込んだ例(<div>{"固定値"}</div>)
スクリーンショット 2020-12-06 2.56.14.png

どちらも コンポーネント の再レンダリングは発生していますが、DOMの差分が無いとDOMの更新が省略されることがわかりました。仮想DOM頼もしい!

再レンダリング時の子コンポーネントの挙動

再レンダリングされた際は、原則、子のコンポーネントも全て再レンダーが走ります。propsの変化とか関係なしに、無条件で実行されます。

以下は、全ての子コンポーネントが再レンダリングされている例です。

App.jsx
function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setInterval(() => setCount((c) => c + 1), 1000);
  }, []);
  return (
    <>
      {/* Appコンポーネント更新時に、全て更新されます。 */}
      <ChildComponent number={1} />
      <ChildComponent number={2} />
      <ChildComponent number={3} />
    </>
  );
}
ChildComponent.jsx
const ChildComponent = ({ number }) => {
  console.log(`Child ${number}`);
  return <div>Component {number}</div>;
};

スクリーンショット 2020-12-06 6.19.48.png

propsが変化している訳でもないのにしっかり再レンダーされているようですが、これは正しい挙動です。ただ、アンマウントされない限りはマウント処理が再度実行されることはありません。

挙動を確認するためにマウントとアンマウントと繰り返すような動きを見てみましょう。上記の処理を少し書き換えます。

App.jsx
<ChildComponent number={1} />
{count % 2 === 0 && <ChildComponent number={2} />}
<ChildComponent number={3} />
ChildComponent.jsx
const ChildComponent = ({ number }) => {
  useEffect(() => {
    console.log(`Child ${number} Mounted`);
  }, []);
  return <div>Component {number}</div>;
};

当然ですが、アンマウントされたコンポーネントだけがマウント処理を実行することがわかります。

ezgif-7-9c929174a9d7.gif

パフォーマンス上、子コンポーネントのレンダリングを省略したいシーン(例えば高頻度で更新処理が発生するなど)においては、React.memo, useCallback たちの出番です。(今回はやりません)

useEffectの挙動...?

まず、useEffectの基本動作からおさらいします。

コンポーネント 内で定義された useEffect はいずれもマウント直後のレンダリング後に実行され、
アンマウント時にクリーンアップ関数を実行します。useEffect(func)useEffect(func, [])useEffect(func, [deps, ...]) も、この動作は変わりません。

  1. useEffect(func) は、コンポーネント更新後に毎度実行されます。
  2. useEffect(func, [deps, ...]) は、コンポーネント更新後にdeps(依存変数)の値が更新前と異なっていた場合に実行されます。
  3. useEffect(func, []) は マウント直後以降は呼び出されることなく、さよならします。2のdepsが指定されないので依存変数が更新されることがなく呼び出されないイメージです。

では、こちらのプログラムはどういう挙動になるでしょうか。(真似しないでください)

App.jsx
function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setCount((c) => c + 1);
  }, [count]);
  return <div>{count}</div>;
}

当然、CPUの許す限りループし続けます。

ezgif-6-bb31337bd79b.gif

これはどういう挙動かというと、

  1. マウント時にuseEffectの第一引数が実行
  2. stateが変更される
  3. stateが変更されたので、コンポーネントが再レンダリングされる(再実行)
  4. countが変更されているので、再びuseEffectの第一引数が実行される
  5. 以下無限ループ

一見すると思わぬところで起こしてしまいそうですが、普通に実装していたらなかなか発生しないので安心してください。挙動を理解するためにあえて扱ってみただけです。

おわりに

あまり時間をかけれなかったので、甘い部分があるかもしれません。ご指摘お願いします。

余裕があったら useRef, useMemo, useCallback もやるかもしれません。(useMemouseCallbackは今回の内容と重複しそうですが)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

国家公務員へ届け!行政文書管理についての提案

この記事は「個人アプリ/サービス開発の進め方と運用、得た学び 【PR】 Lenovo Advent Calendar 2020」の6日目の記事です。

先週に臨時国会が終了し、大変な部署もそうでない部署もお疲れ様でした。今年の中心的な話題は、経済にも大きな影響を与えた新型コロナウィルスへの対策についてで、公務員にもハンコ廃止とデジタル化の改革流れが来ています。

耳鳴りがするほど文書管理について様々な話があったかと思いますが、今一度、従来とは違うアプローチでこれを考えてみました。

書類はあふれるような数やり取りがなされ、文書に一つ一つつけている背表紙を内容とあっているかどうか確認するような人力管理は、限界があり全体のコラボレーションをとる必要があります。
今のトレンドに乗って、タグを使って書類の管理するそんなシステムを実現するモックモデルを作ってみました。

SingaPile

QRコードを用いて文書やフォルダなどの関係性をまとめて、管理対象がどこに含まれてるか確認します。

システムには内容を含めず、書類の関係性だけを記載して、書類の秘匿性に制限を受けず、一律に取り扱いをできるようにして包括的な文書管理システムを目指して考えました。

scrsht.png

Trunkと名前の付けた入れ物に対して、
https://singapile.web.app/scan?p=A7A00F5201DE01AAFF055B5BA037E17D
入っているものを管理しています。
https://singapile.web.app/scan?p=C8AC2224BDD8619296257AFF2F74AD8B
各要素は入れ子にできるようにしています。
以降に、作るまでに考えていたことを下記にまとめていきます。

ソースコードは、GitHubに挙げています。

 要件定義

作ると決めたのなら何が必要か理解しなければいけません。
街を見渡せば、QRコードを読み取って在庫の確認をしているようなソフトはよく出回っています。みんなそんなソフトを見かけて、職場に導入できないかなと考えて文書監査の時などによく話に上がるのでしょう。ユニクロアプリではバーコードをスキャンすれば在庫やレビューがチェックできるそうです。似たようなソフトは、多く出ており制作会社を見つけるのはそれほど難しそうではなさそうですが、ほしいものは自分たちで決める必要があり、ドメイン知識も重要になります。

ユースケース

ほしいものを考えるには、まずは夢を膨らませます。実務上起こるようなケースを理想的に行うためのそんなソフトを考えます。

一般管理

e-Govポータルサイト https://www.e-gov.go.jp をみると文書管理がどのように行われているかわかります。

適当なものをいくつか見てみると、いつ取得したか、分類、文書管理者などが書かれています。
媒体の種別に紙とあるものは、背表紙をつけて紙の保存を行っていますが、保存するだけならまだしも、出したり入れたりしていたらどこから抜いたのか管理が大変になります。利用しないのなら保存する意味はないため、このようなケースは多いと思います。

そこで書類のステータスを確認するにはQRコード(ID)をスキャンし、どこで管理されて書類かわかるようにします

singapileAbstruct

保存期間を過ぎているかどうか確認し、過ぎているものは一律に廃棄します。データベースと実物の情報が常に一致することで、作業の間違いがなくなります。

8

複製

オリジナルと複製は別な管理を行いたいです。複製は配ったものを好きに廃棄していいですが、オリジナルはずっと取っておきたいです。管理者がいくつ複製したのかを明確にして、もらった人は捨てていいのかどうかIDを確認することで処置の必要性がわかります。
6

統合や分割

受け取った書類にはいろいろな情報が含まれています。対外的に提出する情報と中だけで使う情報に分割して管理したい場合があります。

作った情報を2つの情報を1つの子要素として管理することで元の文書の文書管理に合わせて子要素の管理を決めることができます。

9

より複雑な処理

担当がわからないがEメールを受け取り、内容の修正や質問への更問など膨大な量の書類が来ている部署にあなたがいるとき、担当を明確にしたいので一斉に連絡を行い、かつ、担当割が終わったら投げた先に処理を一任したいはずです。

受付のためEメールにIDを付加します。IDを仕分けの仕掛かりの状態に紐づけ、そのIDの受領完了させます。(担当割がスムーズに進んだとして)担当は新たに別のQRコード(ID)を付加します。

5

同じIDで処理すれば一律に管理できそうで、なんだか無駄なIDのように見えます。が、もちろん意味があります。文書管理者を明確にするための作業です。

あなたは担当を決めた時点で仕事終了です。内容をもう知る必要がなく、担当につなぐだけです。しかし担当はそうではありません。もらった内容から作業を行う必要があります。間違っても消されたりしたら困ります。そのため、必要な内容は自分で管理できるようにIDを付加するようにしています。

担当者以外は、もらった情報をどのようにすればいいのでしょうか。検討の段階で複製しているかもしれません。書類のステータスを確認するにはQRコードをみて保存期間を過ぎているかどうか確認し、過ぎているものは一律に廃棄します。

連続してチェインを作ることで、カンバン管理のようなことも処理できるようにします。

7

その他の付帯的な処置

こんな機能とかもあるとうれしいです。

  • QRコードが破損した場合を考え、印刷するコードの下にはデータの内容を人間が読み取れるようにする
  • フローを決定して、その後の処置を表示して、現在のステータスを出せるようにする
  • 管理対象について、CSVで出力をする
  • ユーザ、グループごとの管理
  • 同じものに対して違うQRコードを与えたときの処置

 規律と予算

業務システムで面倒な縛りとなるのが規律と予算です。絶対に破れない規律が法律です。対外的に後ろ暗いものがあるものは絶対にうまくいきません。ほかにも、ローカルルールもありますし、細かく細分化した規則やその解釈のブレがあり、すべてに適用できるものを作るのはむずかしそうですのですので、使用する部署に合わせてシステムを作りこむのがよさそうです。予算については悩みどころです。システムをどこかにホストする必要があるため、無視できない部分です。

必要なドメイン知識

システムを考えるうえで、使い方のほかにも使う側の事情を頭に入れておく必要があります。

  • 必要なものは最初にそろっているわけではなく、文書は減ったり増えたりする
  • 複数の文書にまたがり起案された文章は、複数に影響を受け、複数にまたがる影響を与える n-nのペアが存在する
  • 作成、複製、経由、受領、譲渡、破棄、分割、これらが複数が同時におこる
  • 管理していない文章を引用することがある
  • 必要な時になくてはならないし、期間を過ぎてあってもいけない
  • 当初の予定から、保存期間が変わることがある
  • 文書が前後の文脈なしに、細切れに引用されることがある
  • 無限に複製が起こる
  • 起案と完成時で別に管理を行いたい
  • 受付とは違う経由をしているということを残したい
  • 管理者の移管をどのように管理するか
  • ネットワークが、内部と外部で分かれており、外部に接続されたネットワークでは扱えない文書がある

法律については、公文書制度の概要から、満たすべき規律の取っ掛かりとします。
https://www8.cao.go.jp/chosei/koubun/about/gaiyou/point.pdf

「公文書等の管理に関する法律」の元に内閣府が方針を決め、各省庁より細かい、もしくは、区分を決めて書類を管理しています。

セキュリティに関しては個人情報の保護に関する法律、行政機関(独立行政法人等)の保有する情報の公開に関する法律、安全保障にかかわる行政機関では、日米相互防衛援助協定等に伴う秘密保護法、特定秘密の保護に関する法律などを守る必要があります。
具体的な内容については、柔軟な運用を行うため各省庁の文書管理で細部を決めており、ここらへんはまとまったものがないので、状況に合わせる必要があります。

考えられる問題

検証中に起こりそうな問題も考えておくほうが手戻りが少なくなりそうです。

  • 複製した文書には、どこに送付したかのかわかるようにQRコードが付されるが、そのあとにそれを再利用されると登録に重複が起こる
  • 登録された文書かどうか、見た目だけではわからない
  • 経由された文書など履歴を利用するが、履歴にも公開の範囲が必要
  • 一時的に作成される文書がたくさんある場合、登録が難しく、管理外の文書が生まれる
  • 外部ネットワークのファイル登録をいたずらに増やされることがある

問題の解決策でざっと考えられるものを並べます。

  • 重複したものをスキャンすることで、移動があったことを検出する
  • 対象文書をスキャンされたとき、どこに含まれているか表示する
  • 文書をもらうときには、コードが付されていることを確認する(運用回避)
  • ユーザ登録性にする。デバイスごとのID,IPなどで分けてもよい
  • 文書間の関係性のみを扱うことで、内容に関係なく一律な運用を行うことができる

ほかの要望の追加事項がありそうなものには、ブラックライトのようなものでしか見えないインクでIDを印刷をしたいなどの要望があるかもしれません。

管理動作について

いろいろ上げてきました。やることが多すぎで、やる気がそがれます。複数の部署で利用するものだとすると方向性の合意は取れそうにありません。
一気に完成形にするのは難しく、小さな試行版を動かしながら、ブラシュアップするのがベストのように思います。

システムは、直感的にわかる使いやすいものがいいです。文書を作成するときにはコンピュータを使うので、管理するときもファイルシステムをイメージできると使いやすいと考えられます。

キーワードをつけることができればファイル検索のようにできそうですが、キーワードは中身に大きく関係するため、実装は慎重に行う必要があります。内容をシステムに含めないことであらゆる文書の関係性を一つのシステムで扱うことができるようにすることを意図しているため、ここはどこにホストする場合でも注意しなければいけません。。

これらから、必要な内容はOSファイル操作を見習って作ってく方針とします。

必要なケースをソフトウェア動作に当てはめる

必要な操作はファイル操作のコマンドと突き合わせて内容を考えます。

作成 mkdir, touch
未保存ファイルを保存する。QRコードを印刷した時点では管理対象にならずシステムへ登録して管理対象になる。登録に対しては保存先が必ず設定され、どこにも参照されない関係性の切れた書類ができないようにする。

複製 cp, dd
文書を複製したときに新規ではなく、複製文書であることを明確にする。

移動 mv
ルートディレクトリからのパスを変える。子要素もあわせて移動する。廃棄とにたような動作になりそう。

権限 chown, chmod
文書管理者の変更、経由などがこの動作になりそう。受付先と必要としている先が異なることを解決させる操作であり、いろいろ複雑にできるが、ユーザの操作が煩雑になるため実装する操作は要検討

破棄 rm, rmdir

  • 消費 QRコードを使えないようにする。追記することを禁止し、すべての操作の起点にすること禁止する。
  • 完全消去 QRコードを再利用できるようにする 未使用と再利用の使い分けがある リレーションが完全に壊れるため実装はよく考える
  • 不良 QRコードが無効であることを示す
  • リフレッシュ QRコードを古いものから新しいものに変える

分割 split
一つの文書を複数の文書に分割する操作

参照 ln OSファイルシステムの実装
保管は別なところで行うが、関係があるところから参照できるようにする

merge, ls, find, history, pwd, sort このあたりも必要になりそうです。

技術選定

文書管理のために動き回るのであれば、スマートフォンで動くQRコードを利用するアプリがよさそうです。デバイスはセキュリティ上、専用のものを用意できればいいですが試行版では予算面で難しそうです。スマホは普及率が高いですし、持っている人のみで使用感を確かめていくことにします。

行政文書管理はe-Govで公開しているため、内容さえ含まれていなければいけそうな気もします(未確認)。業務に関する一切を私用デバイスで扱っていけないとなると、お手上げ状態ですが何とかなるものとして考えます。

管理対象には、内容を含まないようにしたいので起点はすべて現物についているIDになります。使い方はWebサイトにアクセスして、QRコードを撮影して、あれこれするようにします。
実装に関する方法は何でもいいので以下のようにしました。

  • QRコード
    • 普及率の高さから採用 コードは何でもいいが、運用後の変更は難しい。
  • React-qr-reader cozmo/jsQRのラッパー
    • MediaDeviceを利用してカメラから画像を取得する
    • httpsである必要がある
  • React
    • 有名で使える人が多い
  • Firebase
    • AWSのサービスは永久無料がないのでこれに決定

Reactを使うにあたって、以下のライブラリも利用させていただきました?

  • react-hook-form
  • react-router

QRコードの内容

Googleレンズなどは、URLを読み込むことでアクセスすることができるため、URL+クエリをQRコードに書き入れます。これによってソフトを立ち上げなくても、URLの読み込み動作から、ソフトがクエリ部分のIDを読み込み、管理の動作につなげられるようにします。
生成したQRコードはシールに印刷して、管理したい文書にぺたぺた張り付けていくことにします。

IDに含める情報は、つける対象ではなく印刷時や固有の情報を与える必要があります。いろいろ考えましたが、時間のSHA1ハッシュをつけて試作しました。衝突しなければなんでも良しにしています。
これは、印刷、保管、貼り付けまでの間に時間があり、複雑なことをしようとするといろいろな種類のコードを管理する必要があるためです。以下のような情報を考えましたが、今回は保留です。

  • ソートできる情報
    • 一か所で一連の番号を作ったとしても実際に使用されるまでに時間差があり、連番に使われると限らないのであまり意味ないかも
  • 所属
    • ほかで使われると管理対象と所属がぐちゃぐちゃになるため、あまりあてにしてシステムを作ることができない。また、可変の内容がQRコードに含まれるのがよろしくない
    • キーワードなどもここに含める
  • 時間
    • 時刻は登録時に使用することが確定されるため、IDにはないほうがいい。使用時の印刷するようなシステムでもあれば有効
  • 大区分、中区分、小区分などの分類
    • 変更されるうえ、シールの種類が多くなりすぎるためこれはおそらく管理できない
  • 作成者のID
    • 一括で検察できるが、移動などによって管理者が変わる場合に対応できない。
  • 電子上のファイルパス
    • 長くなりすぎるうえ、内容がQRコードに含まれるのがよろしくない。
  • 初期動作フロー
    • そのあとの動作を行うにあたってずっと残ってしまい、一意なIDを割り当てるのがむずかしい。
    • 同じ動作をさせるのであれば、ソフトウェアの動作の一つとしていれるのが好ましい。

マイルストン

とりあえず重要そうなところからとりかかりました。

  • 一元管理システムからデータを引っ張ってきて、ユーザの使用感を試す
    • とりあえず登録できる機能をつくりました。Trunkと呼び大区分、中区分、小区分を書き入れられる
  • QRコードをかざし、URLを読み取り、どこに保存されているか表示する。作成、移動ボタンを表示
    • 保存されていない場合には、作成ボタンを表示
    • すでに保管されているものについては、移動を表示
    • QRコードのドメインは運用後変更できず、その後ずっと保管する必要があるため慎重に決定したほうがいい。
  • 10~1000程度のスケールで動作できるもの
  • 機能を限定
    • 作成と移動だけあればいい
  • 画面には、カメラ画像、読み取り内容、作成OR移動ボタンを表示
    • 使っていくうちに不便だったので、リセットとカメラ切り替えを付けました。

政府共通プラットフォーム

実装するにあたって、ホストするためのプラットフォームを決める必要があります。

現存する情報システムがどこで動いているかを真似すれば、実現できそうです。
各省庁、各独立行政法人、各部署で独自にインフラを整えていますが、トレンドは集約化に向いておりそこに逆らわないほうが話が進みやすいかと思います。
ここで、そんなインフラの集約を目的とした政府共通プラットフォームの利用を考えてみます。
政府共通プラットフォームとは、2009年に掲げた霞が関クラウドの基盤システムで、いろいろあって管理業務の事業者にNEC、日立システムズが入り、Amazon Web Services(AWS)上で運用することになりました。以前よりこういったシステムは、富士通とMicrosoftをよく見かけていたので結果には驚きです。
https://www.soumu.go.jp/main_sosiki/gyoukan/kanri/a_01-03.html

政府共通プラットフォームの整備は、第1期と第2期と段階を踏んで計画されており、2020年10月から第2期の運用が開始しました。共通のプラットフォームで運用することで全体のコスト低減など様々な問題を解決することを目的にしています。

公開している資料からは、どのようなものを運用できるのか規定は見つかりませんでした。中で務めている人であれば、問い合わせるのが近道でしょう。

ここでは利用できそうかどうかを公開資料から見てみます。
https://www.soumu.go.jp/main_content/000709984.pdf
移⾏対象システム

  • 政府が直接保有・管理する必要があると考えられる情報システムを移行(パブリック・クラウドとの棲み分け)
  • 移行に当たっては、投資対効果の検証を徹底

国費投入の必要性については、総務省管轄の文書管理システムがまさに政府共通プラットフォームで動作しているため、改善として利用するということで説明が立ちそうです。費用効果については、独自調達と政府共通プラットフォームにした時の差額を示せそうです。

考慮しなければいけない事項としては、以下のようなものを考えられます。

  • 業務内容に文書管理を総括する部署でない場合、所掌範囲外の業務を提案として上げなければいけない
  • 似たようなシステムが乱立するような状況は避けれるなら避けたい
  • 試行版を作ってから内容を詰めるような使い方ができるのか

ほかにもいろいろと出てくると思いますが、一番の問題は利用にあたっては費用負担が必要になることです。つまり予算です。

システムに必要な予算をちゃんととってくるのであれば2年前の概算要求から始まります。政府共通プラットフォームの負担分の予算取りを考えるのであれば、3年前から構想が必要になります。
https://cio.go.jp/guide/manual_t/manual_1/index.html#s1_2
ここをみれば、プロジェクトの標準的な活動スケジュールで運用開始の4年前から線表が引いてあります。

大企業に勤めている人であればわかると思いますが、総合職、一般職を問わず、頻繁に配置換えがあり、4年後に同じ仕事をしている可能性は低いです。必要な時に簡単にホストすることができるような環境にならない限り、立ち上げにかかわったシステムを拝むことはできないでしょう。

現時点で公開されている解決策はなさそうです?
せっかく基盤システムを持たずに資産を必要な時に利用できるような仕組みにしているのですから、予算面で柔軟になれば恩恵を受ける人はかなり多いはずです。

さいごに

この記事では技術的な話より、何を作るかわからない状態でどうやって作るものをまとめるかに重きを置いて記載してきました。ドメインに関する内容は、各官庁でバラバラなこともあり、抽象的な部分も多くなってしまいました。

いろいろな意見を聞いていると訳の分からない方向に進むプロジェクトをよく見かけます。
コンピュータを使った一律な仕組みを作ってしまえば、教育のコストは低く運用できます。検証環境としてパパっと作ってしまい、筋が悪ければ別な方法にピポットしてしまえばいいと思います。

政府共通プラットフォームは、ミスが許されないようなシステムは慎重に作り、もがきながら作っていくような試行版のシステムは軽く運用できる構想が当初より計画されています。厳しくするところは明確にして、利用できそうなころから広げていく、そんな業務の進め方ができればよいと思いこの記事を書きました。

文書の管理に関して思うところとして、行政文書の管理の在り方等に関する閣僚会議発の「公文書管理の適正の確保のための取組について 平成30年7月20日」では財務省、防衛省が名指しで再発防止の重視が示されており、他の官庁は巻き添えを食ったかのように思っているのかもしれません。

しかし、実際動いている担当にしてみれば、対岸の火事でなくどこにでも起こる問題と思っているはずです。今の仕組みは人間の運用能力を超えるものになっていると言い切っていいです。管理の仕組みの中に人間が入り込んでおり、運用で問題を回避するような建付けが、この構造をつくりだしていると考えています。

退職まで逃げ切り決め込んでいる職員には、積極的な改善は期待できないでしょう。入ったばかりの新人にとって今までの負の遺産は、上の世代から降ってきたのもので、この問題を背負わせるのは酷かと思います。

どこかで断ち切るのだとすれば、今問題を抱えている職員です。システムは後発有利に開発できるため、今から取り掛かる場合でも後れを取ることはありません。
この記事が改革のトレンドの後押しになればと思います。

面白いと思ったらLGTMボタンを押して、シェアしてね?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フロントエンド開発の作業環境を整備して開発効率を爆上げする

モダンなフロントエンド開発向けに、作業環境をサクッと整備して、ガンガン開発効率をあげましょう。

VSCode + TypeScript + React の構成を想定しています。

Visual Studio Code

https://code.visualstudio.com/

圧倒的人気1を誇るテキストエディタ。略称 VSCode 。

  • スピード(起動/実行速度)良し
  • コスト(オープンソース/無料)良し
  • 品質(強力なコード補完、見やすいUI 等々)良し

で三拍子揃ってるので言うことなし。

ESLint

https://eslint.org/

JavaScript (EcmaScript) 用リンター。

GitHub Star 数 17.5k 。リントツールとして定番中の定番。

VSCode 拡張機能は VSCode Marketplace から ESLint で検索してインスール。

Prettier

https://prettier.io/

コードフォーマッタ。

HTML 、CSS 、 JavaScript を始めとして様々なプログラミング言語をサポート。
VSCode はもちろん Vim や Emacs 、Sublime Text といったテキストエディタもサポート。

VSCode 拡張機能は VSCode Marketplace から Prettier で検索してインスール。

Node.js

https://nodejs.org/en/

JavaScript 実行環境。

サーバサイド JavaScript とも言われる。
インストール時に同梱されている npm(パッケージマネージャ)がもはやフロントエンド開発において必須になっている。npx も便利。

TypeScript

https://www.typescriptlang.org/

JavaScript 拡張言語。

静的型付け可能な JavaScript と言われる。みんな大好き、型。

React

https://reactjs.org/

UI 構築用の JavaScript ライブラリ。

Vue.js や Angular とともにフロントエンドフレームワークとして人気。
世界ではその他 2 つよりも圧倒的な人気2がある。

みんな React やろう!

最後に

VSCode のターミナルから以下コマンドを実行して、早速開発を始めましょう!
my-app は任意の名前を設定してください。

$ npx create-react-app [my-app] --template typescript

  1. The State of JavaScript 2019 の Other Tools における Text Editors において Which text editor(s) do you regularly use? で 56% 以上を取得 

  2. npm trends では React が Vue.js や Angular のダウンロード数を圧倒している。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む