3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

フロントエンド初心者がNEM Library使ってメッセージから文字列検索できるようにしてみた(その1)

Posted at

以前作った環境は途中で断念しました。。。
途中まで、TypeScript版とJavaScript+Babel版で並行していましたが、
TypeScriptが圧倒的に理解しやすいのと、JavaScript+Babel版ではエラーが出て解消の目途が立たなかったため、TypeScript一本に絞りました。

というわけで、まずはTypeScriptの環境構築です。
BabelはTypeScriptだといらない気がしますので使いません。

TypeScript環境構築

https://ics.media/entry/16329#webpack-ts-react
こちらを見てさらっと構築します。

nem-libraryとnem-sdkもDevDependencyにインストールします。(nmp での -d オプション)

webpack.config.jsonに以下を書くことを忘れないようにします。

  node: {
    console: false,
    fs: 'empty',
    net: 'empty',
    tls: 'empty'
  }

ReactのUIコンポーネントライブラリは以下を見て、適当にMaterial-uiを使うことにします。
https://qiita.com/tashxii/items/6e3e25e8b0f690570920

material-uiと@types/material-uiをDevDependencyに入れます。(nmp での -d オプション)

package.json
    "devDependencies": {
        "@types/material-ui": "^0.20.8",
        "material-ui": "^0.20.1",
        "nem-library": "^1.0.2",
        "nem-sdk": "^1.6.4",
        "ts-loader": "^4.3.0",
        "typescript": "^2.8.3",
        "webpack": "^4.8.1",
        "webpack-cli": "^2.1.3"
    },
    "dependencies": {
        "@types/react": "^16.3.13",
        "@types/react-dom": "^16.0.5",
        "react": "^16.3.2",
        "react-dom": "^16.3.2"
    },

サンプルコード

以下のようなページを作ります。
1.ページ最上部に"Hello NEM World"を表示します
2.その下にTextFeild(Material-ui)でaddressの入力欄を設定します
3.addressを入力すると、ConsoleLogにAccountInfoのBalanceを出力します

まず、dist以下でMaterial-uiでのhtmlとcssを作ります。
特にCSSは手が回っていないので、適切に修正してください。

Material-uiのGetStartedを参考に作りますが、CSSはWebフォント関連の記事を読んで別ファイルにしました。

index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0, maximum-scale=1, minimum-scale=1">
  <link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
<div id="app"></div>
<script defer src="main.js"></script>
</body>
</html>
main.css
@import url('https://fonts.googleapis.com/css?family=Roboto:300,400,500');

html {
    font-family: 'Roboto', sans-serif;
}
  
body {
    font-size: 13px;
    line-height: 20px;
    background:#eee;
}

# app {
    display: flex;
    justify-content: center;
    align-items: center;
    text-align: center;
  }

ここまでのフォルダ構成は以下です。

myproject
 ├dist
 │  ├index.html
 │  └main.css
 ├node_modules
 │  └省略
 ├src
 ├myproject.code-workspace
 ├package.json
 ├tsconfig.json
 ├webpack.config.js
 ├yarn-error.log
 └yarn.lock

src以下にmainのtsxファイルを作ります。
htmlのappタグに入れる仕組みを作る感じですね。

MuiThemeProviderの子要素は<div>でひとまとめにしておいたほうがいいです。
複数要素を入れるとwarning出ました。
Reactコンポーネントを継承しているsub-compornent(後述)を記述します。

main.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {SubComponent} from './sub-component';
import {NEMLibrary, NetworkTypes} from "nem-library";
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';

NEMLibrary.bootstrap(NetworkTypes.TEST_NET);

class App extends React.Component {
  render() {
    return (
          <MuiThemeProvider>
            <div>
              <SubComponent name="Hello NEM World"/>
            </div>
          </MuiThemeProvider>
    );
  }
}

ReactDOM.render(<App/>, document.querySelector('#app'));

main.tsxから呼び出すsub-component.tsxを作ります。
詳細は後述します。

sub-component.tsx
import * as React from 'react';
import {AccountHttp, Address, Account} from "nem-library";
import TextField from 'material-ui/TextField';

interface IProps {
    name: string;
}

interface IState {
    address: string;
}

export class SubComponent extends React.Component<IProps, IState> {
    constructor(props) {
        super(props);
        this.state={
            address:""
        }
        
        this.handleChange_address = this.handleChange_address.bind(this);
    }

    handleChange_address = (event :React.FormEvent<HTMLInputElement>, newvalue:string) => {
        this.setState({address:newvalue});
    };

    private outputBalance(){
        try{
            const address = new Address(this.state.address);
            const accountHttp = new AccountHttp();
            accountHttp.getFromAddress(address).subscribe(accountInfoWithMetaData => {
                console.log(accountInfoWithMetaData.balance);
            });
        }catch(e){
            console.log("err : " + e);
        }
    }

  render() {
    this.outputBalance();
    return (
        <div>
          <h2>{this.props.name}</h2>
          <TextField
                    id="address"
                    value={this.state.address}
                    floatingLabelText="address"
                    onChange={this.handleChange_address}
                    style={{
                        margin: '0 auto',
                        width:`400px`
                    }} 
                />
        </div>
    );
  }
}

ビルド後のフォルダ構成はこんな感じです。

myproject
 ├dist
 │  ├index.html
 │  ├main.css
 │  └main.js
 ├node_modules
 │  └省略
 ├src
 │  ├main.tsx
 │  └sub-component.tsx
 ├myproject.code-workspace
 ├package-lock.json
 ├package.json
 ├tsconfig.json
 ├webpack.config.js
 ├yarn-error.log
 └yarn.lock

Reactについて

超ざっくりな理解だと、状態遷移を監視して、状態遷移したらDOM(HTMLを作る要素)を描画する仕組みのようです。
状態(state)と描画(render( ))が中心ですね。

あまり理解していませんが、Redux(最上位でまとめてstate管理?)っぽくするならprops(引数)の重要度が上がると思いますが、とりあえずReduxっぽくしないので、propsの重要度は低いという認識です。

以下でぐるぐる回る感じです。(動作からの推測)
1.オブジェクトが呼び出されるとconstructor( )がコールされます。
2.その後にrender( )がコールされ、returnにDOMを書くこと(JSX)で、描画します。
3.値の取得等は、このrender( ) return内のJSXにeventとして書きます。
4.eventハンドラはコンストラクタ内でbind(this)して、setState( )を含んだメソッドとして定義します。
5.setState( )がコールされると次にrender( )がコールされる
6.2へ戻る

1.オブジェクトが呼び出されるとconstructor( )がコールされます。

TypeScriptでは、型定義が必要になります。
React.Componentを継承し、IPropsとIStateをinterfaceとして型定義しています。
IPropsで定義したID(?)を上位のJSXで渡します。

サンプルコードでは、

main.tsx
      <SubComponent name="Hello NEM World"/>

でSubComponent のconstructor( )が呼ばれる感じです。
(IProps:)nameに文字列を渡すと、

sub-component.tsx
interface IProps {
  name: string;
}

//省略

export class SubComponent extends React.Component<IProps, IState> {
    constructor(props) {
        super(props);

ここのconstructorのpropsにprops:nameとして渡ってきます。
propsはinterface で定義しているIPropsになります。

constructor内で初期化処理する際に、propsに引数として入ってきますが、
propsはメンバとして確保されているので、constructor外でもクラス内であれば継続的に使えるようです。
たぶん、super(props)でやってるっぽいです。
推奨度合いは不明(書き換えはNG?)ですので、constructorでメンバにコピーしたほうがよいかもしれません。

2.その後にrender( )がコールされ、returnにDOMを書くこと(JSX)で、描画します。

render( )はreturn( JSX形式 )を含めます。
render( )内で、return以外のメソッドもコールできます。
サンプルでは表示したい内容の取得処理(outputBalance())はここでやっています(よいのかはわかりません)

sub-component.tsx
 render() {
    this.outputBalance();
    return (
        <div>
            <h2>{this.props.name}</h2>
            <TextField
                    id="address"
                    value={this.state.address}
                    floatingLabelText="address"
                    onChange={this.handleChange_address}
                    style={{
                        margin: '0 auto',
                        width:`400px`
                    }} 
            />
            <br />
        </div>
    );
  }

outputBarance( )内では、
1.new Address( )でstate.address(文字列)からAddressのインスタンスを作成
2.AccountHttpでAccountWithMetadataを取得
3.AccountWithMetadataのBaranceをconsole.logで出力
しています。

sub-component.tsx
    private outputBarance(){
        try{
            const address = new Address(this.state.address);
            const accountHttp = new AccountHttp();
            accountHttp.getFromAddress(address).subscribe(accountInfoWithMetaData => {
                console.log(accountInfoWithMetaData.balance);
            });
        }catch(e){
            console.log("err : " + e);
        }
    }

1でAddressのインスタンスが作成できないと、エラーログを残すだけにしています。(イマイチ)
初回はstate.addressが空文字ですので、インスタンス作成できず、エラーになります。

JSXとしては、<h2>要素にmain.tsxから渡されたprops.nameを表示し、
TextFieldに、state.addressを表示しています(空になっています)。

3.値の取得等は、このrender( ) return内のJSXにeventとして書きます。

JSX内の以下で、onChangeのイベントとして、handleChange_addressを定義しています。
ここでは、Material-uiのTextFieldのonChangeイベントに紐づけています。

sub-component.tsx
            <TextField
                    省略
                    onChange={this.handleChange_address}
                    省略
            />

4.eventハンドラはコンストラクタ内でbind(this)して、setState( )を含んだメソッドとして定義します。

eventハンドラは以下の2つの処理を実装します。

```tsx:sub-component.tsx(1.コンストラクタでthisをbindします)
constructor(props) {
省略
this.handleChange_address = this.handleChange_address.bind(this);
}

```tsx:sub-component.tsx(2.ハンドラを定義します)
    handleChange_address = (event :React.FormEvent<HTMLInputElement>, newvalue:string) => {
        this.setState({address:newvalue});
    };

1.でthisをbindしておかないと、ハンドラ定義の中で、thisが使えなくなります(自分自身を参照する意図で使えない、という意味)。
MDNを参照してもよくわからないですが、"外側"ではthisはwindowとして定義され、"内側"では自身として定義されるようです。
(eventは外側定義になるっぽい?気になる人は言語仕様を調べてください)

2.でハンドラを定義します。Material-uiのTextFieldのonChangeを見ると以下のように記載がありますので、これに従います。
<引用>
image.png

が、TypeScriptだとここが結構大変でした。
まず、ハンドラでとるeventを型定義しないといけないです。(<any>でもいいのか???)
ここだとevent :React.FormEvent<HTMLInputElement>の部分です。
こことかここをみて、型定義します。
私は適当な感じでそれっぽいのを設定しましたが、実際どうやって決めるのがいいのかよくわかっていません。
後述の(その2)のやり方にある、Reactの公式から、onChangeはFormだ、みたいに見ました。
Elementはカンです。TypeScript知ってる人なら普通なのかしら???

newvalue:stringはそのままです。新しい入力の文字列が取得できますので、これをsetState( )で更新します。

あと、癖があるっぽいです。
this.setStateで、状態を更新していますが、実際に更新されるのは、このハンドラを出た後のようです。
(ハンドラ内で、state.addressをconsole.log( )してみましたが、反映されていません)

ちなみに、このTextFieldのイベントハンドラであれば以下の書き方でも新しい値が取れます。
Reactの公式で載っているやり方ですね。

sub-component.tsx(2.ハンドラを定義します)その2
    handleChange_address = (event :React.FormEvent<HTMLInputElement>) => {
        this.setState({address:event.currentTarget.value});
    };

5.setState( )がコールされると次にrender( )がコールされる

handleChange_addressでsetState( )がコールされると、再びrender( )がコールされます。
outputBalance( )がコールされ、正しいアドレス文字列が入っていればnew Address(this.state.address)が成功し、インスタンスが作成され、console.log( )で、accountInfoWithMetaData.balanceが出力されます。

sub-component.tsx
    private outputBalance(){
        try{
            const address = new Address(this.state.address);
            const accountHttp = new AccountHttp();
            accountHttp.getFromAddress(address).subscribe(accountInfoWithMetaData => {
                console.log(accountInfoWithMetaData.balance);
            });
        }catch(e){
            console.log("err : " + e);
        }
    }

TextFieldには、value={this.state.address}が表示されますので、先ほどsetStateして更新された文字列が表示されています。


今回のTypeScriptで環境構築と、サンプルコード作成は以上です。
・TypeScriptの環境構築は簡単!!
・Reactはぐるぐる回る感じ!
・イベントハンドラの定義が大変!
でした。

次回は、アドレスのTransferTransactionを取得し、その中から指定の文字列をメッセージから検索し、ヒットしたものをTable表示します。


変更履歴
2018/05/23 新規作成

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?