- 投稿日:2020-05-19T22:57:16+09:00
意味のない useCallback とその理由と解消法
reack-hooksの1機能である useCallback はパフォーマンス改善の文脈でよく登場しますが、どうもその利点や使い方には分かりにくい部分があるように感じます。私も少し前まではまでは「コールバックを子コンポーネントに渡す場合に使ったほうがいいらしい」くらいにしか理解していませんでした。
実際 useCallback は単体で利用してもその効果を発揮しにくく、注意しないと「意味のない useCallback 」が生まれてしまう可能性があります。
本記事ではあえて「意味のない useCallback 」になってしまう例を用意し、その理由の考察と解消を通してuseCallbackの基本的な利用方法を説明します。
(紹介するのはあくまで useCallback 利用の1例になりますが、useCallback の基本的な役割を理解するのに役に立つはずです)想定読者
useCallback の存在は知っているがどんな時になぜ必要になるのかはイマイチ理解していない方。
(つまり少し前までの私)結論
最初に本記事の結論を記載します。
(これだけで理解できてしまう方は、恐らく以降を読む必要は無いでしょう。)
- コールバックを受け取るコンポーネントは、
「propsの更新」と「親コンポーネントの再レンダリング」という2つの要因によって、
不要な再レンダリングが行われる(ことがある)- useCallbackを利用すると(不要な)「propsの更新」は抑制できるが、
「親コンポーネントの再レンダリング」という要因は残るので、
結局再レンダリングが起きてしまう。
(この段階では「意味のないuseCallback」)- React.memo等を組み合わせることで、 「親コンポーネントのレンダリング」をトリガーとする不要なレンダリングも抑制できる
- 2.と3.の合わせ技で、全体として不要なレンダリングを無くすことができる。
(こうなって初めて「意味のあるuseCallback」に!)本題: 意味のないuseCallbackとその理由と解消法
ここからが本題。
useCallbackの概要
まずuseCallbackについて、公式ドキュメントの記載を引用します。
https://ja.reactjs.org/docs/hooks-reference.html#usecallback
メモ化されたコールバックを返します。
インラインのコールバックとそれが依存している値の配列を渡してください。useCallback はそのコールバックをメモ化したものを返し、その関数は依存配列の要素のひとつが変化した場合にのみ変化します。これは、不必要なレンダーを避けるために(例えば shouldComponentUpdate などを使って)参照の同一性を見るよう最適化されたコンポーネントにコールバックを渡す場合に便利です。
うん。よく分かりませんね
でもこれわかりやすく説明するの難しいんですよ。。。
以降一応私なりの説明を入れますが、雰囲気を掴むくらいにして次の具体例に進んでください。(あまり細かい表現にツッコミを入れないように!)まず最初にざっくり結論を言うと、
「useCallbackを使うとコールバックを不変の値にできる」
と考えておくと良いと思います。コールバックは大抵コンポーネントの中で宣言すると思いますが、その場合コンポーネントの再レンダリングのたびに、コールバックも再生成されます。このコールバックを子コンポーネントにpropsとして渡す場合、毎回異なるpropsを受け取っていると判断されます(処理内容は同じであるにも関わらず...)。
propsの更新はコンポーネントの再レンダリングの条件です(詳細は後述)。そのためコールバックを受け取ったコンポーネントは不必要に何度も再レンダリングされます。パフォーマンスに悪影響を与える可能性があるためこの動作は望ましくありません。
useCallbackはこれを防ぎます。コンポーネントが何度再レンダリングされても、useCallbackを用いて作成されたコールバックは再生成されず同じ値を返します。その結果コンポーネントのpropsの変更が抑制され、不要な再レンダリングを減らすことができる!というのがuseCallbackに期待するべき役割(の代表例)です。
useCallbackを使いたくなるシーン
具体例をみてみましょう。以下にサンプルAを用意しました。フォームに文字を入力すると、文字の長さを表示する簡単なアプリです。
// サンプルA const App = () => { const [ input, setInput ] = useState("") const onChange = (e: React.ChangeEvent<HTMLInputElement>) => setInput(e.target.value) return ( <> <InputWithLabel onChange={onChange} /> <Length input={input} /> </> ) } const InputWithLabel = (prop: { onChange: (e: React.ChangeEvent<HTMLInputElement>) => void }) => { const { onChange } = prop console.log('!!!!rendering InputWithLabel!!!!') return ( <> <span>Label: </span> <input type="text" onChange={onChange}/> </> ) } const Length = (props: { input: string }) => ( <div>length: {props.input.length}</div> )入力と表示を別コンポーネント(InputWithLabel & Length)に分けており、InputWithLabelには外からコールバック(onChange)を渡しています。フォームの状態(input)は親コンポーネント(App)に持たせることで、InputWithLabelとLengthの両方から状態へのアクセスが可能なようにしています。
またInputWithLabelはレンダリングされるたびに、ブラウザのコンソール上に「!!!!rendering InputWithLabel!!!!」を表示します。
実際に動かすと以下のようになります。
フォームに文字を入力する度に、ブラウザのコンソール上で「!!!!rendering InputWithLabel!!!!」が表示されます(2回目以降は左のバッジ内の数字がインクリメントされる)。文字入力の度にInputWithLabelが再レンダリングされていることが分かります。
フォームに文字を入力してもInputWithLabelのpropsやstateは変化しません。そのため本来であればこの再レンダリングは行われて欲しくありません。
しかし一方でAppはstate(= input)が更新されることで再レンダリングが行われ、それに伴いonChangeも再生成されます。InputWithLabelから見ればprops(= onChange)が更新されたことになるので、再レンダリングが実施されます。これは不本意ながら自明な動作です。
意味のないuseCallback
このサンプルコードをuseCallbackを使って改善します。InputWithLabelの意図しないpropsの更新を止めることができれば、不要な再レンダリングを抑制出来るはずです!
(しかしながらこの章のタイトルは「意味のないuseCallback」...)// サンプルB const App = () => { const [ input, setInput ] = useState("") const onChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => setInput(e.target.value), [setInput]) return ( <> <InputWithLabel onChange={onChange} /> <Length input={input}/> </> ) }修正したのはAppのonChangeだけです。(InputWithLabel, Lengthは割愛しました。)
useCallbackを利用したため、Appが再レンダリングされたとしてもonChangeは再生成されません。結果InputWithLabelのprops(= onChange)は変わらないため、不要な再レンダリングも行われないはずです...!実際に動かしてみましょう。
...あれ?結果が変わりません。フォームに入力する度に「!!!!rendering InputWithLabel!!!!」が繰り返し表示されています。おかしい。こんなことは許されない。。。。せっかくuseCallbackを使ったのに効果を発揮していないじゃないですか!
そう本記事のタイトルの通り、「意味のない useCallback 」になってしまっているのです。
なぜ「意味のないuseCallback」になってしまったか
なぜこんなことになってしまったのでしょうか?
そのためにはまず「Reactコンポーネントの再レンダリング条件」を理解する必要があります。Reactコンポーネントの再レンダリング条件
Reactコンポーネントの再レンダリングはおおよそ以下3つの条件で発生します。
- propsの更新
- stateの更新
- 親コンポーネントが再レンダリングされた時
詳細は以下のサイトを参考にしてください。(もしかしたら簡単に別記事書くかも。。。)
https://ja.reactjs.org/docs/react-component.html#the-component-lifecycle
https://qiita.com/teradonburi/items/5b8f79d26e1b319ac44f
https://www.kirupa.com/react/avoiding_unnecessary_renders.htmその上で2点留意事項があります。この後の説明で重要になってきますので覚えておいてください。
- 3つの条件は重複して発生する
- 条件を1つでも満たした場合には再レンダリングが起こる
InputWithLabel における再レンダリングの条件(useCallback未使用時)
これを踏まえて話をサンプルA(useCallback未使用)に戻します。
サンプルアプリのフォームに文字を入力した際、InputWithLabel は再レンダリングされました。その際に満たされた「再レンダリングの条件」は何でしょうか?
// サンプルA(再掲) const App = () => { const [ input, setInput ] = useState("") const onChange = (e: React.ChangeEvent<HTMLInputElement>) => setInput(e.target.value) return ( <> <InputWithLabel onChange={onChange} /> <Length input={input} /> </> ) } const InputWithLabel = (prop: { onChange: (e: React.ChangeEvent<HTMLInputElement>) => void }) => { const { onChange } = prop console.log('!!!!rendering InputWithLabel!!!!') return ( <> <span>Label: </span> <input type="text" onChange={onChange}/> </> ) } const Length = (props: { input: string }) => ( <div>length: {props.input.length}</div> )正解は
「1. propsの更新」
「3. 親コンポーネントが再レンダリングされた時」
です。InputWithLabelに渡されるコールバック(onChange)が毎回生成されるため、
「1. propsの更新」が満たされます。またonChange内で更新されるinputは親コンポーネントのstateなので、
「3. 親コンポーネントが再レンダリングされた時」も満たされます。つまりコンポーネントの再レンダリング条件は重複して満たされることになります。
もしこの「不要な再レンダリング」を取り除くなら、両方の条件を同時に抑制する必要があります。InputWithLabel における再レンダリングの条件(useCallback使用時)と、「意味のないuseCallback」になってしまった原因
次は再度サンプルB(useCallback使用)を見てみます。
useCallbackを使った結果、満たされる再レンダリングの条件はどのように変わるでしょうか?// サンプルB(再掲) const App = () => { const [ input, setInput ] = useState("") const onChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => setInput(e.target.value), [setInput]) return ( <> <InputWithLabel onChange={onChange} /> <Length input={input}/> </> ) }正解は
「1. propsの更新」は抑制される
「3. 親コンポーネントが再レンダリングされた時」はそのまま
です。useCallbackによってonChangeが不変の値になります。つまりInputWithLabelから見て「1. propsの更新」は抑制されます。
しかしこれだけでは
コールバック内で親コンポーネントの状態が更新されることにより起こる、
「3. 親コンポーネントが再レンダリングされた時」の条件を抑制することができません。InputWithLabelはAppが再レンダリングされた場合には、props/stateが全く変化していなくても再レンダリングされてしまいます。せっかくuseCallbackを用いて「1. propsの更新」を抑制しても、これでは意味がありません。
「意味のないuseCallback」が生み出されてしまった原因はまさにここにあります。
正しく言えばこの場合のuseCallbackは全く意味がないわけではなく、「1. propsの更新」というコンポーネントの更新条件を抑制しています。
ただ他にもコンポーネントの更新条件を満たしてしまっているため、
結果としてuseCallbackだけでは「不要な再レンダリングを抑制できない」ということになります。解消法
さてではこの問題をどのように解消すれば良いでしょうか?
話としては簡単です。
「3. 親コンポーネントが再レンダリングされた時」の条件も一緒に抑制してしまえばいいわけです。ここで出てくるのがReact.memo/PureComponent/shouldComponentUpdateになります。これらを用いることで、「3. 親コンポーネントが再レンダリングされた時」の条件を満たす場合でも、
stateやpropsが更新されていないなら再レンダリングをしない、
という処理が実現できます。
詳細は公式ドキュメント等を参照してください。もしくはググれば参考になる記事がたくさん出てきます!
(そろそろ疲れてきたのでぶん投げ)では実際にReact.memoを利用してサンプルコードを修正してみます。
const App = () => { const [ input, setInput ] = useState("") const onChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => setInput(e.target.value), [setInput]) return ( <> <InputWithLabel onChange={onChange} /> <Length input={input} /> </> ) } const InputWithLabel = React.memo((prop: { onChange: (e: React.ChangeEvent<HTMLInputElement>) => void }) => { const { onChange } = prop console.log('!!!!rendering InputWithLabel!!!!') return ( <> <span>Label: </span> <input type="text" onChange={onChange}/> </> ) }) const Length = (props: { input: string }) => <div>length: {props.input.length}</div>変わったのはInputWithLabelの宣言部分です。元々のコンポーネントをReact.memoでラッピングしています。
では動作を確認してみます。素晴らしい! 先ほどまでと異なり「!!!!rendering InputWithLabel!!!!」が表示されません。つまりInputWithLabelは再レンダリングされていないということになります。これでようやく「不要なレンダリング」を抑制できました。やったぜ!
1点重要なのは、React.memoやPureComponentだけではこの「不要なレンダリングの抑制」はできなかったということです。繰り返しになりますが、InputWithLabelが受け取るコールバック(onChange)は、useCallbackを使って宣言されないと、レンダリングの度に再生成されてしまいます。React.memoだけでは不要なpropsの更新は抑制できないため、今度は逆のパターンで不要なレンダリングの抑制に失敗するというわけです。
useCallbackとReact.memoを組み合わせることによって初めて不要なレンダリングを取り除くことができた形になります。
まとめ
本記事ではuseCallbackが効果を発揮するケースとしないケースの違いを通して、useCallbackの基本的な役割について説明しました。
そもそもコールバック関数は親コンポーネントの状態を変更することが多いと思います。つまり「3. 親コンポーネントが再レンダリングされた時」の条件はコールバック関数を使う時点で満たされるケースが多いはずです。すると必然的にReact.memoと組み合わせないとuseCallbackは効果を発揮できなくなります。
(うーん、何度書いてもややこしくて説明しにくい。。。)この記事を読んだ方のuseCallbackに対する理解が、少しでも深まれば幸いです。
(と言っても私もあまり深く理解しているわけではないですが;)また少し余談になりますが、Reactコンポーネントにおいて「不要な再レンダリング」が話題になる場合、たいてい「3. 親コンポーネントが再レンダリングされた時」が関連しているのではないかと個人的には思っております。その意味でこの3つ目の条件は重要です。(ある意味当たり前すぎるのか)普段あまり話題に登ることが少ないように感じますが、覚えておいて損はないでしょう。
とか偉そうなことをたくさん言っていますが、自信が無い点も多々あります。
(そもそもの解釈が間違ってたらどうしよう。。。)
これは間違ってるよーという話がありましたら是非ともコメントくださいm(_ _)m
- 投稿日:2020-05-19T21:38:55+09:00
React初心者向け | stateとpropsって何?
stateとpropsって何?
stateとpropsはReactのコンポーネントがデータを受け取ったり、状態を管理するために使用します。
まずはproosについてご説明します。
propsとは
propsは親コンポーネントから渡されたプロパティを受け取る時に、使用します。
例えば、Appコンポーネントが親コンポーネント、Childコンポーネントが子コンポーネントだった場合、以下のように書きます。
import React from 'react'; class App extends React.Component { render() { return ( <div> <Child name="AppコンポーネントからChildコンポーネントへ渡った値"/> </div> ) } } class Child extends React.Component { render() { return ( <div> <h3> {this.props.name} </h3> </div> ) } } export default App;AppコンポーネントでChildコンポーネントを呼び出す時に、引数nameに値で「AppコンポーネントからChildコンポーネントへ渡った値」を代入します。
Childコンポーネントは受け取った引数を「this.props.引数名」で受け取ることができます。
propsはこのように親コンポーネントで引数を渡し、子コンポーネントで値を受け取ることです。
またコンポーネントがクラスではなく、関数でChildコンポーネントを定義した場合、関数の引数にpropsを定義して、コンポーネント内で「props.name」とすることで、値を受け取ることができます。
import React from 'react'; function App() { return ( <div> <Child name="AppコンポーネントからChildコンポーネントへ渡った値"/> </div> ) } function Child(props) { return ( <div> <h3> {props.name} </h3> </div> ) } export default App;state
Stateは変数の状態を管理するためのオブジェクトです。stateを使用する場合は使用するコンポーネントクラスのconstructorメソッドで初期値を設定しなければなりません。以下のコードではstateのnameに文字列「hello」を代入できます。
constructor(props) { super(props); this.state={ name: "hello", }; }stateはJavascriptの変数とは違い、値を変える時に関数setState()を使用しなければなりません。以下のコードではstateのnameをsetStateメソッドを使い、「hello」→「こんにちは」に変更できます。
this.setState({name: "こんにちは"}); // stateを更新する場合は、必ずsetStateメソッドstateを変更する場合、以下のように文字列「hello」を代入してはいけません。
this.state.name = "hello" // やってはいけない例stateを使って、nameに文字列「hello」を代入し、Appコンポーネント表示させ、nameの文字列をbuttonを押して、変更できるようにした場合以下のようになります。
import React from 'react'; export default class App extends React.Component { constructor(props) { super(props); this.state={ name: "hello", // nameの初期値を「hello」にする。 }; } changeName = () => { // ボタンを押した時に、changeNameメソッド内のsetStateが作動して、nameがhello→こんにちはになる。 this.setState({ name: "こんにちは" }) } render() { return ( <div> {this.state.name} <button onClick={this.changeName}>changeName</button> // ボタンを押したら、changeNameメソッドが作動する。 </div> ) } }このようにpropsとstateはデータの受け渡し(props)と状態の管理(state)の役割をReactJSでしています。
またpropsとstateを合わせて使うと以下のようになります。Appコンポーネント内でstateのnameを管理し、Childコンポーネントにpropsでデータを渡すの以下のようになります。
import React from 'react'; export default class App extends React.Component { constructor(props) { super(props); this.state={ name: "hello", }; } changeName = () => { this.setState({ name: "こんにちは" }) } render() { return ( <div> <Child name={this.state.name}></Child> // Childコンポーネントに引数でthis.state.nameを渡す <button onClick={this.changeName}>changeName</button> </div> ) } } class Child extends React.Component { render() { return ( <div> <h3> {this.props.name} // Appコンポーネントで受け取ったthis.state.nameをthis.props.nameで受け取る </h3> </div> ) } }
- 投稿日:2020-05-19T20:48:49+09:00
[React]JSX, props, stateについて
はじめに
Reactに今まで全く触れてこなかったのですが、元々興味があったため最近になって少し勉強をし始めました。
その中で、新しい内容や概念が多く登場したため簡単に調べた内容を簡単にまとめてみます。
自分がプログラミングに触れてから時間も浅いため、この内容も初学者向けのものになります。React.jsとは
Facebook社が公開している,UIのパーツ(構成部品)を作るためのライブラリです。
React.jsの特徴
- UIの機能追加に特化しており、ユーザーが使いやすいUIを作りやすくしてくれる
- 効率よく処理を行い、できるだけユーザーが操作にストレスを感じないように高速で動作する
- コンポーネント指向 (UIを部品化して管理を行いやすくし、再利用をしやすくする設計手法の1つ)
JSX
JavaScript XMLの略で、JavaScriptを拡張した言語です。
テンプレート言語の一つになります。JSXの特徴
- HTML風に書けるため可読性が高い
- HTMLのタグが使える
- JSXTransfomerが自動でコンパイルをしてくれる
実際にAppクラスを定義してその中に、JSXで画面にHello world.と表示するコードを書いてみます。
Test.jsimport React, { Component } from 'react'; class App extends Component { render() { return <h1>Hello world.</h1>; } } export default App;このように直感的に書くことができることがメリットとなります。
JSXでの記述だけを見ると、1行目のimport React, { Component } from 'react';
は必要無いようにも思えますが、JSXで記述したものはwebpackコマンド実行時に内部的にbabelが呼ばれて、以下のように変換されることにより一般的なWebブラウザでも解釈できるJavaScript構文へと変換されています。Test.jsimport React, { Component } from 'react'; class App extends Component { render() { return React.createElement ( "h1", null, "Hello world." ); } } export default App;そのため、JSXを使用するためには1行目の記述が必要になります。
Component(コンポーネント)
- クラスコンポーネント
- ファンクショナルコンポーネント(関数コンポーネント)
の2種類があります。
props
コンポーネントのプロパティのことです。
親コンポーネントから子コンポーネントへの受け渡しが出来るが、受け取ったpropsの変更や更新は出来ません。
なお、関数の受け渡しも可能ですpropsの参照
this.propsdefaultProps
propsのデフォルト値を設定できます。
propTypes
propsのバリデーションです。
Test.js// 型のチェック Hoge.propTypes = { num: PropTypes.number } //型と値の有無のチェック Hoge.propTypes = { num: PropTypes.isRequired.number }state
コンポート内部の状態のことであり、コンポーネント内で使用できる値です。
値が変更される度にrender
が走ります。
propsと似ているようで違います。propsとの違い
- 使用箇所
- props: 親コンポーネントから子コンポーネントへ受け渡してが可能
- state: そのコンポーネント内部でのみ使用可能
- 変更、更新
- props: 不可
- state; 可
stateの参照
this.state初期値の設定
constructor
メソッドを使用します。
なお、constructor
メソッドにはprops
を渡すことができます。Test.jsconstructor(props) { super(props); this.state={ hoge: 0, }; }値の更新
setState
メソッドを使用します。Test.jsthis.setState({hoge: 1});
setState
メソッドを使用して更新するとレンダリングがされるのですが、以下の方法ではレンダリングがされないため非推奨となっています。Test.jsthis.state.hoge = 1;おわり
今回はReact.jsで必須となる知識について自分のアウトプットも兼ねて書きました。
最後まで見ていただき、ありがとうございました。
- 投稿日:2020-05-19T20:48:49+09:00
[React][超初級] JSX, props, stateについて
はじめに
Reactに今まで全く触れてこなかったのですが、元々興味があったため最近になって少し勉強をし始めました。
その中で、新しい内容や概念が多く登場したため簡単に調べた内容を簡単にまとめてみます。
自分がプログラミングに触れてから時間も浅いため、この内容も初学者向けのものになります。React.jsとは
Facebook社が公開している,UIのパーツ(構成部品)を作るためのライブラリです。
React.jsの特徴
- UIの機能追加に特化しており、ユーザーが使いやすいUIを作りやすくしてくれる
- 効率よく処理を行い、できるだけユーザーが操作にストレスを感じないように高速で動作する
- コンポーネント指向 (UIを部品化して管理を行いやすくし、再利用をしやすくする設計手法の1つ)
JSX
JavaScript XMLの略で、JavaScriptを拡張した言語です。
テンプレート言語の一つになります。JSXの特徴
- HTML風に書けるため可読性が高い
- HTMLのタグが使える
- JSXTransfomerが自動でコンパイルをしてくれる
実際にAppクラスを定義してその中に、JSXで画面にHello world.と表示するコードを書いてみます。
Test.jsimport React, { Component } from 'react'; class App extends Component { render() { return <h1>Hello world.</h1>; } } export default App;このように直感的に書くことができることがメリットとなります。
JSXでの記述だけを見ると、1行目のimport React, { Component } from 'react';
は必要無いようにも思えますが、JSXで記述したものはwebpackコマンド実行時に内部的にbabelが呼ばれて、以下のように変換されることにより一般的なWebブラウザでも解釈できるJavaScript構文へと変換されています。Test.jsimport React, { Component } from 'react'; class App extends Component { render() { return React.createElement ( "h1", null, "Hello world." ); } } export default App;そのため、JSXを使用するためには1行目の記述が必要になります。
Component(コンポーネント)
- クラスコンポーネント
- ファンクショナルコンポーネント(関数コンポーネント)
の2種類があります。
props
コンポーネントのプロパティのことです。
親コンポーネントから子コンポーネントへの受け渡しが出来るが、受け取ったpropsの変更や更新は出来ません。
なお、関数の受け渡しも可能ですpropsの参照
this.propsdefaultProps
propsのデフォルト値を設定できます。
propTypes
propsのバリデーションです。
Test.js// 型のチェック Hoge.propTypes = { num: PropTypes.number } //型と値の有無のチェック Hoge.propTypes = { num: PropTypes.isRequired.number }state
コンポート内部の状態のことであり、コンポーネント内で使用できる値です。
値が変更される度にrender
が走ります。
propsと似ているようで違います。propsとの違い
- 使用箇所
- props: 親コンポーネントから子コンポーネントへ受け渡してが可能
- state: そのコンポーネント内部でのみ使用可能
- 変更、更新
- props: 不可
- state; 可
stateの参照
this.state初期値の設定
constructor
メソッドを使用します。
なお、constructor
メソッドにはprops
を渡すことができます。Test.jsconstructor(props) { super(props); this.state={ hoge: 0, }; }値の更新
setState
メソッドを使用します。Test.jsthis.setState({hoge: 1});
setState
メソッドを使用して更新するとレンダリングがされるのですが、以下の方法ではレンダリングがされないため非推奨となっています。Test.jsthis.state.hoge = 1;おわり
今回はReact.jsで必須となる知識について自分のアウトプットも兼ねて書きました。
最後まで見ていただき、ありがとうございました。
- 投稿日:2020-05-19T10:07:07+09:00
Shopify JS Buy SDKでProductのtagsが取れない場合(Uncaught Error: No field of name "tags" found on type "Product)
まずRead product tagsがONか確認
サンプルコードを参考にする
Initializing the Client(リンクページの下の方)
// fetch the large, unoptimized version of the SDK import Client from 'shopify-buy/index.unoptimized.umd'; const client = Client.buildClient({ domain: 'your-shop-name.myshopify.com', storefrontAccessToken: 'your-storefront-access-token' });Fetching Products(リンクページの下の方)
// Build a custom products query using the unoptimized version of the SDK const productsQuery = client.graphQLClient.query((root) => { root.addConnection('products', {args: {first: 10}}, (product) => { product.add('title'); product.add('tags');// Add fields to be returned }); }); // Call the send method with the custom products query client.graphQLClient.send(productsQuery).then(({model, data}) => { // Do something with the products console.log(model); });ここで注目しないといけないのはClientのimport
// import Client from 'shopify-buy'; ではだめ import Client from 'shopify-buy/index.unoptimized.umd';上のやり方だと、variantsとかどう取るの?
- 投稿日:2020-05-19T09:57:01+09:00
Next.jsでoptimizeした画像を自動的に適用する方法
Lighthouseなどで画像がデカイとか言われる
個々の画像の処理は面倒
なので、自動化する
- まずはnext-optimized-imagesをいれる
- さらに必要なライブラリを追加で入れる
- JPEGなら
imagemin-mozjpeg
- PNGなら
imagemin-optipng
- WEBPなら
webp-loader
- Responsiveな画像を用意したいなら
responsive-loader
とsharp
...など- 併せてnext-compose-pluginsも入れる(入れなくても良いが、next.config.jsを書く時に綺麗に纏められる)
- 下のように
next.config.js
を設定する(コメント部分はnext-optimized-imagesのデフォルト)- 画像をリンクする際には
/public
からの絶対パスではなく、相対パスでrequire(...)
する(例:<img src={require("...")} />
)- devではoptimizeされないので注意(
optimizeImagesInDev: true
でdevでも動くとあるが、next.jsがハングアップする模様...)- 念のために再起動する
yarn build
で画像のパスが変わっているか確認next.config.jsconst withPlugins = require("next-compose-plugins") const optimizedImages = require("next-optimized-images") const nextConfig = { // あれば } module.exports = withPlugins( [ [ optimizedImages, { // these are the default values so you don't have to provide them if they are good enough for your use-case. // but you can overwrite them here with any valid value you want. // inlineImageLimit: 8192, // imagesFolder: 'images', // imagesName: '[name]-[hash].[ext]', // handleImages: ['jpeg', 'png', 'svg', 'webp', 'gif'], // optimizeImages: true, // optimizeImagesInDev: false, // mozjpeg: { // quality: 80, // }, // optipng: { // optimizationLevel: 3, // }, // pngquant: false, // gifsicle: { // interlaced: true, // optimizationLevel: 3, // }, // svgo: { // // enable/disable svgo plugins here // }, // webp: { // preset: 'default', // quality: 75, // }, }, ], ], nextConfig )require(...)する時に相対パスではなく、絶対パスでやりたい場合
next.config.jsconst { resolve } = require("path") const nextConfig = { webpack: (config) => { // next-optimized-images // 名前は何でも良い(@p、~images、@publicImages ...) config.resolve.alias["@public"] = resolve(__dirname, "public") return config }, }こうしておいて、/publicからいつものように参照するように
@public
から始める(名前は何でも良い)... ... return(<img src={require("@public/../../")} />) ... ...
- 投稿日:2020-05-19T07:49:23+09:00
Reactでメディアクエリを良い感じに処理する
Reactでメディアクエリ
メディアクエリを良い感じにしてくれるreact-responsiveなる物が存在するが、微妙になんか使いにくい。
もっとなんか良い感じに使えるようにラッパーした物を定義してみる。定数定義
なんか良い感じに使えるようにまず良い感じに定数定義をしておく。
constants.tsxexport const BreakPoint = { sm: 640, md: 768, lg: 1024, xl: 1280, } as const; export type BreakPoint = typeof BreakPoint[keyof typeof BreakPoint]; export const WidthCriteria = { max: 'max-width', min: 'min-width', } as const; export type WidthCriteria = typeof WidthCriteria[keyof typeof WidthCriteria];
Criteriaってワードなんか微妙な感じがするけどまぁ。。。いいか。。。良い感じな奴を定義
定数を引数に取る良い感じの関数を再定義する。
useMediaQuery.tsximport { useMediaQuery } from 'react-responsive' import { BreakPoint, WidthCriteria } from "constants"; export default (breakpoint: BreakPoint, criteria: WidthCriteria): boolean => { const emSize = breakpoint / 16; return useMediaQuery({ query: `(${criteria}: ${emSize}em)` }); };使い方はこんな感じ
import React from 'react'; import { BreakPoint, WidthCriteria } from 'constants'; import useMediaQuery from 'useMediaQuery'; const Component: React.FC = () => { const isMobile = useMediaQuery(BreakPoint.sm, WidthCriteria.max); return ( <div> {isMobile ? 'mobile' : 'desktop'} </div> ); }; export default Component;これでなんかもっと良い感じに使えるようになった。
だがまだ良い感じには程遠い
Styled-Componentsでこんな感じに使えるようにしたいが、世の中そんなに甘くない。
Hooksの外でuseを使うなってガチギレされる。import { BreakPoint, WidthCriteria } from 'constants'; import useMediaQuery from 'useMediaQuery'; const Wrapper = styled.div` #{useMediaQuery(BreakPoint.md, WidthCriteria.min) && ` background-color: red; `}; #{useMediaQuery(BreakPoint.lg, WidthCriteria.min) && ` background-color: blue; `}; `; const Component: React.FC = () => { return ( <Wrapper> responsive </Wrapper> ); };Styled-Components用に良い感じな奴を定義
良い感じにメディアクエリを処理できるMixin的な物を定義する。
mediaQuery.tsximport { css, FlattenSimpleInterpolation } from "styled-components"; import { BreakPoint, WidthCriteria } from "constants"; export default (breakpoint: BreakPoint, criteria: WidthCriteria) => { const emSize = breakpoint / 16; return (style: FlattenSimpleInterpolation) => css` @media (${criteria}: ${emSize}em) { ${style}; }; `; };使い方はこんな感じ
import mq from 'mediaQuery'; const Wrapper = styled.div` ${mq(BreakPoint.md, WidthCriteria.min)(css` background-color: red; `)}; ${mq(BreakPoint.lg, WidthCriteria.min)(css` background-color: blue; `)}; `; const Component: React.FC = () => { return ( <Wrapper> responsive </Wrapper> ); };おわり
コード全文 https://gist.github.com/Karibash/15bfc974a76bca4ad6e69dfe6fc8f2c7
良い感じにメディアクエリを処理できる物が完成したが、
React初めて1週間くらいなんで、ぶっちゃけこれが正解なのか良くわからない。
マサカリ大歓迎です、待ってます。
- 投稿日:2020-05-19T06:23:16+09:00
2ステップで実装!Next.js初心者のための画面遷移
1.はじめに
Next.jsを使い始めると「画面遷移処理ってどうするん?」って思って手が止まる人が多いのではないでしょうか?
なのでこの記事はNextの画面遷移がしっかり扱えるようにまとめておきました。2.なぜNext.jsで画面遷移を設定するのか?
まず初めにNextでなぜ画面遷移を設定するのでしょうか?
別に「aタグで記述しておけば、画面遷移するでしょう!」って考える人もいると思いますが実は違いがあるのです。aタグはHTMLファイルを取得して全画面を表示していますが、Nextは前の画面と比較して異なる部分だけを入れ替えています。
そのため、Nextの画面遷移のほうが軽く、動きが滑らかに感じます。3.Next.jsで画面遷移する方法
それでは具体的にNextで画面遷移する方法を見ていきましょう!
Nextで画面遷移する方法は2種類あります。
1. Linkコンポーネントを使う
2. Router.pushを使う
Linkコンポーネント
はJSXでHTMLのように記述する方法でRouter.push
はイベントハンドラーなどの処理の中で設定する方法です。
JSXを使うかどうかで使い分けることができ、JSXを使う場合はLinkコンポーネント
、イベントハンドラの場合はRouter.push
を使います。
- JSXの場合:Linkコンポーネント
- イベントハンドラの場合:Router.push
3-1.Linkコンポーネント
Linkコンポーネントはaタグをラップすることで使うことができ、特徴はJSXでタグとして扱えることです。
Linkコンポーネントは2ステップで実装できます。
- Linkコンポーネントのインポート
- Linkコンポーネントでaタグを包む
次のソースは、
/
から/NextPage
に遷移する方法ですLinkコンポーネントでの画面遷移//①Linkコンポーネントのインポート import Link from "next/link"; export default function Index() ( <div style={{textAlign: "center"}}> <h1>遷移元</h1> {/* ②Linkコンポーネントでaタグを包む */} <Link href="/NextPage"> <a>次のページへ進む</a> </Link> </div> );①でLinkコンポーネントを使うために必要なモジュールを取り込み、②でaタグをLinkコンポーネントでラップして遷移先を設定するだけの処理です。
この2ステップでNextでの画面遷移を実装できます。ちなみにリンクにCSSクラスなどのオプションを設定する場合はaタグに設定します。
例えば、別のタブで画面を開きたいときはtaget
属性をaタグに設定します。
aタグにオプションを設定<Link href="/NextPage"> <a target="_blank">新しいページを開く</a> </Link>3-2.Router.push
次はRouter.pushで遷移する方法です。
Router.pushは処理の中に埋め込めるので柔軟なページ遷移が可能です。
イベントハンドラなどに実装できるのが特徴で、Router.pushでの実装も2ステップで設定できます。
- Routerオブジェクトのインポート
- 遷移先の設定
次のソースは
/NextPage
から/
に遷移するプログラムです。Router.pushでの画面遷移//①Routerオブジェクトのインポート import Router from 'next/router' export default function NextPage() { function clickHandler() { //②遷移先の設定 Router.push('/') return } return ( <div style={{textAlign: "center"}}> <h1>遷移先</h1> <button onClick={clickHandler}>前のページへ戻る</button> </div> ); }①でRouterオブジェクトをインポートして、②のpushメソッドで遷移するパスを設定しています。
Next.jsのindex.jsは特殊なページで/index
でなくて/
でも遷移できます。
また、Pagesフォルダーを基準にパスを設定するので今回の場合だとpagesフォルダーの直下にあるindex.jsファイルのコンポーネントに遷移しています。4.まとめ
この記事ではNextで画面遷移する方法を2種類解説しました
・JSXでの画面遷移はLinkコンポーネント
・イベントハンドラーでの画面遷移はRouter.push画面遷移はNext.jsでもよく使う機能なので覚えていて損はないです。
5.参考文献
- 投稿日:2020-05-19T01:09:52+09:00
[React] 「投稿がありません」コメント描画のロジックでハマった話
前提
- React.js
- データベースはFirestore(Firebaseのcloud datebase)
- 投稿記事のvalueにFirebase Authenticationのuidをアップして連携可能にしている
ハマったこと
現在SNSアプリの仕組みを実装している中、
投稿がない場合、「投稿がありません」と1文を出そうと思っていたところ、
下記コードのように配列でループで表示いるため、リスト分「投稿がありません」と表示されてしまう。
このままだとログイン(auth)投稿者以外の記事分、表示されてしまう。いや〜マジで困った。
やりたいこと
authユーザーの記事のみ表示 ← これのみならすでに可能
かつ
もし記事がなかったら「投稿がありません」の1文を1個のみ表示問題の箇所抽出{list.map(item => ( <React.Fragment key={item.docId + String(new Date())}> {authId === item.authId ? ( <div>{item.msg}</div> ) : ( {/* ループの中に入っているためリスト分表示されてしまう */} <div> 投稿がありません </div> )} </React.Fragment> ))}解決方法
以下の方法で無事解決できました。
うまくいった方法
処理を分けて書けばよかった
js側・・・投稿記事を抽出するロジックを書いて結果を出しておく
- 事前に配列をfilter
して、表示すべき分だけに絞り込んでおく
- 配列のlength
を取得jsx側・・・表示のみにする
- 1つもなければ(lengthが0なら)「ありませんでした」メッセージを表示
-filter
後の配列をmapして出力解決に至った記事
【JavaScript入門】filterで配列のデータを抽出する方法
解決箇所抽出// js側で抽出 // auth userの投稿記事のみ抽出 const result = list.filter((e) => { return e.authId === authId; }); console.log(result); const length = result.length; console.log(length); {/* jsx側で表示 */} <div className="post-list user-post-list"> {length !== 0 ? ( <React.Fragment> {result.map(item => ( <div key={item.docId + String(new Date())}> <div className="auth-inner-post-list"> <div className="auth-inner-post-text"> <div className="post-msg">{item.msg}</div> <span className="delete" onClick={() => handleDelete(item.docId)}>> Delete...</span> </div> </div> </div> ))} </React.Fragment> ) : ( <div className="no-user-posts">まだ記事が投稿されていません</div> )} </div>コード全文import React from 'react'; import { Link } from 'react-router-dom'; import firebase, { db } from '../../Firebase'; import SignOut from '../FirebaseAuthHook/SignOut'; import { useCollectionData } from "react-firebase-hooks/firestore"; import BackTopIcon from '../../assets/img/backtop.png'; import UserIcon from '../../assets/img/user.png'; import styled from 'styled-components'; const BackTopStyle = styled.img` width: 50px; top: 25px; left: 25px; position: fixed; zIndex: 11, `; const Index = () => { // Firebese Auth uid, email取得 const user = firebase.auth().currentUser; let authId; let name; let photoURL; if (user !== null) { user.providerData.forEach(() => { authId = user.uid; name = user.displayName; photoURL = user.photoURL; }); } // Render const [ list, loading, error ] = useCollectionData(db.collection('posts').orderBy('createdAt', 'desc'), { idField: 'docId' }); if (loading) return <div>Loading...</div>; if (error) return <div>Error...</div>; // auth userの投稿記事のみ抽出 const result = list.filter((e) => { return e.authId === authId; }); console.log(result); const length = result.length; console.log(length); // Delete const handleDelete = (uid) => { if (window.confirm('削除しますか?')) { db.collection('posts').doc(uid).delete(); } } return( <React.Fragment> <SignOut /> <Link to="/"><BackTopStyle src={ BackTopIcon } alt="Topに戻る"/></Link> <div className="user-wrapper"> <div className="user"> {photoURL ? ( <img src={ photoURL } className="auth-user-icon" alt="User Thumbnail" /> ) : ( <img src={ UserIcon } className="auth-user-icon" alt="Firebase Thumbnail" /> )} <div className="user-datail"> {name ? ( <div className="user-name">{name}</div> ) : ( <div className="user-name">Firebaseユーザー</div> )} </div> </div> <div className="post-list user-post-list"> {length !== 0 ? ( <React.Fragment> {result.map(item => ( <div key={item.docId + String(new Date())}> <div className="auth-inner-post-list"> <div className="auth-inner-post-text"> <div className="post-msg">{item.msg}</div> <span className="delete" onClick={() => handleDelete(item.docId)}>> Delete...</span> </div> </div> </div> ))} </React.Fragment> ) : ( <div className="no-user-posts">まだ記事が投稿されていません</div> )} </div> </div> </React.Fragment> ); } export default Index;今回のことで学んだこと
分かってみると実に単純なことでした。
今後はView側で全て処理しようとせずにJS側でロジックを組むクセづけが必要だと感じました。
またフレームワークを使っていても素のJSのメソッドがかなり重要だと実感しています。