20210505のReactに関する記事は28件です。

React/Material-UIでDrawer&Routingを実装

./src/components/ui/Header.js import React, { useState } from "react"; import { AppBar, Divider, Drawer, IconButton, List, ListItem, ListItemText, Toolbar, Typography, } from "@material-ui/core"; import MenuIcon from "@material-ui/icons/Menu"; import ChevronLeftIcon from "@material-ui/icons/ChevronLeft"; import MapIcon from "@material-ui/icons/Map"; import TimelineIcon from "@material-ui/icons/Timeline"; import ChatIcon from "@material-ui/icons/Chat"; const Header = () => { const [open, setOpen] = useState(false); const handleDrawerOpen = () => { setOpen(true); }; const handleDrawerClose = () => { setOpen(false); }; return ( <div> <AppBar> <Toolbar> <IconButton onClick={handleDrawerOpen}> <MenuIcon /> </IconButton> <Typography>Header</Typography> </Toolbar> </AppBar> <Drawer open={open}> <IconButton onClick={handleDrawerClose}> <ChevronLeftIcon /> </IconButton> <Divider /> <List> <ListItem button="AAAAA"> <MapIcon /> <ListItemText primary="AAAAA" /> </ListItem> <ListItem button="BBBBB"> <TimelineIcon /> <ListItemText primary="BBBBB" /> </ListItem> <ListItem button="CCCCC"> <ChatIcon /> <ListItemText primary="CCCCC" /> </ListItem> </List> </Drawer> </div> ); }; export default Header; ./src/App.js import logo from "./logo.svg"; import "./App.css"; import Header from "./components/ui/Header"; import { BrowserRouter, Route, Switch } from "react-router-dom"; function App() { return ( <div className="App"> <BrowserRouter> <Header /> <Switch> <Route exact path="/" component={() => <div>/</div>} /> <Route exact path="/AAAAA" component={() => <div>AAAAA</div>} /> <Route exact path="/BBBBB" component={() => <div>BBBBB</div>} /> <Route exact path="/CCCCC" component={() => <div>CCCCC</div>} /> </Switch> </BrowserRouter> </div> ); } export default App;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Typescript環境でReactを使ったWebアプリを作ろう

事前準備 開発に入る前に以下が入っていない場合は先にインストールをしてください Node.jsがインストールされている確認 Node.jsのバージョン確認 node -v yarnがインストールされているか確認 yarnのバージョン確認 yarn -v プロジェクトの作成 プロジェクト名:react_practice プロジェクトの作成 npx create-react-app react_practice --template typescript 確認 作成したアプリケーションのディレクトリに移動し、下記実行 確認 yarn start ブラウザが立ち上がり、Reactのロゴマークがクルクル回っていれば成功 初期化 不要なファイルを削除し、整理します。 またそれに伴いそれらを使っていたところでエラーが発生するのでそれを修正します。 削除する一覧 以下のファイルごと削除します。 publicフォルダ favicon.ico logo192.png logo512.png manifest.json robots.txt srcフォルダ App.test.tsx logo.svg react-app-env.d.ts reportWebVitals.ts setupTests.ts 削除する箇所 ファイルを以下のように書き換えます App.tsx App.tsx import React from 'react'; import './App.css'; export const App = () => { return ( <div>リセット済</div> ); } index.tsx index.tsx import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import {App} from './App'; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); ここでもう一度正常に動作するかどう確認してください。 正常に表示ができればOKです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TailwindCSSってナニ?

はじめに 今回は最近注目度が高いTailwindCSSについて、詳しく解説していきたいと思います! TailwindCSSってナニ? TailwindCSSとは、Adam Wathan氏らによって開発されている、ユーティリティファーストなCSSフレームワークです。 (Tailwindを直訳すると追い風という意味です。) CSSフレームワークといえばBootstrapを思い浮かべる人が多いと思いますが、Bootstrapとの違いは「コンポーネント」が用意されていないことです。 Bootstrapは「Button」などのコンポーネントが用意されていて、それを使っていくというCSSフレームワークですが TailwindCSSの場合はclassの中にstyleを指定していくという形になります。 特に、React, Vue.jsなどスタイルをコンポーネント化して画面を構築するのにあってる気がします。 Reactでの使用例を見てみましょう。 index.jsx <div className=""> <h2 className="">HELLO WORLD</h2> </div> なにもCSSを指定していない空っぽの状態です。 TailwindCSSの場合は、classNameにCSSを指定していきます。 index.jsx <div className="flex justify-center items-center py-48 bg-gray-400"> <h2 className="text-3xl text-yellow-400">HELLO WORLD</h2>  </div> 簡単に解説すると、 flexはdisplay:flex py-48はpaddingの設定 bg-gray-400は背景色の設定 text-3xlはfont-sizeの設定 text-yellow-400は文字の色の設定 こんな感じで、classNameにCSSを指定するだけでstyleが変更できます。 チートシートがあるので、そちらを見ながらコーディングすることをおすすめします。 環境構築 Next.jsでの環境構築 公式ページ通りにやっていきます。 1.create-next-app npx create-next-app my-project cd my-project npm run dev npm run start 2.TailwindCSSをダウンロードします。 npm install -D tailwindcss@latest postcss@latest autoprefixer@latest tailwind.config.js と postcss.config.jsを作成 npx tailwindcss init -p tailwind.config.jsをこのように書き換えてください // tailwind.config.js module.exports = { purge: ["./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"], darkMode: false, // or 'media' or 'class' theme: { extend: {}, }, variants: { extend: {}, }, plugins: [], }; app.jsに以下のコードを書き足してください。 import 'tailwindcss/tailwind.css' TailwindCSSを使うメリット 1.学習コストが低い TailwindCSSのCSSの指定はCSSを触ったことがある人であれば簡単に覚える事が出来ます。 例: .p-64 = padding: 16rem; .font-bold = font-weight: 700; 初心者でも扱いやすく、簡単に実装出来ます。 2.CSSファイルが必要ない。 classに指定するだけなので、CSSファイルは必要ありません。 3.class名を考えなくてもよい CSS設計において、一番大変なのはclass名を決めることだと思います。 TailwindCSSの場合は、class名を決めることがなくなるので悩む時間が減ります。 4.便利なテンプレートがたくさんある。 TailwindCSSはたくさんのテンプレートが配布されてあります。 おしゃれなHeadernavやButtonなどが簡単に実装出来るのは、使っていて楽しいですね。 おすすめ記事 TailwindCSSを使うデメリット 1.多く書いて行くとclass名が多くなる。 TailwindCSSを書いていくとclass名がたくさんになり、コードが見にくくなってしまいます。 index.jsx <a href="#" class="relative flex flex-row items-center h-11 focus:outline-none hover:bg-gray-700 text-gray-500 hover:text-gray-200 border-l-4 border-transparent hover:border-blue-500 pr-6"> 一応@applyという機能を使って、Sassのmixinみたいなことも出来ます。 2.class名を見つけるのが大変な時もある。 class名は大体は分かりやすいのですが、分かりにくいものもあります。 例: items-center = align-items: center; rounded-md = border-radius: 0.375rem; grid-cols-1 = grid-template-columns: repeat(1, minmax(0, 1fr)); VScodeならTailwind CSS IntelliSenseという便利なスニペット拡張機能があるので、使うことを強くおすすめします。 TailwindCSSを使うにあたっておすすめなサイト 1.Tailwindチートシート 必須です。 慣れてくるまではチートシートを眺めながら、コーディングしていく事をおすすめします。 2.公式コンポーネント Tailwindが出している公式のコンポーネントがあります。 有料の物が多いですが、無料のやつもあるのでおすすめです。 3.ユーザーが作ったコンポーネント ユーザーがTailwindCSSで作ったコンポーネントを見ることが出来ます。 4.Gridジェネレーター TailwindのGridを分かりやすく表示してくれます。 慣れてない人にはおすすめです。 最後に 今回はTailwindCSSについて解説しました! 使っていて楽しく、そして何より使いやすいので自分は積極的に使っていこうと思っています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS CloudFront FunctionsでSPAのルーティング処理

緊急事態宣言で暇を持て余していた2021年のGWに、AWSからナイスな機能がリリースされました。 その名も「CloudFront Functions」。CloudFrontでクライアントからのアクセス時にJavaScriptで書かれた処理をかますことができる機能です。 今回は、このCloudFront Functionsを使って、SPA特有の「すべてのアクセスをindex.htmlに向ける」処理を書いてみましたのでご紹介します。 CloudFront Functionsとは? https://aws.amazon.com/jp/about-aws/whats-new/2021/05/cloudfront-functions/ https://aws.amazon.com/jp/blogs/aws/introducing-cloudfront-functions-run-your-code-at-the-edge-with-low-latency-at-any-scale/ 詳しくは以上のAWS公式サイトをご覧いただければと思いますが、要約すると「CloudFrontがリクエストをOriginに転送する前に、1ms以下の実行時間の比較的軽い処理を挟むことができる」という機能です。 同じような機能としてすでにLambda@Edgeが存在しますが、こちらはより軽量版といったところでしょうか。 実行時間が最大1msとかなり厳しく、できる処理はURL・ヘッダーの書き換えだったりとか、リダイレクトとかに限られますが、その代わり高速にレスポンスが返せます。 料金は、100万リクエスト当たり$0.10とのことです。Lambda@Edgeよりも安いですね。 index.htmlへのルーティングをCloudFront Functionsで実装してみる React/Vue/Angular等で作られたシングルページアプリケーション(SPA)は、ルーティングの処理をブラウザで実行されるJSで行うため、サーバー側でルーティングを行う必要がありません。すべての(静的ファイルへのリクエストを除く)リクエストは、index.htmlに向けられる必要があります。でないと、/hogeのようなサブページへのルーティングが404になってしまいます。 下準備 まず、/hogeというサブページを持つAngularアプリケーションを用意しました。 私がAngularの使い手というだけなので、これはVueでもReactでも何でもいいと思います。 用意したSPAアプリケーションを、CloudFront+S3の形でアップロードして公開します。 ここまでで、<ドメイン>/直下、つまりTOPページは正しく表示されます。 しかし、アドレスバーに直接<ドメイン>/hogeと入れて、サブページを表示しようとすると、Access Deniedのエラーが返ってきます。 これは、S3がhogeという名前のファイルを返そうとしているためです。そのようなファイル実体は存在しないので、S3は404を返し、こちらに書かれているようにCloudFrontは403を返します。 なので、/hogeのようなファイル実体が存在しないパスにルーティングしようとした際は、JSでルーティング処理を行わせるために、index.htmlを返す必要があるというわけです。 ということで、ここでCloudFront Functionsの出番です。 CloudFront Functionsの設定 CloudFrontの左側メニューの中に「Functions」が追加されていますので、クリックします。 「Create Function」をクリックします。 適切な名称を入力します。 手順としては4段階、ステージは2つに分かれています。 まず「Build」。コードを書いて保存すると、「Development」ステージに反映されます。この段階では本番反映ではありません。 var indexPage = "index.html" function handler(event) { console.log(event); var request = event.request; var currentUri = request.uri; var doReplace = request.method === "GET" && currentUri.indexOf(".") === -1; if(doReplace) { var indexPath = `/${indexPage}`; request.uri = indexPath; } return request; } 以上のコードを入力して「Save」してください。 やっていることは、リクエストメソッドがGETかつ、URIにドットが入っていない(=拡張子がない)場合、index.htmlを返すように変更するというものです。 console.log()を含めていますが、ここで取れたログはus-east1リージョンのCloudWatch Logsに保存されます。 何やら懐かしいスタイルのJSコードを書いていますが、今のところES5相当の文法にしか対応していないようです。なんでやねん。 次に「Test」フェーズで、保存されたコードをテストできます。 今回は「Viewer Request」のフェーズで処理を挟みますので、「Event Type」をそのように選択します。 HTTPメソッド、URI、クエリパラメータ、リクエストヘッダー、Cookie、クライアントIPなどを変更できるようです。 「Compute utilization」という値は、最大実行時間の1msを100とした時の、実際の実行時間の値だそうです。最大でも70~80ぐらいに抑えておいたほうがよさそう。 このように、InputのURIに/hogeを入力して、OutputのURIが/index.htmlになっていればOKです。 次に「Publish」をして、この段階で「Live」ステージにもコードがデプロイされます。 最後に「Associate」で、このFunctionを紐づけたいCloudFrontディストリビューションを設定します。 先ほども書いたように、「Viewer Request」のフェーズで処理を挟むので、「Event Type」は「Viewer Request」を選んでください。 ディストリビューションの選択時、自動補完が出てきますが、ディストリビューションIDしか出てこないので、事前に紐づけたいディストリビューションのIDをメモっておいたほうがいいです。 設定が完了すると、/hogeをアドレスバーに直入力しても、正常にサブページが表示されるようになります。 CloudFront Functionsで出来ないこと CloudFront Functionsは、Lambda@Edgeで出来ることをもっとそぎ落として軽量高速化した機能であるため、今のところ以下のようなことはできません。 コード内で別のAWSリソースへのアクセスはできない 最大実行時間が1msを超えるような重い処理はできない 「Origin Request」「Origin Response」イベントタイプの処理はできない このような処理を行いたい場合は、Lambda@Edgeを採用するとよいでしょう。 まとめ 従来、SPAのindex.htmlへのルーティング処理をCloudFrontで行う場合、「Custom Error Response」でオリジンが403を返した時に、ドメイン直下(index.html)を返すように設定するというような、若干ハッキーな手法が多く用いられてきました。その後、Lambda@Edgeが出てきて一応解消できるようにはなりましたが、デプロイまでが結構複雑なので二の足を踏んでいた方も多いと思います。今回のCloudFront Functionsの登場で、よりシンプルに同様の処理を実装できるようになりました。ありがとうAWSさん。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【TypeScript】VSCodeでTypeScriptの開発環境を構築する③(React)

概要 Webpackを使用したTypeScriptの開発環境に、Reactを導入します。 通常のライブラリとは違い、Reactはフレームワークに近いので設定をいじる必要があります。 プロジェクトは、以下で作成したものをベースに使用します。 【TypeScript】VSCodeでTypeScriptの開発環境を構築する②(webpack ver.5) ※ こちらを利用する場合は、READMEを読んでください。 この記事は、以下のコースの受講に伴って、備忘録として書いています。詳しく知りたい方は、是非受講してみてください。 環境 Windows10 TypeScript 4.2.4 webpack 5.36.2 React 17.0.2 VSCode Chrome 構築 パッケージのインストール VSCodeでプロジェクトを開き、ターミナルからReactをインストールします。 npm install --save react react-dom npm install --save-dev @types/react @types/react-dom React は JavaScript で書かれたライブラリで、型定義ファイルを含んでいません。 なので、TypeScript で利用する場合は、型定義ファイルもインストールする必要があります。なお、これは開発時にのみ必要なファイルなので、--save-devとしています。 設定ファイルの変更 tsconfig.json tsファイルをjsファイルにコンパイルするための設定 tsconfig.json { "compilerOptions": { /* Basic Options */ "target": "ES6", "module": "ES2015", "sourceMap": true, "outDir": "./dist", // "rootDir": "./src", "removeComments": true, "noEmitOnError": true, /* Strict Type-Checking Options */ "strict": true, /* Additional Checks */ "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, /* Module Resolution Options */ "esModuleInterop": true, /* Experimental Options */ "experimentalDecorators": true, /* Advanced Options */ "skipLibCheck": true, "forceConsistentCasingInFileNames": true, /* React Options */ "jsx": "react", // ★ 追加 } } webpack.config.js webpackを使用してビルド(コンパイル&バンドル)するための設定 開発時用のファイル webpack.config.js const path = require('path'); module.exports = { mode: 'development', entry: './src/app.tsx', // ★ 変更 output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, devServer: { publicPath: '/dist/' }, devtool: 'inline-source-map', module: { rules: [ { test: /\.tsx$/, // ★ 変更 use: 'ts-loader', exclude: /node_modules/ } ] }, resolve: { extensions: ['.ts', '.js', '.tsx'] // ★ 変更 }, target: ['web', 'es5'] // ★ 追加。IE11の設定。Chromeで使用する場合はいらない }; webpack.config.prod.js webpackを使用してビルド(コンパイル&バンドル)するための設定 本番用(ビルド用)のファイル webpack.config.prod.js const path = require('path'); const CleanPlugin = require('clean-webpack-plugin'); module.exports = { mode: 'production', entry: './src/app.tsx', // ★ 変更 output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, devtool: false, module: { rules: [ { test: /\.tsx$/, // ★ 変更 use: 'ts-loader', exclude: /node_modules/ } ] }, resolve: { extensions: ['.ts', '.js', '.tsx'] // ★ 変更 }, target: ['web', 'es5'], // ★ 追加 plugins: [new CleanPlugin.CleanWebpackPlugin()] }; スクリプトの変更&追加 index.html を変更し、srcフォルダ内をすべて削除します。 index.html <div id="app"></div>を追加しています。 index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <script type="module" src="dist/bundle.js" defer></script> <title>Document</title> </head> <body> <div id="app"></div> </body> </html> App.tsx src/App.tsx を追加します。(コンポーネントなのでファイル名は大文字) App.tsx import React, { useState } from 'react'; import ReactDOM from 'react-dom'; import InputForm from './components/InputForm'; const App: React.FC = () => { const [value, setValue] = useState<string>(''); const titleUpdateHandler = (text: string) => { setValue(text); }; return ( <div> <h1>Hello, React! {value}</h1> <InputForm onSubmitInput={titleUpdateHandler} /> </div> ); }; ReactDOM.render(<App />, document.querySelector('#app')); InoutForm.tsx src/components/InputForm.tsx を追加します。(コンポーネントなのでファイル名は大文字) InputForm.tsx import React, { useRef } from 'react'; import { InputFormProps } from './component.model'; const InputForm: React.FC<InputFormProps> = props => { const textInputRef = useRef<HTMLInputElement>(null); const onSubmitHandler = (event: React.FormEvent) => { event.preventDefault(); // どっちでも取れる const enteredText = textInputRef.current!.value; // const enteredText = (document.getElementById('user-input')! as HTMLInputElement).value; props.onSubmitInput(enteredText); }; return ( <form onSubmit={onSubmitHandler}> <input type="text" id="user-input" ref={textInputRef} /> <button type="submit">送信</button> </form> ); }; component.model.tsx src/components/component.model.tsx を追加します。(型定義モジュールなのでファイル名は小文字) component.model.tsx export type InputFormProps = { onSubmitInput: (text: string) => void; }; ビルド&デバッグ ビルド(本番用ファイルの生成)、デバッグに関しては、【TypeScript】VSCodeでTypeScriptの開発環境を構築する②(webpack ver.5)と同様の操作です。 確認 最終的なプロジェクトの構成は、以下のようになります。 distフォルダに、コンパイルされバンドルされたbundle.jsが作成されました。 Tips webpackを使用しない場合の環境構築 webpackを使用しない場合は、以下の操作で環境を構築できます。 1)VSCodeで空のプロジェクトを開き、ターミナルで以下を実行します。 npx create-react-app . --template typescript 2)1の方法だと、うまくいかないケースがあるようです。 その場合は、一度グローバルにcreate-react-appを入れます。そのあと、空のプロジェクトにcreate-react-appを作成します。 npm install -g create-react-app 空のプロジェクトに対して create-react-app . --template typescript 参考 【世界で7万人が受講】Understanding TypeScript - 2020年最新版 webpack+TypeScript+Reactの最小構成
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.js+FirebaseのM1Mac開発環境用の dockerイメージを作ってみた

M1MacでNode.js14+Firebase環境を構築する時にそのまま使えそうなdockerイメージが無かったので、dockerイメージを作成して公開用リポジトリにプッシュしてみた。 dockerイメージ取得方法 dockerをインストール後に以下のコマンドを実行する。 docker pull kazuki0819kazuki/node14-firebase-git
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[ネタ]復活の呪文を解析してみたよ

ゴールデンウィーク(2021-05)に、お勉強を兼ねて React + TypeScript で「ふっかつのじゅもん」の作成ページをGithub Pages 上に作りました(1の方です)。 じつは大昔に、復活の呪文の解析をした日記/作成できる CGI を公開していたことがあります。 (現在は、サイトが消滅して Internet Archive の中だけに存在する) その時のプログラム(+α)をもとに実装しました。 ついでなので、大昔に書いた「復活の呪文の解析日記」を少しリライトしたものも置いておきます。 ちなみに、ROMの解析などはしておらず、普通に遊んでいる範囲の情報をあつめて帰納的に推測したものです。 プログラムの不具合の究明や何かの調査をする場合に、仮説検証を繰り返して解き明かすことはエンジニアであればだれでも多少は行うことがあると思うので、何かの参考にはなるかと思います(と、かなり強引に正当化してみた)。 日記に興味ない人は、ぐぐっと下にスクロールすると最後のあたりに復活の呪文の作成ページ(とソース)へのリンクがあります。 復活の呪文解析日記 【1997/06/08】呪文の解析 ふと思い立って、復活の呪文の解析を始めました。 とりあず、名前は「あ   」で始めて、何も持っていない状態で、復活の呪文を聞いてみました。 同じ状態でも何種類かの復活の呪文を聞くことが出来るのですが、実際にやってみると8種類までは聞くことが出来ました。 おさべつに はほわげげだど べうきさそ さには かこぶちな のへろぐぐぶい かこせつに つへむ けせいなの へごべううつに はほめよれ よごぜ こちおねふ みずいかかすち なのへむゆ むわげ ゆるへたと ねふれぎぎぜづ びあおけす けとね よわみてぬ ひまがごごぼえ くしたとね とまも るげやぬひ まじあおおとね ふみやりわ りじだ れぐもには ほざぼええさそ てぬひまも まれぎ 数字もキリがよいので、これで全部だろうと判断し、解析を始めます。 まず気が付くのは、どの呪文も9文字目と 10 文字目が同じ文字だということです。 それに、 ある文字とその次の文字が4文字だけ離れている場合が多いということでしょうか。 それもある呪文で4文字だけ離れていると、他の呪文でも同じ場所は4文字だけ離れていることが多い。 同様に7文字離れていると、他の呪文の同じ場所でも7文字離れていることがほとんどのようです。 どうやら、文字と文字の差分にこそ情報が隠れているようですね。 しかし、ここで問題が一つ。 差分を抜き出すには文字コードが分からないといけないのですが、この場合はどうなっているんでしょう? まあ、差分から逆に求めればなんとかなりそうな気がするんですが。 【1997/07/08】その2 名前は「あ   」で、持ち物はたいまつを一つだけの状態で呪文を聞いています。 おさべざぞ でぶしたたまも らろぐじだ じせつ かこぶごぜ づびさそそには はめよれぎ れおけ けせいぜづ びちまももれぎ ざぞでぶい ぶのへ こちおぢば ぼとめよよぜづ びあおけす けやり ゆるへげず ぢばこせせへむ ゆるがごぜ ごした よわみじだ どべすちちねふ みやりわげ わきさ るげやだど べてむゆゆわげ ずぢばぼえ ぼひま れぐもぞで ぶつみややじだ どべうきさ きめよ 【1997/07/09】コード表(勘) 復活の呪文で使われている 64 文字のコード表は、おそらく次の表のような感じでしょうか(呪文の入力画面からの推測)。 +0 +1 +2 +3 +4 +00 あ い う え お +05 か き く け こ +10 さ し す せ そ +15 た ち つ て と +20 な に ぬ ね の +25 は ひ ふ へ ほ +30 ま み む め も +35 や ゆ よ +38 ら り る れ ろ +43 わ +44 が ぎ ぐ げ ご +49 ざ じ ず ぜ ぞ +54 だ ぢ づ で ど +59 ば び ぶ べ ぼ この仮定をもとに、前回・前々回の復活の呪文の差分(例: #2の差分 = #2のコード - #1のコード )を取ってみると……。 まずは、名前「あ   」アイテム無し 文字位置 #1 #2 #3 #4 #5 - #6 #7 #8 #9 #10 #11 #12 - #13 #14 #15 #16 #17 - #18 #19 #20 文字 け せ い な の - へ ご べ う う つ に - は ほ め よ れ - よ ご ぜ 文字コード(10進数) 08 13 01 20 24 - 28 48 62 02 02 17 21 - 25 29 36 37 41 - 37 48 52 1つ前の文字との差 -- 05 52 19 04 - 04 20 14 04 00 15 04 - 04 04 04 04 04 - 60 11 04 文字の差(8進数) 10 05 64 23 04 - 04 24 16 04 00 17 04 - 04 04 04 04 04 - 74 13 04 となりました。 最後の行は8進数です(文字は64種類、つまり 6bit なので、8進数の方がビット単位で見やすいと思ったため)。 最初のコード(#1の位置)は、1文字目のコードをそのまま書いてあります。 残りのものも含めて、「文字の差(8進数)」の個所だけ表にしてみます。 まずは、アイテム無しのもの。 名前「あ   」アイテム無し(文字の差(8進数))。 文字位置 #1 #2 #3 #4 #5 - #6 #7 #8 #9 #10 #11 #12 - #13 #14 #15 #16 #17 - #18 #19 #20 1つ目 04 06 64 23 04 - 04 04 16 04 00 07 04 - 04 04 04 04 04 - 74 13 04 2つ目 05 04 64 23 04 - 04 04 16 04 00 17 04 - 04 04 04 04 04 - 74 13 04 3つ目 10 05 64 23 04 - 04 24 16 04 00 17 04 - 04 04 04 04 04 - 74 13 04 4つ目 11 07 64 23 04 - 04 24 16 04 00 07 04 - 04 04 04 04 04 - 74 13 04 5つ目 44 04 64 63 04 - 04 04 16 04 00 07 04 - 04 04 04 04 04 - 74 13 04 6つ目 45 06 64 63 04 - 04 04 16 04 00 17 04 - 04 04 04 04 04 - 74 13 04 7つ目 50 07 64 63 04 - 04 24 16 04 00 17 04 - 04 04 04 04 04 - 74 13 04 8つ目 51 05 64 63 04 - 04 24 16 04 00 07 04 - 04 04 04 04 04 - 74 13 04 次は、名前「あ   」たいまつ1つだけ(文字の差(8進数))。 文字位置 #1 #2 #3 #4 #5 - #6 #7 #8 #9 #10 #11 #12 - #13 #14 #15 #16 #17 - #18 #19 #20 1つ目 04 06 64 63 04 - 04 04 16 04 00 17 04 - 04 04 04 04 04 - 74 33 04 2つ目 05 04 64 63 04 - 04 04 16 04 00 07 04 - 04 04 04 04 04 - 74 33 04 3つ目 10 05 64 63 04 - 04 24 16 04 00 07 04 - 04 04 04 04 04 - 74 33 04 4つ目 11 07 64 63 04 - 04 24 16 04 00 17 04 - 04 04 04 04 04 - 74 33 04 5つ目 44 04 64 23 04 - 04 04 16 04 00 17 04 - 04 04 04 04 04 - 74 33 04 6つ目 45 06 64 23 04 - 04 04 16 04 00 07 04 - 04 04 04 04 04 - 74 33 04 7つ目 50 07 64 23 04 - 04 24 16 04 00 07 04 - 04 04 04 04 04 - 74 33 04 8つ目 51 05 64 23 04 - 04 24 16 04 00 17 04 - 04 04 04 04 04 - 74 33 04 大きな違いは、最後から2番目のコード(#19)。 このあたりに1番目のアイテムのデータが入っているのかな? 【1997/07/10】勇者「ああああ」 復活の呪文の文字と、名前の文字は別のコードですよね。 勝手な想像ですけど、名前のコードは「あ」が 0 で あ行~ま行・やゆよ・ら行・わをん・濁点・半濁点・ っゃゅょ-・数字の0~9・空白の順に並び、 最後の空白が 63 ( 10 進数 ) の様な気がします。 まあ、何の根拠もないんですけどね。 というわけで、名前「ああああ」アイテム何もなし。 なひべおけ すちまもそひま もらろぐじ きした にはぶえく したほめせつに はほめよれ ぶうき のほいくし たやざぞめよれ ぎざぞでぶ つぬひ はむおさそ てらぜづゆごぜ づびあおけ へめよ ぜづへうき さそへむすのへ むゆるがご おこせ ぞばみかこ せつみやたとね ふみやりわ ぼおけ づぼやこせ つよずぢやりわ げずぢばぼ とのへ でべもけす ちゆじだもぐじ だどべうき ひみや そして、その差分。名前「ああああ」アイテムなし(文字の差(8進数))。 文字位置 #1 #2 #3 #4 #5 - #6 #7 #8 #9 #10 #11 #12 - #13 #14 #15 #16 #17 - #18 #19 #20 1つ目 24 06 44 06 04 - 04 04 16 04 54 14 04 - 04 04 04 04 04 - 24 05 04 2つ目 25 04 44 06 04 - 04 04 16 04 54 04 04 - 04 04 04 04 04 - 24 05 04 3つ目 30 05 44 06 04 - 04 24 16 04 54 04 04 - 04 04 04 04 04 - 24 05 04 4つ目 31 07 44 06 04 - 04 24 16 04 54 14 04 - 04 04 04 04 04 - 24 05 04 5つ目 70 07 44 46 04 - 04 24 16 04 54 04 04 - 04 04 04 04 04 - 24 05 04 6つ目 71 05 44 46 04 - 04 24 16 04 54 14 04 - 04 04 04 04 04 - 24 05 04 7つ目 64 04 44 46 04 - 04 04 16 04 54 14 04 - 04 04 04 04 04 - 24 05 04 8つ目 65 06 44 46 04 - 04 04 16 04 54 04 04 - 04 04 04 04 04 - 24 05 04 【1997/07/11】120 bit 今回はいつもとは逆に、復活の呪文 ( 6 bit * 20 文字 = 120 bit ) にどんなデータが含まれているか、を考えます。 名前4文字 ( 6 bit * 4 ) 経験値 0 ~ 65535 ( 16 bit ) ゴールド 0 ~ 65535 ( 16 bit ) 武器 7種 ( 3 bit ) なし / たけざお / こんぼう / どうのつるぎ / てつのおの / はがねのつるぎ / ほのおのつるぎ / ロトのつるぎ 鎧 7種 ( 3 bit ) なし / ぬののふく / かわのふく / くさりかたびら / てつのよろい / はがねのよろい / まほうのよろい / ロトのよろい 盾 3種 ( 2 bit ) なし / かわのたて / てつのたて / みかがみのたて 薬草 0 ~ 6 ( 3 bit ) 鍵 0 ~ 6 ( 3 bit ) 道具 14 種 ( 4 bit * 8 ) なし / たいまつ / せいすい / キメラのつばさ / りゅうのうろこ / ようせいのふえ / せんしのゆびわ / ロトのしるし / おうじょのあい / のろいのベルト / ぎんのたてごと / しのくびかざり / たいようのいし / あまぐものつえ / にじのしずく 以上ですね。 全部合わせると 102 bit かな? ってことは、残りは 18 bit ( 3 文字分 )。 ……チェックコードが 18 bit もあるのかなぁ? 【1997/07/12】差分の見方 差分の見方ですが、04 とかのいつもあるデータはそのビットを XOR するのか、ビット自体を削除してしまうのか、その 04 自体を引くのか、またはそれらを組み合わせた計算をするのだと思います。 どうやっているのかは、まだ分かりませんが。 (なんとなく 特定の数字パターンを引いていくんじゃないかな) それと、同じ状態で聞ける8種類の復活の呪文で相違のあるビットはチェックコードかなにかなのでしょう。 最初の1文字目(#1)はどうやったらいいか分からないのでとりあえずおいといて。 チェックコードらしき箇所。 文字位置 #1 #2 #3 #4 #5 - #6 #7 #8 #9 #10 #11 #12 - #13 #14 #15 #16 #17 - #18 #19 #20 コード 77 03 00 40 00 - 00 20 00 00 00 10 00 - 00 00 00 00 00 - 00 00 00 上記の数字の入っているビットが、チェックコード(かもしれない)ビットでしょう。 最初の1文字目がどうやって決まるかが全く分かりません。 が、ちょっとだけ気になることがあります。 8パターンの復活の呪文をならべると、9文字目(#9)が連続しているように思えることです。 上記の復活の呪文の9文字目(#9)を縦に見てください。 並べ替えると……。 名前「あ   」でアイテム無し:「うえおか」「ぎぐげご」 名前「あ   」でたいまつだけ:「せそたち」「もやゆよ」 名前「ああああ」でアイテム無し:「ぞだぢづ」「むめもや」 このように4つ連続した組が2つあるみたいじゃないですか? わかりにくいかな? 「あいうえおかきくけこ」の中の連続した4文字「うえおか」と、「がぎぐげご」の中の連続した4文字「ぎぐげご」になっている。 もしかすると、このあたりが解析の鍵になるかもしれません。 ただの偶然かもしれませんが。 【1997/07/13】勇者「あああ*」 少しだけ名前を変えて調べてみます。 名前「あああい」アイテム無し(文字の差(8進数))。 文字位置 #1 #2 #3 #4 #5 - #6 #7 #8 #9 #10 #11 #12 - #13 #14 #15 #16 #17 - #18 #19 #20 文字 さ そ じ づ び - あ な も ら ぬ も ら - ろ ぐ じ だ ど - そ と ね 文字の差(8進数) 12 04 44 06 04 - 04 24 16 04 60 14 04 - 04 04 04 04 04 - 24 05 04 面倒なんで、1つだけ。 先日の差分の見方を参考にすると、変わった箇所は名前「ああああ」で 54 (8進数) 固定だった部分(#10)が 60(8進数) になっているってとこでしょうか。 固定の部分を引くと 仮定 すると 60 - 54 = 4 で (注:8進数です)、ビットが1つ立ったわけですね。 で、もう一つの 仮定 として「あ」のコードが 0 、「い」が 1 であれば、このビットこそが名前の4文字目の1番下のビットということでしょう。 続いて、勇者「あああう」アイテム無し(文字の差(8進数))。 文字位置 #1 #2 #3 #4 #5 - #6 #7 #8 #9 #10 #11 #12 - #13 #14 #15 #16 #17 - #18 #19 #20 文字 み ゆ け そ て - ぬ ひ る が む が ご - ぜ づ び あ お - の ほ め 文字の差(8進数) 37 05 44 06 04 - 04 04 16 04 64 14 04 - 04 04 04 04 04 - 24 05 04 変わっている箇所は……やはり 54 固定の場所(#10)が 64 になっていますね。 64 - 54 = 10 で名前の4文字目の下から2番目のビットがここ、ということかな。 さらに続いて勇者「あああ 」アイテム無し(文字の差(8進数))。 文字位置 #1 #2 #3 #4 #5 - #6 #7 #8 #9 #10 #11 #12 - #13 #14 #15 #16 #17 - #18 #19 #20 文字 ど べ も る が - ご お て ぬ ぬ ほ め - よ れ ぎ ざ ぞ - こ そ て 文字の差(8進数) 72 04 44 06 04 - 04 24 16 04 00 07 04 - 04 04 04 04 04 - 24 05 04 空白が 63 ( 10 進数 ) であるという 仮定 であれば、ここで変わるビットが、4文字目の全ビットということでしょう。 で、変わっている箇所は、例の 54 のところとその次のところ(#10~#11)です。 54 のところが 00 になっていますね。 00 - 54 = 24 ( 8 進数 で mod 64 ) です。 あれ? これは、さっきの名前の4文字目の下から2ビット目の位置の 仮定 と矛盾しますね。 ってことは、名前用の文字コードの 仮定 か、もしくは 54 を引くという 仮定 のどちらかが間違っているということ……。 4文字目を色々と変えてみたり、1~3文字目を色々と変えてみれば多少は分かるかもしれないが……うーん、先は長そうだ。 【1997/07/18】勇者「0000」 「大技林」(注:という裏技系情報の本があった)の中の勇者「0000」の呪文「おけすちな のへむゆるがご ぜづびあお けすち」を眺めていたら、差分が全て 04 の様に思えてきました。 早速実際に確認してみると…。 名前「0000」アイテム無し(文字の差(8進数))。 文字位置 #1 #2 #3 #4 #5 - #6 #7 #8 #9 #10 #11 #12 - #13 #14 #15 #16 #17 - #18 #19 #20 文字 お け す ち な - の へ む ゆ る が ご - ぜ づ び あ お - け す ち 文字の差(8進数) 04 04 04 04 04 - 04 04 04 04 04 04 04 - 04 04 04 04 04 - 04 04 04 はっ!本当に全部 04 だ! ということは、差分から4を引く、名前のコードは「0」が 0 ということかな。 おけすちな のへむゆるがご ぜづびあお けすち かしたとね ふみやりわぢば ぼえくした とねふ けたとねふ みずぢばぼした とねふみや りわげ こそてぬひ まじだどべうき さそてぬひ まもら ゆろぐてぬ ひまもらろぐじ だどべうき さそて よれぎつに はほめよれぞで ぶいかこせ つには るぎざには ほざぞでぶこせ つにはほめ よれぎ れごぜのへ むぜづびあおけ すちなのへ むゆる で、差分。全部の数字から4を引いてあります(例: #2の差分 = #2のコード - #1のコード -4 )。 名前「0000」アイテム無し(文字の差-4(8進数))。 文字位置 #1 #2 #3 #4 #5 - #6 #7 #8 #9 #10 #11 #12 - #13 #14 #15 #16 #17 - #18 #19 #20 1つ目 00 00 00 00 00 - 00 00 00 00 00 00 00 - 00 00 00 00 00 - 00 00 00 2つ目 01 02 00 00 00 - 00 00 00 00 00 10 00 - 00 00 00 00 00 - 00 00 00 3つ目 04 03 00 00 00 - 00 20 00 00 00 10 00 - 00 00 00 00 00 - 00 00 00 4つ目 05 01 00 00 00 - 00 20 00 00 00 00 00 - 00 00 00 00 00 - 00 00 00 5つ目 40 02 00 40 00 - 00 00 00 00 00 00 00 - 00 00 00 00 00 - 00 00 00 6つ目 41 00 00 40 00 - 00 00 00 00 00 10 00 - 00 00 00 00 00 - 00 00 00 7つ目 44 01 00 40 00 - 00 20 00 00 00 10 00 - 00 00 00 00 00 - 00 00 00 8つ目 45 03 00 40 00 - 00 20 00 00 00 00 00 - 00 00 00 00 00 - 00 00 00 差分から4を引いている。チェックビットも削除している。先頭1文字目も無視。 いろいろな名前 アイテム無し(文字の差-4(8進数))。 文字位置 #1 #2 #3 #4 #5 - #6 #7 #8 #9 #10 #11 #12 - #13 #14 #15 #16 #17 - #18 #19 #20 「ああああ」 00 00 40 02 00 - 00 00 12 00 50 00 00 - 00 00 00 00 00 - 20 01 00 「あああい」 00 00 40 02 00 - 00 00 12 00 54 00 00 - 00 00 00 00 00 - 20 01 00 「あああう」 00 00 40 02 00 - 00 00 12 00 60 00 00 - 00 00 00 00 00 - 20 01 00 「あああえ」 00 00 40 02 00 - 00 00 12 00 64 00 00 - 00 00 00 00 00 - 20 01 00 「あああお」 00 00 40 02 00 - 00 00 12 00 70 00 00 - 00 00 00 00 00 - 20 01 00 「あああか」 00 00 40 02 00 - 00 00 12 00 74 00 00 - 00 00 00 00 00 - 20 01 00 「あああき」 00 00 40 02 00 - 00 00 12 00 00 01 00 - 00 00 00 00 00 - 20 01 00 「あああぬ」 00 00 40 02 00 - 00 00 12 00 00 02 00 - 00 00 00 00 00 - 20 01 00 「あああ 」 00 00 40 02 00 - 00 00 12 00 74 03 00 - 00 00 00 00 00 - 20 01 00 「ああ  」 00 00 60 17 00 - 00 00 12 00 74 03 00 - 00 00 00 00 00 - 20 01 00 「あ   」 00 00 60 17 00 - 00 00 12 00 74 03 00 - 00 00 00 00 00 - 70 07 00 「あ」が 10 ( 10 進数 ) 、「い」が 11 、「う」が 12 といった感じかな。 【1997/07/19】名前 どうやら、後ろの方から差分を取った方が見やすいようですね。 (注:複数の文字にまたがっているビット列の、上下関係のため。詳細後述) いろいろな名前 アイテム無し(文字の差-4(8進数)・逆順) 文字位置 #20 #19 #18 - #17 #16 #15 #14 #13 - #12 #11 #10 #9 #8 #7 #6 - #5 #4 #3 #2 #1 「ああああ」 00 01 20 - 00 00 00 00 00 - 00 00 50 00 12 00 00 - 00 02 40 00 00 「あああい」 00 01 20 - 00 00 00 00 00 - 00 00 54 00 12 00 00 - 00 02 40 00 00 「あああう」 00 01 20 - 00 00 00 00 00 - 00 00 60 00 12 00 00 - 00 02 40 00 00 「あああえ」 00 01 20 - 00 00 00 00 00 - 00 00 64 00 12 00 00 - 00 02 40 00 00 「あああお」 00 01 20 - 00 00 00 00 00 - 00 00 70 00 12 00 00 - 00 02 40 00 00 「あああか」 00 01 20 - 00 00 00 00 00 - 00 00 74 00 12 00 00 - 00 02 40 00 00 「あああき」 00 01 20 - 00 00 00 00 00 - 00 01 00 00 12 00 00 - 00 02 40 00 00 「あああぬ」 00 01 20 - 00 00 00 00 00 - 00 02 00 00 12 00 00 - 00 02 40 00 00 「あああ 」 00 01 20 - 00 00 00 00 00 - 00 03 74 00 12 00 00 - 00 02 40 00 00 「ああ  」 00 01 20 - 00 00 00 00 00 - 00 03 74 00 12 00 00 - 00 17 60 00 00 「あ   」 00 07 70 - 00 00 00 00 00 - 00 03 74 00 12 00 00 - 00 17 60 00 00 「を   」 00 07 70 - 00 00 00 00 00 - 00 03 74 00 66 00 00 - 00 17 60 00 00 「ん   」 00 07 70 - 00 00 00 00 00 - 00 03 74 00 67 00 00 - 00 17 60 00 00 「っゃゅょ」 00 07 10 - 00 00 00 00 00 - 00 03 54 00 70 00 00 - 00 16 40 00 00 「゛゜  」 00 07 50 - 00 00 00 00 00 - 00 03 74 00 74 00 00 - 00 17 60 00 00 名前の1文字目はおそらく #8 の位置でしょう 2文字目は #20 の下 3bit と #19 の上 3bit 。 3文字目は #4 の下 4bit と #3 の上 2bit 。 4文字目は #11 の下 2bit と #10 の上 4bit 。 (注:名前の2文字目が #20 の下 3bit から #19 の上 3bit へと続いて配置になっているように、ビットイメージで見た場合に、#20 を先頭、#1 を末尾にしたほうが見やすかった) 名前用のコードは以下のようになっているようです。 +0 +1 +2 +3 +4 +00 0 1 2 3 4 +05 5 6 7 8 9 +10 あ い う え お +15 か き く け こ +20 さ し す せ そ +25 た ち つ て と +30 な に ぬ ね の +35 は ひ ふ へ ほ +40 ま み む め も +45 や ゆ よ +48 ら り る れ ろ +53 わ を ん +56 っ ゃ ゅ ょ +60 ゛ ゜ ー   【1997/07/20】各データ(未確定版) 名前以外のデータも大体把握できました。 1ビットづつ変えた復活の呪文を聞いて変わったビットを確認して、というのを延々とやりました。 面倒なんで、結果だけ。 それと 8bit 毎に区切り直した方が見やすいようなので、そうなってます。 また、順番も逆になっています(末尾のバイトが先頭文字含む)。 アイテム2番目(4bit)+1番目(4bit) 鱗装備(1bit)+名前2文字目(6bit)+未使用(1bit) 経験値上(8bit) アイテム6番目(4bit)+5番目(4bit) 鍵(4bit)+薬草(4bit) 所持金上(8bit) 武器(3bit)+鎧(3bit)+盾(2bit) チェックコード(1bit)+未使用(1bit)+名前4文字目(6bit) アイテム8番目(4bit)+7番目(4bit) 名前1文字目(6bit)+未使用(1bit)+チェックコード(1bit) 所持金下(8bit) アイテム4番目(4bit)+3番目(4bit) チェックコード(1bit)+未使用(1bit)+名前3文字目(6bit) 経験値下(8bit) チェックコード(8bit) 鱗装備というのは、「りゅうのうろこ」を身に付けたことがあるかどうかです。 「未使用」といっているものが本当に未使用かは分かりません。 1bit 単位のチェックコードは8パターンある復活の呪文のキーじゃないかと思っています。 かなりのデータが分かり、なんとなくもう少しで復活の呪文の捏造ができるような気になりますが、実際には最後のそして最大の難関が手付かずのままのこってますね。 つまり、チェックコードの計算方法です。 【1997/07/22】チェックコード チェックコードの解析ですね。 とりあえず、一番シンプルな勇者「0000」でしょう。 経験値を1づつ取得して、復活の呪文を聞いてみました。 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - EXP 0 00 00 00 00 00 00 00 00 00 00 00 00 00 01 61 - EXP 1 00 00 00 00 00 00 00 00 00 00 00 00 00 02 c2 - EXP 2 00 00 00 00 00 00 00 00 00 00 00 00 00 03 a3 - EXP 3 00 00 00 00 00 00 00 00 00 00 00 00 00 04 a5 - EXP 4 00 00 00 00 00 00 00 00 00 00 00 00 00 05 c4 - EXP 5 例の 8bit 毎に切り直した結果を 16 進数で表しています。 0x61 * 2 = 0xc2 ですが……偶然かな? それよりも 0x61 xor 0xc2 = 0xa3 の方が気になりますね(経験値 1, 2, 3 を比較)。 それに 0x61 xor 0xa5 = 0xc4 (経験値 1, 4, 5 を比較)。 どうやら、各 bit がある数値と括りついていてそれらの xor がチェックコードということかな? (経験値の下1bit目が 0x61 とくくりついて、下2bit目が 0xc2 とくくりついて、下3bit目が 0xa5 とくくりついている想定) ……いろいろ検証してみたが、どうやらそーらしい。 でも、 14 byte * 8 = 112 bit 分もマジックナンバーがあるの? おそらくはいくつかの数字から計算で求められるんじゃないかとおもいますが、それを調べるのは、 112 個のデータを集めないと……。 【1997/07/25】完成!!かな?(注:未完成です) ここ数日ひたすらチェックコードを解析していました。 で、結果。 一番左に書いてあるのが最上位ビット、一番右が最下位ビットと括りついたマジックナンバーです。 0x88 0xc4 0x62 0x31 0x08 0x84 0x42 0x21 0x98 0xcc 0xe6 0x73 0xa9 0xc4 0x62 0 0x5a 0xad 0xc6 0x63 0xa1 0xc0 0x60 0x30 0x38 0x9c 0x4e 0xa7 0xc3 0xf1 0x68 0xb4 0 0x68 0xb4 0x5a 0 0x06 0x83 0x51 0x20 0x10 0x08 0x84 0x42 0xa1 0x40 0xa0 0xf9 0xec 0xf6 0x7b 0xad 0xc6 0xe3 0x61 0x81 0xd0 0x68 0xb4 0xda 0x6d 0xa6 0xd3 0xb2 0xd9 0xfc 0xfe 0xff 0xef 0x67 0x23 0x34 0x1a 0x0d 0x96 0x4b 0x35 0x8a 0x45 0xaa 0xd5 0x7a 0x3d 0x8e 0x47 0xb3 0x49 0xa1 0x40 0xa0 0x50 0xa8 0xd4 0xea 0x75 0xa0 0 0x68 0xb4 0x5a 0xad 0xc6 0x63 0x7e 0xbf 0xcf 0xf7 0x6b 0xa5 0xc2 0x61 こんな感じでした。 0 が4か所ありますが、そこはチェックできていません。 それと 8byte 目の上から 2bit 目は王女の所のドラゴンを倒したかどうか、のようです。 同様に 10byte 目の下から 2bit 目はゴーレムを倒したかどうか、のようです。 ということは、ほかの未使用ビットも意味があるのかな。 それとなんとなく規則が見えそうで、でも良く分からないです。 上位 4bit と下位 4bit は別に見る。 下位 4bit は 0x08 → 0x0c → 0x06 → 0x03 → 0x09 → 0x04 → 0x02 → ?? の様に 1bit づつ 8 段階にシフトしているのかな。 上位 4bit は……同様にシフトしているけど 4 段階ってことなのか? でも違うところもあるなぁ。単純にミスだったりして。 ま、いいや。とりあえずここまでで CGI を作って公開しました。 (注:昔、公開していました) 【1998/02/07】戦士の指輪 2byte 目の一番下の bit は「戦士の指輪」を装備しているかどうかのようですね。 【1998/05/31】最後のフラグ 情報提供がありまして、最後のフラグは「死の首飾り」を入手したかどうかのようです。 また、チェックコードの求め方についても、判明しました。おそらく CRC を求めているようです。 (CRC-16-CCITT, 多項式は 0x1021) 例のマジックナンバーは、以下のようなコーディングで求める事ができます。 int cd = 0x8000; for (int i = 0; i < 15 * 8; i++) { if (cd & 0x8000) { cd = (cd << 1) ^ 0x1021; } else { cd <<= 1; } printf("0x%.2x\n", cd & 0xff); } 結果:(先頭の下のビットから順番に) 0x21 0x42 0x84 0x08 0x31 0x62 (以下略) マジックナンバーの完全版は、以下の通り。 0x88 0xc4 0x62 0x31 0x08 0x84 0x42 0x21 0x98 0xcc 0xe6 0x73 0xa9 0xc4 0x62 0x31 0x5a 0xad 0xc6 0x63 0xa1 0xc0 0x60 0x30 0x38 0x9c 0x4e 0xa7 0xc3 0xf1 0x68 0xb4 0xd0 0x68 0xb4 0x5a 0x2d 0x06 0x83 0x51 0x20 0x10 0x08 0x84 0x42 0xa1 0x40 0xa0 0xf9 0xec 0xf6 0x7b 0xad 0xc6 0xe3 0x61 0x81 0xd0 0x68 0xb4 0xda 0x6d 0xa6 0xd3 0xb2 0xd9 0xfc 0xfe 0xff 0xef 0x67 0x23 0x34 0x1a 0x0d 0x96 0x4b 0x35 0x8a 0x45 0xaa 0xd5 0x7a 0x3d 0x8e 0x47 0xb3 0x49 0xa1 0x40 0xa0 0x50 0xa8 0xd4 0xea 0x75 0xa0 0xd0 0x68 0xb4 0x5a 0xad 0xc6 0x63 0x7e 0xbf 0xcf 0xf7 0x6b 0xa5 0xc2 0x61 【2001/07/14】各データの最終版 相変わらず 8bit 毎に区切り直してあります(順番も逆になっています)。 アイテム2番目(4bit)+1番目(4bit) 鱗装備(1bit)+名前2文字目(6bit)+戦士の指輪装備有無(1bit) 経験値上(8bit) アイテム6番目(4bit)+5番目(4bit) 鍵(4bit)+薬草(4bit) 所持金上(8bit) 武器(3bit)+鎧(3bit)+盾(2bit) チェックコード(1bit)+見張りのドラゴンを倒したか(1bit)+名前4文字目(6bit) アイテム8番目(4bit)+7番目(4bit) 名前1文字目(6bit)+ゴーレムを倒したか(1bit)+チェックコード(1bit) 所持金下(8bit) アイテム4番目(4bit)+3番目(4bit) チェックコード(1bit)+死の首飾り入手済(1bit)+名前3文字目(6bit) 経験値下(8bit) チェックコード(8bit) 順番を戻して、6bit 毎つまり文字単位に切ると以下の通り。 制限のあるもの(チェックコードやアイテム、それに鍵・薬草など)を強調してあります。 また、5文字,7文字,5文字,3文字に区切って、間に(空白)と記述しておきます。 チェックコード下(6bit) 経験値4(4bit)+ チェックコード上(2bit) 名前3文字目の下(2bit)+経験値3(4bit) チェックコード(1bit)+死の首飾り入手済(1bit)+名前3文字目の上(4bit) アイテム4番目の下(2bit) + アイテム3番目(4bit) (空白) 所持金4(4bit)+ アイテム4番目の上(2bit) ゴーレムを倒したか(1bit)+ チェックコード(1bit) +所持金3(4bit) 名前1文字目(6bit) アイテム8番目の下(2bit) アイテム7番目(4bit) 名前4文字目の下(4bit)+ アイテム8番目の上(2bit) 盾(2bit)+ チェックコード(1bit) +見張りのドラゴンを倒したか(1bit)+名前4文字目の上(2bit) 武器(3bit)+鎧(3bit) (空白) 所持金2(6bit) 薬草(4bit) +所持金1(2bit) アイテム5番目の下(2bit) + 鍵(4bit) アイテム6番目(4bit) + アイテム5番目の上(2bit) 経験値2(6bit) (空白) 名前2文字目の下(3bit)+戦士の指輪装備有無(1bit)+経験値1(2bit) アイテム1番目の下(2bit) +鱗装備(1bit)+名前2文字目の上(3bit) アイテム2番目(4bit) + アイテム1番目の上(2bit) 「ふっかつのじゅもん」の作成ページ 上記の内容をもとに、あらためて「ふっかつのじゅもん」を作成できるページを作りました。 React のいろいろな部品を使って今風なUI/UXにしようかと思ったけど、そんなセンスもないのでとりあえず非常にシンプルな感じの画面になっています。 ソースはこちら (うまく動かなかったらごめんね) 各種項目を入力して呪文を作る方法と、呪文を入力して妥当性を確認する方法があります。 項目を入力して呪文を作る 「ふっかつのじゅもん」のページにいって、初期表示の画面が項目入力画面です。 (また、後述の呪文入力画面で左下の「項目を入力」ボタンを押すことで、画面遷移します) 4文字までで、名前を入力してください。 使用できる文字はひらがな(一部使えない文字があります)、数字、スペースのみです。 濁点、半濁点も一文字として数えます。 具体的な使える文字は以下の通りです(見えませんが、スペースも使えます)。 0123456789 あいうえお かきくけこ さしすせそ たちつてと なにぬねの はひふへほ まみむめも やゆよ らりるれろ わをん っゃゅょ ゛゜-  その他の項目を選択して「呪文を確認」をクリックすると、呪文が表示されます。 クリップボードにコピーするか、ツイッターにツイートすることができます。 呪文を入力して妥当性を確認 「ふっかつのじゅもん」のページにいって、左下の「呪文を入力」ボタンを押すと、呪文入力画面が表示されます。 呪文を20文字で入力してください。 呪文として使える文字はひらがなだけです(一部使えない文字があります)。 濁点・半濁点は、分離せずに1文字と数えます(例:「が」は1文字)。 単独の濁点・半濁点の文字は無効です(例:「か゛」は無効な文字を除外して「か」と判断されます)。 具体的な使える文字は以下の通りです。 あいうえお かきくけこ さしすせそ たちつてと なにぬねの はひふへほ まみむめも やゆよ らりるれろ わ がぎぐげご ざじずぜぞ だぢづでど ばびぶべぼ 見やすさのため、スペースを入力することもできます(呪文の文字数にはカウントしません) 右下の「呪文を確認」ボタンを押すと、呪文をチェックします。 語呂合わせの「ふっかつのじゅもん」を作りたい場合 呪文を入力する際、ひらがな以外に「?」を入力していると、呪文として使えるパターンが表示されます。 例:ふるいけや かわずとびこむ みずのおと ば?? 「?」は最大3つまで書けますが、3つ書くと処理に時間がかかるので注意してください。 また、特定の位置に特定の文字を並べると、例えば不正なアイテムを持っていることになって、残りの場所をどのように変えても有効な呪文にならないことがあります。 (あるいはMAX以上の薬草や鍵を持っていることになるケースもある) その場合には「n~m文字目のどこかを変更してね」といったメッセージが出ますので、修正してください。 また、上記メッセージがでなくても有効な呪文でないことがあります。 これは、呪文全体のチェックコード(内部的には CRC を使っています)での検証が通っていないものです。 呪文のどこかを修正すると有効な呪文になるはずなので、言い回しを変えられそうな部分に「?」を入れて、候補をしらべてください。 おもしろい語呂合わせのふっかつのじゅもんができたら、ツイッターなどで自慢しましょう。 既存の作品を見てみたい方は、以下の「奇妙な「復活の呪文」大辞典」までどうぞ(古い記事を掘り起こしたので、古いネタが多いです。時事ネタとか)。 github のマークダウンを github pages に置いただけなので、見た目がしょぼいのはご容赦を。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

カウントアップアプリを作って、props、stateの理解を深めた件

はじめに react入門いたしました。 reactの基礎である、props、stateについて カウントアップアプリを通じて学びました。 Props 親コンポーネントから子コンポーネントに値を渡す方法 データの属性によって記述の仕方が違います、文字列は""で囲む、数字は{}で囲む 子コンポーネントは引数として受け取る。受けつる際に{}で囲む 親コンポーネント const App = () => { return ( <> <section style={{ paddingTop: "50px" }}> <h1 className="ui container">カウントアプリ</h1> <div className="ui container" style={{ display: "flex" }}> <CountButton text="+1" num={1} /> <CountButton text="+2" num={2} /> <CountButton text="+3" num={3} /> <CountButton text="+4" num={4} /> <CountButton text="+5" num={5} /> </div> </section> </> ); }; text、numという変数で値を子コンポーネントに渡しております。 子コンポーネント const CountButton = (props) => { const [num, setNum] = useState(0); const onCountUp = () => { setNum(num + props.num); }; return ( <> <div className="count_box" style={{ marginRight: "20px" }}> <p style={{ fontSize: "20px" }}> Count<span style={{ fontSize: "24px" }}>{num}</span> </p> <button className="ui button blue" style={{ width: "100%" }} onClick={onCountUp} > {props.text} </button> </div> </> ); }; 子コンポーネントでは引数で(props)の値を受け取る {}で囲んでprops.変数名で値を表示する State 各コンポーネントが持つ状態 Stateが変更されると再レンダリングされる。 条件によって状態が変わる部分をStateで管理する import React, { useState } from "react"; const CountButton = (props) => { const [num, setNum] = useState(0); const onCountUp = () => { setNum(num + props.num); }; return ( <> <div className="count_box" style={{ marginRight: "20px" }}> <p style={{ fontSize: "20px" }}> Count<span style={{ fontSize: "24px" }}>{num}</span> </p> <button className="ui button blue" style={{ width: "100%" }} onClick={onCountUp} > {props.text} </button> </div> </> ); }; まずはuseStateをimportする const [num, setNum] = useState(0); 一つ目いにStateとして使う変数名(num)、二つ目にそのStateを変更するための関数名(setNum) useState(初期値)今回は0です。 setNum(num + props.num); Stateを更新する、numにprposの値分プラスする デモ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React/Material-UIでTab①

./src/components/ui/Header.js import React, { useState } from "react"; import { AppBar, Tabs, Toolbar, Tab } from "@material-ui/core"; const Header = () => { const [value, setValue] = useState(0); const handleChange = (event, value) => { setValue(value); }; console.log(value); return ( <AppBar> <Toolbar> <Tabs value={value} onChange={handleChange}> <Tab label="AAA" /> <Tab label="BBB" /> <Tab label="CCC" /> </Tabs> </Toolbar> </AppBar> ); }; export default Header;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React/Material-UIでタブとreact-route-domによるルーティング

タブの実装 ./src/components/ui/Header.js import React, { useState } from "react"; import { AppBar, Tabs, Toolbar, Tab } from "@material-ui/core"; const Header = () => { const [value, setValue] = useState(0); const handleChange = (event, value) => { setValue(value); }; console.log(value); return ( <AppBar position="static"> <Toolbar> <Tabs value={value} onChange={handleChange}> <Tab label="AAA" /> <Tab label="BBB" /> <Tab label="CCC" /> </Tabs> </Toolbar> </AppBar> ); }; export default Header; ルーティングの実装 react-route-domを使うと便利。 ./src/App.js import logo from "./logo.svg"; import "./App.css"; import Header from "./components/ui/Header"; import { BrowserRouter, Route, Switch } from "react-router-dom"; function App() { return ( <div className="App"> <BrowserRouter> <Header /> <Switch> <Route exact path="/" component={() => <div>AAA</div>} /> <Route exact path="/bbb" component={() => <div>BBB</div>} /> <Route exact path="/ccc" component={() => <div>CCC</div>} /> </Switch> </BrowserRouter> </div> ); } export default App; ./src/components/ui/Header.js import React, { useState } from "react"; import { AppBar, Tabs, Toolbar, Tab } from "@material-ui/core"; import { Link } from "react-router-dom"; const Header = () => { const [value, setValue] = useState(0); const handleChange = (event, value) => { setValue(value); }; console.log(value); return ( <AppBar position="static"> <Toolbar> <Tabs value={value} onChange={handleChange}> <Tab label="AAA" component={Link} to="/" /> <Tab label="BBB" component={Link} to="/bbb" /> <Tab label="CCC" component={Link} to="/ccc" /> </Tabs> </Toolbar> </AppBar> ); }; export default Header;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

シンプルなNFTをデプロイするサンプルプログラムの作成手順と解説

概要 シンプルなNFTをデプロイするサンプルプログラムの作成手順と解説です。NFTをUIからデプロイする開発環境をフルスクラッチから作るためのハンズオンセミナーを開催するような構成にしています。 ksuharaさんのページを参考にさせていただきました。 参考: 基本部分は同じです。内容をシンプルにし、初心者用がよくはまるパターンの補足、craco,Reactの導入とUIでのNFTのDeploy,Mint部分を追記しています。testなどうまく動いていない部分も修正しています。 ゴール NFTの開発環境が構築できる NFTのtest環境が構築できる NFTのReactを使ったUI環境が構築できる。 NFTのdeploy,mintをMetamaskを通じてUIから実行するプログラムが理解できる。 テストネットrinkebyの検証方法が理解できる。 下記のようなNFTを操作するUIが作成できます。 ・結果のコードは下記に置いています。 前提(事前準備) Ubuntu20.04で動作確認してます。 Node.jsがインストール済み。 Ubuntuで開発ができる。 JavaScriptの開発経験がある。 Metamaskの導入、操作がわかる。 テストネットRinkebyでETHをもらっておいてください。 twitterで送金してほしいアドレスをつぶやいてURLを貼り付けると数時間以内にETHが送金されます。 私は上記環境を、DockerのUnintu20.04 + sshで作成し、VisualStudioCode + extentionのsftpで接続してプログラム開発しました。 MacやWindowsのwsl2でも動くと思います。 プロジェクト作成 プロジェクトフォルダーを作成 mkdir simpleNFT cd simpleNFT packagesにfront と contractのフォルダーを作成 mkdir packages cd packages mkdir frontend mkdir contracts 以下のようなフォルダー構成になればOKです。 contractsフォルダーでの環境構築 contractsに移動してプロジェクトを作成します。 cd contracts npm init -y コントラクトの開発環境はhardhatを使います。この環境を作成します。 参考: npx hardhat init 上記を実行するとメニューが表示されます。create a sample projectを選択し、回答はデフォルトでOKです。 次に、hardhatの動作確認を行います。エラーが表示されなければOKです。 yarn hardhat test 次に、contracts/package.jsonのscripts部分を以下に変更してください。 contracts/package.json "scripts": { "test": "hardhat test test/sample-test.js --network localhost", "localchain": "hardhat node", "deploy": "hardhat run scripts/sample-script.js --network localhost" }, 上記のスクリプトの説明 yarn testでdeployのテストを実行する yarn localchainでローカル環境にブロックチェーン環境を立ち上げる yarn deployでローカルチェーンにコントラクトをデプロイする 次に、コントラクトのライブラリーを導入します。openzeppelinというライブラリーを使います。 yarn add @openzeppelin/contracts simpleNFT\packages\contracts\contractsにあるGreeter.solをNFT.solにファイル名を変更して内容を変更します。 ERC721PresetMinterPauserAutoIdを継承して、name, symbol, tokenURIを固定で引数に渡しています。 参考: ちゃんとGreeter.solを削除しないとtestの時にエラーになります。 contracts/NFT.sol //SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; import "hardhat/console.sol"; import "@openzeppelin/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol"; contract NFT is ERC721PresetMinterPauserAutoId { constructor() ERC721PresetMinterPauserAutoId("name", "symbol", "http://localhost:3000") {} } 次に、hardhat.config.jsのsolidityのバージョンをコントラクトのバージョンに合わせて変更します。 solidity: "0.8.0", 次に、NFTコントラクトのデプロイのテストプログラムを作成します。まるっとプログラムを置き換えてください。 test/sample-test.js const { expect } = require("chai"); describe("NFT", function() { it("NFT basic test", async function() { const [signer, badSigner] = await ethers.getSigners(); const NFT = await ethers.getContractFactory("NFT"); const nft = await NFT.deploy(); expect(await nft.name()).to.equal("name"); await nft.mint(signer.address); expect(await nft.balanceOf(signer.address)).to.equal(1); await expect(nft.connect(badSigner).mint(signer.address)).to.revertedWith("ERC721PresetMinterPauserAutoId: must have minter role to mint") }); }); 上記は、コントラクトがデプロイできているか(nameが一致しているか)minter以外がmintできないかをテストで確認しています。 次に、デプロイのtest動作確認します。 yarn localchainで実行環境を起動したまま、別のシェルでyarn testを実行してエラーが出なければOKです。 次に、デプロイするサンプルを作成します。まるっとプログラムを置き換えてください。 scripts/sample-script.js const hre = require("hardhat"); async function main() { const NFT = await hre.ethers.getContractFactory("NFT"); const nft = await NFT.deploy(); await nft.deployed(); console.log("Nft deployed to:", nft.address); } main() .then(() => process.exit(0)) .catch(error => { console.error(error); process.exit(1); }); 上記はdeployしてコントラクトのアドレスを表示しています。 次に、deployの動作確認します。 yarn localchainで実行環境を起動したまま、別のシェルでyarn deployを実行してエラーが出なければOKです。 このときに、simpleNFT\packages\contracts\artifacts\contracts\NFT.sol\NFT.jsonが作成されます。 このファイルにはabi,bytecodeなどがあり、frontendでdeployするときに使うので、NFT.solを修正したらかならずyarn deploy実行してください。 frontendフォルダーでの環境構築 Reactプロジェクトを作成します cd frontend npx create-react-app . 次に、frontend/package.jsonのscriptsに追加します。 frontend/package.json "dev": "react-scripts start", 上記はyarn devでreactを起動する設定です。 次に、設定を上書きするツールcracoをインストールします。ModuleScopePluginを削除して、importでsrc以外の外部ファイルが指定できるようになります。上記のNFT.jsonをimportするためです。 参考: yarn add @craco/craco 次に、package.jsonのscriptsにcraco起動を追加します。 frontend/package.json "dev": "craco start", "start": "craco start", "build": "craco build", "test": "craco test", 次に、frontend/craco.config.jsを作成して以下を記述。ModuleScopePluginを削除する設定です。 frontend/craco.config.js module.exports = { webpack: { configure: (webpackConfig) => { const scopePluginIndex = webpackConfig.resolve.plugins.findIndex( ({ constructor }) => constructor && constructor.name === "ModuleScopePlugin" ); webpackConfig.resolve.plugins.splice(scopePluginIndex, 1); return webpackConfig; }, }, }; 次に、Ethereumライブラリーを追加インストールします。 yarn add ethers 次に、frontend/src/App.jsを下記のようにまるごと修正します。これがTopページになります。 frontend/src/App.js import './App.css'; //ethereumのライブラリー import { ethers } from "ethers"; //NFT.solのdeployファイルをとりこむ。cracoを使ってModuleScopePluginの設定を削除しないとimportできない。 import {abi,bytecode} from "../../contracts/artifacts/contracts/NFT.sol/NFT.json"; //Providerを作成。Ethereumへのネットワーク接続を管理します。 const provider = new ethers.providers.Web3Provider(window.ethereum); let address = "" //NFTのdeploy時のアドレスを指定する。 //Clickの処理.NFTをDeploy const buttonDeploy = async() => { //署名を取得します。コントラクトの作成には署名が必要です。基本的に、コントラクトの生成や値が変わる処理にはマイニングが必要なので署名が必要です。 const signer = provider.getSigner(); //abi,bytecode,署名からコントラクトを作成するためのfactoryを作ります。 const factory = new ethers.ContractFactory(abi,bytecode,signer); //NFTをdeployします。 const contract = await factory.deploy();//metamaskの署名を要求する address = contract.address;//deployしたアドレス。 console.log(contract); console.log(address); //rinkebyのchainidのときはetherscanのリンクを表示 const net = await signer.provider.getNetwork(); if( net.chainId == 4) console.log("https://rinkeby.etherscan.io/tx/" + contract.deployTransaction.hash);//Tx Hash Etherscanで探す。 }; //Clickの処理.consoleにnameが表示されればOK.Txがcommitされる前に押すとエラーになる。 const buttonGetName = async() => { const contract = new ethers.Contract(address, abi, provider); console.log(await contract.name()) console.log(address); }; //Clickの処理.NFTをmint const buttonMint = async() => { const signer = provider.getSigner(); //以前にdeployしたコントラクトのアドレスからコントラクトを特定します。 const contract = new ethers.Contract(address, abi, provider); //署名を付けてコントラクトのmintを実行します。 const {hash } = await contract.connect(signer).mint(signer.getAddress());//metamaskの署名を要求する console.log(contract); console.log(address); //rinkebyのchainidのときはetherscanのリンクを表示 const net = await signer.provider.getNetwork(); if( net.chainId == 4) console.log("https://rinkeby.etherscan.io/tx/" + hash);//Tx Hash Etherscanで探す。 }; //Clickの処理.NFTのtotalSupply const buttonSupply = async() => { const contract = new ethers.Contract(address, abi, provider); console.log(address); console.log(await contract.totalSupply()); }; function App() { return ( <div className="App"> <p>動作</p> <button id="test" onClick={buttonDeploy}>NFT deploy</button><br/> <button id="test1" onClick={buttonGetName}>NFT get name</button><br/> <button id="test2" onClick={buttonMint}>NFT mint</button><br/> <button id="test2" onClick={buttonSupply}>NFT totalSupply</button> </div> ); } export default App; かなり細かくコメントを入れているので、これでUIからコントラクトを操作する処理は理解できると思います。 次に、動作確認をします。 contractsフォルダーでyarn localchainを起動したまま、frontendでyarn devを起動してください。 ブラウザーからhttp://localhost:3000 で接続します。下記の画面が表示されます。chromeのデバッグコンソールも表示してください。 metamaskは下記のようにカスタムrpcに31337のチェーンIDをもつlocalhostのネットワークを追加してください。これがhardhatへの接続設定になります。 さらに下記のようにyarn localchainの起動画面に秘密鍵が表示されているアカウントがいくつか表示されているので、適当な秘密鍵をmetamaskにインポートしてください。これでhardhat用のETHを取得したアカウントを取得することができます。このアカウントで動作確認を行います。 画面のボタンを押せば、上から、NFTのDeploy,nameの値の取得、mint、totalSupplyの値取得、を実行し結果がconsoleに表示されます。 起動時に毎回nonceがリセットされるので実行時にnonceエラーが表示されます。このときは下記のようにカスタムNonceに手動で期待する値を入力してください。 nonceとは実行の順番を管理する番号のことです。アカウントごとにインクリメントされていく数値です。 実行した後に、起動画面の緑のコメントが表示されます。これが実行が完了したメッセージなのでこれらが表示する前にほかのボタンを押すと、エラーになります。表示されるまで待ちましょう。これは実行=トランザクションが完了してブロックに取り込まれたことを意味します。 monorepo化。contractsとfrontendを統合する。 packagesにあるcontractsとfrontendを統合します。 simpleNFTフォルダー(root folder)に移動して作業をします。 simpleNFTフォルダーでプロジェクトを作成します。 npm init -y package.jsonに以下を追加します。workspacesとしてpackages以下を定義しています。 package.json "private": true, "workspaces": [ "packages/**" ], スクリプト実行用のnpm-run-all wait-onをインストールします。 -Wがないとエラーになります。 yarn add -D npm-run-all wait-on -W package.jsonのscriptsを下記のように修正します。yarn test,devで起動するときのスクリプトを記述しています。 yarn dev では"dev:*"が記述されているので、"dev:run-localchain","dev:deploy-contract-to-localchain","dev:frontend"も実行されます。 package.json "scripts": { "test": "yarn workspace contracts test", "dev": "run-p dev:*", "dev:run-localchain": "yarn workspace contracts localchain", "dev:deploy-contract-to-localchain": "wait-on http://localhost:8545 && yarn workspace contracts deploy", "dev:frontend": "wait-on http://localhost:8545 && yarn workspace frontend dev" }, 動作確認します。ローカル環境で起動します。 yarn dev 今まではcontracsとfrontend別々に起動していましたが統合したのでyarn devだけでlocalchain,frontendの起動、contracsのdeployができています。 次に、http://localhost:3000/ にブラウザから接続して確認します。 Staging環境追加 テストネットrinkebyの環境をStaging環境として使います。その設定を行います。 simpleNFT/packages.jsonのscriptsに以下を追加します。 simpleNFT/packages.json "dev-staging": "run-p dev-staging:*", "dev-staging:frontend": "yarn workspace frontend dev" frontendを起動しているだけです。 Metamaskの接続先がrinkebyになるだけなので、特に開発環境には修正を入れていません。Metamaskの接続先がrinkebyになっていれば、NFTはrinkebyにdeployされます。 次に、動作確認します。 yarn dev-staging metamaskはrikebyを選択。テストネットRinkebyでETHをもらっておいてください。https://faucet.rinkeby.io/ twitterで送金してほしいアドレスをつぶやいてURLを貼り付けると数時間以内にETHが送金されます。 次に、http://localhost:3000/ にブラウザから接続して確認します。 いままではローカルのhardhat環境にdeployしていましたが、deployボタンを押すとrinkebyにdeployされます。 deployするとconsoleにetherscanのリンクが表示されるので、successが表示されるまでトランザクションの完了を待ってから別のボタンを押しましょう。 まとめ シンプルなNFTをDeployするための環境を構築構築をしました。DeployからmintまでUIで処理するコードも記載しています。かなり初心者でもわかりやすく書いたつもりです。これをベースにして、UIを含めたNFTサイトの開発は進められると思います。 今後は、 * NFTの処理自体は、シンプルなままなのでOpenseaにも対応した少しカスタムしたコントラクトの開発も追記する予定です。 * バックエンドに、Firebaseを追加して、DeployしたNFTの管理ができるようなサンプルコードも追記する予定です。 * 希望があればデジハリなどでハンズオン形式でセミナーも開きたいですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

After Effectsで作ったアニメーションをreact-lottieで実装する

これは何 以下の流れをまとめた記事です。 After Effectsで作ったアニメーションを Lottie(Bodymovin)でJSONに書き出して React上でonClickで動くようにする 完成物そのものは、buttonをクリックするとアニメーションがスタートするだけのシンプルなものです。 また、GitHub Pagesで実際に触れるようにしつつ、 コードも全て公開しています。 なお、この記事ではアニメーションの作り方自体の説明はしません。 以前私が書いた記事で初歩の初歩だけ紹介しているので、興味のある方はこちらをどうぞ。 全体の流れ Lottieの紹介 After EffectsにBodymovinをインストールする(初回のみ) After EffectsからJSONを書き出す Reactのプロジェクト上でreact-lottieを用いる 余談:サンプルコードの通りだと上手く動かない箇所 import * as animationData from './path/to/json' setupTests.js Lottieの紹介 Lottieについて、公式のドキュメントより説明を引用します。 Lottie is a library for Android, iOS, Web, and Windows that parses Adobe After Effects animations exported as json with Bodymovin and renders them natively on mobile and on the web! 今回はWebでの話、特にReactアプリケーションでLottieを使う際の話です。 Webで動画を扱うにあたって、一番ネックになるのはファイルの容量の大きさではないでしょうか。 綺麗な動画を届けようとすればするほど重くなり、ユーザー体験を損ねかねません。 ですが、Lottieを使うとJSONを読み込むだけでアニメーションを実装できます。 実際に画面に描画されるのもsvg要素なので、かなり軽く済むんですね。 After EffectsにBodymovinをインストールする(初回のみ) After Effectsで作成したアニメーションを、Lottieで扱えるJSONに変換してくれるのがBodymovinです。 導入の仕方は何パターンかありますが、自分はAdobe Exchangeからインストールしました。 アクセスして、画面の右上あたりにあるFreeボタンを押せばCreative Cloudアプリが立ち上がってインストールが始まると思います。 ちゃんとインストールできていれば、ウィンドウ > エクステンションの中にBodymovinの項目が出現しているはずです。 After EffectsからJSONを書き出す アニメーション作成についての説明は飛ばして、書き出し方を説明します。 上記のように、無限にループする円のアニメーションを作成しました。1 次にウィンドウ > エクステンション > Bodymovinを起動。 このようなウィンドウが開くはずです。 更にSettingsを選びましょう。 設定項目の詳細は公式ドキュメントのComposition Settingsの章を読んでいただく方が良いかと思いますが、いつも私がチェックを入れるのは以下の3つです。 Glyphs 今回は使っていませんが、テキストを使っている際は必須 いわゆるアウトライン化をしてくれるオプション Standard 実装する際のJSONを書き出してくれるためオプションのため必須 Demo ローカルで開くだけで動きを確認できるHTMLを吐いてくれる 実際に近い確認ができるのでいつも一緒に書き出している 設定を確認したらSaveを押し、ひとつ前の画面に戻ります。 /Users/..と書いてある箇所をクリックすると保存場所とファイル名を選べるので、設定してRenderを押しましょう。 データや設定に問題がなければRender finishedと画面に表示されるはずです。 ひとつ注意が必要なのが、BodymovinはAfter Effectsの全ての機能に対応しているわけではないこと……。 詳しくは上記のページにまとまっていますが、以下の項目に気をつけていればよっぽど大丈夫だと思います。 シェイプレイヤーかパスデータだけで作る 複雑なマスクを使わない 基本、エフェクトは使えないものと考える Reactのプロジェクト上でreact-lottieを用いる ここからは実際のReactアプリケーション上でアニメーションを動かす流れを説明します。 今回は例なので、Create React Appで生成されるページをそのまま使いますがご了承ください。 まずは先ほど書き出したファイルを適当なディレクトリに格納。 自分はsrc直下にanimation.jsonという名前で置きました。 そしてやっとreact-lottieの登場です。 インストール react-lottieのインストール $ npm i react-lottie # or $ yarn add react-lottie App.jsの編集 App.js import { useState } from 'react'; import './App.css'; import Lottie from 'react-lottie'; import animationData from './animation.json'; function App() { const [stop, setStop] = useState(true); const defaultOptions = { loop: false, autoplay: false, animationData, rendererSettings: { preserveAspectRatio: 'xMidYMid slice' } }; return ( <div className="App"> <header className="App-header"> <Lottie options={defaultOptions} height={'50vmin'} width={'50vmin'} isStopped={stop} isClickToPauseDisabled={true} ariaRole={''} eventListeners={[ { eventName: 'complete', callback: () => setStop(true), }, ]} /> <button onClick={() => setStop(false)} className='App-button' disabled={!stop}>Start</button> </header> </div> ); } export default App; いきなり完成形を出してしまいましたが、順に解説します。 import App.js import Lottie from 'react-lottie'; import animationData from './animation.json'; react-lottieと、書き出して配置したJSONをimportします。 options App.js const defaultOptions = { loop: false, autoplay: false, animationData, rendererSettings: { preserveAspectRatio: 'xMidYMid slice' } }; 例ではこのように書きましたが、最小ならこうです。 App.js const defaultOptions = { animationData }; JSONさえ読めていれば、ループや自動再生は初期値(両方true)として動いてくれます。 TypeScriptを使う使わないに関わらず、型定義ファイルを見れば分かりやすいでしょう。 Lottie component App.js <Lottie options={defaultOptions} height='50vmin' width='50vmin' isStopped={stop} isClickToPauseDisabled={true} ariaRole='' eventListeners={[ { eventName: 'complete', callback: () => setStop(true), }, ]} /> 今回自分がオプションを設定した意図は以下のものです。 SVG要素自体はクリックできないようにしたい 別なbutton要素をクリックすることで再生をスタートしたい buttonをクリックするたびアニメーションが再生してほしい デフォルトだとアニメーションするSVG要素自体がクリックでき、かつroleにbuttonがあたっているのでisClickToPauseDisabledとariaRoleを設定したのと、completeイベントの発火(=アニメーション再生終了)時にアニメーションをリセットするためにstateを操作しています。 Lottieではstopは最初のフレームに戻りつつ動作停止。 対してpauseは一時停止と明確に役割が分かれています。 そのため、アニメーションが終了したらstopを指定することで何度も再生できるようにしています。 このあたりは自分の作りたいアニメーションを想定しつつドキュメントとにらめっこ、実際に動かしながら確認するしかないかもしれません。 余談:サンプルコードの通りだと上手く動かない箇所 見出しの通りで、サンプルをコピペしたら動かなかった箇所がありました。 対処法含めてここに載せておきます。 import * as animationData from './path/to/json' import * as animationDataをimport animationDataにすれば解決しました。 また、それにあわせてoptionsのanimationData: animationDataもanimationDataだけに省略できます。 1つのファイルで1つのアニメーションしか使わないなら、この方が短く書けますね。 こちらのIssueで話されていました。 setupTests.js jest-canvas-mockをインストールしていない状態でテストを走らせるとコケてしまうみたいです。 jest-canvas-mockのインストール $ npm i --save-dev jest-canvas-mock # or $ yarn add -D jest-canvas-mock インストールした上で setupTests.js import '@testing-library/jest-dom'; + import 'jest-canvas-mock'; setupTests.jsでimportすればなおりました。 まとめ After EffectsにBodymovinをインストールする After Effectsでアニメーションを作成する シェイプレイヤーかパスデータだけで作る 複雑なマスクを使わない 基本、エフェクトは使えないものと考える BodymovinでJSONに書き出す react-lottieをインストールして、書き出したJSONを読み込んでLottieコンポーネントを配置する 各種オプションを調整する 例なので非常に簡単なもので完成としていますが実際はここで作り込みます。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

リモートチームの存在感を伝えるツール「Remote Faces」を作ったので使ってみてください

Remote Facesと言うアプリを開発しています。機能改良して一段落したのでアナウンスツイートしました。 続くツイートに書いてあるように、使われている技術スタックは、下記のようになっています。 TypeScript React Valtio Yjs IPFS PubSub 場合によって PeerJS 場合によって Electron 技術的特徴としては、データ共有のためにサーバを介さないことで、peer-to-peerとも呼ばれます。データは参加している人の間で直接通信されます。サーバがないので、実は誰が使っているかもわかりません。ぜひ使ってみたら感想などお聞かせいただけると嬉しいです。 アプリの機能としては、いくつかのものを参考にしています。 Remotty Spatial Chat Gather Town 最大の難点は使い方が慣れるまで難しいことでしょうか。改善していければと思います。 とりあえず、興味を持っていただけた場合は触っていただければと思います。1人で複数のChromeタブでも動作を試すことができます。 ツールはこちら: GitHubはこちら: 「React Fan」というコミュニティを立ち上げていて、そのSlackでもremote-facesに関する議論や質問ができますので、よろしければご参加ください。 詳しくは、下記のページをご参照ください。 React開発者向けオンラインサロン「React Fan」の入り口ページ Slackへの招待リンクも上記ページにあります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactのfaviconの設定

publicにあるfavicon.icoを置き換えるだけでOK。ファイル名が異なる場合は、publicへ配置後、public/index.htmlの<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />のfavicon.pngを書き換えるだけでOK。 public/index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <meta name="description" content="Web site created using create-react-app" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <!-- manifest.json provides metadata used when your web app is installed on a user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ --> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <!-- Notice the use of %PUBLIC_URL% in the tags above. It will be replaced with the URL of the `public` folder during the build. Only files inside the `public` folder can be referenced from the HTML. Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> <title>React App</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> <!-- This HTML file is a template. If you open it directly in the browser, you will see an empty page. You can add webfonts, meta tags, or analytics to this file. The build step will place the bundled scripts into the <body> tag. To begin the development, run `npm start` or `yarn start`. To create a production bundle, use `npm run build` or `yarn build`. --> </body> </html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React/Material-UIでheaderにロゴ

Material-UIのロゴを./src/assets/に配置。 ./src/components/ui/Header.js import { AppBar, Toolbar } from "@material-ui/core"; import React from "react"; import useScrollTrigger from "@material-ui/core/useScrollTrigger"; import { makeStyles } from "@material-ui/styles"; import logo from "../../assets/logo_raw.svg"; function ElevationScroll(props) { const { children, window } = props; const trigger = useScrollTrigger({ disableHysteresis: true, threshold: 0, target: window ? window() : undefined, }); return React.cloneElement(children, { elevation: trigger ? 4 : 0, }); } const useStyles = makeStyles((theme) => ({ toolbarMargin: { ...theme.mixins.toolbar, marginBottom: "64px", }, logo: { height: "64px", }, })); const Header = () => { const classes = useStyles(); return ( <React.Fragment> <ElevationScroll> <AppBar> <Toolbar disableGutters> <img className={classes.logo} src={logo} /> </Toolbar> </AppBar> </ElevationScroll> <div className={classes.toolbarMargin} /> </React.Fragment> ); }; export default Header;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amplify+GraphQL+DynamoDBで時系列順に投稿を取得しようとして詰まりました

はじめに ReactでAmplifyとGraphQLを用いてDynamoDBにあるデータを時系列順に取得しようとして詰まったので、その解決方法を残しておきます。 ※ソートの方法は下記の記事を参考にしました。が私のミスでしっかりと実装できていなかったです。 それにより時系列順になるように実装しましたがデータが返ってこない事象が発生しました。 https://qiita.com/Genmaru/items/cde7f8a3accb2585e391 その際の原因と解決方法を残しておきたいと思います。 原因と解決策 GraphQLのスキーマ定義時に当初は以下のようにしてました。 type Post @model @searchable { id: ID! postBody: String! status: String createdAt: String! comments: [Comment] @connection(name: "PostComments") #relationship likes: [Like] @connection(name: "PostLikes") } このスキーマだと時系列順に取得できませんでした。 何が悪かったのか?それは『createdAt: String!』でした。 正しくは以下のように『 createdAt: AWSDateTime!』とするべきでした。 type Post @model @searchable { id: ID! postBody: String! status: String createdAt: AWSDateTime! comments: [Comment] @connection(name: "PostComments") #relationship likes: [Like] @connection(name: "PostLikes") } 最後に もし同じようなミスをしている人の助けになれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

react-adminのdataProviderのエラーとnotificationをカスタムする

はじめに react-adminは規模のわりにドキュメントが薄くてソースコードやサンプルで読み解くしかないことがけっこうある気がする。dataProviderをカスタムする際にもドキュメントに書かれていることだけではぶっちゃけあんまり実用的なことができない。 今回はカスタムしたdataProviderで発生したエラーをNotification表示するのをちょっといじりたいなというあるあるなケースでいろいろ調べ回って時間を食ったので忘れないように書いておこうと思いました。 dataProviderでのエラー処理 まずdataProviderはPromiseを返さなければならない。エラーが発生した場合Notificationのイベントをどう発生させるかについてはError Formatに書いてある実装がベターな感じ。HttpErrorオブジェクトを生成してPromise.rejectを返す。 customDataProvider.ts const dataProvider = async (type: string, resource: string, params: any): Promise<any> => { try { /* * custom logic for data provider ... */ const result = await nodeFetch(url, options); if (result.status < 200 || result.status >= 300) { const error = await result.json(); throw error; } const res = await result.json(); const response = { data: res, }; return response; } catch (e) { throw new HttpError(e.message, e.statusCode, e); } }; こんな感じでサーバーからエラーステータスで返ってきたメッセージをHttpErrorにしてthrowすればよい。HttpErrorはreact-adminがErrorオブジェクトを拡張しているもので仕様についてはドキュメントには見当たらなかったのでソースコードを読むしかないかも。 onFailure ハンドラ 上記でthrowした例外をReactコンポーネント側でどのようにフックするかについてはonFailureに書かれている。リソースの<Create> <Edit> コンポーネントでは onFailure プロパティによってエラーイベントをフックしてNotificationをカスタマイズすることができる。 Sample.tsx export const SampleCreate = (props: any) => { const notify = useNotify(); const onFailure = (error: any) => { notify(error.message, 'warning'); }; return ( <Create {...props} onFailure={onFailure}> <SimpleForm> <TextInput source="name" validate={required()} /> <TextInput source="description" validate={required()} multiline /> </SimpleForm> </Create> ); }; export const SampleEdit = (props: any) => { const notify = useNotify(); const refresh = useRefresh(); const onFailure = (error: any) => { notify(error.message, 'warning'); refresh(); }; return ( <Edit {...props} onFailure={onFailure}> <SimpleForm> <TextInput source="name" validate={required()} /> <TextInput source="description" validate={required()} multiline /> </SimpleForm> </Edit> ); }; useNotify, useRefreshなどについてはuseDataProviderにさらっと書いてある。notify()の表示タイプを示す第2引数の種類に関してドキュメントのどこにも見当たらないが、notificationActions.tsによるとsuccess info warning errorの4タイプがある。指定するタイプによってNotificationの背景色が変わるようだ。 onFailureと同じように処理成功時のイベントをonSuccessでカスタマイズすることが可能である。これらを<Edit>で扱うときに注意すべきなのがmutationMode。Optimistic renderingが有効になるoptimistic, undoable(default) の場合NotificationはdataProviderに処理が適用される前に副作用として発生する。よって処理の仕方によっては一見正常にデータが更新されたように見えたあと(undoableの場合はローカルでの正常処理の5秒後)にエラーNotificationが表示されることになる。これを嫌って mutationMode="pessimistic" としてOptimistic renderingを無効にするかどうかはUXによりケースバイケースになるだろう。 このあたりは色々試してみたが個人的な趣味としては、とりあえず上記のようにundoableでEditコンポーネントを表示したままエラーを出すのが良いと思った。一点、エラー表示のあとrefresh()を実行してフォーム値を更新前の状態に戻すことで再編集を促すようにするのがよいかなと思う。<Edit>のデフォルト挙動は処理成功時に<List>にリダイレクトするので画面遷移の違いでもエラーに気づきやすいはず。 authProviderのcheckError dataProviderでthrowされた例外はauthProviderのcheckErrorでも補足される。なので認証関係のエラーなのかその他のエラーなのかを区別しておかないといけない。以下のようなテキトーな処理だとAPIエラーが発生するたびにログイン画面に戻ることになるので下記ではerrorの内容によってここで例外をthrowするかどうかを判断したほうがいいかもしれない。 customAuthProvider.ts ... checkError: async (error: any): Promise<any> => { if (error) { throw error; } return; }, ... おわりに 各コンポーネントの関係性が把握できればわりとゆるい仕様で柔軟にカスタマイズできることがわかるが、ここにたどり着くのに時間がかかった。もうちっとケースごとのベストプラクティスなコードがウェブに散在してても良い気がするんだけど意外とないよなreact-admin。便利だと思うんだけど。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

@material-ui/stylesのmakeStyles

./components/ui/Header.js import { AppBar, Toolbar, Typography } from "@material-ui/core"; import React from "react"; import useScrollTrigger from "@material-ui/core/useScrollTrigger"; import { makeStyles } from "@material-ui/styles"; function ElevationScroll(props) { const { children, window } = props; const trigger = useScrollTrigger({ disableHysteresis: true, threshold: 0, target: window ? window() : undefined, }); return React.cloneElement(children, { elevation: trigger ? 4 : 0, }); } const useStyles = makeStyles((theme) => ({ toolbarMargin: { ...theme.mixins.toolbar, }, })); const Header = () => { const classes = useStyles(); return ( <React.Fragment> <ElevationScroll> <AppBar> <Toolbar> <Typography variant="h3">Header</Typography> </Toolbar> </AppBar> </ElevationScroll> <div className={classes.toolbarMargin} /> </React.Fragment> ); }; export default Header;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[簡単]モダン構成なSPAで作るチュートリアル⑤(新規登録機能)

SPAで作るタスク管理アプリのチュートリアル 今回は第⑤弾で新規登録機能をやっていくで 今回は第④弾でReactで一覧テーブル作成する部分をやっていくで ①環境構築(Docker/Laravel/React.js/Material-UI) ②React側でルーティング設定 ③API取得してReactで一覧テーブル作成 ④API取得してReactで一覧表示 ⑤新規登録機能 ⑥編集・削除機能 概要 こんな感じで入力フォームから登録するとDBにデータが登録されて、一覧表示できる机上を実装していくで まずバックエンドから Controllerの処理 function create()を作成する PostController.php public function index() { $posts = Post::all(); return response()->json($posts, 200); } //index()の下に追記する public function create(Request $request) { $post = new Post; $post->name = $request->name; $post->content = $request->content; $post->save(); return response()->json($post, 200); } 一般的なCRUDのうちのceate処理やね。 レスポンスにはjosn形式で登録したpostsを返してるで ルーティングを記載 /routes/api.phpに追記 api.php Route::group(['middleware' => 'api'], function(){ Route::get('posts', 'App\Http\Controllers\Api\PostController@index'); Route::post('post/create', 'App\Http\Controllers\Api\PostController@create'); //追記 }); /api/post/createで叩かれたときに先程定義したcreate()を叩くようにする これはindex()と同じ要領で考えればOKやね PostManでテストしてみる テストとしては * POSTでhttp://localhost/api/post/create * json形式でnameとcontentを飛ばす sendをクリックすると画像のようにresponseが返ってくればOKやで 念のためにDBにデータが入ってるか確認しておこう id=4が追加されていることが分かる 次からフロント側を実装していくな コミットしとくで $ git add . $ git commit -m"新規登録機能のバックエンド実装" 入力フォーム用のコンポーネント作成する ここからはさっきテストした新規登録のAPIを叩くフロントエンドを実装してくで Home.js import React, { useState, useEffect } from 'react'; import { Button, Card } from '@material-ui/core'; import { makeStyles, createStyles } from '@material-ui/core/styles'; import MainTable from '../components/MainTable'; import axios from 'axios'; import PostFrom from '../components/PostFrom'; //新しく作るフォームのコンポーネントの呼び出し jsxの「タスク管理」の直下にCardで囲ったPostFormの呼び出し Home.js <h1>タスク管理</h1> <Card className={classes.card}> //追記 <PostFrom /> //追記 </Card> //追記 <Card className={classes.card}> {/* テーブル部分の定義 */} <MainTable headerList={headerList} rows={rows} /> </Card> </div> 呼び出しの設定をした/components/PostFrom.jsを作成する 作成したファイルに下記を記載する PostForm.js import React from 'react'; import { TextField, Button } from '@material-ui/core'; import { makeStyles, createStyles } from '@material-ui/core/styles'; const useStyles = makeStyles((theme) => createStyles({ textArea: { marginRight: theme.spacing(2), }, })); function PostFrom(props) { const classes = useStyles(); return ( <form> <TextField id="name" label="タスク名" variant="outlined" className={classes.textArea} name="name" value={data.name} /> <TextField id="content" label="内容" variant="outlined" className={classes.textArea} name="content" value={data.content} /> <Button color="primary" variant="contained" href="/" >登録</Button> </form> ); } export default PostFrom; ビルドする $ make npm-dev 下記のように入力フォームが出てきたな 入力しようとすると分かるけど、入力できへんようになっているな? 親コンポーネントから、データと入力値変更用のfunctionを渡してあげなあかんからそれを実装するで postsの下にformDataを設定する Home.js //postsの状態を管理する const [posts, setPosts] = useState([]); //フォームの入力値を管理するステートの定義 const [formData, setFormData] = useState({name:'', content:''});    //追記 getPostData()の下にinputChange()を定義する inputChange()は入力フォームの値変更用のfunctionやで Home.js //一覧情報を取得しステートpostsにセットする const getPostsData = () => { axios .get('/api/posts') .then(response => { setPosts(response.data); }) .catch(() => { console.log('通信に失敗しました'); }); } //入力がされたら(都度)入力値を変更するためのfunction const inputChange = (e) => { const key = e.target.name; const value = e.target.value; formData[key] = value; let data = Object.assign({}, formData); setFormData(data); } PostFormコンポーネントに定義したステートとinputChangeを渡す Home.js - <PostFrom /> + <PostFrom data={newData} inputChange={inputChange} /> PostFrom.js側で渡ってきたpropsを受ける propsで受けてTextFieldに渡す PostForm.js function PostFrom(props) { const classes = useStyles(); const { data, inputChange } = props;//追記 return ( <form> <TextField id="name" label="タスク名" variant="outlined" className={classes.textArea} name="name" value={data.name} onChange={inputChange} /> //valueとonChange追記 <TextField id="content" label="内容" variant="outlined" className={classes.textArea} name="content" value={data.content} onChange={inputChange} /> //valueとonChange追記 <Button color="primary" variant="contained" href="/" onClick={btnFunc}>登録</Button> </form> ); ビルド $ make npm-dev 入力できるようになったな。 あとは登録ボタンが押されたときに入力されている値をjsonとしてhttp://localhost/api/post/createを叩けばOKや cratePost()を実装して登録ボタンに渡す inputChangeの定義部分の下辺りにcreatePostを定義する Home.js const createPost = async() => { //空だと弾く if(formData == ''){ return; } //入力値を投げる await axios .post('/api/post/create', { name: formData.name, content: formData.content }) .then((res) => { //戻り値をtodosにセット const tempPosts = posts tempPosts.push(res.data); setPosts(tempPosts) setFormData(''); }) .catch(error => { console.log(error); }); } PostFormコンポーネントに渡す Home.js //btnFuncを渡す <PostFrom data={formData} btnFunc={createPost} inputChange={inputChange} /> PostForm.jsで受ける PostFrom.js // btnFuncを追記 const { data, inputChange, btnFunc} = props; 登録ボタンにonClickを渡す PostForm.js <Button color="primary" variant="contained" href="/" onClick={btnFunc}>登録</Button> //onClickを追記 ビルドする $ make npm-dev データを登録してみる タスク名='フロント君' 内容='新規データ' で登録ボタンをクリックすると下記のようにデータが作られる データが登録されて一覧にも表示された。 $ git add . $ git commit -m"新規登録のフロントエンド実装" 次はいよいよ最後⑥やな登録したデータに対して編集機能と削除機能を実装していくで ほな、LGTMよろしゅーです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

今から学ぶReduxの基礎(実践編)

Reduxを使ってみる Reduxを使って以下のようなカウントアップする簡単なアプリケーションを作成していきます。 カウントを行うコンポーネントが2つ存在し、propsで受け渡すことなく状態を共有しています。 事前準備 サンプルアプリの作成とReduxのインストールから行います。 // アプリケーション作成 npx create-react-app redux-practice // Reduxインストール yarn add redux react-redux Redux Hooks まずはHooksを使用した書き方で作成します。 アプリケーションの名前は「redux-practice」とします。 以下のようなファイル構成でファイルを作成します。 redux-practice ├── public └── src ├── actions │ └── index.js ├── components │ ├── Another.js │ └── App.js └── reducers │ └── index.js └── index.js 次に以下のようにactions/index.jsを編集していきます。 今回はカウンターアプリなので「increment」と「decrement」というアクションを作成しています。 actions/index.js export const INCREMENT = 'INCREMENT' export const DECREMENT = 'DECREMENT' export const increment = () => { return {type: INCREMENT} } export const decrement = () => { return {type: DECREMENT} } 続いて、reducers/index.jsを以下のように編集します。 こちらは、先ほど定義したActionと最新のstateを自動的に受け取って新しいstateを返しています。 reducers/index.js import { INCREMENT, DECREMENT } from '../actions' const initialState = { value: 0 } const count = (state = initialState, action) => { switch (action.type) { case INCREMENT: return { value: state.value + 1 } case DECREMENT: return { value: state.value - 1 } default: return state } } export default 次にcomponents/App.jsで表示部分を以下のように作成していきます。 以下では「+」ボタンが押された時に「increment」、「-」ボタンが押された時に「decrement」というイベントを発行するようにしています。 components/App.js import React from 'react' import { useDispatch, useSelector } from 'react-redux' import { increment, decrement } from '../actions' const App = () => { const value = useSelector((state) => state.value) const dispatch = useDispatch(); return ( <> <div>count:{value}</div> <button onClick={() => dispatch(increment())}>+</button> <button onClick={() => dispatch(decrement())}>-</button> </> ); } export default App 最後に他のコンポーネントからstateを参照してみましょう。 components/Another.jsでuseSelectorを使うことで、stateの値を参照することができます。 components/Another.js import React from 'react' import { useSelector } from 'react-redux' const Another = () => { const value = useSelector((state) => state.value) return ( <div>AnotherComponentCount:{value}</div> ); } export default Another ReduxがHooksに対応する前の書き方 先ほどはHooksを使用した書き方を紹介しましたが、 古いコードではHooksを使用しない書き方を使っていることもあるので、 先ほどのコードを古いコードで書き換えてみます。 components/App.js import React from 'react' import { connect } from 'react-redux' import { increment, decrement } from '../actions' const App = (props) => { return ( <> <div>count:{props.value}</div> <button onClick={props.increment}>+</button> <button onClick={props.decrement}>-</button> </> ); } // stateから必要な情報をコンポーネントにマッピングする関数 const mapStateToProps = state => ({ value: state.count.value }) // dispatch関数をコンポーネントにマッピングする関数 const mapDispatchToProps = dispatch => ({ increment: () => dispatch(increment()), decrement: () => dispatch(decrement()) }) // 指定したコンポーネントのpropsにstateを混ぜ込む export default connect(mapStateToProps, mapDispatchToProps)(App) components/Another.js import React from 'react' import { connect } from 'react-redux' const Another = (props) => { return ( <div>AnotherComponentCount:{props.value}</div> ); } // stateから必要な情報をコンポーネントにマッピングする関数 const mapStateToProps = state => ({ value: state.count.value }) // 指定したコンポーネントのpropsにstateを混ぜ込む export default connect(mapStateToProps)(Another) connectという関数を使う必要があるため、少し冗長になってしまいます。。。 Redux Toolkitを使う ここまでReduxのコードを見てきて感じたかもしれませんが、Reduxを使うとコード量が増えます。 その課題を解決するのがRedux Toolkitです。 実際にRedux Toolkitでコードを書いていきましょう。 ファイル構成は以下 redux-practice ├── public └── src ├── components │ ├── Another.js │ └── App.js └── counter │ └── counter.js └── index.js まず、counter/counter.jsを以下のように編集します。 counter/counter.js import { createSlice } from '@reduxjs/toolkit' const initialState = { value: 0 } export const counterSlice = createSlice({ name: 'counter', initialState, reducers: { increment: (state) => ({...state, value: state.value + 1}), decrement: (state) => ({...state, value: state.value - 1}) } }) Redux Toolkitで提供されているcreateSlice関数を使用すると、ActionとReducerをまとめて記述することができます。 components/App.jsとcomponents/Another.jsは以下のように編集します。 (ここら辺は以前とほぼ同じです) components/App.js import React from 'react' import { useDispatch, useSelector } from 'react-redux' import { counterSlice } from '../counter/counter'; const App = () => { const value = useSelector((state) => state.value) const dispatch = useDispatch(); const { increment, decrement } = counterSlice.actions; return ( <> <div>count:{value}</div> <button onClick={() => dispatch(increment())}>+</button> <button onClick={() => dispatch(decrement())}>-</button> </> ); } export default App components/Another.js import React from 'react' import { useSelector } from 'react-redux' const Another = () => { const value = useSelector((state) => state.value) return ( <div>AnotherComponentCount:{value}</div> ); } export default Another 最後にindex.jsを編集して終わりです。 index.js import React from 'react' import ReactDOM from 'react-dom' import { Provider } from 'react-redux' import { configureStore } from '@reduxjs/toolkit' import App from './components/App' import Another from './components/Another' import {counterSlice} from './counter/counter' const store = configureStore({reducer: counterSlice.reducer}) ReactDOM.render( <Provider store={store}> <App/> <Another/> </Provider>, document.getElementById('root') ); useReducerとuseContextを使って、Reduxのようなグローバルステートを実現する useReducerとuseContext これまでReduxを使用してグローバルステートを実現していましたが、 HooksのuseReducerとuseContextを使ってReduxのようにグローバルステートを実現することができます。 ではuseReducerとuseContextでコードを書いていきましょう。 ファイル構成は以下 redux-practice ├── public └── src ├── actions │ ├── index.js ├── components │ ├── Another.js │ └── App.js └── counter │ └── counter.js └── index.js まず、actions/index.jsは以下のように編集します。 (最初のコードと同じです) actions/index.js export const INCREMENT = 'INCREMENT' export const DECREMENT = 'DECREMENT' export const increment = () => { return {type: INCREMENT} } export const decrement = () => { return {type: DECREMENT} } 続いて、counter/counter.jsを以下のように編集します。 (こちらも最初のコードとほぼ同じです) counter/counter.js export const counterReducer = (state, action) => { switch (action.type) { case "INCREMENT": return {value: state.value + 1} case "DECREMENT": return {value: state.value - 1} default: return state } } 次にcomponents/App.jsを以下のように編集します。 components/App.js import React, { createContext, useReducer } from "react"; import { counterReducer } from '../counter/counter' import Another from './Another' import { increment, decrement } from "../actions"; export const CounterContext = createContext(); const App = () => { const initialState = { value: 0 } const [state, dispatch] = useReducer(counterReducer, initialState); return ( <> <CounterContext.Provider value={{state, dispatch}}> <div>count:{state.value}</div> <button onClick={() => dispatch(increment())}>+</button> <button onClick={() => dispatch(decrement())}>-</button> <Another/> </CounterContext.Provider> </> ); } export default App ここでのポイントは以下の3つです。 useReducerの引数にReducerとstateの初期値を渡し、その返り値として最新のstateとdispatchを受け取っている CounterContextという定数を定義し、CounterContext.Providerのvalue属性にグローバルで扱いたいstateを設定 CounterContext.Providerコンポーネントに囲まれているコンポーネントは、設定したstateを使用できる 上記のようにReact Hooksを使えばReduxのように状態管理を行えるようになります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

@material-ui/stylesでtypographyのstylesを変更

./components/ui/Theme.js import { createMuiTheme } from "@material-ui/core/styles"; export default createMuiTheme({ palette: { primary: { main: "#19448e", }, }, typography: { h3: { fontWeight: 300, }, }, }); ./components/ui/Theme.js import { createMuiTheme } from "@material-ui/core/styles"; export default createMuiTheme({ palette: { primary: { main: "#19448e", }, }, typography: { h3: { fontWeight: 600, }, }, });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React/Material-UIのheaderでTypography

./components/ui/Header.js import { AppBar, Toolbar, Typography } from "@material-ui/core"; import React from "react"; import useScrollTrigger from "@material-ui/core/useScrollTrigger"; function ElevationScroll(props) { const { children, window } = props; const trigger = useScrollTrigger({ disableHysteresis: true, threshold: 0, target: window ? window() : undefined, }); return React.cloneElement(children, { elevation: trigger ? 4 : 0, }); } const Header = () => { return ( <ElevationScroll> <AppBar> <Toolbar> <Typography variant="h3">Header</Typography> </Toolbar> </AppBar> </ElevationScroll> ); }; export default Header;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

@material-ui/stylesでheaderのcolorを変更

./components/ui/Header.js import { AppBar, Toolbar } from "@material-ui/core"; import React from "react"; import useScrollTrigger from "@material-ui/core/useScrollTrigger"; function ElevationScroll(props) { const { children, window } = props; const trigger = useScrollTrigger({ disableHysteresis: true, threshold: 0, target: window ? window() : undefined, }); return React.cloneElement(children, { elevation: trigger ? 4 : 0, }); } const Header = () => { return ( <ElevationScroll> <AppBar color="primary"> <Toolbar>Header</Toolbar> </AppBar> </ElevationScroll> ); }; export default Header; ./components/ui/Theme.js import { createMuiTheme } from "@material-ui/core/styles"; export default createMuiTheme({ palette: { primary: { main: "#19448e", }, }, }); App.js import logo from "./logo.svg"; import "./App.css"; import { ThemeProvider } from "@material-ui/styles"; import theme from "./components/ui/Theme"; import Header from "./components/ui/Header"; function App() { return ( <ThemeProvider theme={theme}> <Header /> <p>Hello Material-UI!</p> </ThemeProvider> ); } export default App;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React/Material-UIでheaderを実装④(colorを変更)

./components/ui/Header.js import { AppBar, Toolbar } from "@material-ui/core"; import React from "react"; import useScrollTrigger from "@material-ui/core/useScrollTrigger"; function ElevationScroll(props) { const { children, window } = props; const trigger = useScrollTrigger({ disableHysteresis: true, threshold: 0, target: window ? window() : undefined, }); return React.cloneElement(children, { elevation: trigger ? 4 : 0, }); } const Header = () => { return ( <ElevationScroll> <AppBar color="primary"> <Toolbar>Header</Toolbar> </AppBar> </ElevationScroll> ); }; export default Header; ./components/ui/Header.js import { AppBar, Toolbar } from "@material-ui/core"; import React from "react"; import useScrollTrigger from "@material-ui/core/useScrollTrigger"; function ElevationScroll(props) { const { children, window } = props; const trigger = useScrollTrigger({ disableHysteresis: true, threshold: 0, target: window ? window() : undefined, }); return React.cloneElement(children, { elevation: trigger ? 4 : 0, }); } const Header = () => { return ( <ElevationScroll> <AppBar color="secondary"> <Toolbar>Header</Toolbar> </AppBar> </ElevationScroll> ); }; export default Header;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Router 初歩の初歩

はじめに Reactで開発を進める上で必ずと言っていいほど使用されているReact Router。 ドキュメントが充実しているので、目を通せば大体のことは、解決すると思いますが、使い方の整理の意味を込めて文章としてまとめます。 公式ドキュメント | React Router 対象読者 React初学者 SPAにおけるルーティングのおさらい さらっとですが、SPAにおけるルーティングについて整理します。 ※ここら辺、理解してる人は読み飛ばして下さい。 SPAにおけるルーティングを一言で説明すると、『DOMの動的な書き換えによってページ遷移を擬似的に実現するとともに、ブラウザのセッション履歴をそれに同期させる事』 です。 SPAはサーバーへの初回リクエストに対して、アプリ全体が記述されたJSのコードと、そこで使用されるアセットファイルがごそっと返されます。 以降、ページ遷移はアプリが動的にDOMを書き換えることで、移動してるように見せてるに過ぎない点が大きな特徴です。即ち、ブラウザのアドレスバーのURLが書き換わっても、実際にサーバーへリクエストを投げることは原則的には無いという事です。 これは、リクエストされたURLに対して、そのURLに紐づく適切なページ内容をアプリケーションサーバーがクライアントに返すRailsやLaravelのようなフレームワークとは異なりますね。 では、SPAでルーティングを実現するために、どうしてるかというと、これには、History APIという技術が使われています。HTML5から導入されたpushStateとreplaceStateで、ブラウザのセッション履歴に任意のURLを追加したり、特定の履歴を書き換えたりしながら、JSによって履歴のURLを制御することを可能にしています。 そして、SPAでのルーティングの適用単位はコンポーネントです。 RailsやLaravel等は、Controllerがルーティングに対応する名前を持つViewを選び、それを使用してレスポンスを返しますが、Reactはルートのコンポーネントから階層を下り、「ここから先はこのコンポーネントがマウントされる、このパスの時は別のコンポーネントがマウントされるといったようにコンポーネントそのものがルーティングとなります。 上記を意識しながら、次セクションで実際に試してみましょう。 実際にルーティングを定義してみる まずはreact-router-domをインストールします。 npm install react-router-dom 次にサンプルでいくつかコンポーネントを作っておきましょう。 Home.tsx export const Home = () => { return ( <> <h1>Homeページです</h1> </> ); }; Sample01.tsx export const Sample1 = () => { return ( <> <h1>Page01</h1> </> ); }; Sample02.tsx export const Sample02 = () => { return ( <> <h1>Page02</h1> </> ); }; 次にsrc直下にrouteディレクトリを作成します。 その中に、ルーティングを定義していきます。 手順としては以下の通り ① ルーティング設定(URLとそれに対応するコンポーネントを決める) ② ①が決まったら、Linkタグを使用して、Linkを設置 コードを書く上で気をつけることは2点です。 ・Router(BrowserRouter)の中にRouteを定義 ・Routeタグにpathと、コンポーネントを定義 以下に例を示します。 route/index.tsx(コンポーネントの読み込みは任意で) import { React } from "react"; import { Route, Switch } from "react-router-dom"; import { Home } from "../component/reactRouter/Home"; import { Sample01 } from "../component/reactRouter/Sample01"; import { Sample02 } from "../component/reactRouter/Sample02"; export const RootRouter: React.VFC = () => ( <Switch> <Route exact path="/"> <Home /> </Route> <Route exact path="/sample01"> <Sample01 /> </Route> <Route exact path="/sample02"> <Sample02 /> </Route> </Switch> ); 続いて、App.tsxに上記のルーティングを読み込んで、Linkタグでリンクを設置していきます。 App.tsx import * as React from "react"; import { RootRouter } from "./route"; import { Link, BrowserRouter as Router } from "react-router-dom"; function App(): React.VFC { return ( <div className="App"> <Router> <RootRouter /> <Link to="/">Home</Link> <br /> <Link to="/sample01">Page1</Link> <br /> <Link to="/sample02">Page2</Link> </Router> </div> ); } export default App; 上記で、一番シンプルな実装が実現しました。 React Routerでパラメータを扱う 実案件では、URLパラメータを扱う場面は多々あります。以下にその方法を示します。 URLパラメータ 例えば、UserIdをAPIの引数にして、UserIdに応じたページを取得するというケース等、動的に変化する部分をReactRouterでどのように扱うのでしょうか。 サンプルとして以下のルーティングの追加と新しいコンポーネントを作成します。UserId:1のユーザーがログインしている状態というケースを想定して、サンプルを書きます。 (※ APIはJSONPlaceholderを使わせてもらいます) URLパラメータを扱う場合、パスの書き方として/:idといった書き方になります。:idの部分は任意で書くことが可能で、例えば、/:useridとかでも問題ありません。 route/index.tsx(抜粋) import { Profile } from "../component/reactRouter/Profile"; export const RootRouter: React.VFC = () => ( <> {/* 以下コンポーネントを追加 */} <Route exact path="/user/:id"> <Profile /> </Route> </> ); 以下が新しく作成したコンポーネントです。 URLパラメーターを扱いたい場合は、useParamsを使用します。これを使って、ルーティングで定義した:idの部分を抽出し、取得したidを元にAPIを叩いています。 Profile.tsx import React, { useState, useEffect } from "react"; import { useParams } from "react-router-dom"; export const Profile = () => { const [user, setUser] = useState(null); const { id } = useParams(); // useIdを取得  // useParamsを使って取得したIDを使ってAPIを叩く useEffect(() => { const fetchUser = async () => { const res = await fetch( `https://jsonplaceholder.typicode.com/users/${id}` ); const userData = await res.json(); await setUser(userData); }; fetchUser(); }, [id]); return ( <> <h1>Profile Detail</h1> {user ? ( <ul> <li>UserId: {user.id}</li> <li>UserName: {user.name}</li> <li>E-mail{user.email}</li> <li>Address: {user.address.street}</li> </ul> ) : ( <>Loading</> )} </> ); }; 最後に先ほど作成したHomeコンポーネントに遷移先を設置して、動作確認します。遷移先のパスは本来であれば、グローバルステートから、UserId情報を取得する形が一般的かと思いますが、サンプルとして/user/1をベタ書きしてます。(本来であれば/user/:idになる) Home.tsx import { Link } from "react-router-dom"; export const Home = () => { return ( <> <h1>Homeページです</h1> <div> <Link exact to="/user/1"> Profile </Link> </div> </> ); }; 最後に 他にも便利なhooksが用意されているので、公式ドキュメントに目を通しておくと良いかと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React/Material-UIでheaderを実装③

./components/ui/Header.js import { AppBar, Toolbar } from "@material-ui/core"; import React from "react"; import useScrollTrigger from "@material-ui/core/useScrollTrigger"; function ElevationScroll(props) { const { children, window } = props; const trigger = useScrollTrigger({ disableHysteresis: true, threshold: 0, target: window ? window() : undefined, }); return React.cloneElement(children, { elevation: trigger ? 4 : 0, }); } const Header = () => { return ( <ElevationScroll> <AppBar position="fixed"> <Toolbar>Header</Toolbar> </AppBar> </ElevationScroll> ); }; export default Header; App.js import logo from "./logo.svg"; import "./App.css"; import Header from "./components/ui/Header"; function App() { return ( <div className="App"> <Header /> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> <p>Hello Material-UI!</p> </div> ); } export default App;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React/Material-UIでheaderを実装②

./components/ui/Header.js import { AppBar, Toolbar } from "@material-ui/core"; import React from "react"; const Header = () => { return ( <AppBar position="static"> <Toolbar>Header</Toolbar> </AppBar> ); }; export default Header; App.js import "./App.css"; import Header from "./components/ui/Header"; function App() { return ( <div className="App"> <Header /> Hello Material-UI! </div> ); } export default App;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React/Material-UIでheaderを実装①

./components/ui/Header.js import { AppBar, Toolbar } from "@material-ui/core"; import React from "react"; const Header = () => { return ( <AppBar> <Toolbar>Header</Toolbar> </AppBar> ); }; export default Header; App.js import "./App.css"; import Header from "./components/ui/Header"; function App() { return ( <div className="App"> <Header /> Hello Material-UI! </div> ); } export default App;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む