20210512のReactに関する記事は6件です。

Reactのコンポーネントの出し消しを滑らかにする

コンポーネントの出現を滑らかにする motion.divコンポーネントを使用する。 - initial propでアニメーションの初期状態を設定(透明から出現させたい時はopacity:0を設定)。 - animate propでアニメーションの最終状態(出現する直前の状態)を設定。 コンポーネントの消滅を滑らかにする motion.divコンポーネントをAnimatePresenceコンポーネントでラッピングする。 - 上記に加え、motion.divコンポーネントのexit propで、アニメーションの最終状態(消える直前の状態)を定義。 なお、AnimationPresenceコンポーネントのinitial propをfalseに設定すると、最初のレンダリング時にはコンポーネントの出現時にアニメーションが効かなくなる。 その他 variantsをhashとして定義し、これをmotion.divコンポーネントのvariants propsに突っ込むことで、initial, animate, exitpropをこのhashのキーとして設定すると、hashのvalueがそれぞれのpropとして解釈される。 const variants: Variants = { initial: { opacity: 0, }, animate: { opacity: 1, transition={duration: 2} }, exit: { opacity: 0, transition={duration: 2} } }; <AnimatePresence> {isVisible && ( <motion.div variants={variants} initial="initial" animate="animate" exit="exit" > <MyComponent/> // このcomponentの入出現時にアニメーションがかかる </motion.div> )} </AnimatePresence> おまけのサンプルコード 職場の先輩にかいてもらったやつ。 import { useState } from "react"; import { AnimatePresence, motion, Variants } from "framer-motion"; import "./styles.css"; const variants: Variants = { initial: { opacity: 0, y: -16 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -16 } }; export default function App() { const [isVisible1, setIsVisible1] = useState(false); const [isVisible2, setIsVisible2] = useState(false); return ( <div className="App"> <div> <button type="button" onClick={() => setIsVisible1((v) => !v)}> Toggle </button> <AnimatePresence> {isVisible1 && ( <motion.div variants={variants} initial="initial" animate="animate" exit="exit" style={{ display: "block", padding: "8px", backgroundColor: "rgba(0, 0, 0, 0.06)", borderRadius: "4px" }} > initial="initial" </motion.div> )} </AnimatePresence> </div> <div style={{ marginTop: "32px" }}> <button type="button" onClick={() => setIsVisible2((v) => !v)}> Toggle </button> <AnimatePresence> {isVisible2 && ( <motion.div variants={variants} initial={false} animate="animate" exit="exit" style={{ display: "block", padding: "8px", marginTop: "32px", backgroundColor: "rgba(0, 0, 0, 0.06)", borderRadius: "4px" }} > initial=false </motion.div> )} </AnimatePresence> </div> </div> ); } Ref https://www.framer.com/api/motion/animate-presence/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

motion.divを使ってReactのコンポーネントの出し消しを滑らかにする

コンポーネントの出現を滑らかにする motion.divコンポーネントを使用する。 - initial propでアニメーションの初期状態を設定(透明から出現させたい時はopacity:0を設定)。 - animate propでアニメーションの最終状態(出現する直前の状態)を設定。 コンポーネントの消滅を滑らかにする motion.divコンポーネントをAnimatePresenceコンポーネントでラッピングする。 - 上記に加え、motion.divコンポーネントのexit propで、アニメーションの最終状態(消える直前の状態)を定義。 なお、AnimationPresenceコンポーネントのinitial propをfalseに設定すると、最初のレンダリング時にはコンポーネントの出現時にアニメーションが効かなくなる。 その他 variantsをhashとして定義し、これをmotion.divコンポーネントのvariants propsに突っ込むことで、initial, animate, exitpropをこのhashのキーとして設定すると、hashのvalueがそれぞれのpropとして解釈される。 const variants: Variants = { initial: { opacity: 0, }, animate: { opacity: 1, transition={duration: 2} }, exit: { opacity: 0, transition={duration: 2} } }; <AnimatePresence> {isVisible && ( <motion.div variants={variants} initial="initial" animate="animate" exit="exit" > <MyComponent/> // このcomponentの入出現時にアニメーションがかかる </motion.div> )} </AnimatePresence> おまけのサンプルコード 職場の先輩にかいてもらったやつ。 import { useState } from "react"; import { AnimatePresence, motion, Variants } from "framer-motion"; import "./styles.css"; const variants: Variants = { initial: { opacity: 0, y: -16 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -16 } }; export default function App() { const [isVisible1, setIsVisible1] = useState(false); const [isVisible2, setIsVisible2] = useState(false); return ( <div className="App"> <div> <button type="button" onClick={() => setIsVisible1((v) => !v)}> Toggle </button> <AnimatePresence> {isVisible1 && ( <motion.div variants={variants} initial="initial" animate="animate" exit="exit" style={{ display: "block", padding: "8px", backgroundColor: "rgba(0, 0, 0, 0.06)", borderRadius: "4px" }} > initial="initial" </motion.div> )} </AnimatePresence> </div> <div style={{ marginTop: "32px" }}> <button type="button" onClick={() => setIsVisible2((v) => !v)}> Toggle </button> <AnimatePresence> {isVisible2 && ( <motion.div variants={variants} initial={false} animate="animate" exit="exit" style={{ display: "block", padding: "8px", marginTop: "32px", backgroundColor: "rgba(0, 0, 0, 0.06)", borderRadius: "4px" }} > initial=false </motion.div> )} </AnimatePresence> </div> </div> ); } Ref https://www.framer.com/api/motion/animate-presence/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React チュートリアルで useReducer 入門

はじめに 「React チュートリアルを Hooks + TypeScript へリファクタリングする」の続編です。 前回までの Tic Tac Toe (三目並べ)へ useReducer を投入します。 前回までの Tic Tac Toe See the Pen react-tutorial-hooks-ts by sprout2000 (@sprout2000_jp) on CodePen. 1. useReducer のおおよそ useReducer フックの構文 引用元:フック API リファレンス - React 以下同様 useReducer は、useState の代替品です。(state, action) => newState という型のリデューサ (reducer) を受け取り、現在の state を dispatch メソッドとペアにして返します。 const [state, dispatch] = useReducer(reducer, initialState); state: 現在のステート dispatch: reducer の呼び出し関数。ステートの更新を行う reducer: state と action (後述)を引数に取り、新しいステートを返す関数 initialState: ステートの初期値 通常、useReducer が useState より好ましいのは、複数の値にまたがる複雑な state ロジックがある場合や、前の state に基づいて次の state を決める必要がある場合です。また、useReducer を使えばコールバックの代わりに dispatch を下位コンポーネントに渡せるようになるため、複数階層にまたがって更新を発生させるようなコンポーネントではパフォーマンスの最適化にもなります。 reducer 関数の構文 reducer(state: State, action: Action) => newState: State; Action は、どのようなときにどのステートをどのように更新するかを定義したオブジェクトで、以下のような型式になります。 { type: 'add', value: number } type: string が「どのようなとき」をあらわす action の識別子で、value がその action が受け取る引数(不要な場合もある)です。 上の例を reducer 関数に組み込むと以下のようになります。 interface State { count: number; name: string; } type Action = { type: 'add'; value: number }; const reducer = (state: State, action: Action): State => { if (action.type === 'add') { return { ...state, count: action.value }; } else { return state; } }; add タイプの action が呼び出されたとき、state.count に引数の値を代入してステート全体を返していることが分かると思います。 dispatch で reducer を呼び出す方法 前項の例を使って dispatch を発行してみましょう。 dispatch({ type: 'add', value: state.count + 1 }); add という識別子を持つ action を呼び出し、ステートの count を 1 増加させています。 参考記事 2. tic-tac-toe のリファクタリング 前掲の tic-tac-toe のコードを見ると、history や stepNumber、xIsNext といったステートを更新している箇所は2つしかありません。 handleClick = (i: number) => void; jumpTo = (step: number) => void; の2つのコールバック関数です。 このステートを更新する2つの関数を Action へ移設し、それを dispatch を通じて呼び出すような仕組みへとリファクタリングすればいいのです。 1. 初期ステートを作成する 現状のステートの型を定義しておきます。 State interface State { history: History[]; stepNumber: number; xIsNext: boolean; } 上の型定義を利用してステートの初期値を設定します。 initialState const initialState: State = { history: [{ squares: Array(9).fill(null) }], stepNumber: 0, xIsNext: true, }; 2. Action の型を検討する action へとその処理を移設させるのは、handleClick(i: number) と jumpTo(i: number) の2つのコールバック関数でした。 どちらも number 型を引数に取り、返り値はありません。Action の型は以下のようになります。 Action type Action = | { type: 'click'; value: number; } | { type: 'jump'; value: number; }; 3. useState を useReducer へ変換する useState 文をすべて削除し、useReducer でステートをひとつにまとめます。 src/index.tsx import React, { useReducer } from 'react'; ~ snip ~ const Game: React.VFC = () => { const [state, dispatch] = useReducer(reducer, initialState); ※ まだ reducer 関数を実装していないのでエラーになります。 4. reducer 関数を実装する 上記の型定義を利用して reducer の構文だけを作成してみます。 reducer const reducer = (state: State, action: Action): State => {}; ここへ action の type ごとに前項のコールバック関数の内部処理を移記していきます。 handleClick 1. 現在のステートを参照している変数へ state. を追加する reducer case 'click': { const historyCurrent = state.history.slice(0, state.stepNumber + 1); const current = historyCurrent[historyCurrent.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) return; squares[i] = state.xIsNext ? 'X' : 'O'; 2. ステート以外を return している部分を修正する reducer // State 型以外は return できないので引数の state をそのまま返す if (calculateWinner(squares) || squares[i]) return state; 3. 元の handleClick の引数を action.value として受け取る reducer if (calculateWinner(squares) || squares[action.value]) return state; squares[action.value] = state.xIsNext ? 'X' : 'O'; 4. setHoge() 文を return 文へ書き換える reducer case 'click': { const historyCurrent = state.history.slice(0, state.stepNumber + 1); const current = historyCurrent[historyCurrent.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[action.value]) return state; squares[action.value] = state.xIsNext ? 'X' : 'O'; return { ...state, history: [...historyCurrent, { squares }], stepNumber: historyCurrent.length, xIsNext: !state.xIsNext, }; } jumpTo jumpTo 関数も同様に action 内へ移記します。 reducer case 'jump': { return { ...state, stepNumber: action.value, xIsNext: action.value % 2 === 0, }; } default: return state; } 5. dispatch から reducer 関数を呼び出す 従来のコールバック関数の内部処理を dispatch 発行に置き換えます。 src/index.tsx const handleClick = (i: number) => { dispatch({ type: 'click', value: i }); }; const jumpTo = (step: number) => { dispatch({ type: 'jump', value: step }); }; 6. 従来のステートを参照している変数を修正する 変数の頭に state. を付け加えます。 src/index.tsx const [state, dispatch] = useReducer(reducer, initialState); const handleClick = (i: number) => { dispatch({ type: 'click', value: i }); }; const jumpTo = (step: number) => { dispatch({ type: 'jump', value: step }); }; const historyCurrent = [...state.history]; const current = historyCurrent[state.stepNumber]; const winner = calculateWinner(current.squares); const moves = state.history.map((_step, move) => { const desc = move ? `Go to move #${move}` : 'Go to game start'; return ( <li key={move}> <button onClick={() => jumpTo(move)}>{desc}</button> </li> ); }); const status = winner ? `Winner: ${winner}` : `Next player: ${state.xIsNext ? 'X' : 'O'}`; ここまででエラーメッセージはいちおう無くなって Tic Tac Toe が動作するようになったと思います。 7. 子コンポーネントを memo 化する コンポーネントを memo 化すると、渡される props に変化がなければ再計算されないのでパフォーマンスが向上することがあります。 1. memo のインポート src/index.tsx import React, { useReducer, memo } from 'react'; 2. 各コンポーネントを memo でラップする src/index.tsx const Square: React.VFC<SquareProps> = memo((props) => { return ( <button className="square" onClick={props.onClick}> {props.value} </button> ); }); eslint-plugin-react / eslint-plugin-react-hooks の設定によっては、以下のようなエラーが表示されることがあります。 コンポーネントの displayName を設定しましょう。 src/index.tsx Square.displayName = 'Square'; 3. ここまでの Tic Tac Toe See the Pen react-tutorial-reducer-ts by sprout2000 (@sprout2000_jp) on CodePen. 姉妹編
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】useCallbackとuseMemoによる表示パフォーマンス改善

以前、React.memoによるReactの表示パフォーマンスの改善についてまとめました。 それに引き続き、今回はReact.memoとあわせて使われるHooksである、useCallbackとuseMemoについて調べ、レンダリングの挙動について検証しました。 useCallback useCallbackはメモ化されたコールバック関数を返すReact Hooksです。 インラインのコールバック関数とそれが依存している値の配列を渡すと、useCallbackはそのコールバック関数をメモ化したものを返し、その関数は依存配列の要素のいずれかが変化した場合にのみ変化します。 useCallbackは主に親コンポーネントで使用し、メモ化されたコールバック関数は子コンポーネントのpropsとして渡します。 子コンポーネントをReact.memoでエクスポートしていれば、propsの変化がない限り余計なレンダリングは起こりません。 このようにして、Reactの表示パフォーマンスを向上させてくれます。 検証 Log nameを押すとconsole.logに文言が出力され、Increase Count1(2)を押すとカウントが表示されるような画面をつくりました。 レンダリング時にfunctionsへlogName関数が追加されるようにし、useCallbackの使用有無でその挙動がどのように変わるのかを調べました。 useCallbackを使わない場合 画面の実装は以下のようになります。 App.js import React from 'react'; import logo from './logo.svg'; import './App.css'; const functions = new Set(); const App = () => { const [count1, setCount1] = useState(0); const [count2, setCount2] = useState(0); const incrementCount1 = () => setCount1(count1 + 1); const incrementCount2 = () => setCount2(count2 + 1); const logName = () => console.log('Yihua'); functions.add(logName); //レンダリングの度にfunctionsインスタンスにlogName関数が追加される console.log(functions); return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> Count1: {count1} <button onClick={incrementCount1}>Increase Count1</button> Count2: {count2} <button onClick={incrementCount2}>Increase Count2</button> <button onClick={logName}>Log name</button> </header> </div> ); }; export default App; Increase Count1(もしくは2)を押すとState(count1, count2)の変化が起こるため、カウントが増える度に再レンダリングされます。 その結果、レンダリングの度にfunctionsにはlogName関数が追加されています。 useCallbackを使う場合 const logName = () => console.log('Yihua');と記述していた箇所をuseCallbackで書き換えます。 このとき、第2引数には空配列を与えているので、初期表示でメモ化を行った後に関数の再生成は起こりません。 App.js const logName = useCallback(() => console.log('Yihua'), []); その結果、Increase Count1(2)を何回押しても、functionsにはlogName関数は追加されていきません(Set(1)じゃなかった理由は調査中) useMemo useCallbackではコールバック関数のメモ化を行いましたが、useMemoは値のメモ化を行います。 “作成用” 関数とそれが依存する値の配列を渡し、依存配列の要素のいずれかが変化した場合にのみメモ化された値を再計算します。 この最適化によりレンダリングごとにコストの高い計算が実行されるのを避けることができます。 useMemoもuseCallbackと同様に、React.memoとあわせて使われます。 検証 Increase Count1を押したときだけdoSomethingComplicated関数が実行され、計算結果がcomplexValueとして表示されるような画面を実装しました。 useMemoを使わない場合 doSomething関数を追加して、complexValue: {doSomethingComplicated()}として表示します。 App.js //レンダリングの度に実行される const doSomethingComplicated = () => { console.log('I am computing something complex'); return ((count1 * 1000) % 12.4) * 51000 - 4000; }; return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> Count1: {count1} <button onClick={incrementCount1}>Increase Count1</button> Count2: {count2} <button onClick={incrementCount2}>Increase Count2</button> complexValue: {doSomethingComplicated()} <button onClick={logName}>Log name</button> </header> </div> ); するとレンダリングのたび(Increase Count1だけではなくIncrease Count2を押しても)にdoSomethingComplicated内のconsole.logが実行されます。 useMemoを使う場合 doSomethingComplicatedをuseMemoで書き換えると以下のようになります。 第2引数に与えたcount1の値が変化したときだけ新しい値を返すようになります。 const doSomethingComplicated = useMemo(() => { console.log('I am computing something complex'); return ((count1 * 1000) % 12.4) * 51000 - 4000; }, [count1]); また、doSomethingCompletedは値を返すようになるので、以下の箇所も書き換えます。 complexValue: {doSomethingComplicated} こうすることで、Increase Count2をいくら押してもdoSomethingCompletedは実行されないようになります。 参考資料
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactでLinkタグにstyled-componentでスタイルをあてたいとき

React開発でstyled-componentを使用してスタイリングしていた際に、react-routerのLinkを使った時にどのようにCSSをあてるのか少し詰まったので自分用で記事にしておきます。 styled-componentの書き方 基本の書き方 <ThreadTitle>スレッド一覧</ThreadTitle> export const ThreadTitle =styled.h2` font-weight:bold; font-size:20px; ` ThreadTitleという定数を定義して、ラップしてあげる。 今回でいうとThreadTitleでラップされたスレッド一覧がh2タグで囲まれたことになり、スタイルがあたります。 aタグでも同様のことができると考え、下記のように記述しましたが、うまくいかず、、 <ThreadLink>リンク先</ThreadLink> export const ThreadLink = styled.a` text-decoration: none; color: blue; font-size: 20px; margin: 5px 0; &:hover { opacity: 0.7; color: red; } `; 結論、下記のように書き換えることで正しくCSSをあてることができました。 <ThreadLink>リンク先</ThreadLink> export const ThreadLink = styled(Link)` text-decoration: none; color: blue; font-size: 20px; margin: 5px 0; &:hover { opacity: 0.7; color: red; } `; ■参考 https://styled-components.com/docs/basics#styling-any-component
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

APIを叩いて天気情報を一週間分取ってみた

はじめに 今回はopenweatherのAPIを利用して天気情報を取得して 表示することを目標に作成していきました。 【完成図】 今回使用した技術 HTML SASS React TypeScript Redux ToolKit 初めてAPIを叩いてみた 先ほども述べたように今回はopenweatherのAPIを使用しました。 すでにopenweatherのAPIが用意されてたのであまり苦労せず、導入することができました! しかし、地域名だけで取得するAPIだと「大阪」と検索しても大阪の天気を取得することができないので あまり精度は高くないなと感じました。 fetchとaxiosの違いについて APIを叩くにあたってサーバと通信する必要があります。 そこで必要になるのでfetchまたはaxiosです。 ここにつていは ①参考記事 ②参考記事が分かりやく纏められてました! 大変だった点 今回のopenweatherのAPIは緯度経度から天気情報を取得するやり方を選択しました。 そのためには緯度経度を入力する必要がありました。 そこでgoogleMapのGeocodeを使用して緯度経度を取得したいと考えました。 googleMapの導入方法は簡単でgoogleMapの記事がとても完結に書かれててます! 苦労点① 緯度経度を取得する箇所で躓いたところがあります。 変更前のコードで実行すると 1回目「北海道」で検索。デフォルトで設定されてる東京の天気情報が取得できる。 2回目「大阪」で検索。北海道の天気情報が取得できる。 このように ①位置情報取得APIを実行する ②位置情報の取得完了する前に天気APIを実行している(更新前の位置情報を使っている) ③位置情報取得APIのレスポンスが返ってくる or 天気APIのレスポンスが帰ってくる という順番で処理されてました。 理想は ①位置情報を取得するAPIを実行する ②位置情報が返ってくる(非同期処理) ③返ってきた位置情報を元に天気APIを実行する に変更する必要がありました。 どのタイミングだったらAPIが返ってくるのかconsole.logを使いながらなんとか 位置情報を取得してから天気APIを実行することに成功しました。。。!!(半日近くかかりましたw) 結論,axiosする場所を変えました! 【変更前のコード】 react.js //省略 const [city, setCity] = useState(""); const [latstate, setLatstate] = useState(35.6761919); const [lngstate, setLngstate] = useState(139.7690174); const weatherData = async (e:any) => { dispatch(setLocationCity(city)); Geocode.setApiKey(APIKEY); Geocode.fromAddress(city).then( (response) => { const searchPosition = { lat: response.results[0].geometry, lng: response.results[0].geometry } console.log(latstate); console.log(lngstate); }, (error) => { alert("error"); } ); e.preventDefault(); if (city === "") { alert("値を追加してください"); } else { await axios .get( `http://api.openweathermap.org/data/2.5/weather?lat=${latstate}&lon=${lngstate}&appid=${APIKEY_GEOCODE}` ) .then((response) => { console.log("status:", response.status); // 200 console.log("body:", response.data); // response body. // catchでエラー時の挙動を定義する }) .catch((err) => { console.log("err:", err); }); console.log(city); setCity(""); } }; 【変更後コード】 react.js const weatherData = async (e: any) => { dispatch(setLocationCity(city)); Geocode.setApiKey(APIKEY); Geocode.fromAddress(city).then( async (response) => { const { lat, lng } = response.results[0].geometry.location; setLatstate(lat); setLngstate(lng); await axios .get( `http://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lng}&lang=ja&appid=${APIKEY_GEOCODE}` ) .then((response) => { const data: any = response.data; setWeather(data); console.log(weather); console.log("status:", response.status); }) .catch((err) => { console.log("err:", err); }); }, (error) => { alert("error"); } ); e.preventDefault(); if (city === "") { alert("値を追加してください"); } else { console.log(city); setCity(""); } }; 苦労点② グラフで1時間ごとの時間だけを取得したく、for文を使って取得したい時に無限ループに陥ってしまったことです。 陥った原因として、if文でデータが渡ってきた時だけ実行するコードを記述したところ無限ループが発生しました。 for文の中でuseStateを更新したり。。。←無茶苦茶ですね そこでuseEffectを使用してデータが渡ってきた時のみコードを実行することを行いました。 useEffectの存在は知っていましたが、いまいち使いどころが分からず敬遠しがちでしたが、今回の経験でグッと理解することができました! 【無限ループコード】 chart.js const Chart = (props: any) => { const [nowtimes, setNowTimes] = useState<number[]>([]); const weatherData = props.data; const honlyDatas = weatherData.hourly; if (honlyDatas) { let time = []; for (let i = 0; i < 7; i++) { time.push(new Date(honlyDatas[i].dt * 1000).getHours()); setNowTimes(time); } } 【修正コード】 chart.js useEffect(() => { const weatherData = props.data; const honlyDatas = weatherData.hourly; console.log(honlyDatas); if (honlyDatas) { let timeList = []; let temperature = []; for (let i = 0; i < 7; i++) { timeList.push(new Date(honlyDatas[i].dt * 1000).getHours()); } setNowTimes(timeList); for (let i = 0; i < 7; i++) { temperature.push(Math.fround(honlyDatas[i].temp - 273.15)); } setTempHourly(temperature); } }, [props.data]); 出てきた課題 ①TypeScriptの型定義をany型に逃げてしまう。 →頭ではany型を使用しないと思っていてもオブジェクトなどの時はany型で指定してしまってた。 ※要復習!!※ ②自分でコードを見返した時に追えない時がある。 理由として ①変数に分かりやすがない ②このコードは何をしてるというコメントがない 上記2点が主な原因かなと感じました。 ①に関しては誰が見ても容易に想像できる変数名をつける ②は、コードを読み直す時間がもったいないので、最初は時間がかかってもコメントを残して読み返す時間を短くする 次回から意識して取り組みたいと思います。 参考記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む