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

React Reduxについて

先日、Reduxに関する備忘録を書いたのですが、React Reduxに関しても学習したので、その備忘録としてQiitaにまとめてみました!

どこか間違いなどあれば、コメントにてご指摘頂けると助かります!:bow_tone1:

また、解説するコードの一部は、僕が学習に使用した教材を参考に自身で作成したアプリケーションの内容となっています。(以下のGithubリポジトリ内のコードを参照していただくと少しは記事の内容を追えるかと思います:bow_tone1:
アプリのGithubリポジトリ:https://github.com/TaikiYamano/React-Tweet-With-Redux

こちらの記事は以前投稿した、Reduxに関する記事とセットになっているので、こちらの記事をご一読されることをお勧めします:pray_tone1:

React Reduxとは

React Reduxとは、ReactとReduxを繋げる役割を持つものです。Reduxに関してまとめた記事でも説明したのですが、Reactでアプリを作成する際、Reduxを使用すると、Stateを別の場所で管理できて、どのコンポーネントからでもStateにアクセスできるようになります。こうすることで、ReactのコンポーネントはStateの管理をする必要が無くなります。

しかし、ReactとReduxを直接繋ぐと、Reactのコンポーネント内でStoreを操作する必要があります。そこで、ReactとReduxを繋ぐReact Reduxを使うことによって、ReactはStoreを操作する必要も無くなり、見た目の表示の役割に専念できるようになります。

また、React Reduxが行うReactとReduxを繋げる役割はコンテナーコンポーネント(Container Components)が行います。

React Redux(コンテナーコンポーネント)の具体的な役割

上記でReact ReduxはコンテナーコンポーネントでReactとReduxを繋げる役割を行うと説明しましたが、主に3つの役割でReactとReduxを繋いでいます。
1、StoreのStateが更新されたら、更新されたStateを取りにいく
2、Storeに取りに行ったStateの必要なものだけをReactのコンポーネントにProps経由で渡す
3、dispatchメソッドを使ってStoreのStateを更新する(更新されたStateは1、2の役割によってReactのコンポーネントに渡されます。)

コンテナーコンポーネントはReactとReduxの召使い的な役割のように見えますね笑

コンテナーコンポーネントの作成方法

ReactとReduxを繋げるコンテナーコンポーネントはReact Reduxが提供するconnectメソッドを用いて作成することができます。(公式ドキュメントによると、store.subscribe()を用いて手書きでも作成できるとのことですが、推奨はされていませんでした笑)

connectメソッドの第一引数にはmapStateToProps、第二引数にはmapDispatchToPropsを渡します。(第三引数にはmergeProps、第四引数にはoptionsを任意で渡すことができるらしいですが、今回の学習では使用しなかったので説明は省きます:bow_tone1: また学習した際にこの記事に追加しようと思います。)

以下は、簡易的なブログアプリ(Twitterのような)ものを作成した際に、投稿一覧を表示するためのコンポーネント(PostListコンポーネント)を作成し、ReactのPostListコンポーネントとReduxを繋げるコンテナーコンポーネントを作成した場合のコードです。

import { connect } from 'react-redux';

export default connect(
    mapStatetToProps,
    mapDispatchToProps
)(PostsList);

第一引数のmapStateToPropsと第二引数のmapDispatchToPropsに関しては後ほど説明します。
connectメソッドの返り値は関数なので、その引数にコンポーネント(今回はPostListコンポーネント)を渡すと、引数に渡したコンポーネントと結びつくコンテナーコンポーネント(今回の場合はPostListコンテナーコンポーネント)が作られます。
(こちらに関して、少し解釈が曖昧です...もし間違いがあればご指摘お願いします!:bow_tone1:

mapStateToProps

mapStateToPropsは、Storeのstateをprops経由でReactのコンポーネントに渡す際に、どのような形でコンポーネントに渡すかを定義する関数でオブジェクトを返します。また、第一引数にはStoreのStateを受け取ります。

因みにmapには「地図」と言う意味もありますが、IT用語で「何かと何かを関連づける」と言う意味も持つそうで、今回は後者の意味でmapが使われています。参考記事
なので、mapStateToPropsは「ReduxのStateとReactのPropsを関連づける」と言う意味になります。

以下は、自分が作った簡易的なブログアプリの機能の一つでStateのpostFilterで表示する投稿を変更しており、stateのpostFilterの値がLIKED_POSTSの場合、いいねが付いた投稿のみを表示する処理をmapStateToPropsに記述した際のコードです。

//react-reduxからconnectメソッドをインポート
import { connect } from 'react-redux';
//postFilterActionCreatorから定数LIKED_POSTをインポート
import {
    LIKED_POSTS
} from '../../actions/postFilterActionCreator';

const mapStatetoProps = (state) => {
    let posts;
//StateのpostFilterの値がLIKED_POSTSの場合、filterメソッドで全投稿(state.posts)の内、いいねフラグ(hasLiked)がtrueの投稿のみ、変数postsに格納する
    if(state.postFilter == LIKED_POSTS){
        posts = state.posts.filter(post => post.hasLiked());
    }else{
//StateのpostFilterの値がLIKED_POSTSでない場合、スプレッド構文でコピーした全投稿(state.posts)を変数postsに格納する
        posts = [...state.posts]
    }
    //変数postsを返す
    return { posts };
};

このmapStateTopropsをconnectの第一引数に渡すことで、Reactのコンポーネント内にてprops.postsと記述することで、Propsを経由してStoreのStateで管理している値(今回の場合はposts)にアクセスすることができます。
また、もしコンポーネント内でmapStateTopropsを定義していない場合、connectメソッドの第一引数にはnull、もしくはundefinedを渡します。

mapDispatchToProps

mapDispatchToPropsはどのようにしてStoreのStateを更新するかを定義する関数、もしくはオブジェクトです。mapDispatchToPropsが関数である場合は第一引数にdispatchを受け取ります。また、返り値はオブジェクトで、コールバック関数などが値としてセットされます。

因みに、mapDispatchToPropsは上記のmapの意味で「PropsとDispatchを関連づける」すなわち、PropsからStoreのStateをDispatchで更新すると言う意味になります。

以下は自分が作成した簡易的なブログアプリで投稿(ツイート)に「投稿者名」「本文」「画像」の情報を記述して投稿する処理をmapDispatchToPropsに関数をセットして記述した際のコードです。

import { connect } from 'react-redux';
//postActionCreatorからcreatePostをインポート
import { createPost } from '../../actions/postActionCreator'

const mapDispatchToProps = (dispatch) => {
    return {
//返り値のオブジェクトにコールバック関数をセット
        createPost: (name,text,image) => {
//変数actionにcreatePostアクションによって作られたActionを格納
            const action = createPost(name,text,image);
//dispatchメソッドの引数に変数actionを渡して、StoreのStateを更新
            dispatch(action);
        }
    }
};

また、mapDispatchToPropsにオブジェクトをセットした場合、上記のコードは以下のようになります。

import { connect } from 'react-redux';
import { createPost } from '../../actions/postActionCreator'

const mapDispatchToProps = { createPost };

この記述で関数をセットした場合と同じ処理を行います。こちらの方が処理の追いやすさは上記のコードよりは劣りますが、コードの量自体は大幅に減るのでいいですね。

そして、このmapDispatchToPropsをconnectメソッドの第二引数に渡すことによって、Reactのコンポーネント内にて props.createPost(name,text,image)と記述することで、Props経由でStoreのStateを更新することができます。(dispatch(action)を通じてコンテナーコンポーネントがStoreのStateを更新してくれている為です。)

また、もしコンポーネント内でfを定義していない場合、connectメソッドの第二引数にはnullを渡します。

コンテナーコンポーネントをApp.jsに記述する

先程説明しましたが、connectメソッドの引数にmapStateToPropとmapDispatchToPropsを渡し、connectメソッドの返り値になる関数の引数にコンポーネントを渡すとコンテナーコンポーネントを作成することができます。

以下は投稿一覧を表示するPostListコンテナーコンポーネントを作成した場合のコードです

import { connect } from 'react-redux';

export default connect(
    mapStatetToProps,
    mapDispatchToProps
)(PostsList);

このコンテナーコンポーネントは以下のように、普通のコンポーネントと同じようにApp.js内に記述することができます。
(以下のコードはもう少し色々記述していましたが、できるだけシンプルにするよう大幅に省略しています:sweat_smile:

App.js
import React from 'react';
import PostList from '../src/components/PostsList/PostsList';

function App() {
  return (
    <div className="App">
      <PostList />
    </div>
  );
}

export default App;

しかし、このままの記述だとアプリを立ち上げた際に以下のようなコンテナコンポーネントにてStoreが見つからないと言うエラーが発生します。
スクリーンショット 2020-08-09 22.22.31.png

このエラーを解決するにはindex.jsにReact Reduxが提供しているProviderを記述する必要があります。
Providerによってネストされた全てのコンテナーコンポーネントはReduxのStoreにアクセスすることができるようになるからです。

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
//react-reduxからProviderをインポート
import { Provider } from 'react-redux';
//ProviderコンポーネントのPropsであるstoreにセットするstoreをインポートする
import store from './store/index';
ReactDOM.render(
//AppコンポーネントをProviderコンポーネントでネストする。Propsであるstoreには上記でインポートしたstoreをセットする
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

こうすることで先程発生したエラーは出なくなります。

参考記事

React Reduxの学習をするにあたり、使用した教材の他に以下の記事を参考にさせて頂きました!:pray:

React + Redux の基本的な使い方
React Redux の難しかった点をできるだけシンプルに図解

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

react-hooks 自分用メモ

はじめに

reactのhooksについて勉強しましたのでここで自分用にまとめておきたいと思います!
参考にしたのではトラハックのyoutubeのreact講座とreactの公式ページになります。
reactについてであれば、トラハックさんの動画を見ることをお勧めします。

目次

1.react-hooksの利点
2.useState
3.useEffect
4.useCallback
5.最後に

react-hooksの利点

・functional componentではstate宣言とライフサイクルが管理できなかった
そのためstateは親コンポーネントのクラスからpropsとして引き継ぎながら利用していた!!

しかしReact16.8で追加されたhookという機能により、stateの管理が、functional componentで宣言できるようになった!!

このことで複雑なクラスコンポーネントを使わなくて良くなった。
なぜ難しいのか?

1.thisという概念
2.stateを扱うとき複雑になる
3.複数のライフサイクルメソッドに副作用のある処理がまたがる

useState

・ステートフック
・クラスコンポーネントでいうthis.setState()
・複数のstateを扱うときはstateごとに宣言

宣言方法はこちら

const [name, setName] = useState("")
      //state名, state変更関数名 = 初期値

useEffect

・ライフサイクルメソッドの代替
・functional componentでライフサイクルが行える
.コードをまとめられる

ライフサイクルメソッドだとときの流れをベースにレンダリングされていた!!
useEffectだと機能をベースにレンダリングされている!

代替できるライフサイクルメソッド

・componentDidMount()
・componentDidUpdate()
・componentDidUnmount()

マウント時

useEffect = (() => {
    //何らかの処理
}, [])

マウント&アンマウント時

useEffect = (() => {
    //マウント時の処理
    return () => {
       //アンマウント時の処理
   }
}, [])

return内に書くと、それがアンマウント時の処理となる!!

特定のレンダー時

useEffect = (() => {
    //何らかの処理
}, [name])

こうすることでnameが変更された際にこのuseEffect内の処理が呼び出されるようになる!!

このuseEffectでライフサイクルを管理できる!!

useCallback

useCallbackがやることは「コールバック関数の不変値化」
アロー関数は全て更新して関数を実行しますが、実行結果が同じだった場合以前と同じものを返してくれるのがuseCallbackになる。
これを使うことで無駄なレンダリングを抑制できる。
似たようなものでuseMemoもある!!

const 関数名 = useCallback(() => {
    何らかの処理
},[])

最後に

hooksについては他にもありますが、幾分始めたばかりなので主要メソッドだけここでは書かせていただきます。
これから更新してきたいと思っております。
記事に関して何かアドバイスしていただけると幸いです。

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

SSR フレームワーク Next.js を使って React を快適ルーティング part2

SSR フレームワーク Next.js を使って React を快適ルーティング part2

前回の続きです。
まだ、ページをちょこっと編集しただけなので、今回はアプリケーションにページを追加してみたいと思います。

ページ間を移動する

新しいページを作成する

  • 必要なファイルを作成する
mkdir pages/posts/
touch pages/posts/first-post.js
  • pages/posts/first-post.js を編集する
pages/posts/first-post.js
export default function FirstPost() {
  return <h1>First Post</h1>
}

リンクコンポーネントを使う

  • pages/index.js を開く
  • 以下を先頭行に追記する
pages/index.js
import Link from 'next/link'
  • 次に h1 タグの内容を変更する
pages/index.js
Learn <a href="https://nextjs.org">Next.js!</a>
↓
Read <Link href="/posts/first-post"><a>this page!</a></Link>
  • 次に pages/posts/first-post.js を以下の通り書き換える
pages/posts/first-post.js
import Link from 'next/link'

export default function FirstPost() {
  return (
    <>
      <h1>First Post</h1>
      <h2>
        <Link href="/">
          <a>Back to home</a>
        </Link>
      </h2>
    </>
  )
}
  • 動作確認しよう links.gif

クライアント側のナビゲーション

クライアント側のナビゲーションとは、ページ遷移がJavaScriptを使用して行われることを意味します。これは、ブラウザが行うデフォルトのナビゲーションよりも高速です。

開発者ツールを使って html 全体の背景を設定してみましょう。
黄色の背景がページ遷移しても持続していることが分かります。

これは、ブラウザがページ全体を読み込まず、クライアント側のナビゲーションが機能していることを示しています。
client-side.gif

コード分割とプリフェッチの話

Next.js は自動的にコード分割を行うので、各ページはそのページに必要なものだけをロードします。
つまり、ホームページがレンダリングされるとき、他のページのコードは読み込まれないことを意味します。
これは大変素晴らしいですね!
これにより、数百ページを追加した場合でも、ホームページがすばやく読み込まれます。

リクエストしたページのコードをロードするだけでも、ページが分離されます。
特定のページがエラーをスローしても、アプリケーションの残りの部分は引き続き機能します。

さらに、Next.jsのプロダクションビルドでは、Linkコンポーネントがブラウザのビューポートに表示されるたびに、Next.js がバックグラウンドでリンクされたページのコードを自動的にプリフェッチします。リンクをクリックするまでに、リンク先ページのコードはすでにバックグラウンドで読み込まれており、ページ遷移はほぼ瞬時に行われます!

今回使った Link コンポーネントの詳細はこちらを参照ください。

https://nextjs.org/docs/api-reference/next/link

続く...

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

SSR フレームワーク Next.js を使って React を快適ルーティング part1

SSR フレームワーク Next.js を使って React を快適ルーティング part1

はじめに

Next.js とは

Next.jsReact を使って SSR (サーバサイドレンダリング)アプリケーションを構築できる軽量フレームワークになります。
すぐに使えるスタイルやルーティングのソリューションを含んでいるため、手軽にスタイルを適用したり、ページ遷移を実装することができます。
サーバ環境としては、 Node.js を利用することが想定されています。

SPA と SSR と React

通常 React 単体では すべての JS や CSS 、 HTML を一気に読み込むいわゆる SPA なので、初回アクセスが遅いといった問題があります。
これを解決するために誕生したのが SSR であり、パフォーマンス改善の他、SEO に強くなるといった特徴があります。
また、 React 単体でルーティングを実現しようとすると React Router が有名ですが、これはページ遷移のたびにブラウザリロードが走ってしまうため、パフォーマンスがよくありませんでした。Next.js ではそういった部分も改善されているため、ぜひ触ってみながら体験してみましょう。

Next.js と Gatsby

Next.js は Gatsby と比較されることがよくありますが、 Gatsby はランディングページなどの静的Webサイトを構築するにはとても快適なのですが、ページが複数あるようなWebアプリケーションを構築するとなると Next.js の方に軍配が上がるのではないかと思います。

Next.js アプリを作成する

Next.js アプリを作成する

npx create-next-app nextjs-blog --use-npm --example "https://github.com/vercel/next-learn-starter/tree/master/learn-starter"

開発サーバーを実行する

cd nextjs-blog
npm run dev

Next.js へようこそ

ブラウザで http://localhost:3000/ にアクセスする
welcome-to-nextjs.png

ページの編集

pages/index.js を以下のように変更する
ホットリロードが効いているので、ブラウザに即時反映されたかと思います。

pages/index.js
“Welcome to”
↓
“Learn”

learn-nextjs.png

続く...

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

Reactで作るVSCodeのSidebarっぽいアコーディオンメニュー

VSCodeのSidebarとは以下の図のBのようなものです。

VSCode_Sidebar

基本的にはアコーディオンメニューと呼ばれる、タイトル領域とコンテンツ領域からなるメニュー領域が縦にスタックされており、タイトルをクリックすることでメニューの表示・非表示が切り替わるものになっています。

VSCodeのSidebarでは普通のアコーディオンメニューの機能に加え、
* アコーディオンメニュー全体の高さが決まっている(高さはそれぞれのメニューの高さの和になっている)
* メニュー内のコンテンツ領域は必ずしもすべて表示されず、高さが足りないときはスクロールバーが表示される
* メニューの高さはメニュー間にあるスライダーをドラッグすることで変更できる
といった機能があります。

これらをReactとHooksを使用して実装していきます。

(作ったものはこちら(CodeSandbox)にあります)

image.png

STEP1

まずアコーディオンメニュー全体に相当するColumnコンポーネント、それに含まれるメニューに相当するContentコンポーネントを用意します。
これらを使用して、以下のコードのような感じでアコーディオンメニューを定義できるようにします。

コード(App.js)
import React, { useState } from "react";
import Content from "./Content";
import Column from "./Column";

const App = () => {
  return (
    <div className="App">
      <div style={{ height: "600px", display: "flex", position: "relative" }}>
        <Column>
          <Content title="Title 1">
            <div style={{ height: "300px" }} />
          </Content>
          <Content title="Title 2">
            <div style={{ height: "300px" }} />
          </Content>
          <Content title="Title 3">
            <div style={{ height: "300px" }} />
          </Content>
        </Column>
        <div />
      </div>
    </div>
  );
};

STEP2

まずContentコンポーネントの設計を行っていきます。
Contentコンポーネントを、メニューのコンテンツを表示するBody(下図橙色部)、ドラッグによりContentコンポーネントの高さを変更できるSlider(下図赤色部)、クリックによりBodyの表示・非表示を切り替えられるTitle(下図緑色部)の3つのコンポーネントから構成します。

image.png

この時必要な、1) Bodyの高さ、2) Titleをクリックした際のコールバック関数、3) Sliderをクリックした際のコールバック関数は、Reactコンテクストを利用して取得します。

コード(Content.js)
import React, { useLayoutEffect, useRef, useContext } from "react";
import styled from "styled-components";
import { splitPaneContext } from "./Column";

const Slider = styled.div`
  height: 5px;
  background-color: red;
  cursor: row-resize;
`;

const Title = styled.div`
  height: 20px;
  background-color: yellowgreen;
  cursor: pointer;
  font-size: small;
  user-select: none;
`;

const Body = styled.div`
  overflow: auto;
  background-color: orange;
`;

const Container = styled.div`
  display: flex;
  flex-direction: column;
`;

const Content = props => {
  const { index, title="", children } = props;
  const bodyRef = useRef(null);
  const { heightArr, setExpand, onMouseDown } = useContext(splitPaneContext);

  useLayoutEffect(() => {
    bodyRef.current.style.height = `${heightArr[index]}px`;
  }, [heightArr]);

  return (
    <Container>
      <Slider onMouseDown={onMouseDown(index)} />
      <Title onClick={setExpand(index)}>
        {title}
      </Title>
      <Body ref={bodyRef}>{children}</Body>
    </Container>
  );
};

STEP3

Columnコンポーネントの基本的な設計を行っていきます。
最終的には、アコーディオンメニュー全体の高さが変更になったときにそれぞれのメニューの高さをうまい具合に調整するような設計にしますが、ここではアコーディオンメニュー全体の高さが最初レンダリングされたときから変わらない状態を想定した設計をしていきます。

ColumnコンポーネントはuseStateを使用して以下のような状態を保持します。

  • heightArr: Column以下のContent(内のBody)の高さの配列(以降、indexと書かれたものはこの配列のインデックスと同じです)
  • activeIndex: ドラッグ中のSliderのindex(非ドラッグ時は-1)
  • ulIndex: ドラッグ中のSliderのindexより前に開かれているメニューのindexの配列とドラッグ中のSliderのindexより後に開かれているメニューのindexの配列 の 配列
  • separatorYRange: ドラッグ中のSliderの、ドラッグ可能なY座標の範囲
  • separatorY: Sliderをクリックした際のY座標

useEffectを使用した副作用は、コンポーネントマウント時の状態を指定するもの以外では、マウスのイベント(mousemove, mouseup)の登録と解除を行うものがあります。
Sliderをクリック(mousedown)し上記のactiveIndexにindexが設定されるとイベントの登録を、-1が設定されるとイベントの解除を行います。

Titleをクリックした際にクリックされたTitle(Content)のindexを引数に呼び出されるコールバック関数setExpandは、(Content内の)Bodyの高さが0より大きい場合はそのBodyの高さを0に、0であった場合はある定数(以下の例ではminimumContentHeight)をそのBodyの高さに設定します。(因みにminimumContentHeightは、Bodyの高さとして設定できる最小値になります。)
但しこのときすべてのContentの高さの和が、アコーディオンメニュー全体の高さであるColumnの高さに等しくする必要があります。

Sliderをクリック(mousedown)した際にクリックされたSliderのindexを引数に呼び出されるコールバック関数onMouseDownは、上記のactiveIndexへの引数のindexの設定や、クリックされたSliderが上下にドラッグ可能なY座標な範囲のseparateYRangeへの設定などを行います。

ドラッグ中に呼び出されるコールバック関数onMouseMoveでは、onMouseDown関数で設定したドラッグ可能なY座標をもとに、ドラッグ中の各Content内のBodyの高さを都度計算し反映します。

ドラッグ終了時に呼び出されるコールバック関数onMouseUpでは、onMouseDown関数で設定した状態をリセットします。

コード(Column.js)
import React, { useState, useEffect, useRef } from "react";
import styled from "styled-components";

export const splitPaneContext = React.createContext();

export const SLIDER_HEIGHT = 5;
export const TITLE_HEIGHT = 20;

const Container = styled.div`
  display: flex;
  flex-direction: column;
  width: 300px;
  overflow: auto;
  height: 100%;
`;

const Blank = styled.div`
  flex-grow: 1;
  flex-shrink: 1;
  background-color: blue;
`;

const Column = ({ children }) => {
  const [heightArr, setHeightArr] = useState([]);
  const [activeIndex, setActiveIndex] = useState(-1);
  const [ulIndex, setUlIndex] = useState([[], []]);
  const [separatorYRange, setSeparatorYRange] = useState([0, 0]);
  const separatorY = useRef(null);

  const height = 600;
  const minimumContentHeight = 100;
  const count = React.Children.count(children);

  // 指定したindexより前の開かれたメニューのindexを取得する
  const getPrevIndex = (arr, index) => {
    const retArr = [];
    for (let i = index - 1; i >= 0; i--) {
      if (arr[i] > 0)
        retArr.push(i);
    }
    return retArr;
  };

  // 指定したindex以降の開かれたメニューのindexを取得する
  const getNextIndex = (arr, index) => {
    const retArr = [];
    for (let i = index; i < count; i++) {
      if (arr[i] > 0)
        retArr.push(i);
    }
    return retArr;
  };

  // 最も上にある開かれたメニューのindexを取得する
  const getFirstIndex = arr => {
    for (let i = 0; i < count; i++) {
      if (arr[i] > 0)
        return i;
    }
    return -1;
  };

  // 初期表示設定
  useEffect(() => {
      const hArr = Array(count).fill(0);
      hArr[0] = height - (SLIDER_HEIGHT + TITLE_HEIGHT) * count;
      setHeightArr(hArr);
  }, []);

  // マウスのイベントの登録と解除を行う
  useEffect(() => {
    if (activeIndex < 0) {
      document.removeEventListener("mousemove", onMouseMove);
      document.removeEventListener("mouseup", onMouseUp);
    } else {
      document.addEventListener("mousemove", onMouseMove);
      document.addEventListener("mouseup", onMouseUp);
    }
  }, [activeIndex]);

  const setExpand = index => () => {
    const hArr = [...heightArr];
    const opened = heightArr[index] === 0;
    hArr[index] = opened ? minimumContentHeight : 0;
    // メニューが1つしか開いてないとき
    if (hArr.filter(e => e > 0).length === 1) {
      const firstIndex = getFirstIndex(hArr);
      hArr[firstIndex] = height - (SLIDER_HEIGHT + TITLE_HEIGHT) * count;
    // 2つ以上のメニューが開いているとき
    } else if (!hArr.every(e => e === 0)) {
      const nextIndex = getNextIndex(hArr, index + 1);
      const prevIndex = getPrevIndex(hArr, index);
      // クリックされたメニューが開いたとき
      if (opened) {
        let currentHeight = minimumContentHeight;
        // クリックされたメニューより下に開かれてるメニューがないとき
        if (nextIndex.length === 0) {
          let currentIndex = index - 1;
          while (currentIndex > -1) {
            if (heightArr[currentIndex] === 0) {
              currentIndex--;
              continue;
            }
            const tmpHeight = heightArr[currentIndex] - minimumContentHeight;
            if (tmpHeight >= currentHeight) {
              hArr[currentIndex] -= currentHeight;
              break;
            } else {
              hArr[currentIndex] = minimumContentHeight;
              currentHeight -= tmpHeight;
            }
            currentIndex--;
          }
        // クリックされたメニューより下に開かれてるメニューがあるとき
        } else {
          let currentIndex = index + 1;
          while (currentIndex <= count - 1) {
            if (heightArr[currentIndex] === 0) {
              currentIndex++;
              continue;
            }
            const tmpHeight = heightArr[currentIndex] - minimumContentHeight;
            if (tmpHeight >= currentHeight) {
              hArr[currentIndex] -= currentHeight;
              currentHeight = 0;
              break;
            } else {
              hArr[currentIndex] = minimumContentHeight;
              currentHeight -= tmpHeight;
            }
            currentIndex++;
          }
          if (currentHeight > 0) {
            currentIndex = index - 1;
            while (currentIndex > -1) {
              if (heightArr[currentIndex] === 0) {
                currentIndex--;
                continue;
              }
              const tmpHeight = heightArr[currentIndex] - minimumContentHeight;
              if (tmpHeight >= currentHeight) {
                hArr[currentIndex] -= currentHeight;
                break;
              } else {
                hArr[currentIndex] = minimumContentHeight;
                currentHeight -= tmpHeight;
              }
              currentIndex--;
            }
          }
        }
      // クリックされたメニューが閉じたとき
      } else {
        if (nextIndex.length === 0) {
          // 下にあるメニューが1つも開いていないとき、すぐ上で開いているメニューを拡大する
          hArr[prevIndex[0]] += heightArr[index];
        } else {
          // すぐ下で開いているメニューを拡大する
          hArr[nextIndex[0]] += heightArr[index];
        }
      }
    }
    setHeightArr(hArr);
  };

  const onMouseDown = index => e => {
    e.preventDefault();
    if (index === 0)
      return;
    const prevIndex = getPrevIndex(heightArr, index);
    if (prevIndex.length > 0) {
      const nextIndex = getNextIndex(heightArr, index);
      separatorY.current = e.clientY;
      const minus = heightArr.reduce((a, c, i) => (i < index && c > 0) ? a + c - minimumContentHeight : a, 0);
      const plus = heightArr.reduce((a, c, i) => (i >= index && c > 0) ? a + c - minimumContentHeight : a, 0);
      setSeparatorYRange([-minus, plus]);
      setActiveIndex(index);
      setUlIndex([prevIndex, nextIndex]);
    }
  };

  const onMouseMove = e => {
    if (ulIndex[0].length === 0 || ulIndex[1].length === 0 || separatorY.current === null)
      return;
    let yPos = e.clientY - separatorY.current;
    if (separatorYRange[0] > yPos) 
      yPos = separatorYRange[0];
    if (separatorYRange[1] < yPos)
      yPos = separatorYRange[1];
    const hArr = [...heightArr];
    if (yPos > 0) {
      hArr[ulIndex[0][0]] = heightArr[ulIndex[0][0]] + yPos;
      ulIndex[1].reduce((a, c) => {
        const asobi = heightArr[c] - minimumContentHeight;
        if (a === 0) {  
          return 0
        } else if (asobi < a) {
          hArr[c] = heightArr[c] - asobi;
          return a - asobi;
        } else {
          hArr[c] = heightArr[c] - a;
          return 0;
        }
      }, yPos);
    } else if (yPos < 0) {
      hArr[ulIndex[1][0]] = heightArr[ulIndex[1][0]] - yPos;
      ulIndex[0].reduce((a, c) => {
        const asobi = heightArr[c] - minimumContentHeight;
        if (a === 0) {  
          return 0
        } else if (asobi < a) {
          hArr[c] = heightArr[c] - asobi;
          return a - asobi;
        } else {
          hArr[c] = heightArr[c] - a;
          return 0;
        }
      }, -yPos);
    }
    setHeightArr(hArr);
  };

  const onMouseUp = e => {
    setActiveIndex(-1);
    setUlIndex([[], []);
    separatorY.current = null;
  };

  return (
    <splitPaneContext.Provider value={{ heightArr, setExpand, onMouseDown }}>
      <Container ref={ref}>
        {React.Children.map(children, (child, index) =>
          React.cloneElement(child, { index })
        )}
        <Blank />
      </Container>
    </splitPaneContext.Provider>
  );
};

STEP4

CodeSandboxにあげているサンプルでは、STEP3に加えて、ResizeObserverColumnのサイズ変更を監視し、サイズが変更されるとuseEffectによってColumn下のContentの高さが再計算されるような処理を追加しています。
詳細については、ソースコードをご覧ください。

以上

です。
こういったReactのライブラリがないかな~と思って探したんですが見当たらなかったので手探りで作ってみました。

参考にしたページなど

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

テクトロジーによる実践的組織構造学

今回の記事では、ソ連の革命家、医師、哲学者、小説作家であったアレクサンダーボグダノフが提唱したテクトロジーと呼ばれる実践的組織構造学について紹介する。
テクトロジーでは組織が安定、成長、破綻する環境、条件について詳細に解説し、安定した組織を生成する手法について解説している。
テクトロジーの概念を拝借し、創造的な組織創造の手法を紹介したいと思う。

テクトロジーにおける組織の定義

テクトロジーでは、組織をオープンクローズ型の成長する有機体システムと定義している。
組織とは以下の要素で構成されている。
ビジョン・・・組織の目指すべき方向性。
経済・・・組織のボディ。巨大であるほど収容できる人の人数が増加する
金融・・・組織を循環する血液。
生産・・・もの、サービスを生産し、組織の経済を巨大化する手段。

これらの有機的な要素が相互作用し、成長することで組織という有機体が構成されていると考える。

テクトロジーにおける成長する有機体システムとは

テクトロジーでは、組織をオープンクローズ型の成長する有機体システムと定義している。
テクトロジーにおける組織の定義を中国の陰陽論によって説明することができる。
陰陽論とは、原初は混沌(カオス)の状態であると考え、この混沌の中から光に満ちた明るい澄んだ気、すなわち陽の気が上昇して天となり、重く濁った暗黒の気、すなわち陰の気が下降して地となった。この二気の働きによって万物の事象を理解し、また将来までも予測しようというのが陰陽思想である。
組織が外部からエネルギーを取り入れ、出力するインプット、アウトプットの運動を陰陽論における陽の気と捉えることができる。
逆に組織内部に沈殿し、成長し、ヒエラルキーを形成する秩序生成を担う運動を陰陽論における陰の気と捉えることができる。

テクトロジーにおける生産の定義

テクトロジーでは、組織における生産活動は以下の3つに分類されている。
人の生産・・・人に教育を施し、組織活動に従事する生産者を作成する
モノ、サービスの生産・・・外部から取得した資材を用いて、モノ、サービスの生産を行う。
アイデア・・モノ、サービスを生成するための知識、アイディアを作成する。
組織における生産活動を高めることで、組織の経済を成長させることができる。

テクトロジーおける組織のフォーム(形態)について

現実の世界で、人が活動を行う場合、必ず外部からの影響、抵抗を受ける。
外部からの影響、抵抗を抑えるために、組織は環境に合わせた最適なフォーム(形態)を取る必要がある。
魚やイルカなど、魚と哺乳類で種族は異なるが、水の抵抗を抑えるために同様の流線形フォルムを取っている。
組織のフォーム(形態)は外部環境によって決定される。
外部環境からの抵抗を最小限にするために、外部との接触の最小化、不要な組織的機能の削除などが求められる。
最適なフォーム(形態)によって、組織は外部からの抵抗を減少させ、健全に成長することができる。

テクトロジーにおける組織が不安定化する条件

テクトロジーにおける組織が不安定化する条件として以下の2点が挙げられる。
・外部からのエネルギー取得の減少・・外部から人、モノ、金の循環が減少することで組織のサイズ、経済を維持することできなくなる。
・ヒエラルキーシステムの固定化・・・ヒエラルキーシステムが巨大化し、組織が硬直化してしまう。
組織不安定化を回避する手法として以下の手段が有効とされている。
生産手段を研究、開発、更新を行い、組織の経済成長のスピードを増加させる。
組織が硬直化の原因になっているヒエラルキーシステムを解体し、適切なサイズに組み替える。

まとめ

アレクサンダーボグダノフテクトロジーに関するアイディアを発表した時期は1920年代である。
独学で組織が破綻する条件、環境を発見し、持続可能な成長のコンセプトを提唱したアレクサンダーボグダノフの先見性は恐るべきものである。
ソ連は軍事、IT、経済においてアメリカと張り合うことができた超大国だった。
ソ連時代に考えられたアイディア、思想などは現代においても見直されるべきものだと思われる。

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

Ruby on RailsにReactとReduxの環境を標準のWebpackで構築する

概要

Ruby on Rails上でReactを利用する場合、通常はGemのWebpackerを利用します。

ただ、アセットパイプライン上でのビルドが遅かったり、
Webpackのカスタマイズ方法が特殊だったりで使いずらいので、
Gemを使わずに設定します。

今回は標準のWebpackを導入した上で、Reactとの繋ぎ込みを目的としています。

Rails側から呼び出すコンポーネントを管理したいため、
ストラクチャ設計が分かりやすいReduxのRe-ducksパターンを採用した方法で構築を行っていきます。

また、ReactとReduxについての設定方法がSPAを全体としたものが多いですが、
Railsに実装する場合は既存プロジェクトにページ毎にReactを使うことが多いため、
Railsでルーティングを管理する場合での使用方法を書いていきます。

スペック

  • CentOS7
  • Ruby 2.7.1p83
  • Ruby on Rails 5.2.4
  • node js 13.14.0
  • react 16.12.0

今回はWebpackでAssetを用意して、ファイルを読み込むだけの設計になるため、Ruby on Rails6でも同様に設定できるかと思います。

手順

インストール

今回はRuby, Railsを使っている人がReactを実装することを想定しているため、インストール方法を省きます。

NodeJSとYarnをインストールします

curl -sL https://rpm.nodesource.com/setup_8.x | sudo bash -
sudo yum install nodejs
npm install -g yarn

WebpackやReact、Redux等のパッケージをインストールします。
その他Reduxの運用に必要なパッケージやビルド用のLoaderをインストールしていきます。

yarn add webpack webpack-cli webpack-manifest-plugin
yarn add react react-redux redux-logger redux-thunk reselect axios
yarn add babel-core babel-preset-react babel-preset-es2015 babel-loader
yarn add typescript ts-loader
yarn add node-sass sass-loader postcss-loader style-loader mini-css-extract-plugin
yarn add file-loader expose-loader url-loader
yarn add thread-loader hard-source-webpack-plugin

作成されたpackage.jsonにscriptsを書き込みます。

package.json
{
  ...
  },
  "scripts": {
    "webpack-dev": "webpack --watch --progress --mode=development --config webpack.config.js",
    "webpack-build": "webpack --mode=production",
    "webpack-clear": "rm ./public/assets/*",
    "cache-clear": "rm ./node_modules/.cache/hard-source/*"
  }
}

これでWebpackのビルドができます。
webpack-dev を利用すれば開発中にwatchした状態で利用できます。

yarn webpack-dev

Webpackの設定

まずは必要なモジュールを読み込みます。

webpack.config.js
const webpack = require('webpack');
const ManifestPlugin = require('webpack-manifest-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');

次にパッケージ化するファイルを指定します。
Webpack管理下のディレクトリは app/frontend/以下にします。
また、app/frontend/containers/以下にReactのコンテナー(ルートコンポーネント)を
app/frontend/packs/以下に通常利用できるJS等を配置します。

ビルドしたファイルは public/assets/以下に作成します。

webpack.config.js
const path = require('path');
const glob = require('glob');
const glob_path = './app/frontend/{containers,packs}/**/*.{js,jsx,ts,tsx,css,scss,sass}';
const entries = Object.fromEntries(glob.sync(glob_path).map((f) => ([f.split('/').reverse()[0].split('.')[0], f])));
const outputs = {
  filename: "[name]-[hash].js",
  path: path.join(__dirname, 'public', 'assets'),
  publicPath: "/"
};

loaderとモジュールの設定を行います。
Babel(JS)、Typescript、Sass等のローダーの設定をファイル拡張子によって作成します。

ManifestPluginを利用することで、manifest.jsonでファイルを管理でき、
Rails等で利用しやすくなります。
MiniCssExtractPluginではCSSファイルの圧縮を行います。
HardSourceWebpackPluginはビルド時にパッケージ等をキャッシュしてくれるため、ファイルの変更時などにビルドが早くなります。

純正のWebpackの設定のため、Webpackerと比べて設定方法については検索しやすいと思います。

webpack.config.js
module.exports = {
  entry: entries,
  output: outputs,
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        loader: 'babel-loader',
        query: {
          presets: ['react', 'es2015']
        }
      },
      {
        test: /\.(ts|tsx)$/,
        use: [
          {
            loader: 'thread-loader',
            options: {
              workers: require('os').cpus().length - 1
            }
          },
          {
            loader: 'ts-loader',
            options: {
              transpileOnly: true,
              happyPackMode: true
            }
          }
        ]
      },
      {
        test: /\.css$/,
        use:['style-loader', 'css-loader']
      },
      {
        test: /\.scss$/,
        use: [
          { loader: process.env.NODE_ENV !== 'production' ? 'style-loader' : MiniCssExtractPlugin.loader},
          { loader: 'css-loader',},
          { loader: 'postcss-loader',
            options: {
              plugins: function () {
                return [
                  require('precss'),
                  require('autoprefixer')
                ];
              }
            }
          },
          { loader: 'sass-loader'}
        ]
      },
      {
        test: /.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/,
        use: "url-loader?limit=100000"
      },
    ],
  },
  resolve: {
    modules:[path.join(__dirname, 'node_modules')],
    extensions: ['.js', '.jsx', '.ts', '.tsx']
  },
  plugins: [
    new ManifestPlugin({
      writeToFileEmit: true
    }),
    new MiniCssExtractPlugin({
      filename: '[name].css'
    }),
    new HardSourceWebpackPlugin(),
  ],
};

Typescriptの設定

Typescriptを導入したため、設定ファイルも用意しておきます。
ReduxのAction等でTypescriptの構文など使いたいため、最低限の設定を施しておく。

いきなり、Anyタイプを禁止してしまうと、ReactとRedux部分のクラスとか調べるのが大変なため、noImplicitAny: falseで一旦許可しておく。

tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "noImplicitAny": false,
    "allowJs": true,
    "skipLibCheck": true,
    "module": "esnext",
    "jsx": "react"
  },
  "include": [
    "app/frontend"
  ]
}

Rails Helperの設定

Rails側からのコンポーネントの呼び出しはWebpacker Gemがやっていてくれていたが、
今回は導入しないため、自分でHelperを用意する。

webpack_asset_pathでは、app/frontend/packs/以下からファイルのパスが取得できる
react_componentでは、app/frontend/components/以下からReactのコンテナーが取得できる

app/helpers/react_helper.rb
module ReactHelper
  # /app/frontend/packs/
  def webpack_asset_path(file_name)
    @webpack_manifest ||= JSON.parse(File.read("public/assets/manifest.json")
    if @webpack_manifest.has_key?(file_name)
      "/assets#{manifest.fetch(file_name)}"
    else
      raise Exception.new("not found #{file_name} in manifest.")
    end
  end

  # /app/frontend/components/*
  def react_component(name, **props)
    id_name = "#{name}-container"
    js_file = "#{name.underscore}_container.js"
    props[:flash] = flash.presence&.to_h || {}
    valid_file?(js_file)

    content_tag :section do
      concat(content_tag(:div, "", id: id_name, data: {react_props: props}))
      concat(javascript_include_tag(asset_bundle_path(js_file)))
    end
  end
end

React側の設定

これまでの設定から、ストラクチャ設計は以下の通りにする。

./app
└── frontend
    ├── rails_container.tsx
    ├── packs
    │   └── application_pack.js
    ├── containers
    │   └── sample
    │       └── sample_container.js
    ├── components
    │   └── sample
    │       └── sample_component.tsx
    ├── reducks
    │   └── samples
    │       ├── actions.js
    │       ├── index.js
    │       ├── operations.js
    │       ├── reducers.ts
    │       ├── selectors.js
    │       ├── store.js
    │       └── types.ts
    └── stylesheets
        └── application.scss

Railsのヘルパーで作ったIDにDOMでReactコンポーネントを作成しなければならないため、
関数を作ってコンポーネントを作成できるようにします。

SPAとは違い、ページ毎に使用するオブジェクトは異なるため、
ビルドのコスト等を考慮して、コンテナー毎にReducerが指定できるようにします。

app/frontend/rails_container.js
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, combineReducers } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';

type Component = React.FC<any> | React.ComponentClass<any>;
type Reducers = { [key: string]: any };

// ./app/helpers/react_helper.rb: ReactHelper.react_component(name, **props)
export default function railContainer(name: string, component: Component, reducers: Reducers) {
  const app: HTMLElement | null = document.getElementById(name + '-container');

  if (app) {
    const store = createStore(
      combineReducers(reducers),
      applyMiddleware(logger, thunk)
    );
    const reactProps: object = JSON.parse(app.dataset.reactProps || "{}");
    render(
      <Provider store={store}>
        {React.createElement(component, {railsProps: reactProps})}
      </Provider>,
      app
    );
  } else {
    console.log("not found "+name+" container");
  }
}

コンテナーで使用する、コンポーネントとReducerを読み込んで、先ほど作成した関数でコンテナーを作成します。

app/frontend/containers/sample/sample_containers.js
import railsContainer from "../../lib/rails_container";
import { SamplesReducer } from "../../reducks/samples/reducers";
import { SampleComponent } from "../../components/sample_component";

railsContainer("SampleComponent", SampleComponent, {
  samples: SamplesReducer,
});

railsContainerで設定したコンポーネントにはPropsの中に railsPropsが入っています。
Rails側からHashで渡したものがJS側のObjectで取得できます。

Re-ducksパターンにおいてOperationsはActionを呼び出す役割なので、dispatch(mountSamples(props.railsProps))でStoreにデータを設定できます。

useEffect(()=>{}, [])を使うことでコンポーネント起動時に実行されるため、
コンテナーを呼び出す際にRailsで渡した値を読み込むことができます。
(componentDidMountと同じです。)

app/frontend/components/sample/sample_component.tsx
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from "react-redux";
import { mountSamples } from "../../reducks/samples/operations";
import { getSamplesState } from "../../reducks/samples/selectors";
import { Samples } from "../../reducks/samples/types";
import { ChildComponent } from "./child_component";

type Props = {
  railsProps: {
    samples: Samples,
  },
}

const SampleComponent: React.FC<Props> = (props) => {
  const dispatch = useDispatch();
  const selector = useSelector(state => state);

  const samples = getSamplesState(selector);

  useEffect(() => {
    dispatch(mountSamples(props.railsProps));
  }, []);

  return (
    <ChildComponent/>
  )
}

export default SampleComponent;

補足

React Hooks, Re-ducksパターンについて

上記ではReact Hooksを使った実装をしていますが、
Re-ducksパターンやReact Hooksの説明はトラハックさんのYoutubeが分かりやすいと思います。
https://www.youtube.com/watch?v=FBMA34gUsgw&list=PLX8Rsrpnn3IWavNOj3n4Vypzwb3q1RXhr

ビルドされたファイル

yarn webpack-build等でビルドすると、public/assets/以下にapp/frontend/packs/ app/frontend/containers以下に配置したファイルがビルドされていることが分かると思います。

この2つのディレクトリが必ずJSのプログラムの開始位置になると覚えておくと、コードの管理が楽になるかと思います。

public/assets/
├── manifest.json
├── application_pack-1732212f4623eefd2c49.js
└── sample_container-1732212f4623eefd2c49.js

hard-source-webpack-pluginについて

今回RailsにWebpackerを使わないで、標準のWebpackで設定する方法を選んだのは、
コードが膨らんできてビルド時間が遅くなってしまう問題を抱えていたからでした。

Railsのアセットパイプラインから切り離して体感的にはビルドが早くなったのですが、あと一押し欲しい感じでした。
hard-source-webpack-pluginはキャッシュを作成することでビルド時間の短縮を図れるため、使用しました。
watchモードで開発する際などにキャッシュが効いていると、毎回のようにビルドされていたのが短縮されるのでとても便利です。

たまにキャッシュが悪さして上手くビルドができなくなる時があるらしいので、
その場合はnode_modules/.cache/hard-source/以下のキャッシュデータを削除する方法が推奨されています。
(今回はyarn cache-clearでコマンドを用意しました。)

参考

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

犬猫スイッチ

以下の画像のようなものをイメージに作ります.
image.png

STEP1 ボタンの作成

App.jsとapp.cssを用意し,その中に以下のプログラムを入力し,ボタンを作成する

App.js

import React, {Component} from 'react';
import './app.css';

class App extends Component{
  render(){
    return(
      <div>
        <h1></h1>
        <div className='buttons'>
          <button></button>
          <button></button>
        </div>
      </div>
    );
  }
}
export default App

app.css

h1{
  text-align: center;
}

.buttons {
  margin-top: 40px;
  padding: 0;
  font-family: sans-serif;
  text-align: center;
}

button {
  display: inline-block;
  font-size: 16px;
  width: 200px;
  height: 48px;
  border-radius: 48px;
  margin-right: 24px;
  margin-left: 24px;
  color: #fff;
  background-color: #66ccff;
  border: none;
  outline: none;
  box-shadow: 4px 4px #d8d8d8;
  cursor: pointer;
}

button:active {
  position: relative;
  top: 4px;
  left: 4px;
  box-shadow: none;
}

すると
image.png

ができました.

STEP2 犬と猫の表示切り替え

犬と猫のボタンを押すことでStateを更新して表示させます.

初期状態の定義

constructorとsuperで構成される定番の形の中でstateを定義します.
そしてstateを表示させるために<h1>犬</h1><h1>{}</h1>

class App extends Component{
  constructor(){
    super();
    this.state={name: '犬or猫'}
  }
  render(){
    return(
      <div>
        <h1>{this.state.name}</h1>
  省略
}

image.png

ボタンの実装

handleClick=()=>{処理}をベースにボタンを以下のように実装しました.

handleDogClick=()=>{
    this.setState({name:''});
  }
  handleCatClick=()=>{
    this.setState({name:''});
  }

省略

   <button onClick={this.handleDogClick}></button>
   <button onClick={this.handleCatClick}></button>

 完成

import React, {Component} from 'react';
import './app.css';

class App extends Component{
  constructor(){
    super();
    this.state={name: '犬or猫'}
  }

  handleDogClick=()=>{
    this.setState({name:''});
  }
  handleCatClick=()=>{
    this.setState({name:''});
  }


  render(){
    return(
      <div>
        <h1>{this.state.name}</h1>
        <div className='buttons'>
          <button onClick={this.handleDogClick}></button>
          <button onClick={this.handleCatClick}></button>
        </div>
      </div>
    );
  }
}
export default App
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Mac セットアップ(3)

Reactインストール

https://itsakura.com/react-hello

npm install -g create-react-app

Elixirインストール

https://elixir-lang.jp/install.html

brew install elixir

ログ

Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/core).
==> Updated Formulae
Updated 1 formula.

==> Downloading https://homebrew.bintray.com/bottles/openssl%401.1-1.1.1g.catalina.bottle.tar.gz
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/1926679569c6af5337de812d86f4dad2b21ff883ad3a5d2cd9e8836ac5ac7ffe?response-content-disposition=attachment%3Bfilename%3D%22openssl%401.1-1.1.
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/jpeg-9d.catalina.bottle.tar.gz
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/8f7b82a952fb3937889c7f22da1403e5338cd320495917eb26b0c5b2e614791c?response-content-disposition=attachment%3Bfilename%3D%22jpeg-9d.catalina.b
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/libpng-1.6.37.catalina.bottle.tar.gz
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/c8e74da602c21f978cd7ee3d489979b4fc6681e71f678a1d99012943ee3a909f?response-content-disposition=attachment%3Bfilename%3D%22libpng-1.6.37.cata
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/libtiff-4.1.0.catalina.bottle.tar.gz
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/449bd9123e73e4c4eab85b77322d769cc9df0f6adab05e9b9319b012d1215a68?response-content-disposition=attachment%3Bfilename%3D%22libtiff-4.1.0.cata
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/wxmac-3.0.5.1_1.catalina.bottle.tar.gz
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/110aa0b2134d8bff1647de0cd8500f160133794b347f789bba3e1894b991b788?response-content-disposition=attachment%3Bfilename%3D%22wxmac-3.0.5.1_1.ca
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/erlang-23.0.3.catalina.bottle.tar.gz
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/cc5b720aa2ef96e1d4f29620f2cc1243008bf450ef6451140ab6ab0679877916?response-content-disposition=attachment%3Bfilename%3D%22erlang-23.0.3.cata
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/elixir-1.10.4.catalina.bottle.tar.gz
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/ed5f1be4059b41113c35f0a3ae1f01ae5042b143869d22457428507c4a976812?response-content-disposition=attachment%3Bfilename%3D%22elixir-1.10.4.cata
######################################################################## 100.0%
==> Installing dependencies for elixir: openssl@1.1, jpeg, libpng, libtiff, wxmac and erlang
==> Installing elixir dependency: openssl@1.1
==> Pouring openssl@1.1-1.1.1g.catalina.bottle.tar.gz
==> Caveats
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /usr/local/etc/openssl@1.1/certs

and run
  /usr/local/opt/openssl@1.1/bin/c_rehash

openssl@1.1 is keg-only, which means it was not symlinked into /usr/local,
because macOS provides LibreSSL.

If you need to have openssl@1.1 first in your PATH run:
  echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrc

For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"

==> Summary
?  /usr/local/Cellar/openssl@1.1/1.1.1g: 8,059 files, 18MB
==> Installing elixir dependency: jpeg
==> Pouring jpeg-9d.catalina.bottle.tar.gz
?  /usr/local/Cellar/jpeg/9d: 21 files, 775.2KB
==> Installing elixir dependency: libpng
==> Pouring libpng-1.6.37.catalina.bottle.tar.gz
?  /usr/local/Cellar/libpng/1.6.37: 27 files, 1.2MB
==> Installing elixir dependency: libtiff
==> Pouring libtiff-4.1.0.catalina.bottle.tar.gz
?  /usr/local/Cellar/libtiff/4.1.0: 247 files, 3.7MB
==> Installing elixir dependency: wxmac
==> Pouring wxmac-3.0.5.1_1.catalina.bottle.tar.gz
?  /usr/local/Cellar/wxmac/3.0.5.1_1: 810 files, 23.0MB
==> Installing elixir dependency: erlang
==> Pouring erlang-23.0.3.catalina.bottle.tar.gz
==> Caveats
Man pages can be found in:
  /usr/local/opt/erlang/lib/erlang/man

Access them with `erl -man`, or add this directory to MANPATH.
==> Summary
?  /usr/local/Cellar/erlang/23.0.3: 7,949 files, 466.9MB
==> Installing elixir
==> Pouring elixir-1.10.4.catalina.bottle.tar.gz
?  /usr/local/Cellar/elixir/1.10.4: 430 files, 5.9MB
==> Caveats
==> openssl@1.1
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /usr/local/etc/openssl@1.1/certs

and run
  /usr/local/opt/openssl@1.1/bin/c_rehash

openssl@1.1 is keg-only, which means it was not symlinked into /usr/local,
because macOS provides LibreSSL.

If you need to have openssl@1.1 first in your PATH run:
  echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrc

For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"

==> erlang
Man pages can be found in:
  /usr/local/opt/erlang/lib/erlang/man

Access them with `erl -man`, or add this directory to MANPATH.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails][React]Module not found: Error: Can't resolve '../bundles/HelloWorld/components/HelloWorld'

起こったこと

既存のRails プロジェクトにReact on Rails を入れる。

rails generate react_on_rails:install
を実行するも

どうやら Webpackerのbuild が通らない。

ERROR in ./app/javascript/packs/hello-world-bundle.js
Module not found: Error: Can't resolve '../bundles/HelloWorld/components/HelloWorld' in '/home/vagrant/dev/rails-proj/app/javascript/packs'
 @ ./app/javascript/packs/hello-world-bundle.js 2:0-69 5:14-24
...

解決法

どうやら別途、以下コマンドで設定ファイル生成が必要だったよう。

$ bin/rails webpacker:install:react
Copying babel.config.js to app root directory
       force  babel.config.js
Copying react example entry file to /home/vagrant/dev/rails/udr-tinder/app/javascript/packs
      create  app/javascript/packs/hello_react.jsx
Updating webpack paths to include .jsx file extension
      insert  config/webpacker.yml
Installing all react dependencies
         run  yarn add react react-dom @babel/preset-react prop-types babel-plugin-transform-react-remove-prop-types from "."
..

参考

https://www.chrisblunt.com/rails-and-react-fixing-module-not-found-errors-for-jsx-files/

https://github.com/shakacode/react_on_rails/

https://shakacode.gitbooks.io/react-on-rails/content/docs/tutorial.html

https://www.botreetechnologies.com/blog/how-to-add-react-js-to-your-ruby-on-rails-app-with-webpacker

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

[Rails][React]Module not found: Error: Can't resolve '../bundles/HelloWorld/components/HelloWorld' 解決法

起こったこと

既存のRails プロジェクトにReact on Rails を入れる。

rails generate react_on_rails:install
を実行するも

どうやら Webpackerのbuild が通らない。

ERROR in ./app/javascript/packs/hello-world-bundle.js
Module not found: Error: Can't resolve '../bundles/HelloWorld/components/HelloWorld' in '/home/vagrant/dev/rails-proj/app/javascript/packs'
 @ ./app/javascript/packs/hello-world-bundle.js 2:0-69 5:14-24
...

解決法

どうやら別途、以下コマンドで設定ファイル生成が必要だったよう。

$ bin/rails webpacker:install:react
Copying babel.config.js to app root directory
       force  babel.config.js
Copying react example entry file to /home/vagrant/dev/rails/udr-tinder/app/javascript/packs
      create  app/javascript/packs/hello_react.jsx
Updating webpack paths to include .jsx file extension
      insert  config/webpacker.yml
Installing all react dependencies
         run  yarn add react react-dom @babel/preset-react prop-types babel-plugin-transform-react-remove-prop-types from "."
..

参考

https://www.chrisblunt.com/rails-and-react-fixing-module-not-found-errors-for-jsx-files/

https://github.com/shakacode/react_on_rails/

https://shakacode.gitbooks.io/react-on-rails/content/docs/tutorial.html

https://www.botreetechnologies.com/blog/how-to-add-react-js-to-your-ruby-on-rails-app-with-webpacker

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