20210503のReactに関する記事は10件です。

今から学ぶReduxの基礎(概要編)

Reduxとは 一言で言うと、「state(状態)を管理をするためのライブラリ」となります。 しかし、これだけだと何も分からないので順を追って説明していきます。 状態管理とは まず、状態管理とは何かを簡単に説明します。 例としてアプリケーションには、以下のような情報を保持する場合があります。 ユーザーがログインしている チェックボックスがチェックされている モーダル が表示されている これらの情報を「状態」と呼び、状態管理とはこれらの情報を管理するものです。 Reactで状態を管理する方法とReduxを使う理由 Reactで状態を管理する方法 Reactで状態を管理する方法として、以下のようなものがあります。 関数コンポーネントでReact HooksのuseStateを使用 クラスコンポーネントを使用 useStateに関して、詳しくは以下を参照 https://qiita.com/seira/items/f063e262b1d57d7e78b4 これらはローカルステートと呼ばれています。 Reduxを使う理由 では、なぜReactで状態を管理する方法があるのにReduxを使うのか。 その理由として、コンポーネントを跨いで状態を共有したいと思った時にローカルステートでは以下の方法で状態を共有する必要があります。 上位のコンポーネントに必要な状態を全て持たせる 上位のコンポーネントから子、孫コンポーネント必要な情報バケツリレーしていく しかし、この方法だと以下のような問題が発生します。 どんどんコードが複雑化していく 親子関係のないコンポーネント間では状態を共有することができない これらの解決策としてReduxを使うことができます。 Reduxで何ができるのか Reduxを利用することで、コンポーネントの親子関係の有無に関わらず複数のコンポーネントからアクセスできるステート(グローバルステート)を作ることができます。 ※ その他にも色々あるが、ここでは省略 Reduxの登場人物とデータフロー Reduxを理解する上で避けては通れない言葉や概念を解説していきます。 登場人物 Store アプリケーションの全ての状態を保持するオブジェクト { value: 0, } Action 「何が起きたのか」という情報を持ったオブジェクト 以下の例では、deposit(入金)と入金金額を表したアクションを作成しています { type: 'deposit', payload: 10 } Action Creater Actionを作成するメソッド 毎回Actionを書くのは手間であり、typo時にバグの原因にもなるため指定されたActionを返すメソッドを作成する const deposit = (value) => { return { type: 'deposit', payload: value } } Reducer stateとActionを受け取り、変更したstateを返す純粋関数(同じ引数を渡せば、毎回必ず同じ結果を返す関数) const reducer = (state = [], action) { switch (action.type) { case 'deposit': return { ...state, value: action.payload }; default: return state; } } dispatch 発送(する)、派遣(する)などの意味を持つ英単語 ActionをStoreに送る役目を担う 以下の例では、ボタンがクリックされた時にdepositというActionをStoreに送る <button onClick={()=>{ dispatch(deposit()) }}>$10を入金</button> データフロー ここまでコードと文章でまとめましたが、なかなか理解しづらいので処理の流れを図で表したものを紹介します。 (引用元:https://redux.js.org/tutorials/essentials/part-1-overview-concepts#redux-application-data-flow) 上の図はイベントによってUIに表示されている\$0という状態を\$10という状態に変更しています。 (UIと書いてある部分が普段私たちが見ている画面の表示部分です) これをもとにstateが更新される流れを順に見ていきましょう。 クリック等のイベントが起きるとreducerに向けてActionをdispatch(送信)します reducerは現在のstateとActionを受け取り、新しいstateを作成します 最後にUIの表示変更をして終わりです Reduxの三原則 Reduxは公式ドキュメントで以下の3つの原則を掲げています。 Single source of truth(信頼できる唯一の情報源) Reduxでは複数の状態を一元管理させることで、デバッグのしやすさを実現しています State in read-only(stateは読み取り専用にする) 状態を直接変更するのではなく、actionを発行します Changes are made with pure functions(変更はすべて純粋関数で行われる) 同じ引数を渡せば、毎回必ず同じ結果を返す関数で状態の変更を行います
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.js + Redux Toolkit でページ遷移時に Store がクリアされてしまった場合に疑うべき箇所

概要 Next.js と Redux Toolkit を使ってみたくて検証していたところ、 A画面→B画面に遷移した際にA画面で保存したStoreの内容がB画面で 取得できないという事象に遭遇しました。 解決してみれば超単純な修正でしたが、そこに至るまで迷走したのでメモ残します。 技術要素 主な技術要素は以下の通りですが、今回の記事では技術要素の説明は割愛します。 Next.js Redux Redux Toolkit ダメな例 まずはダメだったコードです。 補足 Reducer の中身はここでは割愛 configureStore は Redux Toolkit の Store を作るための便利関数 store.ts const createStore = () => { const middlewareList = [...getDefaultMiddleware(), logger]; return configureStore({ reducer: rootReducer, middleware: middlewareList, devTools: process.env.NODE_ENV !== 'production', preloadedState: preloadedState(), }); }; export default createStore; _app.tsx import { Provider } from 'react-redux' // 省略 ... return ( <> // 省略 ... <Provider store={createStore()}> <Component {...pageProps} /> </Provider> </> ); 原因はここでした。 <Provider store={createStore()}> これは Next.js を使ってなければ動くっぽいですが、Next.js の場合は動きません。 ページ遷移する度にこのcreateStore関数が呼び出され、Storeがクリアされてしまっていました。 (クリアというか正確には InitialState で初期化されます) Redux Toolkit を使わず Redux だけを使用する場合も同様、関数呼び出しではダメです。 ちゃんと動いた例 ではどうするのか。 store.ts const middlewareList = [...getDefaultMiddleware(), logger] const store = configureStore({ reducer: rootReducer, middleware: middlewareList, devTools: process.env.NODE_ENV !== 'production', preloadedState: preloadedState(), }) export default store _app.tsx import { Provider } from 'react-redux' // 省略 ... return ( <> // 省略 ... <Provider store={store}> // <--- ここです <Component {...pageProps} /> </Provider> </> ); はい。関数呼び出しをやめただけで動きました。 公式ページ でもこの書き方になっていますね。 まとめ Next.js を使用する際は store の関数呼び出しはしないこと! ただググると関数呼び出し例がヒットする時があるので、Next.js を使う際は気をつけましょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactとの連携 7 完成

app/javascript/pack/***jsx import React from 'react' import ReactDOM from 'react-dom' import PropTypes from 'prop-types' var target_dom = null; document.addEventListener('DOMContentLoaded', () => { target_dom = document.querySelector('#data'); const url = new URL(location.href); let f = url.searchParams.get("name"); if (f == null){ f = ''; } getData(f); }); function getData(f){ let url = "http://localhost:3000/data/ajax"; fetch(url) .then( res => res.json(), (error) => { const el = ( <p>ERROR!!</p> ); ReactDOM.render(el, target_dom); } ) .then( (result) => { console.log(result); let arr =[]; for(let n in result.rss.channel.item){ let data = result.rss.channel.item[n]; arr.push( <tr> <th>{data.title}</th> <td class="small">{data.pubDate}</td> </tr> ); } const el = ( <table class="table mt-4"> <thead class="thead-dark"> <tr><th><a href={data.link}>{data.title}</a></th> <th>Date</th></tr> </thead> <tbody>{arr}</tbody> </table> ); ReactDOM.render(el, target_dom); }, (error) => { console.log(error); const el = ( <p>ERROR!!</p> ); ReactDOM.render(el, target_dom); } ); }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactとの連携 6 YahooNewsを表示してみる

以下に書き換えます。 app/controllers/data_controller.rb class DataController < ApplicationController def index end def ajax url = 'https://news.yahoo.co.jp/pickup/rss.xml'#1 YahooNewのURL uri = URI.parse(url) response = Net::HTTP.get_response(uri) render plain:Hash::from_xml(response.body).to_json end end ・Rubyの外部サーバーにアクセスする機能「Net::HTTP(アドレス)」クラス ・URI.parseを使うと、URLの文字列をURIオブジェクトへと生成いたします。 require 'uri' uri = URI.parse("https://www.google.co.jp/") p uri.scheme p uri.host p uri.port p uri.path uri.scheme => "https" uri.host => "www.google.co.jp" uri.port => "443" uri.path => "/" 実行するとこのようになります。これはhttpの構成要素を以下のように分解して出力しています。 ・ Net::HTTPResponse 指定した対象に GET リクエストを送り、そのレスポンスを Net::HTTPResponse として返します。 対象の指定方法は URI で指定するか、 (host, path, port) で指定するかのいずれかです。 uri データの取得対象を URI で指定します。 host 接続先のホストを文字列で指定します。 path データの存在するパスを文字列で指定します。 port 接続するポートを整数で指定します。 lcalhost:3000/data/ajaxにアクセス 取得できました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactとの連携 その5 Ajax利用の基本

Reactのスクリプトを作る。 javascript:app>javascript>packs>dummy_data.jsx import React from 'react' import ReactDOM from 'react-dom' import PropTypes from 'prop-types' var target_dom = null; #① document.addEventListener('DOMContentLoaded', () => {#② target_dom = document.querySelector('#data');#③ const url = new URL(location.href); #③ let f = url.searchParams.get("name"); #④ if (f == null){ f = ''; } getData(f); }); function getData(f){ let url = "http://localhost:3000/data/ajax"; if (f != ''){ url += '?name=' + f; } fetch(url) .then( res => res.json(), (error) => { const el = ( <p>ERROR!!</p> ); ReactDOM.render(el, target_dom); } ) .then( (result) => { let arr =[]; for(let n in result){ #⑤ let val = result[n]; arr.push(<li class="list-group-item"> {val.id}:{val.name} ({val.mail})</li>); } const el = ( <ul class="list-group">{arr}</ul> ); ReactDOM.render(el, target_dom); }, (error) => { const el = ( <p>ERROR!!</p> ); ReactDOM.render(el, target_dom); } ); } 受け取ったデータをjavascriptのデータとして取り出す。 ① 変数を宣言するvar var 変数名 = 値 ②発火ポイントを決定:addEventListener ③指定されたセレクターまたはセレクターのグループに一致する、文書内の最初の Element を返す:querySelector locationとは locationとは、現在表示されているウェブページのURLを抽出したり、別のページへ遷移する場合などに便利なオブジェクト URlを操作 const url = new URL(location) で初期化 url.searchParams.set("hoge", "fuga") などで設定。URLに含まれるGETクエリ引数にアクセス プロパティ名 内容 location.href 指定したURLに画面遷移する location.protocol 現在のプロトコル情報(http:など)を取得する location.host プロトコル情報を除外したURLを取得する(port情報あり) location.hostname プロトコル情報を除外したURLを取得する(port情報なし) location.port ポート番号を取得・設定する location.pathname URLでパスの部分を取得・設定する location.search URL内のクエリ情報を抽出して取得する location.hash URL内のハッシュ情報を抽出して取得する location.origin プロトコルやポートを含めたURLを取得する JavaScript で URL のクエリパラメータを操作する方法 append( ) 指定されたキーと値のペアを新しい検索パラメーターとして追加します。 delete( ) 指定された検索パラメーターとその値を、検索パラメーターのリストから削除します。 entries( ) このオブジェクトに含まれる全てのキーと値のペアを列挙するための iterator を返します。 get( ) 指定された検索パラメーターに対応する最初の値を返します。 getAll( ) 指定された検索パラメーターに対応する全ての値を返します。 has( ) 指定された検索パラメーターが存在するかを表す Boolean 値を返します。 keys( ) このオブジェクトに含まれる全てのキーと値のペアのキーを列挙する iterator を返します。 set( ) 指定された検索パラメーターに対応する値を設定します。複数の値が存在していた場合、それらは削除されます。 sort( ) 全てのキーと値のペアを、キーを基準にソートします。 toString( ) URL で使用するのに適したクエリー文字列を返します。 values( ) このオブジェクトに含まれる全てのキーと値のペアの値を列挙する iterator を返します。 const constとは、一般的なJavaScriptの定義の宣言。varと違い、値の上書き・同一名での宣言を禁止。 const el = document.querySelector('p'); console.log(el)); // → <p>Hellow World!!</p> <例>メソッドが実行されると、定数 el に p エレメントが代入されます const 定数 = メソッド(); ↓ const 定数 = 戻り値; メソッド(や関数)が戻り値に置き換わることを「戻り値が返ってくる」といいます。 メソッドの立場からすると「戻り値を返す」わけです。そのため、“返り値” という表現をする人もいます。 getData() 計算後のデータを返します。 基本的なfatchメソッドの書き方 JSONファイルを取得するまでは下記のような流れが基本。functionを使った場合とアロー関数を使った場合の2通りがあります。 //通常のバージョン fetch('URL', { method: 'GET' }).then(function (response) { return response.json(); }).then(function (myjson) { consol.log(myjson); }).catch(function (error) { console.log(error); }); //アロー関数の場合 fetch("URL", { method: 'GET' }).then((response) => { return response.json(); }).then((myjson) => { console.log(myjson); }).catch((err) => { console.error(err); });  fetchメソッドを呼び出して、第1引数にURL、第2引数に実はオプションが付けられます。「method」プロパティに「GET」としていますが、他にも「POST」があります。ただ「GET」はデフォルトの状態なので、省略することができます。  fetchメソッドは非同期処理が可能で戻り値をPromiseオブジェクトとして返します。*詳しくは「javascript promise」などと検索すれば出てきます。  jsonメソッドでJSON形式にして返す fecthメソッドは Promiseの為、Promiseオブジェクトにあるthenメソッドで繋げて処理ができます。 fetchメソッド実行した後はその結果をResponse オブジェクトとして受け取れます。その為thenメソッドでResponseオブジェクトを受け取っています。変数は「response」としていますが、「res」とかでも大丈夫です。 Responseオブジェクトにはデータを読み取る関数としてjsonメソッドがあります。jsonメソッドを使用することでResponseオブジェクトとして受け取ったデータをJSON形式として返す事ができます。 JSONファイルをJavascriptJavascriptで扱う場合はparseメソッドでパースするのですが、どうやらResponseオブジェクトのjsonメソッドでは内部的にパースされてるようです。今回はJSONファイルだったのでjsonメソッドを使用しましたが、テキストを読み込みたい場合はtextメソッドもあります。 for - in文 一般的な構文としては次のようになります。 for( var 変数 in オブジェクト ) { //ここに繰り返し処理を書く } 変数宣言 let / const / var (1)再宣言 var、let、constの中で再宣言が可能なのはvarのみです。 //一度宣言した後に、、、 var techacademy = '宣言1回目'; //再度同じ変数名で宣言しなおせる var techacademy = '宣言2回目' let、constで同じことをしようとするとエラーが出るため、再宣言を予防することができます。 (2)スコープの違い スコープとは、簡単にいうと「変数の有効範囲」です。宣言した変数はコード内のどこでも使えるわけではなく、このスコープによって使える範囲が定められています。 varは関数スコープ varはlet、constに比べて広いスコープを持っています。 名前の通り、ある関数内でvarを用いて宣言した変数は、その関数をどこからでも呼び出すことが可能です。 let, constはブロックスコープ ブロックとは、{ }で括られた処理のことです。 let、constを使うと、同じ関数内であってもブロックの外側からは変数を呼び出せません。 (#⑤)val = result[n]で配列の要素を取り出して、先ほどのダミーデータをval.id val.name val.mailで取り出している。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React] useEffectのキホンのキ

Background 自分の理解と初学者向けにuseEffectの必要性や使い方をまとめて行く What is useEffect? 関数コンポーネント内で副作用(side-effect)を実行するためのHook 副作用(side-effect)とは、コンポーネントの出力(レンダリング)に関係ない処理のこと useEffectを用いることでレンダリングと副作用を分離することが可能 How to use 第1引数: 副作用関数 第2引数: 副作用関数の実行タイミングを制御する依存配列。値により第1引数の関数の実行タイミングをコントロールできる No 第2引数の値 実行タイミング サンプル 1 指定なし レンダーの完了時に毎回実行される(危険) useEffect(funcA) 2 空の配列 コンポーネントのマント時とアンマウント時 useEffect(funcA, []) 3 値の配列 最初のマウント時と与えられた値の変化時 useEffect(funcA, [value] useEffect(func1, []); 例 useEffect(() => { console.log('Executed side-effect function') }, []) Why that's necessary? useEffectを使わない場合、Appは副作用とレンダリングが分離されておらず、レンダリングの度にログが出力される import React, { useState } from "react"; export default function App() { const [count, setCount] = useState(0); const addCount = () => setCount(count + 1); console.log("function App called!"); return ( <div className="App"> <p>count: {count}</p> <button onClick={() => addCount()}>click me</button> </div> ); } 前述の function App()を下記のように実装することで、コンポーネントのマウント時とアンマウント時のみログが出力されるようになる import React, { useState, useEffect } from "react"; export default function App() { const [count, setCount] = useState(0); const addCount = () => setCount(count + 1); // useEffectの第2引数に空の配列を渡すことで、コンポーネントのマウント時とアンマウント時のみログ出力される useEffect(() => { console.log("function App called!"); }, []); return ( <div className="App"> <p>count: {count}</p> <button onClick={() => addCount()}>click me</button> </div> ); } Points of second argument 上述の通り、コンポーネントのMount時のみに実行させたい場合、第2引数に[]を指定すれば良い しかし、第2引数に[]を指定できるのは下記の条件を満たしている場合のみ 第1引数に指定した副作用関数でpropやstateを参照していない場合 通常、このようなケースは限定的なので、副作用関数がpropやstateを利用する場合の、第2引数の指定の仕方について記載する useEffect(() => { function doSomething() { console.log('Hello'); } doSomething(); }, []); //It's OK because we don't use any props and states in `doSomething` function. Case1: useEffect内で副作用関数を定義し、第2引数に依存するpropやstateを指定する 副作用関数がpropsやstateを使う場合は、useEffec内で宣言することが望ましい この場合、副作用関数が依存するpropや、stateを第2引数に指定する必要がある useEffect(() => { function doSomething() { console.log(someProp); } doSomething(); }, [someProp]); Case2: useEffect外にuseCallbackでラップした副作用関数を定義し、第2引数に副作用関数を指定する 通常Javascriptでは関数は都度生成されるため、レンダリングの際に新しい関数が生成される useCallbackを使うことで、関数自体の依存が変わらない場合に、関数が変化しないことを保証できる これにより、第2引数に副作用関数を指定しても、関数の依存が変わらない限りuseEffectが実行されない function ProductPage({ productId }) { // Wrap with useCallback to avoid change on every render const fetchProduct = useCallback(() => { // ... Does something with productId ... }, [productId]); // All useCallback dependencies are specified return <ProductDetails fetchProduct={fetchProduct} />; } function ProductDetails({ fetchProduct }) { useEffect(() => { fetchProduct(); }, [fetchProduct]); // Important! we should add fetchProduct function to second argument } Case2の派生版: useEffect外に定義した、propやstateに依存する副作用関数をマウント時のみ実行させる useRefを活用してコンポーネントのmountフラグを作成して、それをuseCallbackの第2引数に設定する これにより、useCallbackでラップした関数はマウント時のみ作成され、以降は変化しないため副作用関数もマウント時のみ実行される //マウントフラグを返す関数 function useIsMountedRef() { // マウント時にフラグを設定 // useRefで設定した値は固定化され、レンダリングに関与しない const isMounted = useRef(true); // クリーンアップ関数を定義することでアンマウント時にフラグを`false`に設定 useEffect(() => () => { isMounted.current = false; }, []); return isMounted; } function ProductPage({ productId }) { // 追加 const isMountedRef = useIsMountedRef(); // Wrap with useCallback to avoid change on every render const fetchProduct = useCallback(() => { // ... Does something with productId ... }, [isMountedRef]); // Mount時のみ関数が生成されるようになる return <ProductDetails fetchProduct={fetchProduct} />; } function ProductDetails({ fetchProduct }) { useEffect(() => { fetchProduct(); }, [fetchProduct]); // Important! we should add fetchProduct function to second argument } Reference 更新時に副作用をスキップすることはできますか? React HooksのuseCallbackを正しく理解する useEffectフックのしくみ useEffect完全ガイド
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React入門 - Part7 - mockサーバを利用してみる

目次 mockサーバとは 外部APIにRESTアクセスし、結果を使用しフロント側のみ実装のテストをする場合などに使用します。 私が参加したプロジェクトだと、フロントのReactの実装が完了しAPIイベント部分の単体テストは、mockサーバで行っていました。 (実際のAPIへの接続は結合テスト段階で行っていました。) mockサーバを試してみよう ここを参考にしました。 「リクエストを投げてみる」までやればOKです。 手順はこれだけですね。 1. VSCodeにSwagger Editor拡張をインストール 2. prism-cli.exeをダウンロードしておく 3. sample.ymlをコピペで作成しておく 4. ./prism-cli.exe mock sample.ymlで起動 5. 画面で確認 1.2.は参考URLを見て準備してください。 3.のサンプルymlです。※UTF8で保存してください。 openapi: '3.0.2' info: title: API Title version: '1.0' servers: - url: https://api.server.test/v1 paths: /test: get: responses: '200': description: OK 5.表示画面はこのようになります。 データがないですね。 取得データを表示してみよう sample.ymlを拡張して、以下のようにします。 (yamlの書き方は、ググれば沢山でてきますのでここでは割愛します。) openapi: '3.0.2' info: title: API Title version: '1.0' servers: - url: https://api.server.test/v1 paths: /test: get: responses: '200': description: OK content: application/json: # レスポンスの形式指定 schema: type: array items: $ref: '#/components/schemas/User' # 参照するモデル example: # サンプルデータ - id: 1 name: John Doe - id: 2 name: Jane Doe components: schemas: # スキーマオブジェクトの定義 User: # モデル名 type: object # 型 required: # 必須フィールド - id properties: id: # プロパティ名 type: integer # 型 format: int64 # フォーマット(int32, int64等) name: type: string Product: type: object required: - id - price properties: id: type: integer format: int64 example: 1 name: type: string example: Laptop price: type: integer example: 1200 データが表示されました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactとの連携(4)

rails g model data name:text mail:text dataの複数形で単数形はdatumです。 モデルを用意してバリデーションを設定します。 内容はnameが空でないこととメールがからでないこと class Datum < ApplicationRecord validates :name, presence: true validates :mail, presence: true end テーブル設定してマイグレーションします。 2021050293420_create_data.rb class CreateData < ActiveRecord::Migration[6.0] def change create_table :data do |t| t.text :name t.text :mail t.timestamps end end end ダミーデータを3件ほど入力します。 db/seed.rb Datum.create(id:1, name:'Taro', mail:'taro@yamada') Datum.create(id:2, name:'Hanako', mail:'hanako@flower') Datum.create(id:3, name:'Sachiko', mail:'sachiko@happy') terminal rails db:seed 実行します。 terminal rails g controller data index ajax コントローラを作ります。 app/controllers/data_controller.rb class DataController < ApplicationController def index end def ajax if params[:name] then data = Datum.where 'name like ?', "%" + params[:name] + '%' else data = Datum.all end render plain:data.to_json end end jsonデータとしてとり出してrender plainでテキストとして送信します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails6 × React】devise_auth_tokenでのSNS認証時に初回ログインの場合のみ特定の処理を促す

環境 API Rails6.0 フロント React 目的 Omniauthを用いたSNS認証でのユーザー登録で初回ログイン時にSNS上のアカウント情報から一部変更したり追加情報の入力を促したりしたい。 devise_auth_tokenを用いているが、deviseでも基本は同じように設計していいと思う。 実装方法 ①usersテーブルにTrackable系のカラムを追加する class AddColumnsToUsers < ActiveRecord::Migration[6.0] def change add_column :users, :sign_in_count, :integer, default: 0, null: false add_column :users, :current_sign_in_at, :datetime add_column :users, :last_sign_in_at, :datetime add_column :users, :current_sign_in_ip, :string add_column :users, :last_sign_in_ip, :string end end これらのカラムを追加。上から順に サインイン回数 現在のログインをした日時 最後にログインした日時 現在のログイン元IPアドレス 最後のログインしたときのログイン元IPアドレス の情報を持っている ②models/user.rbにてtrackableを設定 class User < ActiveRecord::Base # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :trackable, #←これ :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:twitter] モデルにも設定が必要。逆に言うとこれだけでサインイン時に勝手にカラムに値が入っていく。 ③omniauthでの認証時にユーザーが初回ログインかどうかを判定する def render_data_or_redirect(message, data, user_data = {}) # 初回ログイン時にはcookieに情報をセット。Oauth認証後ユーザー情報の編集ページに飛ばす cookies[:first_session] = { value: true, path: root_path, expres: 10.minutes } if @resource.sign_in_count == 0 devise_auth_tokenではomniauth_callbacksコントローラーでユーザーが認証できた場合に render_data_or_redirectメソッドが呼ばれる。このメソッドでアプリ側のページに返す直前で 保存したユーザーのsign_in_countが0の場合にCookie上にfirst_sessionという値を仕込む。期限は10分とかにしとく ④フロント側でCookieから初回ログインのtmpデータを取得する class App extends React.Component { componentDidMount() { const cookies = new Cookies(); // ユーザー認証が済んでいる場合 if (authToken) { if (cookies.get('first_session')) { // 実際にはユーザー情報編集ページに飛ばす処理を入れる。次のブランチで alert('初回ログイン') } // 中略 return authToken } else { return null } } 今回はルートコンポーネントのAppでログイン済みかどうかの判定をするので、 そこにcookieの中にfirst_sessionが存在しているかどうかの判定を行う条件式を実装。 なお、Cookieを読み込むライブラリとしては事前にuniversal-cookieを導入している。 npm install universal-cookie ⑤2回目以降のログイン時にCookieのtmpデータを削除する tmpデータの有効期限が10分とかにしてるので、10分以内に2回ログインされることも想定して2回目以降のログインでは Cookieのデータを削除するようにした。 # 2回目以降のログイン時にはfirst_sessionのクッキーデータを削除 cookies.delete(:first_session, path: root_path) if cookies[:first_session] 挙動 ここまでの流れで挙動は以下のようになる。 感想 思ったよりかんたんにできた。 Trackable系のカラムちゃんと使ったことなかったのでこういう使い方ができるのか、と感動した。 Cookieを使って条件分岐しているのでセキュリティ的にどうなの?とかはあるけど一旦置いておく。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Blitz.js、Next.js辞めるってよ

Blitzは、ざっくり言うとReact版Ruby on Railsです。 Reactの面倒なところを全てすっ飛ばし、技術選定なんてどうでもいいから今すぐアプリを動かしたいんだよ、という要望を叶えるのに適したフルスタックフレームワークです。 概要はBlitz.js - React on Rails、実際の使い方はBlitz.jsチュートリアル:投票サービスを15分で作ってみるあたりを見てもらうとして、とにかくチュートリアルに従ってコマンド打てばとりあえず動くものが一瞬でできるとかそんなかんじです。 技術的にはReact + Next.jsで動いています。 ということだったのですが、先日どうも雲行きの怪しいRFCが提出されました。 以下は[RFC] Time to maintain a fork of Next.js?の紹介です。 なお提案者のBrandon BayerはBlitz.jsの作者です。 [RFC] Time to maintain a fork of Next.js? Problem 我々のカスタムコンパイラのアプローチは、これまではうまくいっていましたが、そろそろ限界に達しつつあります。 現在は、我々はblitzのコードベースをnext.jsのコードベースにコンパイルし、そして.blitz/buildからnextを実行しています。 これによって、next.jsのほとんどの機能をオーバーライドすることが可能になっています。 しかし、正直これはハックじみた方法であり、そして多くの問題が発生していて、問題回避のために余分な労力が発生しています。 たとえばランタイムの実体は.blitz/buildの中にあって、思っているファイルのパスと異なる場所にあります。 しかしnext.jsのログを操作しているので、ログに表示されるパスは正しくなっています。 ただブラウザのエラーには対応していないので、こちらでは正しくないパスが表示されます。 さらに、これのせいでnode.jsのデバッガーが正しく動きません。 そしてブレークポイントは.blitz/buildの中にあるコードに仕掛けなければなりません。 また、このディレクトリ構造のため、blitzが開発ディレクトリを監視し、変更があれば.blitz/buildにコピーし、さらにnext.jsが.blitz/buildを監視し、変更があればコンパイルするという、二重の監視プロセスになってしまっています。 さらに他にも様々な問題があり、多くは未だに解決していません。 Solution カスタムコンパイラを取り除き、よりネイティブなアプローチを採用する。 Needs babelプラグインで多くの事柄を解決できます。 以下はbabel以外に必要なものです。 必須:queries/mutationsファイルをAPI routesに変換する機能。 必須:複数のページとAPIフォルダを一括でマージする。 ほしい:Next.jsより前の段階で任意のカスタマイズ。next.config.jsのかわりにblitz.config.jsを使い、TypeScriptに対応させるなど。 Approaches ふたつのアプローチが考えられます。 1. Change blitz to be only an add-on to next.js blitzをnext.jsのアドオンとしてのみ使用する。 この場合、blitzで行っている幾つかの変更を元に戻す必要があります。 たとえばblitz.config.jsをnext.config.jsに戻し、その中にblitz用のラッパーを入れるなどです。 このためには、next.jsチームと協力し、next.js側にRPCレイヤーを追加しなければなりません。 技術的には可能ですが、next.jsチームとこれらの変更について合意できるかどうかは不明です。 Next.jsの内部実装を変更できないので、やりたいカスタマイズやオーバーライドができません。 たとえば、多くの人が望んでいるにもかかわらず、設定ファイルのTypeScriptサポートを追加することができません。 これによって、Next.jsの保守的な実装に縛られてしまうことになります。 Next.js上での実装は、たとえばRedwoodと比較すると今でも多くの制限がありますが、これではさらに制限が強まってしまいます。 2. Fork Next.js, make changes to core, and keep up to date with the main next.js repo Next.jsをforkし、コアに変更を加える。 これによってメンテナンスの複雑さは増加しますが、同時に全ての制限から解放されます。 フルスタックのDXを改善するために、Next.jsを直接変更することができるからです。 うまく実装すれば、本家をマージする際のコンフリクトは最小限に抑えられます。 Next.jsの保守的な実装から解放され、より迅速に機能を確信することができます。 またファーストクラスレイアウトのように、変更がNext.jsにバックポートされる可能性もあります。 もうひとつの利点としては、babelプラグインではなくnext.jsを変更することは、よりシンプルになることです。 私はforkアプローチを採用すべきだと考えています。 なにしろ我々は、新機能の追加やDXのフルスタックフレームワークへの最適化などにおいて、Next.jsよりずっと積極的です。 forkすることで、多くのチャンスを得ることができます。 これまでのところ、ネイティブなNext.jsよりも、我々が加えたカスタマイズのほうに満足している人の方が多いと思います。 この変更についてどう思いますか? Blitzを使ったことがあるかないかも含め、回答をください。 コメント stevenpetryk fork自体には賛同です。 ただ、Blitz固有の機能をNextに追加する意図でforkするのではなく、BlitzをNextのプラグインとしてビルドする意図でforkしたほうがいいでしょう。 そうすればそのうちforkがNextにマージされ、独自にメンテする必要がなくなるかもしれません。 adamsoffer stevenpetryk氏の言うとおり、プラグインとして構築することをお勧めします。 Nextチームはプラグインシステムに取り組んでいます。 flybayer ところがどっこい彼らは当初のプラグインシステムを放棄しました。 既に完全に削除されています。 かわりに実装されたのは、一度にひとつのフックだけを追加するという、より保守的なアプローチです。 さらにこのフックは、任意のコードを実行することができません。 leerob 待って放棄されてないよ。 mdxみたいにプラグインはあるし、より多くのフックを提供しようとするFRCもあるよ。 AndriusBartulis Blitzがいったいどこを目指しているのか、そのゴールによると思います。 フルスタックなモノリシックフレームワークを目指しているのであれば、Nextをforkするのは理にかなっているでしょう。 独自のエコシステムを構築していくのであれば、Nextの互換性を維持することは意味がないと思います。 感想 正確には辞めるのではなく、forkして独自の変更を加えていくという方向です。 これまではNextの機能を使っていて、そしてNextで対応しきれないところはNextをハックして上書きするような使い方をしていたのですが、その運用が辛くなってきたのでどうにかしようということです。 Nextを使わないようにするという手段もあったかもしれませんが、Blitzの開発チームはNextをforkする方向を選んだようです。 ただ、チケットでは本家の変更もマージしていくよとは言っていますが、実際それができるかというと正直疑問です。 そのうちマージに耐え切れなくなって、Hackのように分離の道を歩むことでしょう。 しかし道をたがえたFork元とFork先が両方ともうまくいった例ってのはこれまでもあんまりないので、そのまま続いていくかというと怪しい気がしないでもありません。 果たしてBlitz.jsの未来はどうなるでしょうか。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む