- 投稿日:2020-06-21T23:16:11+09:00
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.jsximport 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
を使ってそれを実現できます。正確に言うと
componentDidMount
とcomponentDidUpdate
とcomponentWillUnmount
がまとまったものだと考えることができます。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.jsximport 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.jsximport React from "react" import Title from "./Title" const Header = () => ( <header> <Title /> </header> ) export default Header;親のプロバイダで設定された値を使いたいコンポーネントのみ
useContext
を使う
注意すべき点は、useContext
に渡す引数はコンテクストオブジェクト自体であることTitle.jsximport 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に馴染みがあれば簡単ですが、初見だとわかりにくく使用するのにも気がひけるので順をおって説明します。下記のようなテキストボックスに入力するした値をリストにできるサンプルをつくりました。
2つのコンポーネントによって構成され、
下記はuseRecucerをもち、子コンポーネントからの入力によってリストをレンダーさせる親側のコンポーネントです。Todo.jsximport 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
の中身になります。値の更新は、
reducer
とaction
の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.jsximport 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を使わずに、useContext
+useReducer
でコードの肥大化を防ぐことができるでしょう。レンダーごとに計算を実行されないように処理をキャッシュしたい
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
の場合はメモ化された関数を返します。
- 投稿日:2020-06-21T22:25:16+09:00
React-redux
- 投稿日:2020-06-21T18:10:32+09:00
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 cocoapods2 - 以下のコマンドを実行
cd ios && pod install && cd ../ && react-native run-ios3 - もし上のコマンドで、pod install にエラーが出る場合、以下を実行
xcrun -k --sdk iphoneos --show-sdk-path4 - この時にもし以下のエラーが出る場合
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で無事エミュレータを起動することができた。
おわり。
- 投稿日:2020-06-21T12:45:45+09:00
ON/OFFスイッチ~React HPより~
ReactのHPでは,チュートリアルなど様様な情報を提供しています.
その中で,メインコンセプトとして,「イベント処理」というページでON/OFFスイッチを作っているのですが,僕の頭では見ても書いてもよくわからなかったので,登場するコードについてまとめてみます.最初に
このページのわからないことを,どうやって調べたらいいかわからないので助けてください.
完成形
HPに書いてある以下のコードを書くと,クリックするONとOFFの文字で行き来するボタンが表示される時計が作れます
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: true
,bind
,!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の真偽値を!で変更しているのかな??参考
- 投稿日:2020-06-21T06:53:47+09:00
Reactのコンポーネントとpropsについて
プログラミングの勉強日記
2020年6月21日 Progate Lv.140
ReactⅡコンポーネント
コンポーネントは部品やパーツという意味。Reactでは見た目を機能ごとにコンポーネント化して、コンポーネントを組み合わせてWebサイトの見た目を作る。今回はコンポーネントにLanguageという名前を付ける。
コンポーネントの構成
Language.jsimport React from 'react'; //Reactのインポート //React.Componentを継承するLanguageクラスを作成(このクラスがコンポーネント) class Language extends React.Component{ render(){ //renderメソッドを定義 return( //JSX ); } }コンポーネントの表示
LanguageコンポーネントをApp.jsに呼び出して表示させることで、Languageコンポーネントがブラウザに表示される。JSX内に
<コンポーネント名 />
と書くことでコンポーネントを表示できる。コンポーネントは何度でも表示することが可能。Language.jsexport default Language; //コンポーネントをexportApp.jsimport 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.jsrender(){ 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.jsconst languageList=[ {name:'HTML&CSS', image:'https://~'} ]; ... <div> {languageList.map((languageItem)=>{ return( <Lanuage name={languageItem.name} image={languageItem.image} /> ...
- 投稿日:2020-06-21T06:22:32+09:00
Reactの表示の仕組みとCSSの適用
プログラミングの勉強日記
2020年6月21日 Progate Lv.140
ReactⅡ表示の仕組み
App.jsに書かれているJSXは最終的にHTMLに変換されることでブラウザに表示される。Reactのコードを実際にブラウザに表示するためには、index.jsとindex.htmlのファイルも必要である。
App.jsとindex.jsとidex.htmlの関係
App.jsclass App extends Component{ render() return( <div> <h1>HelloWorld</h1> </div> ); } }index.jsimport 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.jsrender(){ retunr( <div> <h1='title'>Hello World</h1> <p className='subTitle'>Hello React</p> </div> ); }stylesheet.cssh1{ /*JSXのタグ名を指定してCSSを適用する*/ color:red; } .subTitle{ color:blue; }
- 投稿日:2020-06-21T01:50:23+09:00
値が確定されてから実行される遅延評価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> ); };解説
入力完了を待っているかどうかの状態を
waiting
で保持します。
ひとつめのuseEffect
でwaiting
をtrue
にしてdelaytime
ミリ秒後にwaiting
をfalse
にするタイマーをセットします。
useEffect
は入力1文字1文字で実行されるので、その都度タイマーを解除してdelaytime
を測り直しています。
レンダリングのたびにカスタムフック関数が実行されるため、let timer; // 中略 window.clearTimeout(timer); // 中略 timer = window.setTimeout(() => setWaiting(false), delaytime);では実装できないことに注意してください(毎回
timer
が初期化されるため)。useRef
を用いることで再レンダリングされても参照できる変数を定義します。ふたつめの
useEffect
でwaiting
がtrue
からfalse
になったとき、つまり入力がdelaytime
ミリ秒以上行われなかったときに引数effect
関数を実行します。ユースケース
ユーザーの入力値を自動保存する場合などに利用できると思います。
通常のuseEffect
に保存のためのHTTPリクエストを仕込んでおくと、例えば "Hello world." を入力すると12回HTTP送信してしまいます。
そうではなくユーザーの入力が終わってから送信すれば無駄なHTTPリクエストをへらすことができます。まとめ
入力が確定してから実行される
useEffect
を紹介しました。
もっといい書き方やすでに実装されているライブラリ等あったらぜひおしえてください。