20200621のReactに関する記事は7件です。

React hooks 【シュチュエーション別 hooks利用方法と使い方まとめ】

モダンReactの代表になっているhooksですが、使いこなせるととても便利です。
今まで、クラスコンポーネントや
SFC(Stateless Functional Component)として高階コンポーネントやレンダープロップス、Recomposeなどを使っていた方は使い勝手の良さに驚くでしょう。

また、これからReactを学習する人に向けてもhooksを使いこなすことはとても価値があり、
FC(Function Component)+hooksから入門するべきだと思います。
実際の現場でclassやSFCはレガシーであり、FC+hooksへの移行が進んでいるからです。

復習がてら、どんなhooksがあるのか、どんな時にどう使うのかをまとめました。

前提として

React version 16.8.0 以降
hooksはFCでないと使用できない

Hooks.jsx
import React, { useState, useEffect, useContext } from 'react'


const hooks = () => {
  //関数コンポーネント内にhooksを書く

  return null
}

読み込みが必要ですがここから先ははしょりますので、上記のように読込んでください。
またはReact.hooksの名前でも可能です。

コンポーネント内で状態管理をしたい

useState

const hooks = () => {
  const [count, setCount] = useState(0)
  return(
    <>
      <div>{count}</div>
      <button onClick={() => setCount(count + 1)}>ボタン</button>
    </>
  )  
}

ステートフルな値と、それを変化させるための関数を分裂代入します。
上記は、ボタンを押すたびにsetCountによって状態が変化して数字が増えていきます。

また、useStateの第一引数はinitialState(初期値)となっていて、ロジックが含まれている場合は、関数を渡すこともできます。

  const [count, setCount] = useState(()=> 1*2*3)

オブジェクトの更新の仕方に注意

クラスコンポーネントの setState メソッドとは異なり、下記では正常にstateが更新されません。

const hooks = () => {
  const [user, setUser] = useState({ name: "test", age: "20" })
  return (
    <>
      <div>{user.name}</div>
      <button onClick={() => 
        setUser({user.name = 'testNEW'})
      }>
        ボタン
      </button>
    </>
  )
}

consoleでuserを出すとtestNEWが出力されてしまいます。
正常にnameの値のみ変更するためには、下記のようにスプレッド構文と併用してマージさせるように書きます。

const hooks = () => {
  return (
    <button onClick={() => 
      setUser({...user, ...{ name: "testNEW"}})
    }>
      ボタン
    </button>
  )
}

renderされた後に何か処理をしたい

useEffect

いままでcomponentDidMountをつかって、render後にAPIリクエストを行うといったアクションを取っていたと思いますが、useEffectを使ってそれを実現できます。

正確に言うとcomponentDidMountcomponentDidUpdatecomponentWillUnmount がまとまったものだと考えることができます。

const hooks = () => {
  const [user, setUser] = useState({ name: "test", age: "20" })

  //APIリクエストのサンプルです
  const sampleGetRequest = () => {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve({ name: "NEW" })
      }, 2000)
    })
  }
  //render後に発火
  useEffect(() => {
    sampleGetRequest().then(response => {
      setUser({ ...user, ...response })
    })
  }, [])

  return <div>{user.name}</div>
}

上記のコードを実行すると、renderされてから2秒後に
useStateで持っている、userが更新され、viewが書き換わります。

第二引数について

第二引数を引数なし、空の配列、値を入れた配列で使い分けることによって、3種類の使い方ができます。

引数なし
useEffect(() => {})

コンポーネントがマウントされた後、更新された後に関わらず、毎回のレンダー時に処理を実行します。
componentDidMount と componentDidUpdateを併用しているのと似ています。

空の配列
useEffect(() => {}, [])

コンポーネントが初回にマウントされた後のみ処理を実行します。
componentDidMountを記述しているのと似ています。

値を入れた配列
const [name, setName] = useState('test')
const [age, setAge] = useState(25)

useEffect(() => {
  console.log(`${name}${age}才だ`)
}, [count])

配列内に入れた値を監視してくれます。
上記の例だと、配列の中に記入したcountのデータの内容が書き換わった時に処理が実行されます。
なので、ageのデータ内容が更新されても何も起きず、countが更新された時のみlogが出るようになります。

無限ループに注意

引数を無し、配列に値を入れた引数の場合、無限ループになる可能性があります。
useEffectの説明の最初のコードの引数を無くしてみました。
これだと無限ループに陥って、処理を繰り返してしまいます。
レンダー => setUser({ ...user, ...response }) => レンダー => setUser({ ...user, ...response }) => レンダー => 続く...

const sampleGetRequest = () => {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve({ name: "NEW" })
      }, 2000)
    })
  }

useEffect(() => {
  sampleGetRequest().then(response => {     
    setUser({ ...user, ...response })
  })
})

なぜならsetUser({ ...user, ...response })によってuserの参照は毎回切れてしまうからですね。
{ name: "NEW", age: "20" }から{ name: "NEW", age: "20" }に変更されても、値は変わってないように見えて、参照は変わっているので、仮想DOMの差分を検知して再レンダーしてuseEffect発火という流れができてしまいます。
空の配列を引数に指定すれば治ります。
useEffect内で状態を変化させる際は気をつけましょう。

もっとuseEffectについて詳し知りたい方はuseEffect完全ガイドをご覧ください。

コンポーネントツリー内で、値を共有したい

useContext

通常だとデータを親から子に渡す際、propsとしてバケツリレーで渡していく必要があります。
ですが、useContextを使えば、バケツリレーの必要がなく、一気に下層コンポーネントに値を渡すことができます。
多くの階層を経由していくつかの props を渡すことを避けたいときはうってつけです。
クラスコンポーネントでもコンテクストは使えましたが、それのhooks版という感じです。
下記は、APP.jsxからTitle.jsxまで一気に(Header.jsxを飛び越えて)値を渡している例です。

App.jsx
import React, { createContext } from "react"
import Header from "./Header"

//最上部のコンポーネントではクラスコンポーネントと同じように、
//値を共有するためのコンテクストをcreateContextで作成
export const PageMetaContext = createContext("");

const App = () => {
  const PageMeta = {
    title: "タイトル",
    description: "詳細です"
  };

  return(
  //Header以下のツリーに共有できるようにプロバイダを使用、
  //valueに値を設定することで、ツリー内のどの子コンポーネントにも渡せる(今回は PageMeta)
    <PageMetaContext.Provider value={PageMeta}>
      <Header />
    </PageMetaContext.Provider>
  )
}

export default App;

値を使用する必要のないコンポーネントは何も特別なことはしなくてもいい

Header.jsx
import React from "react"
import Title from "./Title"

const Header = () => (
  <header>
    <Title />
  </header>
)

export default Header;

親のプロバイダで設定された値を使いたいコンポーネントのみuseContextを使う
注意すべき点は、useContext に渡す引数はコンテクストオブジェクト自体であること

Title.jsx
import React, { useContext } from "react";
//ツリーの親で作成されたコンテキストを読み込む
import { PageMetaContext } from "./App";

const Title = () => {
//ここでuseContextを使う
  const { title, description } = useContext(PageMetaContext);

  return (
    <>
      <h1>{title}</h1>
      <span>{description}</span>
    </>
  )
}

export default Title;

これで親で設定したPageMetaの中の文字列がviewに表示されます。

useContext に渡す引数に注意

useContext に渡す引数はコンテクストオブジェクト自体でなければいけません。

上記の例だと、titleのみ使いたいからといって、

const title = useContext(PageMetaContext.title);

にするとエラーが起きてしまうので気をつけましょう。

複数の複雑なstateを1つにして、templateをスッキリさせたい

useReducer

useReducerはuseStateと同じく、コンポーネント内で状態管理をするためのhookで、
useStateの状態管理をより堅牢であり、複雑なロジックが絡んだステートを更新するのに適しています。
Reduxに馴染みがあれば簡単ですが、初見だとわかりにくく使用するのにも気がひけるので順をおって説明します。

下記のようなテキストボックスに入力するした値をリストにできるサンプルをつくりました。

スクリーンショット 2020-06-20 19.58.12.png

2つのコンポーネントによって構成され、
下記はuseRecucerをもち、子コンポーネントからの入力によってリストをレンダーさせる親側のコンポーネントです。

Todo.jsx
import React, { useReducer } from "react"
import InputArea from "./InputArea"

const initialState = { input: "", items: [] }

const reducer = (state, action) => {
  switch (action.type) {
    case "updateInput":
      return { ...state, input: action.payload };
    case "resetInput":
      return { ...state, input: "" };
    case "addItem":
      return { ...state, items: [...state.items, action.payload] };
    case "removeItem":
      const filteredItems = state.items.filter(v => v.key !== action.payload);
      return {
        ...state,
        items: filteredItems
      };
    case "resetItems":
      return initialState;
    default:
      throw new Error();
  }
}

const Lists = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <ul>
        {state.items.map((item, index) => (
          <li key={index}>
            {item.title}
            <button
              onClick={() =>
                dispatch({ type: "removeItem", payload: item.key })
              }
            >
              削除
            </button>
          </li>
        ))}
      </ul>
      <InputArea input={state.input} dispatch={dispatch} />
    </div>
  )
}

export default Lists

まずuseReducerの宣言部分ですが、

const [state, dispatch] = useReducer(reducer, initialState);
  • state :ステートフルな値
  • dispatch :値を更新したいという旨をreducerに通知するための関数
  • useReducerの第1引数のreducer:値を更新するためのロジックが書いてある関数
  • useReducerの第2引数のinitialState:値の初期値、関数でもOK。

実際にレンダリングのために扱う値は、stateの中身になります。

値の更新は、reduceractionの2つの重要な役割によって行われます。

reducer

const reducer = (state, action) => {
  switch (action.type) {
    case "updateInput":
      return { ...state, input: action.payload };
    case "resetInput":
      return { ...state, input: "" };
    case "addItem":
      return { ...state, items: [...state.items, action.payload] };
    case "removeItem":
      const filteredItems = state.items.filter(v => v.key !== action.payload);
      return {
        ...state,
        items: filteredItems
      };
    case "resetItems":
      return initialState;
    default:
      throw new Error();
  }
};

reducer関数は、actionのtypeによって行う処理を分岐させるため、switch文で書きます。

引数の中身は

  • state:現在のステート
  • action:typeとpayloadが入ったオブジェクト
    • type:"updateInput"のような処理をするための名前
    • pyload:action関数から渡させる任意の値

今回のreducerの中には下記のしたい処理が書いてあります。

  • inputの値を更新
  • inputの値を空にする
  • 配列にinputの値を追加
  • 配列から、keyが同じオブジェクトを削除
  • inputと配列の内容を初期化
  • typeがない時エラーを投げる

それぞれのtypeごとに実行される関数から返される値は、新しいステートになります。
それにより、新しいレンダーが行われます。
そして何を元にtypeがわかり、処理が振分けされるのかというと、actionによって行われます。

action

dispatch({ type: "removeItem", payload: item.key })

actionは簡単にいうと、reducerへの更新依頼です。
useReducerで宣言時に代入されたdispatchを使うことで実現します。
{ type: "removeItem", payload: item.key }はreducer内のactionで使うことができます。

下位コンポーネントでstateの更新をさせる

下記は先ほどのTodo.jsxの子コンポーネントで、テキストボックスから値を入力し、stateを更新する役割を持っているコンポーネントです。

inputArea.jsx
import React from "react";

const InputArea = ({ input, dispatch }) => {
  const hundleChange = event => {
    dispatch({ type: "updateInput", payload: event.currentTarget.value });
  };

  const addItem = () => {
    dispatch({
      type: "addItem",
      payload: { title: input, key: new Date().getTime() + Math.random() }
    });
    dispatch({ type: "resetInput" });
  };

  const resetItems = () => {
    dispatch({ type: "resetItems" });
  };
  return (
    <div>
      <input type="text" onChange={e => hundleChange(e)} value={input} />
      <button onClick={addItem}>追加</button>
      <button onClick={resetItems}>リセット</button>
    </div>
  );
};

export default InputArea;

もしuseStateを使っている場合は親コンポーネントからコールバック関数を受け取り、発火させることでstateの更新ができましたが、
useReducerを使っている場合は、dispatchを渡し、子コンポーネント内でstate変更のactionを記述すれば良いので、stete更新のためのロジックが親コンポーネントに溜まらないので、見通しがよくなりますし、コンポーネントの役割が明確になります。
また、コンポーネントツリーが大きくなっている場合は、propsでなく、useContextと組み合わせて使えば、小規模アプリならreduxを使わずに、useContextuseReducerでコードの肥大化を防ぐことができるでしょう。

レンダーごとに計算を実行されないように処理をキャッシュしたい

useMomo

useMemoはメモ化されたを返します。
メモ化というのはプログラムの高速化のための最適化する技術の1つで、処理結果を保持しておき、あるトリガーがあるまで処理を行わずに保持してある値を返すことを言います。

下記の例はuseMomeを使っていないコンポーネントで、

  • テキストボックスの値を入力することで、stateのinputが更新
  • ボタンを押すと、stateのcountの三乗が計算され、consoleが出る

というものです

import React, { useState, useMemo } from "react";

const Memo = () => {
  const [count, setCount] = useState(2);
  const [input, setInput] = useState("");

  const newCount = () => {
    console.log("計算します");
    return Math.pow(count, 3);
  };

  return (
    <div>
      <input
        type="text"
        value={input}
        onChange={e => setInput(e.target.value)}
      />
      <p>{newCount()}</p>
      <button onClick={() => setCount(prev => prev + 1)}>increment</button>
    </div>
  );
};

export default Memo;

上記だとonChangeが発火されるたびに、consoleに計算しますが出力されます。
これは計算結果が変わることはないのにnewCount関数が実行され、無駄な計算を繰り返していることになります。
今回の例は、そこまで複雑な計算ではないですが、もっと複雑になったり、他の処理も重なってくるとレンダリング速度のパフォーマンスの低下に繋がります。

ここでuseMomeを使います

 const newCount = useMemo(() => {
    console.log("render");
    return Math.pow(count, 3);
 }, [count]);

これによりnewCountはメモ化された値の入る変数になり、引数として配列に入っているcountに変更があるまで処理は実行されません。
先ほどの問題は解消され、onChangeが発火して、stateのinputが書き換わっても、再度計算されなくなりました。

useMemoの第二引数の指定に注意

  • 引数なし:全てのstate, propに依存します。結果的に不必要に処理が行われるためメモ化する意味がなくなります。
  • 空配列[]:何にも依存しません。処理が行われるのはレンダー直後のみになります。
  • 値を入れた配列:配列内の変数に変更があるたびに、処理が実行されます。複数入力可能です。useMemoを使う場合は、忘れないようにしましょう。

親からpropsとして渡される関数による無駄なレンダーを避けたい

useCallback

簡単にいうとuseMomeの関数版です。
useCallbackの場合はメモ化された関数を返します。

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

React-redux

なぜReduxを使うのか?

1.stateの見通しをよくするため
2.どこからでもstateを参照/変更可能にするため
3.モジュールを疎結合にするため→これにより機能Aと機能Bが影響し合わない

storeで管理してあげることで伝達による意図しない副作用を阻止することができる

Fluxフローってなに?

1.データフロー設計の一つ
2.データが常に1方向に流れる
3.イベントが発生したらデータが変化(イベント駆動)

Flux思想をReactの状態管理に適用したライブラリ=Redux
2020/06/21

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

react-native run-ios で error Could not find "Podfile.lock" at のエラーが出た時の対処法

概要 

react-native init <project name>

をした後に、

react-native run-ios

を実行すると、

error Could not find "Podfile.lock" at .....

のエラーが出た時の対処法。

解決コマンド

1 - cocoapods をインストールする

sudo gem install cocoapods

2 - 以下のコマンドを実行

cd ios && pod install && cd ../ && react-native run-ios

3 - もし上のコマンドで、pod install にエラーが出る場合、以下を実行

xcrun -k --sdk iphoneos --show-sdk-path

4 - この時にもし以下のエラーが出る場合

xcrun:_ error: SDK "iphoneos" cannot be located
xcrun: error: SDK "iphoneos" cannot be located
xcrun: error: unable to lookup item 'Path' in SDK 'iphoneos'

5 - この場合は以下のコマンドを実行し、2のpod install コマンドを再度実行する。

sudo xcode-select --switch /Applications/Xcode.app

以上で

react-native run-ios

で無事エミュレータを起動することができた。

おわり。

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

ON/OFFスイッチ~React HPより~

ReactのHPでは,チュートリアルなど様様な情報を提供しています.
その中で,メインコンセプトとして,「イベント処理」というページでON/OFFスイッチを作っているのですが,僕の頭では見ても書いてもよくわからなかったので,登場するコードについてまとめてみます.

最初に

このページのわからないことを,どうやって調べたらいいかわからないので助けてください.

完成形

HPに書いてある以下のコードを書くと,クリックするONとOFFの文字で行き来するボタンが表示される時計が作れます
image.png

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

わからないこと

とりあえずわからないコードは,is ToggleOn: truebind!state.isToggleOn{this.state.isToggleOn ? 'ON' : 'OFF'}
調べ方が悪いのかbind以外,解説が全然出てこない(笑)

なので,以下に書いてあることは根拠がなく,すべて私の今の理解ですので,何かヒントや参考になるページ,修正があればコメントを頂きたいです.
よろしくお願いいたします.

私の理解

  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};
    this.handleClick = this.handleClick.bind(this);
  }

ここでは初期値を設定してますね.
最初はisToggleOnはtrueです

render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }

ここでは,ボタンを設定していますね.
trueだと「ON」,falseだと「OFF」ってことですかね.

 handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

handleClickメソッドを定義していますね.
stateの内容を変更しています.
このメソッドにおけるisToggleOnは,その前の時点におけるstateのisToggleOnの真偽値を!で変更しているのかな??

参考

React HP

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

Reactのコンポーネントとpropsについて

プログラミングの勉強日記

2020年6月21日 Progate Lv.140
ReactⅡ

コンポーネント

 コンポーネントは部品やパーツという意味。Reactでは見た目を機能ごとにコンポーネント化して、コンポーネントを組み合わせてWebサイトの見た目を作る。今回はコンポーネントにLanguageという名前を付ける。

コンポーネントの構成

Language.js
import React from 'react'; //Reactのインポート
//React.Componentを継承するLanguageクラスを作成(このクラスがコンポーネント)
class Language extends React.Component{ 
  render(){  //renderメソッドを定義
    return(
      //JSX
    );
  }
}

コンポーネントの表示

 LanguageコンポーネントをApp.jsに呼び出して表示させることで、Languageコンポーネントがブラウザに表示される。JSX内に<コンポーネント名 />と書くことでコンポーネントを表示できる。コンポーネントは何度でも表示することが可能。

React.3png.png

React5.png

Language.js
export default Language; //コンポーネントをexport
App.js
import React from 'react'; 
import Language from './Language'; //コンポーネントをインポート
class Language extends React.Component{ 
  render(){ 
    return(
      <div>
        <h1>言語一覧<h1>
        <Language /> 
    );
  }
}

props

 App.jsから各言語の名前と画像のデータをLanguageコンポーネントに渡すことで言語ごとに表示を変えることができる。App.jsから渡すこのデータをpropsという。props=値という形でコンポーネントを呼び出す箇所で渡す。

App.js
<Language
  name='HTML&CSS'
  image='https://~'
/>
Language.js
render(){
  console.log(this.props);
  return( ... );
}

 渡された値はthis.propsで取得できる。{props名:値}というオブジェクトになる。

コンソール
{
  props:"HTML&CSS",
  image:"https://~~"
}

propsの取得

 this.propsと書くことで{props:値}というオブジェクトを取得できるので、this.props.props名とすることで、propsの値を取得できる。

Language.js
<div className='language-name'>
  {this.props.name)
</div>
<img className='language-image' src={this.props.image} />

mapメソッド

 配列内のすべての要素に処理を行い、その戻り値から新しい配列を作成するメソッド。JSXをmapを用いて効率的に表示している。mapメソッドで配列の各要素に対して順番に処理を行い表示することができる。

App.js
const languageList=[
  {name:'HTML&CSS', image:'https://~'}
];
...
<div>
  {languageList.map((languageItem)=>{
  return(
    <Lanuage
      name={languageItem.name}
      image={languageItem.image}
    />
  ...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactの表示の仕組みとCSSの適用

プログラミングの勉強日記

2020年6月21日 Progate Lv.140
ReactⅡ

表示の仕組み

 App.jsに書かれているJSXは最終的にHTMLに変換されることでブラウザに表示される。Reactのコードを実際にブラウザに表示するためには、index.jsとindex.htmlのファイルも必要である。
React.png

React.2png.png

App.jsとindex.jsとidex.htmlの関係

App.js
class App extends Component{
  render()
    return(
      <div>
        <h1>HelloWorld</h1>
      </div>
    );
  }
}
index.js
import App from './component/App';
ReactDOM.rendr(<App/>, document.getElementByld('root');

 ReactDOM.rendr(<App/>でApp.jsのJSX(render()の括弧内の部分)がHTMLに変換されている。

index.html
<body>
  <div id="root"></div>
</body>

 document.getElementByld('root')で指定したid名の場所に挿入される。index.jsでは('id名')を記述する。

CSSの適用

 JSXは最終的にindex.htmlに挿入されてブラウザに表示する。なので、indx.html内でstylesheet.cssを読み込むとCSSを適用することができる。
 JSXにクラス名を付ける場合、HTMLと書き方が違う。className='クラス名'と書く。
 

index.html
<head>
  <link rel="stylesheet" href="stylesheet.css">
</head>
<body>
  <div id="root">
    <!--ここにHTMLが挿入される-->
  </div>
</body>
App.js
render(){
  retunr(
    <div>
      <h1='title'>Hello World</h1>
      <p className='subTitle'>Hello React</p>
    </div>
  );
}
stylesheet.css
h1{ /*JSXのタグ名を指定してCSSを適用する*/
  color:red;
}
.subTitle{
  color:blue;
}

React3.png

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

値が確定されてから実行される遅延評価useEffect

株式会社ムラウチドットコムのフロントエンドエンジニアです。
ムラウチドットコムのOrganizationができて初めての投稿です。

今回はユーザーの入力で値が確定してから実行されるカスタムuseEffectを考えたので紹介します。

コード

import React from "react";

export const useDelayedEffect = (
  effect: React.EffectCallback,
  deps?: React.DependencyList,
  delaytime: number = 1000,
) => {
  const [waiting, setWaiting] = React.useState(false);
  const timer = React.useRef<number>();

  React.useEffect(() => {
    window.clearTimeout(timer.current);

    setWaiting(true);

    timer.current = window.setTimeout(() => {
      setWaiting(false);
    }, delaytime);
  }, deps);

  React.useEffect(() => {
    if (!waiting) {
      effect();
    }
  }, [waiting]);
};

使い方

普通のuseEffectの引数に加えて入力が確定したと判断するまでのdelaytimeを指定するだけです。

import React from "react";
import { useDelayedEffect } from "./useDelayedEffect";

export const App = () => {
  const [text, setText] = React.useState("");
  const [confirmedText, setConfirmedText] = React.useState("");

  useDelayedEffect(
    () => {
      setConfirmedText(text);
    },
    [text],
    1500,
  );

  return (
    <div>
      <input onChange={e => setText(e.target.value)} value={text} />
      <p>{`text: ${text}`}</p>
      <p>{`confirmedText: ${confirmedText}`}</p>
    </div>
  );
};

画面収録 2020-06-21 0.39.58.mov.gif

解説

入力完了を待っているかどうかの状態をwaitingで保持します。
ひとつめのuseEffectwaitingtrueにしてdelaytimeミリ秒後にwaitingfalseにするタイマーをセットします。
useEffectは入力1文字1文字で実行されるので、その都度タイマーを解除してdelaytimeを測り直しています。
レンダリングのたびにカスタムフック関数が実行されるため、

let timer;
// 中略
window.clearTimeout(timer);
// 中略
timer = window.setTimeout(() =>  setWaiting(false), delaytime);

では実装できないことに注意してください(毎回timerが初期化されるため)。useRefを用いることで再レンダリングされても参照できる変数を定義します。

ふたつめのuseEffectwaitingtrueからfalseになったとき、つまり入力がdelaytimeミリ秒以上行われなかったときに引数effect関数を実行します。

ユースケース

ユーザーの入力値を自動保存する場合などに利用できると思います。
通常のuseEffectに保存のためのHTTPリクエストを仕込んでおくと、例えば "Hello world." を入力すると12回HTTP送信してしまいます。
そうではなくユーザーの入力が終わってから送信すれば無駄なHTTPリクエストをへらすことができます。

まとめ

入力が確定してから実行されるuseEffectを紹介しました。
もっといい書き方やすでに実装されているライブラリ等あったらぜひおしえてください。

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