- 投稿日:2019-08-28T21:16:40+09:00
ReactのComponentの中での条件分岐
子要素を分岐させたい時のメモ。
基本的に、子要素はReact.createElementの第3引数になるので、
JSXのタグの間に挟まるものは式ではなくてはならない。
が、javascriptのifは式ではなくて文なので少し面倒。対応策1:三項演算子を使う
三項演算子を使えば文ではなく式になるのでOK
<div> {someCondition ? <Hoge/> :<PIYO/>} </div>対応策2
無名関数中でif+returnを書き即実行する。
<div> {(() => { if(someCondition){ return <HOGE/>; } return <PIYO/>; })()} </div>対応策3
対応策2の応用で関数部分を関数コンポーネント化する
const HogePiyo = (props) => { if(props.someCondition){ return <HOGE/>; } return <PIYO/>; }; //中略 render () { return ( <div> <HogePiyo someCondition={someCondition}/> </div>); }
- 投稿日:2019-08-28T16:09:06+09:00
react npm moduleのバージョンを確認する
bashにて
はじめどうして良いか分からずnode_moduleフォルダを開いたりしていたのですが、
調べてみると簡単にインストールしたmoduleのバージョンを確認できました。localにインストールされたパッケージ
$ npm list --depth=0 ...@0.1.0 /home/ubuntu/environment/... ├── @material-ui/core@4.3.1 ├── classnames@2.2.6 ├── immutable@4.0.0-rc.12 ├── node-sass@4.12.0 ├── prop-types@15.7.2 ├── react@16.8.6 ├── react-dom@16.8.6 ├── react-redux@7.1.0 ├── react-router-dom@5.0.1 ├── react-scripts@3.0.1 ├── redux@4.0.4 ├── redux-devtools-extension@2.13.8 ├── redux-saga@1.0.5 ├── reselect@4.0.0 └── typeface-roboto@0.0.75参考
- 投稿日:2019-08-28T14:33:51+09:00
Next.js[v9]のgetInitialPropsの中でapolloを使いたい、しかもTypescriptで
これを出したい
探した内容
- next-with-apolloを見つけるが、Support hooks #74の中でapollo-hookも使いたいなら、next-with-apolloは使わずにwith-apollo/lib/apollo.jsこれをベースにした方が良い、とある
- with-apollo/lib/apollo.jsがjsベースなのでtsベースにするために色々探す
- How would you type this?を見つけて参考にしてみる
- 因みにv8以前まで
_app.jsを作る時にContainerって言うのも使っていたけど、今ではdeprecated- 2.、3.をベースに
withApollo.tsxを考えてみるwithApollo.tsx
基本with-apollo/lib/apollo.jsと同じだが、
PageComponent.getInitialPropsに渡す時のctxにapolloClientを生やしている。... ... const apolloClient = initApolloClient() ctx.ctx.apolloClient = apolloClient let pageProps = {} if (PageComponent.getInitialProps) { pageProps = await PageComponent.getInitialProps(ctx) } ... ...下の方にある
function createApolloClient内のuri: "https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn",は自分のサーバーへ変更必要。
withApollo.tsximport React, { useMemo } from "react" import Head from "next/head" import { ApolloProvider } from "@apollo/react-hooks" import { InMemoryCache } from "apollo-cache-inmemory" import { ApolloClient } from "apollo-client" import { HttpLink } from "apollo-link-http" import fetch from "isomorphic-unfetch" let apolloClient = null /** * Creates and provides the apolloContext * to a next.js PageTree. Use it by wrapping * your PageComponent via HOC pattern. * @param {Function|Class} PageComponent * @param {Object} [config] * @param {Boolean} [config.ssr=true] */ export function withApollo(PageComponent, { ssr = true } = {}) { const WithApollo = ({ apolloClient, apolloState, ...pageProps }: { apolloClient: ApolloClient<{}> apolloState: any [key: string]: any }) => { const client = useMemo( () => apolloClient || initApolloClient(apolloState), [] ) return ( <ApolloProvider client={client}> <PageComponent {...pageProps} /> </ApolloProvider> ) } // Set the correct displayName in development if (process.env.NODE_ENV !== "production") { const displayName = PageComponent.displayName || PageComponent.name || "Component" if (displayName === "App") { console.warn("This withApollo HOC only works with PageComponents.") } WithApollo.displayName = `withApollo(${displayName})` } // Allow Next.js to remove getInitialProps from the browser build if (typeof window === "undefined") { if (ssr) { WithApollo.getInitialProps = async ctx => { const { AppTree } = ctx // Run all GraphQL queries in the component tree // and extract the resulting data const apolloClient = initApolloClient() ctx.ctx.apolloClient = apolloClient let pageProps = {} if (PageComponent.getInitialProps) { pageProps = await PageComponent.getInitialProps(ctx) } try { // Run all GraphQL queries await require("@apollo/react-ssr").getDataFromTree( <AppTree pageProps={{ ...pageProps, apolloClient, }} /> ) } catch (error) { // Prevent Apollo Client GraphQL errors from crashing SSR. // Handle them in components via the data.error prop: // https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error console.error("Error while running `getDataFromTree`", error) } // getDataFromTree does not call componentWillUnmount // head side effect therefore need to be cleared manually Head.rewind() // Extract query data from the Apollo store const apolloState = apolloClient.cache.extract() return { ...pageProps, apolloState, } } } } return WithApollo } /** * Always creates a new apollo client on the server * Creates or reuses apollo client in the browser. * @param {Object} initialState */ function initApolloClient(initialState = {}): ApolloClient<{}> { // Make sure to create a new client for every server-side request so that data // isn't shared between connections (which would be bad) if (typeof window === "undefined") { return createApolloClient(initialState) } // Reuse client on the client-side if (!apolloClient) { apolloClient = createApolloClient(initialState) } return apolloClient } /** * Creates and configures the ApolloClient * @param {Object} [initialState={}] */ function createApolloClient(initialState = {}): ApolloClient<{}> { // Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient const isBrowser = typeof window !== "undefined" return new ApolloClient({ connectToDevTools: isBrowser, ssrMode: !isBrowser, // Disables forceFetch on the server (so queries are only run once) link: new HttpLink({ // このuriは各自変更必要 uri: "https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn", // Server URL (must be absolute) credentials: "same-origin", // Additional fetch() options like `credentials` or `headers` // Use fetch() polyfill on the server fetch: !isBrowser && fetch, }), cache: new InMemoryCache().restore(initialState), }) }Page
実際に
/pages配下で使う時はNextPageをextendしたapolloClientを盛り込んだ独自のPageを使う。NextPageWithApolloClient.tsimport { NextPage, NextPageContext } from "next" import { ApolloClient } from "apollo-client" export interface NextPageWithApolloClient<P = {}, IP = P> extends NextPage<P, IP> { getInitialProps?(ctx: NextPageContext & { apolloClient: ApolloClient<{}> }): Promise<IP> }index.tsxinterface Props { results: Array<any> } // NextPageWithApolloClient を使う const Index: NextPageWithApolloClient<Props> = props => { console.log('props:', props) return <>index</> } Index.getInitialProps = async ({apolloClient}) => { // awaitを忘れずに const {data, loading, error} = await apolloClient.query({query: QUERY}) // interface Propsと合わせる必要がある return { results: [] } } export default Index_app.tsx
ApolloProviderはwithApollo内で既に作られているので、ここまでシンプルになる。
(もしくはそもそも_app.tsxは作らず、apolloが必要なPageだけでwithApolloを使う)_app.tsximport App from "next/app" import { withApollo } from "../lib/withApollo" export default withApollo(App)
- 投稿日:2019-08-28T14:17:11+09:00
vte.cxによるバックエンドを不要にする開発(その4)
前回=> vte.cxによるバックエンドを不要にする開発(その3)
今回は条件検索や全文検索、OR検索など様々な検索を行う方法について説明します。
条件検索
前回の記事において、
http://{サービス名}.vte.cx/d/foo?x&fをGETすることで、foo配下のentryのリストを取得できることを説明しましたが、さらに、URLパラメータに検索条件を加えることでデータの絞り込みを行うことができます。つまり、
user.name=bazという条件で絞り込みたければ、http://{サービス名}.vte.cx/d/foo?x&f&user.name=bazというようにURLパラメータに検索条件を追加すればいいわけです。条件検索の文法は以下の通りです。
https://{サービス名}.vte.cx/{Key}?f&{name}{=|-eq-|-lt-|-le-|-gt-|-ge-|-ne-|-rg-|-fm-|-bm-|-ft-}{value}&{name}{=|-eq-|-lt-|-le-|-gt-|-ge-|-ne-|-rg-|-fm-|-bm-|-ft-}{value}&...&l={n}&p={カーソル文字列}&s={ソート項目名}記号の種類と意味は以下の通りです。
eq : = (等しい) lt : < (未満) le : <= (以下) gt : > (より大きい) ge : >= (以上) ne : != (等しくない) rg : regex (正規表現に合致する) fm : 前方一致 bm : 後方一致 ft : 全文検索前方一致では、指定した文字の先頭文字が一致する条件となり、後方一致では、末尾文字が一致する条件になります。SQLで例えると、-fm-町は、LIKE '町%'に相当し、-bm-田は、LIKE '%田'に相当します。
あいまい検索を実行するには正規表現を使います。例えば、 -rg-.田町.は、`LIKE '%田町%'に相当します。詳しくは、ドキュメントを参照してください。
では、実際に条件検索を実行する以下のコードを見てみましょう。
axios.get('/d/foo?f&user.name=baz')が条件検索を実行する箇所になります。検索結果は複数件戻る可能性がありますのでentryの配列(VtecxApp.Entry[])になっている点に注意してください。index.tsximport * as React from 'react' import * as ReactDOM from 'react-dom' import { useState,useEffect } from 'react' import axios from 'axios' const App = () => { const [x, f] = useState(0) const getdata = async () => { try { axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest' const res = await axios.get('/d/foo?f&user.name=baz') const feed: VtecxApp.Entry[] = res.data if (feed.length>0&&feed[0].user&&feed[0].user.name) { alert(`res= ${feed[0].user.name} `); } } catch (e) { alert('error') console.log(e) } } useEffect(() => { getdata() }) return ( <div> <button onClick={() => { f(x+1) }}> {x} times </button> </div> ) } ReactDOM.render(<App/>, document.getElementById('container'))これを実行して、
res= bazが表示されたら成功です。indexの設定
条件検索はメモリ内で実行しますが、データ量が多いとパフォーマンス悪化を招きます。そのような場合、Indexを設定することで、パフォーマンスを向上させることができます。
ただし、Index検索を適用できるのは検索条件の最初の項目(一番左の項目)のみとなります。2番目以降の項目についてはIndex検索で絞り込み後にメモリ内で検索を実行します。例えば、?foo=123&bar=456という条件で検索した場合、fooにIndex設定があればIndex検索して絞り込みを行い、それからbarの検索をインメモリで実行します。Indexの設定はテンプレートで行います。テンプレートとは、(/_settings/template)エントリのことで、Index設定はtemplateエントリのrightsタグに記述します。(管理画面のエントリスキーマ管理でも設定できます)
以下のように、左辺の項目名に続けて:(コロン)の後に正規表現を記述することでIndexを設定できます。また、右辺に登録するURIのうち正規表現にマッチするものをIndexとして登録します。URIにはエントリの実体(self)や別名(alias)などを指定します。
以下は、user.name項目を/fooから検索した際にIndex検索となる設定例です。user.name:/fooでは、実際にsetup/settings/template.xmlを書き換えてみましょう。
rightsタグの内容がIndex設定になります。template.xml<?xml version="1.0" encoding="UTF-8" ?> <feed> <entry> <content>user name email</content> <link href="/_settings/template" rel="self"/> <rights>user.name:/foo </rights> </entry> <entry> <link href="/_settings/template_property" rel="self"/> </entry> <entry> <link href="/_settings/template_property/user" rel="self"/> <title>ユーザ</title> </entry> <entry> <link href="/_settings/template_property/user.email" rel="self"/> <title>メールアドレス</title> </entry> <entry> <link href="/_settings/template_property/user.name" rel="self"/> <title>名前</title> </entry> </feed>template.xmlファイルを書き換えたら、以下のコマンドを実行して、サーバを更新してください。
npm run upload:template以下のように表示されたら成功です。これで、user.nameを/fooから検索する際にIndexが使われるようになります。ただし、index設定を行った後に更新したデータだけが対象になりますので注意してください。(※ index設定する以前に登録したデータは見れなくなります)
$ npm run upload:template > vtecxblank@1.0.0 upload:template /Users/takezaki/temp/demo/temp/tutorial > npx vtecxutil upload:template setup/_settings/template.xml --> http://{サービス名}.vte.cx/_settings/template.xml {"feed" : {"title" : "Accepted."}}全文検索の設定
通常の検索以外に全文検索を行うことができます。全文検索を行うには、下記の全文検索Indexを設定したうえで、検索条件の記号を{項目名}-ft-{値}のように指定してください。
全文検索では、検索文字を形態素解析し、形態素解析結果の文字列数分、全文検索indexのクエリ検索を行います。(形態素解析エンジンにはLuceneとlucene-analyzers-kuromoji-ipadic-neologdを使用しています。)
全文検索Indexの設定はテンプレート(/_settings/template)のrightsタグに記述します。
以下のように、左辺の項目名に続けて;(セミコロン)の後に正規表現を記述することで全文検索Indexを設定できます。また、右辺に登録するURIのうち正規表現にマッチするものを全文検索Indexとして登録します。URIにはエントリの実体(self)や別名(alias)などを指定します。
以下は、user.description項目を/fooから検索した際に全文検索となる設定例です。user.description;/fooまず、user.descriptionは新しい項目なのでスキーマに追加します。
管理画面のエントリスキーマ管理画面の新規エントリ項目追加タブでdescription(説明)項目を追加してください。親項目にuserを指定するのを忘れずに。追加できたら以下のコマンドでダウンロードしてローカルのファイル(template.xmlとindex.d.ts)を更新してください。
npm run download:template // template.xmlの更新 npm run download:typings // index.d.tsの更新また、ダウンロードしたtemplate.xmlを修正し、
user.description;/fooをrightsに追加します。(改行は必要ですが空白などが入らないように注意してください。)template.xml<?xml version="1.0" encoding="UTF-8" ?> <feed> <entry> <content>user name email description </content> <link href="/_settings/template" rel="self"/> <rights>user.name:/foo user.description;/foo </rights> </entry> <entry> <link href="/_settings/template_property" rel="self"/> </entry> <entry> <link href="/_settings/template_property/user" rel="self"/> <title>ユーザ</title> </entry> <entry> <link href="/_settings/template_property/user.description" rel="self"/> <title>説明</title> </entry> <entry> <link href="/_settings/template_property/user.email" rel="self"/> <title>メールアドレス</title> </entry> <entry> <link href="/_settings/template_property/user.name" rel="self"/> <title>名前</title> </entry> </feed>修正したら以下のコマンドでサーバを更新してください。
これでuser.description項目を/fooから検索した際に全文検索となります。npm run upload:template全文検索の実行
まずは以下のプログラムを実行させて、user.descriptionに文章を登録します。
index.tsximport * as React from 'react' import * as ReactDOM from 'react-dom' import { useState,useEffect } from 'react' import axios from 'axios' const App = () => { const [x, f] = useState(0) const req: VtecxApp.Entry[] = [ { user: { name: 'foo', email: 'foo@vte.cx', description: 'フー' }, link: [ { "___href": "/foo/1", "___rel": "self" } ] }, { user: { name: 'bar', email: 'bar@vte.cx', description: 'バー' }, link: [ { "___href": "/foo/2", "___rel": "self" } ] }, { user: { name: 'baz', email: 'baz@com', description: 'バーチャルには仮想という意味の他に「本来の、本質的な」という意味があります。私たちバーチャルテクノロジーは、本質的な価値を追求することをミッションにしています。また、エンドユーザの価値にフォーカスし、Webサービスの迅速な市場投⼊を⽀援していきたいと考えています。それが本質的な価値だと思うからです。' }, link: [ { "___href": "/foo/3", "___rel": "self" } ] } ] const putdata = async () => { try { axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest' const res = await axios.put('/d/foo',req) alert(`res= ${res.data.feed.title} `); } catch (e) { alert('error') console.log(e) } } useEffect(() => { putdata() }) return ( <div> <button onClick={() => { f(x+1) }}> {x} times </button> </div> ) } ReactDOM.render(<App/>, document.getElementById('container'))次にブラウザーから以下を開いて該当のエントリが検索できることを確認してみましょう。
http://{サービス名}.vte.cx/d/foo?x&f&user.description-ft-バーチャルテクノロジー以下のように表示できれば成功です。
OR検索の実行
&|(と&)でOR条件を囲むことでOR検索ができます。括弧を省略する&|も使用できます。以下はOR条件の例です。(詳しくは、ドキュメントを参照してください)
&|(user.description-ft-バーチャルテクノロジー&)&|(user.name=foo) &|user.description-ft-バーチャルテクノロジー&|user.name=fooブラウザで以下を開いて確認してみてください。
http://{サービス名}.vte.cx/d/foo?f&x&|user.description-ft-バーチャルテクノロジー&|user.name=foo以下のように2件検索できれば成功です。
このように、条件検索だけでなく全文検索を含めてもOR条件で検索できます。ソートの指定
?s={項目}パラメータを指定すると、その項目は昇順でソートされます。(OR条件で検索した場合でも検索結果すべてについてソートされます。本機能は近日中にリリース予定です)
ソート指定する項目にIndexの設定がなされているとレスポンスは高速になります。(Index設定がない項目でもソートは可能です)
また、index項目を検索する場合、その項目は昇順ソートされているため、ソート指定は不要です。
ソート指定をした項目が存在しない場合は検索できませんので注意してください。今回は以上となります。お疲れ様でした。
- 投稿日:2019-08-28T13:46:22+09:00
【Vue・React・Elm】ビューの書き方を比べてみた
Vue.jsの場合
Vueのビューはこんな感じ
<template> <div> <button v-on:click="decrement">-1</button> <div>{{ count }}</div> <button v-on:click="increment">+1</button> </div> </template>ほぼHTMLですね。見やすいです。
Vueでの条件分岐や繰り返し
v-ifやv-forなどのディレクティブというものが用意されていて、それをHTMLの属性みたいに書きます。
【例】記事タイトル一覧
記事があれば、その分だけ繰り返し表示するし、1件もなければ「記事がありません。」と表示する例です。
<template> <ul v-if="items.length"> <li v-for="item in items"> {{ item.title }} </li> </ul> <p v-else>記事がありません。</p> </template>v-ifとかv-forとか色々覚えないといけませんが、個人的には割と分かりやすいと思います。
Reactの場合
Reactのビューはこんな感じ
render() { return ( <div> <button onClick={decrement}>-1</button> <div>{ count }</div> <button onClick={increment}>+1</button> </div> ) }Babelというトランスパイラを使うことで、ReactのビューもHTMLみたいに書けます。
これはJSXという書き方なんですが、ほぼHTMLそのままで個人的には読みやすいと思します。ちなみにJSX記法を使わない場合は、以下の様にReact.createElementメソッドを使用します。
render() { return ( React.createElement('div', null, React.createElement('button', { onClick: decrement }, '-1'), React.createElement('div', null, count), React.createElement('button', { onClick: increment }, '+1') ) ) }
Reactでの条件分岐や繰り返し
JSXの中では、if文やfor文は使えません。
そのため三項演算子や配列のmapメソッドを使って記述します。【例】記事タイトル一覧
render() { return ( <div> { items.length > 0 ? ( <ul> { items.map((item) => { return <li>{ item.title }</li>; })} </ul> ) : ( <p>記事がありません。</p> )} </div> ) }JSXの中でJavaScriptを書くときには波括弧で囲まなければいけません。
Elmの場合
賢い人は考えた
「HTML要素って、要は
タグ名と属性と中に入ってる子要素たち・・・」
「この3つから構成されているやん?」<div class="container" id="hoge"> <span>子要素</span> </div>「じゃあ、
divとかspanっていう関数を作って・・・」
「第一引数には[ class "containor", id "hoge" ]みたいに属性のリストを渡して」
「第二引数には子要素のリストを渡せばええやん」div [ class "container", id "hoge" ] [ span [] [ text "子要素" ] ]「↑こう書いたらHTML要素を返してくれるような・・・」
「各タグ名に対応したHTML関数たちを作ればええんや!」
「ただの関数やから、ループさせるのに(v-ifとかv-forのような)専用の書き方とか要らんで」
「ElmのifやList.map関数とかと、シームレスに組み合わせられるんや!」
Elmでは
divやspanすらも関数Elmでは、各HTML要素に対応した関数があらかじめ用意されています。
(header, section, p 等々・・・)このHTML関数たちを使って、ビュー部分とプログラム部分をシームレスかつ柔軟に書くことができます。
「配列の分だけループして表示させるための特別な構文」もありませんし、
「属性値を動的に生成するための特別な書き方」もありません。
ということで、Elmのビューはこんな感じ
view model = div [] [ button [ onClick Increment ] [ text "+1" ] , div [] [ text <| String.fromInt model.count ] , button [ onClick Decrement ] [ text "-1" ] ]
Elmでの条件分岐や繰り返し
条件分岐や繰り返しに関して、ビュー専用の構文はありません。
普通にElmのList.map関数とかifなどを使用します。【例】記事タイトル一覧
view model = div [] [ if List.length model.items > 0 then ul [] (model.items |> List.map liComponent) -- 記事(items)が1件以上あれば、 -- その分だけliComponentを呼び出して表示する else p [] [ text "記事がありません。" ] ] -- ただ関数を作ればコンポーネントのように利用できる liComponent title = li [] [ text title ]
シンプルでいい感じ!
完
- 投稿日:2019-08-28T12:17:35+09:00
coc-typescriptで最初に開いたtsxのファイルだけtsとして認識される問題の対処法
vim起動後最初に開いた
tsxのファイルだけtsとして認識されてしまい、正しくtsserverに解析されない問題が発生していた。原因
vim側の
filetypeがtypescriptに設定されてしまっていた。
2ファイル目以降もtypescriptとして認識されていたが、いい感じに解析してくれていた。対策
typescriptのハイライトをしてくれるleafgarland/typescript-vimが
filetypeをtypescriptに設定していたので消去した。
ハイライトはmaxmellon/vim-jsx-prettyだけで十分してくれたので消しても問題なかった。
~/.config/nvim/init.vimに以下の設定を追記し、tsxのfiletypeがtypescript.tsxとして認識されるようにする。autocmd BufNewFile,BufRead *.tsx let b:tsx_ext_found = 1 autocmd BufNewFile,BufRead *.tsx set filetype=typescript.tsx参考
https://github.com/neoclide/coc.nvim/issues/48
https://github.com/ianks/vim-tsx/blob/master/ftdetect/typescript.vim
- 投稿日:2019-08-28T11:27:26+09:00
React Component を WebdriverIO react$ で取得してみたら失敗した
はじめに
WebdriverIOのBlogにReact Selectorsの話が出ていたので試す。
Material-uiのコンポーネントを取得してみようと思う。
ソースはここに置く環境
- Mac
- Next.js
- Material-ui
- WebdriverIO
Getting Started
next.js with material-ui
Next.jsのGithubにはExamplesが豊富にあるので、探す。
Material-uiを見てみると、Material-uiの方をみろとあるので、こっちを見る。
書かれている通り、ソースのダウンロード&npm install&npm run devして、http://localhost:3000 にアクセス
install WebdriverIO
先ほどのディレクトリのまま、WebdriverIOをGetting Startedを参考にインストールする。
- サービスはSelenium-Standaloneを選択した
- baseURLは
http://localhost:3000いざテスト
テストコード
test/specs/basic.jsdescribe('material-ui component', () => { it('go to about page and back main page', () => { browser.url('/'); //baseURLで指定したのがここで活きる browser.$('=Go to the about page').click(); //リンク名Selector const button = browser.react$('Button'); //React Selector button.waitForExist(5000); button.click(); browser.$('=Go to the about page').waitForExist(5000); }); });結果
失敗・・・
console抜粋[0-0] Error in "material-ui component go to about page and back main page" element ("Button") still not existing after 5000ms Spec Files: 0 passed, 1 failed, 1 total (100% completed) in 00:00:13
- 投稿日:2019-08-28T11:27:26+09:00
Material-ui React Component を WebdriverIO react$ で取得してみたら失敗した
はじめに
WebdriverIOのBlogにReact Selectorsの話が出ていたので試す。
Material-uiのコンポーネントを取得してみようと思う。
ソースはここに置く環境
- Mac
- Next.js
- Material-ui
- WebdriverIO
Getting Started
next.js with material-ui
Next.jsのGithubにはExamplesが豊富にあるので、探す。
Material-uiを見てみると、Material-uiの方をみろとあるので、こっちを見る。
書かれている通り、ソースのダウンロード&npm install&npm run devして、http://localhost:3000 にアクセス
install WebdriverIO
先ほどのディレクトリのまま、WebdriverIOをGetting Startedを参考にインストールする。
- サービスはSelenium-Standaloneを選択した
- baseURLは
http://localhost:3000いざテスト
テストコード
test/specs/basic.jsdescribe('material-ui component', () => { it('go to about page and back main page', () => { browser.url('/'); //baseURLで指定したのがここで活きる browser.$('=Go to the about page').click(); //リンク名Selector const button = browser.react$('Button'); //React Selector button.waitForExist(5000); button.click(); browser.$('=Go to the about page').waitForExist(5000); }); });結果
失敗・・・
console抜粋[0-0] Error in "material-ui component go to about page and back main page" element ("Button") still not existing after 5000ms Spec Files: 0 passed, 1 failed, 1 total (100% completed) in 00:00:13
- 投稿日:2019-08-28T09:06:56+09:00
Sign in with Slack x Firebase Authenticationやってみた話
この資料は5分LT用に作成されたもののため、だいぶ端折って説明されています。
https://yuruhachi-it.connpass.com/event/141835/
Sign in with Slackとは
- https://api.slack.com/docs/sign-in-with-slack
- 名前の通り「Slackアカウントを使ったログイン」を実現できる仕組み。
- (ログイン時に要求する権限によっては)ユーザー情報やチーム情報を取得する事が可能
- あんまり知られてないような気がした(僕が知らなかっただけかも)
こんなケースに使えそう
![]()
- Slackのアカウント情報と連携したWebサービス
- 社員だけに限定公開するサイト
※ちなみにいろいろサンプルを用意しようとしたら全然間に合いませんでした
Firebase Authenticationとは
- DBやらを用意せずとも認証・ユーザーの永続化などを一手にやってくれるFirebaseのサービスの一つ
- Firebase内の他のサービスの認証にも用いられる
- Cloud FirestoreやCloud Storageなどで用いられるセキュリティールールなどでも使用するFirebaseの基盤とも言えるサービス
- 標準でGoogle/Facebook/Twitter等のソーシャルログインに対応している
- Slackは残念ながら非対応
- だが、カスタム可能な認証方法が用意されているのでこちらを利用する
カスタム認証について
- Firebase Authenticationに用意されている認証プロバイダを利用する場合はフロントエンドのコードだけで認証を行う事が可能
// 例えばGoogleログイン const provider = new firebase.auth.GoogleAuthProvider(); firebase.auth().signInWithPopup(provider)
- この方法はカスタム認証では利用できない。
カスタム認証のフロー
- サーバーサイドでFirebaseのAdmin SDKを使ってトークンを発行する
- トークンをクライアントに受け渡す
- クライアントはFirebase SDKの
signInWithCustomTokenメソッドにトークンを渡す以上のフローでサインインを行う。
今回のケースに当てはめる
- クライアントからSlackの認証ページへ飛ぶ
- ユーザーはSlackの画面でアクセス許可を行う
- Slackは認証用codeを載せて指定しておいたリダイレクト先に飛ぶ(今回はCloud Functionsを利用)
- codeを使ってSlackユーザーのアクセストークンを取得、必要なら永続化などを行う
- FirebaseのAdmin SDKを使ってトークンを発行する
- トークンを載せてクライアントにリダイレクト
- クライアントは
signInWithCustomTokenメソッドを叩く
コード見た方が簡単だと思うのでもしよろしければ
https://github.com/uutarou10/sign-in-with-slack
サーバサイド(Cloud Functions)とクライアント(React)でどちらもTypeScriptで実装されています。
1. クライアントからSlackの認証ページへ飛ぶ
- 事前にSlack Appを作成しておく
- 必要な権限を付与しておく
- あとは
slack.com/oauth/authorize?client_id=CLIENT_ID&scope=identity.basicSlack指定のURLへのリンクを用意しておく
2. ユーザーはSlackの画面でアクセス許可を行う
3. Slackは認証用codeを載せて指定しておいたリダイレクト先に飛ぶ
- ユーザーが許可を行うと、指定しておいたリダイレクト先に飛ぶ
- ここだけ唯一サーバーサイドが必要となるのでFirebase Cloud Functionsを利用
- その際に
codeというクエリパラメータで認証用の一時的なトークンを渡してくる
4. ユーザーのアクセストークンを取得、永続化
- 先ほどのcodeを使って
oauth.accessというエンドポイントへリクエストを投げると、アクセストークンを取得できる- 必要であれば永続化処理などを行う
5. FirebaseのAdmin SDKを使ってトークンを発行する
- FirebaseのAdmin SDKを使ってFirebase Authenticationのカスタムトークンを作成する
const customToken = await admin.auth().createCustomToken(userCredential.user_id);
createCustomTokenメソッドにuidを渡すことで作成する。uidは一意である必要があるので、今回はSlackのuser_idをそのまま渡した。
6. トークンを載せてクライアントにリダイレクト
const url = new URL(redirectUri); url.search = `t=${customToken}`; res.redirect(303, url.toString());今回はクエリパラメータに入れてリダイレクト。
- クライアントは
signInWithCustomTokenメソッドを叩くconst queryPrams = new URLSearchParams(window.location.search); const token = queryPrams.get('t'); if (token) { window.history.replaceState(undefined, window.document.title, window.location.href.replace(window.location.search, '')); await firebase.auth().signInWithCustomToken(token); }
ハマったところ・注意すべきところ
- Cloud FunctionsからGoogle外のAPI(Slack API)を叩くにはBlazeプラン(従量課金プラン)にする必要あり
- もちろん無料枠はあるので滅多なことでは課金されない
![]()
- カスタムトークンを作る場合は、GCPのIAMの画面でサービスアカウントに権限をつける必要あり
- めっちゃハマった
- ここに思いっきり書いてた。ドキュメントをしっかり読もう。。。
所感
- 基本的にはドキュメントに書いてあるフローをそのまま実装すれば動く
- Slack APIは実に様々な操作ができるので、付ける権限によってはかなり面白いことが色々できそう
- サンプル用意全然間に合わなかった
![]()
- 投稿日:2019-08-28T08:55:26+09:00
ReactでuseChildrenを使って子ノードのpropsを書き換えるコンポーネントを作る
ReactでuseChildrenを使って子ノードのpropsを書き換える
1.子ノードを自動で書き換えるコンポーネントの作成
1.1 基本のソースコード
まずはこのコンポーネントの出力結果を見てください
function App() { return ( <Parent> <input type="button" value="A" /> <input type="button" value="B" /> <input type="button" value="C" /> <div><input type="button" value="E" /></div> </Parent> ); }1.2 表示結果
Parentコンポーネントがinputタグのvalueを書き換えています。
これによって、「のボタン」というテキストが付加されます。1.3 Parentコンポーネントのソースコード
useChildrenを使って子ノード中のelementを列挙し、inputタグのpropsを書き換えています。
valueに限らずpropsのあらゆるパラメータが書き換え可能です。function Parent(props: { children?: ReactNode; }) { let children = useChildren(props, element => { if (element.type === "input") { return { value: element.props.value+"のボタン" }; } return null; }); return <>{children}</>; }2.イベントを仕込む
2.1 ソースコード
onChildClickというイベントを定義して、ボタンが押されたらvalueの内容を返します。
function Parent(props: { children?: ReactNode; onChildClick: (value: string) => void; }) { let children = useChildren(props, element => { if (element.type === "input") { return { onClick: () => props.onChildClick(element.props.value) }; } return null; }); return <>{children}</>; }こちらはParentにのみイベントを設定し、各inputにはイベントの設定を行う必要はありません。
ちなみにParentタグが見える範囲であれば、後から動的に追加した子ノードに対してもPropsの書き換えは行われます。function App() { const [msg, setMsg] = React.useState(""); return ( <Parent onChildClick={setMsg}> <input type="button" value="A" /> <input type="button" value="B" /> <input type="button" value="C" /> <div> <input type="button" value="E" /> </div> <div>押されたボタン:{msg}</div> </Parent> ); }2.2 表示結果
3.useChildrenのソースコード
以下のモジュールをインポートすれば、useChildrenが使えるようになります。
内容的には単純で、ReactElementをクローンしつつ列挙しているだけです。UseChildren.tsimport React,{ ReactNode, ReactElement } from "react"; const useChildren = ( props: { children?: ReactNode }, proc: (element: ReactElement) => { [key: string]: unknown } | null ): ReactNode | null | undefined => { const hook = ( node: ReactNode | null | undefined ): ReactNode | null | undefined => { if (typeof node !== "object" || node == null) return node; if (node instanceof Array) { return node.map(hook); } else { let element = node as ReactElement; let children: ReactNode | undefined = element.props ? element.props.children : undefined; if (children) { let flag = false; children = React.Children.map(element.props.children, e => { const newElement = hook(e); if (newElement !== e) { flag = true; } return newElement; }); if (!flag) children = null; } const props = proc(element); if (props) { if (children) props.children = children; return React.cloneElement(element, props); } if (!children) return element; return React.cloneElement(element, { children }); } }; return hook(props.children); }; export default useChildren;4.まとめ
props.childrenで子ノードのpropsの書き換えが出来るという記事はあるのですが、実際に活用している内容が見当たらなかったのでソースコードに落とし込んでみました。
今回の内容を使って、現在作成中のReact用仮想ウインドウモジュールのイベント処理を行おうと目論んでいます。
仮想ウインドウシステムではウインドウ同士の重ね合わせやアクティブなウインドウの調整など、コンポーネント間の調整が必要となり、それをまとめる管理コンポーネントがあるとべんりだと考えています。
現在の書き方は、イベント処理を生DOMのメッセージで行っています。
- ソースコード
https://github.com/JavaScript-WindowFramework/jwf-react-sample01- 作りかけ仮想ウインドウモジュールの動作サンプル
https://javascript-windowframework.github.io/jwf-react-sample01/dist/- 動作画面
![]()
今後の予定としては、今回の内容につけ加えて自由なタイミングでPropsの再投入できるようにしたり(たぶん可能)、仮想ウインドウシステムの方の完成を目指したいと思います。
- 投稿日:2019-08-28T07:55:16+09:00
VimでReactを書く(2019年版)
2019年の情報がなかったのでまとめました。
目標
- VimでTypeScript or JavaScriptのReactをVSCodeのように書きたい
完成形
必要なもの
NeoVim のインストール
今回はneovim dev版を使います。
Installing Neovim · neovim/neovim Wiki
# dev版で導入 (floating windowが使える) $ brew install --HEAD neovim # version 確認 $ nvim -v NVIM v0.4.0-dev Build type: Releasecoc.nvimとは
coc.nvimは、vim8.0 / neovim のためのインテリセンスエンジン。
TypeScriptで作られており、言語ごとの機能拡張をプラグイン内のextentionでサポートする言語サーバクライアントです。
要は、VSCodeのような力をvim / neovim に与えるもの。IDEに必要な言語解析機能を組み込みで実装するのではなく、言語サーバとして外部実装する Language Server Protocolが策定された頃から、VimからLSPを呼び出すことでのIDE化、超高機能化が囁かれてきましたが、とうとうTypeScript製でnpmを使用するLSPプラグインが現れました。
まるでVSCodeみたい。 conquer of completion (補完の征服)と名付けられたのもすごいですね。
coc.nvimの導入
導入には vim 8.0 以降、または neovim 0.3.1 以降が必要です。更に vim/neovim上で nodeのサポートが必要。
まず、neovimのnodeサポートを整えます。
node
公式ではcurlを使ったワンライナーでのnodejsインストール手順が書かれていますが、nodeはversion管理システム経由で入れることにします。 使用するversion管理システムはnodebrew。
# nodebrewの導入 $ brew isntall nodebrew # nodebrew用のディレクトリの生成 $ mkdir -p ~/.nodebrew/src # v12のnode.jsを導入 $ nodebrew install 12 $ nodebrew use 12 # neovimにnode.jsの環境を $ npm i -g neovimnodeのパスを通すため、init.nvim に以下を記載。
init.nvimlet g:node_host_prog = system('echo -n $(which neovim-node-host)')
:checkhealthを行い、以下の表示がでれば neovimのnodejsサポートはOK。## Node.js provider (optional) - INFO: Node.js: v12.9.0 - INFO: Neovim node.js host: /usr/local/bin/neovim-node-host - OK: Latest "neovim" npm/yarn package is installed: 4.5.0coc.nvimの導入
coc.nvim は
dein.nvim経由で導入。coc.nvim はnpm製なので導入後にプラグインのディレクトリで
npm installを走らせる必要があります。あらかじめinstall.shが用意されているので、実行されるようbuildオプションを記載。また、公式リポジトリに記載された設定例を
coc-options.vimとして取り込みます。 coc.nvim: Example vim configurationdein.toml設定[[plugins]] repo = 'neoclide/coc.nvim' build = './install.sh nightly' hook_add = 'source ~/.config/nvim/coc-options.vim'extensionsの導入
coc.nvimにはextensionsが用意されており、言語ごとに機能拡張できます。公式にあるように、coc.nvimの拡張はVSCodeの拡張からforkされています。
Using coc extensions · neoclide/coc.nvim Wiki
Reactに対応するため下記のextentionsを
:CocInstallで導入。
- neoclide/coc-tsserver: Tsserver extension for coc.nvim
- TSServerを使用して、JavaScript/TypeScript/JSX/TSXに対応
- neoclide/coc-eslint: Eslint extension for coc.nvim
- ESLint対応
:CocInstall coc-tsserver coc-eslintCocConfig
:CocConfigでcoc-settings.jsonが開きます。ちなみに、
coc-settings.jsonを開くと自動で:CocInstall coc-jsonが走ります。設定として、TsserverをJavaScriptに適用するように、またESLintをTypeScript/TSXに適用するように変更します。
こちらの設定はVSCodeとほぼ同じですね。
coc-settings.json{ "tsserver.enableJavascript": true, "eslint.filetypes": ["javascript", "typescript", "typescriptreact", "javascriptreact", "typescript.tsx"] }完成
これでVimでコード補完しつつReact(+TypeScript)を書けるようになりました。












