20190828のReactに関する記事は11件です。

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>);
}


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

参考

https://medium.com/@rukurx/npm%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%81%AE%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B-6c82286ceb78

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.js[v9]のgetInitialPropsの中でapolloを使いたい、しかもTypescriptで

これを出したい

Screen_Shot_2019-08-28_at_14_21_22.png

探した内容

  1. next-with-apolloを見つけるが、Support hooks #74の中でapollo-hookも使いたいなら、next-with-apolloは使わずにwith-apollo/lib/apollo.jsこれをベースにした方が良い、とある
  2. with-apollo/lib/apollo.jsがjsベースなのでtsベースにするために色々探す
  3. How would you type this?を見つけて参考にしてみる
  4. 因みにv8以前まで_app.jsを作る時にContainerって言うのも使っていたけど、今ではdeprecated
  5. 2.、3.をベースにwithApollo.tsxを考えてみる

withApollo.tsx

基本with-apollo/lib/apollo.jsと同じだが、PageComponent.getInitialPropsに渡す時のctxapolloClientを生やしている。

...
...

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.tsx
import 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.ts
import { 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.tsx
interface 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

ApolloProviderwithApollo内で既に作られているので、ここまでシンプルになる。
(もしくはそもそも_app.tsxは作らず、apolloが必要なPageだけでwithApolloを使う)

_app.tsx
import App from "next/app"
import { withApollo } from "../lib/withApollo"

export default withApollo(App)

:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.tsx
import * 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を指定するのを忘れずに。

スクリーンショット 2019-08-28 12.45.48.png

追加できたら以下のコマンドでダウンロードしてローカルのファイル(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.tsx
import * 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-バーチャルテクノロジー

以下のように表示できれば成功です。

スクリーンショット 2019-08-28 13.27.22.png

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条件で検索できます。

スクリーンショット 2019-08-28 13.45.16.png

ソートの指定

?s={項目}パラメータを指定すると、その項目は昇順でソートされます。(OR条件で検索した場合でも検索結果すべてについてソートされます。本機能は近日中にリリース予定です)
ソート指定する項目にIndexの設定がなされているとレスポンスは高速になります。(Index設定がない項目でもソートは可能です)
また、index項目を検索する場合、その項目は昇順ソートされているため、ソート指定は不要です。
ソート指定をした項目が存在しない場合は検索できませんので注意してください。

今回は以上となります。お疲れ様でした。

次回=>vte.cxによるバックエンドを不要にする開発(その5)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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-ifv-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のifList.map関数とかと、シームレスに組み合わせられるんや!」


Elmではdivspanすらも関数

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 ]


シンプルでいい感じ!


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

coc-typescriptで最初に開いたtsxのファイルだけtsとして認識される問題の対処法

vim起動後最初に開いたtsxのファイルだけtsとして認識されてしまい、正しくtsserverに解析されない問題が発生していた。

原因

vim側のfiletypetypescriptに設定されてしまっていた。
2ファイル目以降もtypescriptとして認識されていたが、いい感じに解析してくれていた。

対策

typescriptのハイライトをしてくれるleafgarland/typescript-vimfiletypetypescriptに設定していたので消去した。
ハイライトはmaxmellon/vim-jsx-prettyだけで十分してくれたので消しても問題なかった。

~/.config/nvim/init.vim に以下の設定を追記し、tsxfiletypetypescript.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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 にアクセス
スクリーンショット 2019-08-28 10.06.26.png

「Go to the about page」に飛ぶと
スクリーンショット 2019-08-28 10.07.55.png

install WebdriverIO

先ほどのディレクトリのまま、WebdriverIOをGetting Startedを参考にインストールする。

  • サービスはSelenium-Standaloneを選択した
  • baseURLはhttp://localhost:3000

いざテスト

テストコード

test/specs/basic.js
describe('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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 にアクセス
スクリーンショット 2019-08-28 10.06.26.png

「Go to the about page」に飛ぶと
スクリーンショット 2019-08-28 10.07.55.png

install WebdriverIO

先ほどのディレクトリのまま、WebdriverIOをGetting Startedを参考にインストールする。

  • サービスはSelenium-Standaloneを選択した
  • baseURLはhttp://localhost:3000

いざテスト

テストコード

test/specs/basic.js
describe('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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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アカウントを使ったログイン」を実現できる仕組み。
  • (ログイン時に要求する権限によっては)ユーザー情報やチーム情報を取得する事が可能
  • あんまり知られてないような気がした(僕が知らなかっただけかも)

こんなケースに使えそう:thinking:

  • Slackのアカウント情報と連携したWebサービス
  • 社員だけに限定公開するサイト

※ちなみにいろいろサンプルを用意しようとしたら全然間に合いませんでした:sob:


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)
  • この方法はカスタム認証では利用できない。

カスタム認証のフロー

  1. サーバーサイドでFirebaseのAdmin SDKを使ってトークンを発行する
  2. トークンをクライアントに受け渡す
  3. クライアントはFirebase SDKのsignInWithCustomTokenメソッドにトークンを渡す

以上のフローでサインインを行う。


今回のケースに当てはめる

B3730765-4CEA-49AF-B732-399230E194AB.jpeg

  1. クライアントからSlackの認証ページへ飛ぶ
  2. ユーザーはSlackの画面でアクセス許可を行う
  3. Slackは認証用codeを載せて指定しておいたリダイレクト先に飛ぶ(今回はCloud Functionsを利用)
  4. codeを使ってSlackユーザーのアクセストークンを取得、必要なら永続化などを行う
  5. FirebaseのAdmin SDKを使ってトークンを発行する
  6. トークンを載せてクライアントにリダイレクト
  7. クライアントはsignInWithCustomTokenメソッドを叩く

コード見た方が簡単だと思うのでもしよろしければ

https://github.com/uutarou10/sign-in-with-slack

サーバサイド(Cloud Functions)とクライアント(React)でどちらもTypeScriptで実装されています。


1. クライアントからSlackの認証ページへ飛ぶ

  • 事前にSlack Appを作成しておく
    • 必要な権限を付与しておく

image.png

  • あとは slack.com/oauth/authorize?client_id=CLIENT_ID&scope=identity.basic Slack指定のURLへのリンクを用意しておく

2. ユーザーはSlackの画面でアクセス許可を行う

image.png


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());

今回はクエリパラメータに入れてリダイレクト。


  1. クライアントは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プラン(従量課金プラン)にする必要あり
    • もちろん無料枠はあるので滅多なことでは課金されない:heart:
  • カスタムトークンを作る場合は、GCPのIAMの画面でサービスアカウントに権限をつける必要あり
    • めっちゃハマった
    • ここに思いっきり書いてた。ドキュメントをしっかり読もう。。。

所感

  • 基本的にはドキュメントに書いてあるフローをそのまま実装すれば動く
  • Slack APIは実に様々な操作ができるので、付ける権限によってはかなり面白いことが色々できそう
  • サンプル用意全然間に合わなかった:sob:
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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を書き換えています。
 これによって、「のボタン」というテキストが付加されます。

image.png

 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 表示結果

ボタンを押すとメッセージが切り替わります
image.png

3.useChildrenのソースコード

 以下のモジュールをインポートすれば、useChildrenが使えるようになります。
 内容的には単純で、ReactElementをクローンしつつ列挙しているだけです。

UseChildren.ts
import 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のメッセージで行っています。

 
 今後の予定としては、今回の内容につけ加えて自由なタイミングでPropsの再投入できるようにしたり(たぶん可能)、仮想ウインドウシステムの方の完成を目指したいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VimでReactを書く(2019年版)

2019年の情報がなかったのでまとめました。

目標

  • VimでTypeScript or JavaScriptのReactをVSCodeのように書きたい

完成形

cnfly-fff7t.gif

必要なもの

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: Release

coc.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 neovim

nodeのパスを通すため、init.nvim に以下を記載。

init.nvim
let 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.0 

coc.nvimの導入

coc.nvim はdein.nvim経由で導入。

coc.nvim はnpm製なので導入後にプラグインのディレクトリでnpm installを走らせる必要があります。あらかじめinstall.shが用意されているので、実行されるようbuildオプションを記載。

また、公式リポジトリに記載された設定例をcoc-options.vimとして取り込みます。 coc.nvim: Example vim configuration

dein.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で導入。

:CocInstall coc-tsserver coc-eslint

CocConfig

:CocConfigcoc-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)を書けるようになりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む