- 投稿日:2020-11-16T16:12:04+09:00
Reactを学ぶX 〜ReactHooks〜
■ はじめに
タイトルについて記事にしました。
この記事で得る内容は以下の通りです。・ Reactの基礎知識が増える
・ ReactHooksについて■ フック(hook)とは
・ クラスの機能(stateやライフサイクル)をFunctional Componentでも使える
・ React 16.8から導入(2020年2月に正式リリース)
・ 100%後方互換 → ReactHooksと従来記述していた構文は干渉しない
→ ただ、あくまでReactHooksは選択肢の一つであり、Class Componentを置き換えるものではない
■ なぜフックを使うのか?
A. シンプルさを保つため
・ Class Componentは難しい
→
this
がReactは他言語と比べ異なる挙動をするので、なるべく使いたくない
ですが、Class Componentを使うと、どうしてもthis
を使わないといけない→ stateを扱うロジックが複雑
→ 複数のライフサイクルメソッドに(同じような)副作用のあるメソッドがまたがる
sample.jsxcomponentDidMount() { this.counter.addEventListener( 'click', this.countUp ) } componentWillUnmount() { this.counter.removeEventListener( 'click', this.countUp ) } // 前回のいいねボタンは、componentDidMountでイベントリスナーを設定して、componentWillUnmountでイベントリスナーを解除していた // 時間の流れでコードを分けるのではなくて、一つの機能を一つの場所にまとめて書いておく方がわかりやすいこれらの課題を解決する手段として、次第にフックが使われ始めました
■ usestate()メソッド
・ Functional Copmponentでもstateを使う方法
・ ステートフックと呼ばれている
・ Class Componentでいう
this.state
とsetState
の代替・ 複数のstateを扱う時は、usestate()をstate毎に宣言します
■ usestate()メソッドの使い方
sample.jsx// ① useState関数をインポート import React, {useState} from 'react'; // ② 宣言をする const [isPublished, togglePublished] = useState(false); // state変数名 state変更関数名 state初期値 // ③ JSX内で使う <input /** 中略 */ onClick={() => togglePublished(!isPublished)}/> // 例:inputタグでクリックされたら、stateを変更する関数を呼び出して、isPublishの値を反転させる■ 例
前使っていたプロジェクトのBlog.jsxとArticle.jsxを使って
Blog.jsxで記述していたBlogクラスをFunctional Componentに置き換えます✳︎今回、不要な所をコメントアウトしています
Blog.jsximport React from "react"; import Article from "./Article"; import * as FooBar from "./components/FooBar"; import Hoge from "./components/Hoge"; const Blog = () => { // componentDidMount() { // // ボタンがクリックされたらいいねをカウントアップする // document.getElementById("counter").addEventListener("click", this.countUp); // } // componentDidUpdate() { // if (this.state.count >= 10) { // this.setState({ count: 0 }); // } // } // componentWillUnmount() { // document.getElementById("counter").removeEventListener("click", this.countUp); // } // countUp = () => { // this.setState({ // count: this.state.count + 1, // }); // }; return ( <> <Article title={"Reactの使い方"} // count={this.state.count} /> <FooBar.Foo /> <FooBar.Bar /> <Hoge /> </> ); }; export default Blog;Functional ComponentだったArticle.jsxは、stateを持つことができませんでしたが
usestate()メソッドを使うことで、stateを持たせることができますArticle.jsximport React, { useState } from "react"; // useStateを使えるようにする // import LikeButton from "./LikeButton"; const Article = (props) => { const [isPublished, togglePublished] = useState(false); // useStateメソッド(state変数名・stateを変更する変数名・useState初期値) return ( <div> <h2>{props.title}</h2> <label htmlFor="check">公開状態:</label> <input type="checkbox" checked={isPublished} id="check" onClick={() => togglePublished(!isPublished)} /> {/* <LikeButton count={props.count} /> */} </div> ); }; export default Article;チェックボックスをクリックしたら、useStateで作ったtogglePublishedの引数(isPublished)を渡して!でFalseを反転させます
Functional Componentでもstateを扱う事ができて、同時にstateの変更も行えるようになります
- 投稿日:2020-11-16T13:28:12+09:00
ビデオツールなどの背景機能をReactとTensorflow.jsで再現してみる
前提
ビデオツール部分の実装などは行っていません。
ビデオツールの背景機能ってどうやってるんだ?
ZoomやGoogle Meetなどで実装されている背景機能は自分で実現できないだろうかと思って。
React.jsとTensorflow.jsを使って実装してみることにした。完成物
逆に自分を白く塗りつぶした。
クライアント完結で機械学習を使える!
機械学習のアプリを作ろうと思うと、Pythonでテンプレートを使って書くか、PythonでREST APIを書いて、React.jsなどでフロント側を作るという方法しか知らなかった。
個人的にはReact.jsを使い慣れているので、UIがある時にはReact.jsを使いたい。そんな時、Tensorflow.jsを知った!
Tensorflow.jsを使うことで、Pythonを書くことなく、機械学習を使ったアプリが作れる!すぐに使えるモデルがたくさん
Tensorflow.jsでは事前トレーニング済みのモデルが用意されている。
詳しくはこちら。今回は人体セグメンテーション(BodyPix)のモデルを使用して背景機能を作っていく。
準備
最初に
$ npx create-react-app bodypix-web $ cd bodypix-web必要ライブラリのインストール
$ yarn add @tensorflow-models/body-pix @tensorflow/tfjs # 機械学習用 $ yarn add react-webcam # カメラ用 $ yarn add @material-ui/core # UI用BodyPixを使って人体セグメンテーション
- モデルのロード
- セグメンテーションの生成
- セグメンテーションの描画
モデルのロード
@tensorflow-models/body-pix
のload()
メソッドがあり、呼び出すことでロードができる。import React, { useState, useEffect } from "react"; import "@tensorflow/tfjs"; import * as bodyPix from "@tensorflow-models/body-pix"; const App = () => { const [model, setModel] = useState(); useEffect(() => { bodyPix.load().then((net) => { setModel(net); }) }, []); return (); }また、
load()
の引数として以下のようなオプションを与えることができる。
- architecture: セグメンテーションのモデルを指定できる。MobileNetV1やResNet50が指定できる(デフォルトはMobileNetV1)。
- outputStride: セグメンテーションのモデルの出力ストライドを指定できる。値が小さいほど出力される解像度が高くなり、モデルの精度が向上するが速度は遅くなる。
- multiplier: MobileNetV1のみで使用できる畳み込みの深さを指定できるパラメータ。値が大きいほど精度が向上するが速度は遅くなる。
- quantByes: 重みの量子化に使用するバイト数を指定できる。何ができるかあまりわかっていない。
セグメンテーションの生成
ロードしたモデルの
segmentPerson()
メソッドを使うことでセグメンテーションを生成することができる。import React, { useState, useEffect } from "react"; import { Button, Box } from "@material-ui/core"; import "@tensorflow/tfjs"; import * as bodyPix from "@tensorflow-models/body-pix"; import WebCam from "react-webcam"; const App = () => { const [model, setModel] = useState(); useEffect(() => { bodyPix.load().then((net) => { setModel(net); }) }, []); // ここに注目 const estimate = useCallback(() => { const webcam = document.getElementById("webcam"); model.segmentPerson(webcam).then((segmentation) => { console.log(segmentation); }, [model]) }); return ( <> <Box> <Button onClick={estimate}>推定</Button> </Box> <Box> <WebCam id="webcam" width={640} height={480} /> </Box> </> ); }
segmentPerson()
の引数としてはImageData、HTMLImageElement、HTMLCanvasElement、HTMLVideoElementが指定できる。今回はHTMLVideoElementを指定している。セグメンテーションの描画
@tensorflow-models/body-pix
のtoMask()
メソッドでセグメンテーションに対応したピクセル値を生成し、drawMask()
メソッドでそのマスクを画像やキャンバスに描画する。import React, { useState, useEffect } from "react"; import { Button, Box } from "@material-ui/core"; import "@tensorflow/tfjs"; import * as bodyPix from "@tensorflow-models/body-pix"; import WebCam from "react-webcam"; const App = () => { const [model, setModel] = useState(); useEffect(() => { bodyPix.load().then((net) => { setModel(net); }) }, []); const estimate = useCallback(() => { const webcam = document.getElementById("webcam"); model.segmentPerson(webcam).then((segmentation) => { showResult(segmentation); }, [model, showResult]) }); // ここに注目 const showResult = useCallback((seg) => { const foregroundColor = { r: 0, g: 0, b: 0, a: 0 }; const backgroundColor = { r: 127, g: 127, b: 127, a: 255 }; const mask = bodyPix.toMask(seg, foregroundColor, backgroundColor); const webcam = document.getElementById("webcam"); const canvas = document.getElementById("canvas"); const opacity = 0.7; bodyPix.drawMask(canvas, webcam, mask, opacity, 0, false); }, ); return ( <> <Box> <Button onClick={estimate}>推定</Button> </Box> <Box> <WebCam id="webcam" width={640} height={480} /> <canvas id="canvas" width={640} height={480} /> </Box> </> ); }描画するためにまず
toMask()
メソッドを使用しマスクを作成する。toMask()
メソッドでマスクを作るためには前景と背景の色を決める必要がある。ここではrgba指定で作成し、toMask()
メソッドにセグメンテーション結果とともに渡している。次に、
drawMask()
メソッドを使用してキャンバスに描画する。drawMask()
メソッドは以下の引数が必要となる。
- canvas: キャンバスの要素。
- image: 適用する元画像。VideoElementも指定できるため、webcamを指定している。
- maskImage:
toMask()
などで生成されたマスクデータ。- maskOpacity: 画面上にマスクを描画する際に不透明度。デフォルトは0.7となっている。
- maskBlurAmount: マスクをぼかすピクセルすう。0~20が指定できる。デフォルトは0となっている。
- flipHorizontal: 結果を反転させるかどうかを指定できる。デフォルトはfalseとなっている。
他にやったこと
- リアルタイム描画
- 背景塗りつぶし、人物塗りつぶし、塗りつぶしなしの切り替え
コードはこちら
https://github.com/makky0620/bodypix-web最後に
サンプルコードとかではReact.jsを使った例がなかったので、ちゃんとかけているか分からないですが、結構簡単に実装できて嬉しかった。
他のモデルも触ってみよう!
- 投稿日:2020-11-16T09:54:12+09:00
Apollo ClientのReactive variablesが良さげ [Local resolvers deprecated] [Local-only fields]
久々にドキュメント読んだらLocal resolversがdeprecatedになっていた事件
なんてこった。
Local resolversの代わりにLocal-only fields使うといいらしい。Local-only fields
今までのようにapollo cacheに値を保存する方法と、Reactive variablesという新しいものがあるという。
この記事ではReactive variablesのほうについて書く。Reactive variables
apollo clientのキャッシュ以外の場所でローカル値を保有できるらしく、キャッシュと分離してるがゆえに、どんな型だろうと保存できる。
また、GraphQL queryを書かずにアクセスできるのも特徴。
うーん、良い。作る
makeVar
関数で作ることができる。import { makeVar } from '@apollo/client'; // 空の配列を作る。 const cartItemsVar = makeVar([]);簡単。
アクセス
関数のように呼べば値を取得でき、引数を渡せば変更できる。
// 値を出力。 console.log(cartItemsVar()); // 値を変更 cartItemsVar([...cartItemsVar(), newItem]);簡単。
Reactive
値が変更されたらコンポーネントの更新もしてほしいのが普通。
useReactiveVar
直接値を使うときは
useReactiveVar
hook。import { useReactiveVar } from '@apollo/client'; export function Cart() { const cartItems = useReactiveVar(cartItemsVar); return ( <div class="cart-items"> {cartItems.map(productId => ( <CartItem key={productId} /> ))} </div> ); }簡単。
GraphQL queryで問い合わせる
export const cache = new InMemoryCache({ typePolicies: { Query: { fields: { cartItems: { read() { return cartItemsVar(); } } } } } });cacheに登録をすれば今まで通りGraphQL query投げて値を取得できる
export const GET_CART_ITEMS = gql` query GetCartItems { cartItems @client } `; export function Cart() { const { data, loading, error } = useQuery(GET_CART_ITEMS); return ( <div class="cart-items"> {data && data.cartItems.map(productId => ( <CartItem key={productId} /> ))} </div> ); }が、果たしてこれをする必要があるのかは疑問。
特有のメリットとしては@exportが使えるようになること。@export
↓ローカルにある値を取得し、それをサーバーに投げるクエリの引数として使うことを一つのGraphQL queryで出来る。
query CurrentAuthorPostCount($authorId: Int!) { # currentAuthorIdの戻り値をauthorIdとしてエクスポート。 currentAuthorId @client @export(as: "authorId") # エクスポートされたauthorIdを通常のクエリの引数に使う。 postCount(authorId: $authorId) }便利っちゃ便利。
感想
手軽そう。
今までの仕組み(Local resolvers)だと、ローカルのデータへのアクセスも基本的にGraphQLの仕組みで行われていて、GraphQL queryを書く必要があった。
サーバー側のデータと同じように扱えるという一貫性はあった一方で、面倒でもあった。
クエリ書かずにローカルステートの管理ができるのは非常にありがたい。