20200729のReactに関する記事は12件です。

react-three-fiberとreact-springでアニメーション無限ループのオンオフをする方法

react-spring v8では素直に無限ループをする方法が用意されていない。(たぶん)
whileで無限ループを作ってしまうしかないようだ。
Best practice for infinite loops?

v9ではloop propが用意されてるらしいのでそちらを使うと良さそう。
How can one-way animation looping be implemented? #956

ちなみにv9.0.0-rc3を使おうとしたところ、変なところでハマってうまくいかなかった。
コンポーネントの再レンダーのタイミングでしかアニメーションが更新されないような現象。
内部的にはreact-springはちゃんと動いているけど、コンポーネントの再レンダーのトリガーにできてない感じの挙動。
これが解決できればv9系を使う方が幸せっぽいのでできた人は情報求む。

閑話休題。

一応while(true)で無限ループは作れるし動くんだけど、それを使ってるコンポーネントが再レンダーされるとブラウザがクラッシュする。
無限ループしてる関数がゾンビ的な挙動をしているのではないかと推測する。

再レンダー時にクラッシュするコード
const { opacity } = useSpring({
    from: { opacity: 0 },
    // フワッと現れて、その後おぼろげにフワフワする感じの不透明度アニメーション
    to: async (next, cancel) => {
        await next({ opacity: 1 })
        while (true) {
            await next({ opacity: 0.2 })
            await next({ opacity: 0.5 })
        }
    }
})

ちなみに今回はこのアニメーションのオンオフを親コンポーネントからコントロールしたい。
例えばactive:booleanみたいなpropで、trueならさっきのふわふわアニメーションを頭から再生して、falseなら{opacity:0}に落ち着くみたいな挙動をさせたい。

やりたいのはこんな感じ(まだ再レンダー時にクラッシュする)
const { active } = props
const { opacity } = useSpring({
    from: { opacity: 0 },
    to: active ?
        // active==trueならふわふわさせる
        async (next, cancel) => {
            await next({ opacity: 1 })
            // ※※※ここが大事※※※
            // activeの値がキャプチャされてしまうので、
            // あとでfalseになってもループを抜けられない
            while (active) {
                await next({ opacity: 0.2 })
                await next({ opacity: 0.5 })
            }
        } :
        // active==falseなら消える
        { opacity: 0 }
})

最終的にうまくいったコードはこちら。
方針としては、
1. useSpringに関数を渡すことで、toの再設定関数(この例ではsetAnimation)を取得する
2. activeを依存関係に持つuseEffectを使用することで、toの再設定をactiveの変更時に限定する
(v9系ではuseSpringに依存関係を持たせてよりスマートに書けるようになるっぽい)
3. whileでactiveRef=useRef(active)を見るように変更することで、activeの値の変更をキャッチできる
-> これにより、whileループがゾンビ化するのを防げる

const { active } = props
const activeRef = useRef(active)
// useSpringは関数を渡されると、SpringValueだけじゃなく更新関数やstop関数も返す
const [{ opacity }, setAnimation] = useSpring(() => ({
    // toの値はあとで設定するので初期値のみ指定
    opacity: 0
}))
useEffect(() => {
    // activeが変更されたことをwhileに通知する
    activeRef.current = active
    setAnimation({
        to: active ?
            // active==trueならふわふわさせる
            async (next, cancel) => {
                await next({ opacity: 1 })
                // activeRef.current==falseならループを抜ける
                while (activeRef.current) {
                    await next({ opacity: 0.2 })
                    await next({ opacity: 0.5 })
                }
            } :
            // active==falseなら消える
            { opacity: 0 }
    })
},
// useEffectはactiveが変わった時に呼ばれる
[active])

たぶん、タイミングによっては{ opacity: 0 }setAnimationした後にwhile内のawait next()が効いて{ opacity: 0.5 }で止まっちゃう現象が起こるような気もする。
ちゃんと対応するならPromiseをrejectさせるみたいなことをやると良いのだと思う。
今回はとりあえず動いてそうなので無指する。

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

React Hooksのメモ

・hooksは関数コンポーネント内でstateに値の追加や更新が行えるようにするもの?
・this.setState()のような処理を行う関数を自動で作成してくれる?

index.js
import React, {useState} from "react";
import ReactDOM from "react-dom";

//function App()でも可
var App = function() { 

    //現在の値と値を更新する関数を取得
    //useState(n)のnがcntの初期値になる
    var [cnt, setCount] = useState(0);

    var addCount = function() {
        setCount(cnt + 1);
    }

    var minusCount = function() {
        setCount(cnt - 1);
    }

    return (
        <>
          <div>
              <p>Hello React!!</p>
              <input type="button" value="+1" onClick={() => addCount()} />
              <input type="button" value="-1" onClick={() => minusCount()} />
              <p>{cnt}</p>
          </div>
        </>
    )

}


/* 同じものをクラスコンポーネントで書く */
/*
class App extends React.Component {

    constructor(props) {
        super(props);
        this.state = {"cnt": 0};
        this.addCount = this.addCount.bind(this);
        this.minusCount = this.minusCount.bind(this);
    }

    addCount() {
        var cnt = this.state.cnt + 1;
        this.setState({"cnt": cnt});
    }

    minusCount() {
        var cnt = this.state.cnt - 1;
        this.setState({"cnt": cnt});
    }

    render() {
        return(
            <div>
                <p>Hello React!!</p>
                <input type="button" value="+1" onClick={this.addCount} />
                <input type="button" value="-1" onClick={this.minusCount} />
                <p>{this.state.cnt}</p>
            </div>
        );
    }

}
*/

/* クラスコンポーネントと関数コンポーネントで共通 */
ReactDOM.render(<App />, document.getElementById("app"));

hooksは複数の状態(this.state)を管理できる。
ここでは{gender: "", place: ""}の状態を管理している。

index.js
import React, {useState} from "react";
import ReactDOM from "react-dom";

var App = function() {

    //genderの値を書き換えるsetGender()関数が自動で作成される?
    var [gender, setGender] = useState("男性"); 
    var [place, setPlace] = useState("東京都");

    //genderの値を更新する
    var onClick = function() {
        if (gender === "男性") {
            setGender("女性");
        } else {
            setGender("男性");
        }
    }

    //処理が1行だけなのでonChange={() => setPlace(document.getElementById("place").value()}でも良い
    var onChange = function() {
        setPlace(document.getElementById("place").value);
    }

    return (
        <>
          <p>性別 : {gender}</p>
          <p><input type="button" value="性別変更" onClick={() => onClick()} /></p>
          <p>居住地 : {place}</p>
          <select id="place" onChange={() => onChange()}>
              <option value="東京都">東京都</option>
              <option value="神奈川県">神奈川県</option>
              <option value="埼玉県">埼玉県</option>
          </select>
        </>
    )

}


/* クラスコンポーネントと関数コンポーネントで共通 */
ReactDOM.render(<App />, document.getElementById("app"));

コンポーネントの命名について(2020/07/31追記)

コンポーネントの名前は必ず大文字で始めなければならない。
小文字だとReactDOM.render()の第一引数とコンポーネント名が一致していてもエラーが発生する。

index.js
//良い例
class App extends React.Component { (中略) }
//ダメな例
class app extends React.Component { (中略) }

//良い例
var App = function() { (中略) }
//ダメな例
var app = function() { (中略) }


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

Material-UI の Dialogのスタイルを変更する方法

はじめに

Material-UIで画像をモーダルで表示したい時に、やり方を探したのでメモとして残します。

やり方

PaperPropsにstyleを渡す。

import { Dialog } from "@material-ui/core";

...
const [showingDialog, setShowingDialog] = useState(false);
...

<Dialog
  onClose={() => setShowingDialog(false)}
  open={showingDialog}
  scroll="body"
  PaperProps={{
    style: {
      backgroundColor: "transparent",
      boxShadow: "none",
      textAlign: "center",
    },
  }}
>
  <img src="image url" />
</Dialog>
...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactでsetStateをしても画面表示が切り替わらない問題の解決方法メモ

はじめに

概要

Reactでフロントエンドのツールを作っている時に、stateの中に設定されているjson形式のデータを、HTMLのテーブルとして表示する機能を作りました。

その際に、表示の基となるデータを変更しても、テーブル内に表示される値は切り替わらないというエラーに出会ったため、簡単な解決方法をここにメモしておきます。

TL;DR

Reactを使うときに、イテラブルなオブジェクトを基に画面表示をする場合は、各要素ごとにidが振られていないと、同オブジェクトの値が変わっても画面表示が切り変わらないことがあるようです。

やりたかったこと

stateに登録されているjsonの配列形式のデータを、HTMLの<table>要素の中に上手く表示させることがしたかったです。
具体的には、stateには以下のようなデータが登録をされていました。

this.state.dummyData = [
  {
    "key":"aa",
    "value":"bb"
  },{
    "key":"aa",
    "value":"bb"
  },{
    "key":"aa",
    "value":"bb"
  }
]

こんな感じで、単純なオブジェクトの配列になっていました。
これを基に画面表示をする、概略化したコードは、以下のような感じです。

render(){
  // 省略
  <table>
    <tbody>
      {this.state.dummyData.map((d,i) => 
        <tr>
          <td>{d.key}</td>
          <td>{d.value}</td>
        </tr>
      )}
    </tbody>
  </table>
  // 省略
}

配列の中身の数だけ<tr>を生成して、その中の<td>に値を表示しているだけです。
しかしこれだと、this.state.dummyDataの値が変わっても、テーブルの中身は変わらない場合がありました。

解決方法

解決方法のヒントは、Reactの開発モードの時に出るコンソール内のwarningにちゃんと出ていました。

Warning: Each child in a list should have a unique "key" prop.

Reactで、リストを基に画面に要素を複数個表示する場合は、各要素にkeyを設定することが推奨されています。そのため、以下のように変更をしました。

まず、表示基のstate内のオブジェクトの各項目には、idを振るようにしました。

this.state.dummyData = [
  {
    "id":001,
    "key":"aa",
    "value":"bb"
  },{
    "id":002,
    "key":"aa",
    "value":"bb"
  },{
    "id":003,
    "key":"aa",
    "value":"bb"
  }
]

そして、画面表示をする際は、<tr>keyとしてidを設定します。

render(){
  // 省略
  <table>
    <tbody>
      {this.state.dummyData.map((d,i) => 
        <tr key={d.id}>
          <td>{d.key}</td>
          <td>{d.value}</td>
        </tr>
      )}
    </tbody>
  </table>
  // 省略
}

こうすることで、表示基のオブジェクトが変更されたときに毎回render関数が走るようになり、きちんと画面が切り替わりました。

まとめ

Reactを使うのは初めてだったので、単純なところで引っかかってしまいました。

「React 配列 展開」とかでググって出てくるサンプルコードの多くは、map関数の中でHTML要素にkeyを設定していなかったのですが、表示内容が変更される可能性がある場合には、きちんとkeyを設定しなければならないようです。

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

[Next.js + Storybook] RouterのMockを作成

Cannot read property 'pathname' of null

RouterのMockを作成せずに、
Material-UIの <Link> を使うと上記エラーがでる。

その対処法。

storybook/nextRouterMock.js
import * as nextRouter from 'next/router'

nextRouter.useRouter = () => ({
  route: "/",
  pathname: "/"
});

nextRouter.router = {
  push: () => {},
  prefetch: () => new Promise((resolve, reject) => {}),
};
storybook/config.js
import './nextRouterMock';

参考
https://gist.github.com/novascreen/f1c44ead31e5a494556793be2c408840

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

React+Gatsby jsでダイナミック(onCreate)にページを生成する際のsafariでの崩れに関しての注意点

React+Gatsby jsでダイナミック(onCreate)にページを生成する際のsafariでの崩れに関しての注意点。

結論から言うと、上記の組み合わせでページをレンダリングする場合のみに関わらず、日ごろから<></>は使わない方が賢明かもしれない。
Qiitaでも<></>を利用した例は多数見つかるのと、性質上、開発後半で発見されて厄介になる可能性が極めて高いので、今回のエラーと解決までの流れは記載して共有する事にした。

--現象--
ReactとGatsbyで何時もの通り開発していてスマホ(iphnenのサファリ)で確認していた際に、ページを更新してみたら一部表示がされなくなっており、他のブラウザは通常通りレンダリングされていた。
更新すると一部表示が消えるページは、onCreateで動的にダイナミックに表現してるページのみで、他のページはそのような現象はなかった。

--調査--
上記の理由から、恐らく崩れた箇所のコンポーネントに問題があるのだろうと思い該当箇所を調べてみたがそれっぽい所は見つからず、エラーも確認してが何も出てなかった。
そこで、最近書き換えた箇所が無いか考えてみたところ、先日記述を追加した際に、エレメントをまとめる為に、コンポーネントの全体を<>~</>で括った箇所があった。
その個所を<div>~</div>に変更してみてビルドしてsafariで確認したら、崩れているが表示はされるようになった。

--解決--
そこで、恐らく<>~</>の箇所に問題があると思い、記述パターンを準拠して(今回は<nav>~</nav>)記載してみたら問題なくレンダリングされるようになった。

--原因--
まだ詳しくは調べてないが、恐らくDOMのレンダリングの方法か、表示のタイミングが、safariでは<></>の扱いが異なる可能性が高い。
この問題の厄介な所は、ダイナミックにページを生成する際に、しかもビルドするとsafariのみでこの現象が発生する点。
恐らく通常のエラーよりも問題の発見が遅れて、開発の後半で発見するとかなり厄介になる可能性もある。

今後、絶対にダイナミックな表現はしないとかならいいのだが、Reactの性質上、コンポーネントは再利用される可能性が高いので、常日ごろから<></>は利用しない方が賢明だと思わせるようなエラーだった。
昔はブラウザ毎でタグの扱いが異なり、苦労していたが、久しぶりに同じような問題に時間を使った。

追記と一部訂正
少し考えてみれば分かる事だが、ダイナミックレンダリングをした場合のみ表示が崩れるというのは恐らく異なっていて、safariの場合は恐らく<></>自体の扱いが異なると思うので、ダイナミックでなくても発生する可能性は大いにある。今回たまたまダイナミックなページでDOMを構成する順番が異なるとかで発生した可能性が高いと見るのが正しいだろう。すなると恒久的に、<></>問題が発生する可能性は大いにあるという事だ。
windows環境で開発してるので、テストの際にsafariは使っていなかった為、後日safariを入れて試してみよう(以前、純正のsafariはwinでは使えない事があったので入れてない)かなと思い検索してみだが、レビューを見るとセキュリティ的に問題があるレビューが何点か見つかりやめた。
いずれにしてもこういう挙動が起こる場合は、経験上使わないに越したことはない。しかも今回のケースは代替手段がある。まだsafariでない弱小ブラウザなら良いのだが無視できないブラウザだけにこの点は考慮する必要はある。
safariの仕様が今後変わったり、スマートな解決方法があればそちらを優先していいと思う。

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

Reactにおける配列処理(for文・mapメソッド)

はじめに

現在Reactを用いて画面開発を行っているのですが、selectタグの中でoptionを使う際、以下のようにコンポーネントに直接記述してしまうと使い回しができなくなってしまうためpropsで値を渡してやりたいと考えました。

sample.js
<select title="サンプル">
    <option disabled selected>選択してください</option>
    <option value="0">サンプル1</option>
    <option value="1">サンプル2</option>
    <option value="2">サンプル3</option>
    <option value="3">サンプル4</option>
</select>

しかし、optionタグ1つ1つをpropsとして値渡しすると数が多くなってしまうため、propsで配列を受け取りその配列の要素をoptionタグ内に記述して表示してあげよう!と考えました。

①for文での記述

sample.js
list = []

for(let i=0;i<props.opt.length;i++){
    list.push(<option value={i}>{props.opt[i]}</option>
}

return(
    <select title="サンプル">
        {list}
    </select>
)

for文を用いて各配列の要素に変更を加え、それらを別の配列に格納することで実装いたしました。
ちなみに、
opt=["test","sample",...]
といった配列データが親コンポーネントから渡されています。

②mapでの記述

for文でも実装できましたが、JavaScriptのES6以降ではmapメソッドを用いることでスマートに配列処理を行うことができます。

sample.js
var list = props.opt; //見やすくするため、受け取った値を変数に格納

const option = list.map((data,index)=><option value={index}>{data}</option>)

return(
    <select title="サンプル">
        {option}
    </select>
)

こちらのほうが自分は見やすくおしゃれだなと思いました。

おまけ:mapメソッドのコールバック関数について

mapメソッドのコールバック関数の引数は3つあります。

sample.js
配列.map((value,index,array)=> )

value:配列の値
index:配列のインデックス番号
array:現在処理している配列

これらを覚えておくと、配列処理をさらにスマートに行えます!

最後に

ありふれた内容ですが、開発を行う際に引っかかったため自分用の備忘録として書かせていただきました。始めたばかりの初心者ですのでご指摘があればコメントお願いします。

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

React ref初心者の覚書

初心者がrefがよくわからなかったのでメモしています。

ref のフォワーディングの記事メモ

  • ref のフォワーディングはあるコンポーネントを通じてその子コンポーネントのひとつに ref を自動的に渡すテクニック

  • ref のフォワーディングはオプトインの機能であり、それにより、コンポーネントが ref を受け取って、それをさらに下層の子に渡せる(つまり、ref を “フォワーディング” できる)ようになります。

下の例では、FancyButton は渡された ref を取得して、それをレンダーする button DOM にフォワーディングするために、React.forwardRef を使っています。

ref.js
const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
  • フォアード 転送 意味は「特定の〜にやってきたデータを別の〜に向かって送り出してやること」です。

ReactのRefsを正しく取り扱うの記事メモ

  • Refsを使うべきシチュエーション

    • そもそも、Refsはあまり多用すべきでないと公式でアナウンスされています。というのもReactでは、親コンポーネントから子プロセスへの一方向データフローを原則としていて、Refsを使ってコンポーネントに作用をすると、その原則が崩れてしまうためです。
  • Refsを使って良い例は

    • テキストの選択、フォームへのフォーカスもしくはメディア(動画、音声)の再生の制御
    • アニメーションを発火させる場合
    • サードパーティのDOMコンポーネントを組み込む場合>mapboxを組み込むのもサードパーティ

これで何となくイメージだけ、、
下記の本家で詳しい説明があった。

Ref と DOMからのメモ

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

Ref へのアクセス

  • ref が render メソッドの要素に渡されると、そのノードへの参照は ref の current 属性でアクセスできる。
  • ref の値はノードの種類によって異なります。
  • HTML 要素に対して ref 属性が使用されている場合、React.createRef() を使ってコンストラクタ内で作成された ref は、その current プロパティとして根底にある DOM 要素を受け取る
  • ref 属性がカスタムクラスコンポーネントで使用されるとき、ref オブジェクトはコンポーネントのマウントされたインスタンスを current として受け取ります 関数コンポーネント (function components) には ref 属性を使用してはいけません。なぜなら、関数コンポーネントはインスタンスを持たない

まだ把握しきれない、、

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // textInput DOM 要素を保持するための ref を作成します。
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // 生の DOM API を使用して明示的にテキストの入力にフォーカスします。
    // 補足:DOM ノードを取得するために "current" にアクセスしています。
    this.textInput.current.focus();
  }

  render() {
    // コンストラクタで作成した `textInput` に <input> ref を関連付けることを
    // React に伝えます。
    return (
      <div>
        <input
          type="text"
          ref={this.textInput} />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React初学者によるReactについて(感性独特)

都内のIT企業でデザイナー兼フロントエンジニア(見習い)をやってます。
前職ではHTML、CSS、PHPを触ってた僕にとってReactの壁は厚い。。。。
そんな経験からReactってこういうものなんだよ!!!ってことをまとめておきます。

そもそもReactってなんぞや?

おそらくですが、Reactってなんなん??って初学者はめちゃめちゃいると思ってます。
僕自身がそうだったこと、前職同期に話しても分かってもらえなかったことからなんとなく想像してます。
というわけでまずはReactってなんぞや?って話から。

とりあえず、公式です、お納めください。
React

とまあ、アクセスしていただいてなんとなくわかる方もいるかもわかりませんが、
Reactってユーザインターフェース構築のための JavaScript ライブラリなんですねぇ〜〜。

世間でいうところのUIをいい感じにすることに特化したJSのライブラリということなわけです。
ライブラリっていうのはLibraryっていうように図書館みたいな感じ。
よく使うよねっていうものをまとめて、使えるようにしてくれています。

だから、利用者である我々はそのライブラリに準じた書き方をすることでライブラリにある機能を使用することができるようになっています。(実際は)
Reactはその中でもJSでUIを構築していくのに便利なものを提供してくれているライブラリということです。

ライブラリは当然Reactだけではないので、何を使うかは所属している組織だったり用途によって検討して決定すれば良いかと思っています。

具体的にReactは何をしてくれるの?

じゃあ、Reactがそういうライブラリなんだ〜〜ってのは分かってもらえたと仮定しまして。
Reactを使うと、HTMLをごりっごり書くのと何が違うの??って思わない人はいないと思います、僕も正直そう思ってました。

でも正直、すんげぇ捗るからトライしてみて欲しいなぁて思うんで、React初学者の僕でさえ「これええなぁ」って思ったことを書きたいなって思います。

コンポーネントって考え方

コンポーネントはComponentなんですが、直訳すると構成子と訳すことができます。
そう、ウェブサイトやサービスを組んでいく中で繰り返し使用される構成要素があるじゃないですか。

これHTMLとかで書いていくと

<a class="button">
  お申し込みはこちら
</a>

にみたいなことにして、

.button {
  display: block;
  width: 300px;
  padding: 10px 50px;
}

みたいなスタイルをつけるってのをたくさん書かなくちゃいけないわけです。
書かなくてなんとかすることもできんのかな??って思ってはいますが、調べてません。

でも、Reactだったらこれをコンポーネントとして定義しておくことができるんで、一度作成したらそれを呼び出して使用することができます。
スタイルもコンポーネントに対して行うことができるので、一括で変更することができます。
僕はここに感動を覚えました、楽やし管理楽やな〜〜って。

業務分担みたいな考え方

Reactはそもそも、ユーザインターフェース構築のための JavaScript ライブラリってだけあって
状態を保持したりだとかは業務範囲じゃなくて、描画していくことが業務範囲だそうです。
いわゆるビューを作っていく部分ですかね。

「でも、ぶっちゃけ状態保持したりできないなら実務で使えないんじゃ。。。」
ってなったらしく、そこからfluxってアーキテクチャが生まれて、Reactでも状態管理ができるようになったんですね。

このfluxってアーキテクチャが僕はすごくスッキリしていて好きで、また今度かければいいなって思うんですが
すんごいざっくり言うともうフォルダを役割で分けちゃって、そこに各役割を担当するファイルを置いて書いていきましょう!!みたいな感じ。

そのスッキリさが気に入っています。
初学者の僕にとって関数とかが入り乱れてると頭ごちゃごちゃになるんで。。。

他にもたくさんいいことがあるから勉強していきます

上司からも言われていることなんですが、まだまだReactにはすごい機能が盛り沢山です。
Hooksだったり。

そこら辺はまだ理解できてないから今後の課題にしていくとします。

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

[メモ] useState をラップしたようなフック

[メモ] useState をラップしたようなフック

useState をラップしたようなフックです。
親子関係のコンポーネントにて子側で props.value を処理するような際に使用します。

  • 親側からの props.value の変更を受けて値を同期
    • 値の比較により同期するかを制御
  • propsOnChange を関数として確定した onChange を第三要素として返す
useValue
import { useState, useEffect, Dispatch, SetStateAction } from 'react'

/** オプション指定 */
type Options = {
  /** `propsValue` に変更があった場合 `setValue` による同期を行う (デフォルトは `true` 扱い) */
  sync?: boolean
  /**
   * `useEffect` にて `value` と `propsValue` を比較して差があった場合に更新する制御
   * - `boolean` 指定の場合そのまま更新制御に使用
   */
  diff?: ((value: unknown, propsValue: unknown) => boolean) | boolean
}

/**
 * `useState` をラップしたような関数
 * - `propsValue` の変更が立った場合 `options?.sync` 次第で同期する (デフォルト同期)
 * - 戻り値として `onChange` を関数として確定したものを返す
 */
const useValue = <T, F extends Function | undefined>(
  /** useState に渡す値 */
  propsValue: T,
  /** props などで渡される `onChange` を `Function` として確定させてから返す */
  propsOnChange: F,
  /** オプション指定 */
  options?: Options,
): [T, Dispatch<SetStateAction<T>>, NonNullable<F>] => {
  const [value, setValue] = useState<T>(propsValue)
  const sync = options?.sync ?? true
  const onChange = propsOnChange ?? (() => {})
  const optionsDiff =
    options?.diff ??
    ((val: T, pVal: T) => {
      if (Array.isArray(val) && Array.isArray(pVal)) {
        return Array.from(new Set([...val, ...pVal])).length !== val.length
      }
      // オブジェクトの場合の比較・・・
      if (typeof val === 'object' || typeof pVal === 'object') {
        return JSON.stringify(val) !== JSON.stringify(pVal)
      }
      return val !== pVal
    })
  const diff = typeof optionsDiff === 'boolean' ? () => optionsDiff : optionsDiff
  useEffect(() => {
    if (sync && diff(value, propsValue)) {
      setValue(propsValue)
    }
  }, [propsValue, sync])
  return [value, setValue, onChange as NonNullable<F>]
}

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

ReactでAPIを使用する際によく見る"fetch(URL).then"のメソッドチェーンについて

背景

Reactからjson形式のAPIを利用する方法として下記のような方法が挙げられている。 (Reactに限った書き方ではないが)

useApi.js
1 fetch('http://example.com/api/')
2   .then((response) => response.json())
3   .then((response) => {
4     console.log(response.hoge);
5   })

しかし、データ(response.hoge)のみを利用するのであれば、

useApi.js
2   .then((response) => response.json())

は不要なのでは?と思い、コメントアウトしたところ、

useApi.js
4     console.log(response.hoge);

がundefinedとなった。
2行目の有無で3行目の引数の中身が変わっているような挙動だ。

調べた結果

Promiseの.then(というより、アロー関数)には、下記の特徴があるようだ。

  • 単文の場合は、{}を省略可能。
  • {}を省略した場合は、単文の処理結果をPromiseでwrapしたもの(?)が自動でreturnされる。

この特徴をメソッドチェーンとして活用したのが冒頭のコードとなっている。

ちなみに、自分で

useApi_2.js
1 fetch('http://example.com/api/')
2   .then((response) => {
3     const res_json = response.json()
4     console.log(res_json.hoge);
5   })

としても同じ実行結果にはならない。(Promiseでwrapしていないため)

結論

自分でPromise返す処理を書くより、メソッドチェーンを活用したほうが楽。

useApi.js
1 fetch('http://example.com/api/')
2   .then((response) => response.json())
3   .then((response) => {
4     console.log(response.hoge);
5   })

参考にさせていただいたサイト様

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

Nxを利用してgatsbyのプロジェクトを始めるメリット

Nxを利用してgatsbyのプロジェクトを始めるメリット

Table of Contents
  1. Nxとは?
  2. Nxでプロジェクトを作成する
  3. Nxを利用してgatsbyのプロジェクトを作成するメリット

1. What is Nx?

Nrwl Incが提供する monorepo / micofrontendのワークスペースを管理・作成するためのOpen Sourceのライブラリです。
lernaなどの違いとしては、packageをマネジメントするのではなく複数のプロジェクトを管理、共通のlibraryを作成することに特化している気がします。

Nx-Extensible-Dev-Tools-for-Monorepos.png

1.NxでGatsbyのProjectを作成する

自分が感じたメリットを記載するよりもプロジェクトを作成した方がメリットを感じれると思うので、先にnxを利用してGatsbyのプロジェクトを作成します。

1-1. nxのでワークスペースを作成する

create-nx-workspaceを利用してワークスペース(複数のプロジェクトを管理、作成する)を作成します。

$ pwd
/your/project/path
$ npx create-nx-workspace@latest

? Workspace name (e.g., org name)     my-workspace # ディレクトリの名前になります

? What to create in the new workspace empty #emptyを選択します
react             [a workspace with a single React application] 
react-express     [a workspace with a full stack application (React + Express)] 
next.js           [a workspace with a single Next.js application] 
empty             [an empty workspace with a layout that works best for buildi
ng apps] 
oss               [an empty workspace with a layout that works best for open-s
ource projects] 
web components    [a workspace with a single app built using web components] 
angular           [a workspace with a single Angular application]
angular-nest      [a workspace with a full stack application (Angular + Nest)]

? CLI to power the Nx workspace    Nx
Nx           [Recommended for all applications (React, Node, etc..)] 
Angular CLI  [Recommended for Angular only workspaces]

? Use the free tier of the distributed cache provided by Nx Cloud No
Yes [Faster command execution, faster CI. Learn more at https://nx.app] 
No  [Only use local computation cache]

$ cd my-workspace
$ tree -L 2 -I node_modules
├── README.md
├── apps
│   ├── .gitkeep
├── jest.config.js
├── libs
├── nx.json
├── package.json
├── tools
│   ├── schematics
│   └── tsconfig.tools.json
├── tsconfig.base.json
├── tslint.json
├── workspace.json
└── yarn.lock

これで基本的なワークスペースの環境が作成されました。
基本的にnrwlが提供するcliを利用するためglobalにinstallしていきます。

$ pwd
my-workspace
$ yarn global add @nrwl/cli

1-2. Gatsyのプロジェクト作成

installが完了したら、gatsbyの環境を作成していきます。

$ pwd
my-workspace
$ yarn add -D @nrwl/gatsby #gatsbyの依存関係をinstallします
$ nx g @nrwl/gatsby:app sample # gatsbyのapplicationを作成します
$ tree -L 2 -I node_modules
├── README.md
├── apps
│   ├── sample
│   └── sample-e2e
├── jest.config.js
├── libs
├── nx.json
├── package.json
├── tools
│   ├── schematics
│   └── tsconfig.tools.json
├── tsconfig.base.json
├── tslint.json
├── workspace.json
└── yarn.lock

これでgatsbyのアプリケーションの環境を作成できました。
gatsbyのプロジェクトを確認するため、dev serverを起動します。コマンドは、nx serve <>でrun serverを起動できます。

$ nx serve sample
ou can now view gatsby-starter-default in the browser.
⠀
  http://localhost:4200/
⠀
View GraphiQL, an in-browser IDE, to explore your site's data and schema
⠀
  http://localhost:4200/___graphql

localhost:4200をブラウザで開いて、下記のような画面が表示されれば完成です。

localhost-4200.png

これで基本的なプロジェクトは完成しました。

2. Nxを利用してgatsbyのプロジェクトを作成するメリット

個人的にnxを利用しての利点は、大きく分けて2つ。

  1. 開発で必要な最低限の依存関係を構築できる(react, typescript, jest, cypress)
  2. 企業レベルでのlibrary(特にUI library Util Function)やapplicationを一つのレポジトリで管理できる

2-1. 開発で必要な最低限の依存関係を構築できる(react, typescript, jest, cypress)

初期設定にtypescript, jest (unit test, integration test), cypress(e2e test)が設定されておりオレオレ環境をさけてメンテンアンスコストを下げることが可能。
また、nxを利用して story bookを追加することもできるので、初期セットとしては十二分だと思う。

2-2. 企業レベルでのlibraryやapplicationを一つのレポジトリで管理できる

例えば、ui libraryを独自に作ろうと考えた時に従来であればgit のレポジトリを作成して、そこに作成してgitで依存関係を構築してpackageに依存関係かいたりなど1つのメインレポジトリならいいが、LP Application ApiServerなどやることが増えると毎回依存関係を構築しないといけないが、nxを利用すれば当該関係を1つで管理できる。

例えば、現行のProjectにreactのui libraryを作成するとすると、

  1. react libraryの環境を作成
  2. gatsby applicationで利用する

と3つのステップで対応が可能です。

2-1-1. react libraryの環境を作成

Gatsbyの依存関係しかいれていないので、reactの依存関係を追加します。
(20200729のタイミングで@nrwl/reactを利用しない場合path is undefinedの形式になりエラーになる)

$ yarn add -D @nrwl/react
$ nx g @nrwl/react:lib ui-button --style=css
$ tree -L 2 -I node_modules
.
├── README.md
├── apps
│   ├── sample
│   └── sample-e2e
├── babel.config.json
├── jest.config.js
├── libs
│   └── ui-button # 先ほど指定した ui-buttonが追加される
├── nx.json
├── package.json
├── tools
│   ├── schematics
│   └── tsconfig.tools.json
├── tsconfig.base.json
├── tslint.json
├── workspace.json
└── yarn.lock
  1. gatsby applicationで利用する
apps/sample/pages/index.tsx
// @<<workspace-name>>/<<library name>>で指定が可能
// 参照先はlibs/@<<workspace-name>>/src/index.ts だと思ってもらえればいいです
// 下記でimport可能
import { UiButton } from '@gatsby-sample/ui-button';


上記のような短いステップでlibraryの作成が可能です。

以上がNxを利用してgatsbyのプロジェクトを始めるメリットでした、これ以外にもpluginやschematicsなど色々なことが可能なため、大く利点を感じられるとおもいます。

repo

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