反応するフェニックス:正しい方法™


これは、最初のポストの完全なセットアップの反応をフェニックスを使用するには、待望の書き直しは、元の投稿my website . フェニックス1.4船Webpack したがって、デフォルトではセットアップを以前よりずっと簡単にします.長い過度の書き換えは、終了されており、また、最新の更新フェニックスへの最新の日付に行われている.The sample repo も更新されました.
私のTypescriptガイドへの移行のパート2をお探しの方は安心!来週以内に終わります.
私は遊んでいるElixir 最近たくさん.最近友達が私を見せてくれたthis blog post 彼らがどのようにエリクサーの力を通して彼らのプラットホームを拡大することができたかについての不協和工学チームによって、そして、それを読んだ後に、私はそれを試みることを確信していました.あなたが言語について学んでいるならば、あなたはノードから来ました.閉じるこの動画はお気に入りから削除されています.
RubyがRails , PHPはLaravel , それからエリクサーPhoenix . 前にレールを使用した場合は、自宅で感じるでしょう.それはあなたの典型的なWebフレームワークの裸の要点を持っていますChannels , これは、ソケットを構築するWebアプリケーションをはるかに簡単になります.
Webアプリのための理想的なスタックは、通常、反応フロントエンドが含まれています.だから、自然に、私はどのように私はフェニックスのアプリを反応フロントエンドを構築することを知りたかった.残念ながら、フェニックスとの反応の設定は、多くの人々が考えるほど簡単ではありません.私がインターネット上で遭遇したほとんどすべてのガイドは、単一の反応コンポーネントをレンダリングするだけでなく、ルーティングやAPIフェッチのような重要なものをカバーしません.それはしばらくかかったが、最終的に、私は実際に動作するセットアップを見つけた™.
それで、あなたが私のようであるならば、Heckがあなたが実際にそれを働かせる方法を疑問に思っていたならば、私は方法を示すつもりです.うまくいけば、これは一度、そして、すべてのこの質問に答えます.

TLドクター


読書があなたのものでないならば、私はこのガイドの最終結果を準備しましたhere . 一度設定したら、以下のスタックを使用してフェニックスのセットアップを行う必要があります.
  • エリクシール^1.7.4 )
  • ノード.js^10.15.0 )
  • NPM^6.4.1 )
  • フェニックス^1.4.0 )
  • 反応する^16.7.0 )
  • タイプスクリプト(^3.0.0 )
  • Webpack^4.0.0 )
  • 始める


    このガイドでは、私はあなたが既に持っていると仮定しますElixir , Phoenix , and Node.js インストール.あなたがすでにしていないならば、新しいタブで上記の関連を開けて、それをしてください.心配しないで待ちましょう.
    我々はまた、フェニックス1.4、書き込み時に利用可能な最新バージョンを使用するつもりです.

    ボイラ板


    私たちは、新しいフェニックスプロジェクトを設定します.
    バージョン1.4の時点で、フェニックスの船Webpack デフォルトでは.以下のコマンドを実行することによって、我々はJS束リングのためにビルトイン支持でフェニックスセットアップをします.
    $ mix phx.new phoenix_react_playground
    
    あなたが依存関係を取得してインストールするかどうか尋ねられたら、答えてください.
    デフォルトではpackage.json ファイル、WebPackの設定、および.babelrc ファイルはassets/ プロジェクトルートの代わりにフォルダ.閉じるこの動画はお気に入りから削除されていますVisual Studio Code . では、代わりにプロジェクトルートに移動しましょう.
    $ cd phoenix_react_playground
    $ mv assets/package.json .
    $ mv assets/webpack.config.js .
    $ mv assets/.babelrc .
    
    これは、フェニックスが提供するデフォルトのいくつかを変更する必要があることを意味します..gitignore
    @@ -26,7 +26,7 @@ phoenix_react_playground-*.tar
     npm-debug.log
    
     # The directory NPM downloads your dependencies sources to.
    -/assets/node_modules/
    +node_modules/
    
     # Since we are building assets from assets/,
     # we ignore priv/static. You may want to comment
    
    package.json
    @@ -6,8 +6,8 @@
         "watch": "webpack --mode development --watch"
       },
       "dependencies": {
    -    "phoenix": "file:../deps/phoenix",
    -    "phoenix_html": "file:../deps/phoenix_html"
    +    "phoenix": "file:deps/phoenix",
    +    "phoenix_html": "file:deps/phoenix_html"
       },
       "devDependencies": {
         "@babel/core": "^7.0.0",
    @@ -18,7 +18,7 @@
         "mini-css-extract-plugin": "^0.4.0",
         "optimize-css-assets-webpack-plugin": "^4.0.0",
         "uglifyjs-webpack-plugin": "^1.2.4",
    -    "webpack": "4.4.0",
    -    "webpack-cli": "^2.0.10"
    +    "webpack": "4.28.4",
    +    "webpack-cli": "^3.2.1"
       }
     }
    
    webpack.config.js
    @@ -13,11 +13,11 @@ module.exports = (env, options) => ({
         ]
       },
       entry: {
    -      './js/app.js': ['./js/app.js'].concat(glob.sync('./vendor/**/*.js'))
    +    app: './assets/js/app.js'
       },
       output: {
         filename: 'app.js',
    -    path: path.resolve(__dirname, '../priv/static/js')
    +    path: path.resolve(__dirname, 'priv/static/js')
       },
       module: {
         rules: [
    @@ -36,6 +36,10 @@ module.exports = (env, options) => ({
       },
       plugins: [
         new MiniCssExtractPlugin({ filename: '../css/app.css' }),
    -    new CopyWebpackPlugin([{ from: 'static/', to: '../' }])
    -  ]
    +    new CopyWebpackPlugin([{ from: 'assets/static/', to: '../' }])
    +  ],
    +  resolve: {
    +    // Add '.ts' and '.tsx' as resolvable extensions.
    +    extensions: ['.ts', '.tsx', '.js', '.jsx', '.json']
    +  }
     });
    
    上記のWebpackの設定は、上のunbunled資産を配置する理想的なフェニックスのセットアップのために動作しますassets/ フォルダ.フェニックスがWebpackコマンドを我々のウォッチャーとして正しく実行することを確認する必要があります.これを行うにはconfig/dev.exs 次のようになります.
    -  watchers: []
    +  watchers: [
    +    {"node", [
    +      "node_modules/webpack/bin/webpack.js",
    +      "--watch-stdin",
    +      "--colors"
    +    ]}
    +  ]
    
    すべてを確認するには、次のコマンドを実行します.
    $ mix deps.get
    $ npm install
    
    すべてうまくいくかグッド!次に、タイプスクリプト環境を設定します.
    最初に、私たちはBabelのためにTypescript +反応プリセットをインストールして、我々のものにそれを入れます.babelrc .
    $ yarn add --dev @babel/preset-react @babel/preset-typescript @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread typescript
    
    @@ -1,5 +1,10 @@
     {
    -    "presets": [
    -        "@babel/preset-env"
    -    ]
    -}
    +  "presets": [
    +    "@babel/preset-env",
    +    "@babel/preset-react",
    +    "@babel/preset-typescript"
    +  ],
    +  "plugins": [
    +    "@babel/plugin-proposal-class-properties",
    +    "@babel/plugin-proposal-object-rest-spread"
    +  ]
    +}
    
    その後、我々は標準を作成しますtsconfig.json ファイルを入力し、次のように入力します.
    {
      "compilerOptions": {
        "allowJs": true,
        "allowSyntheticDefaultImports": true,
        "esModuleInterop": true,
        "isolatedModules": true,
        "lib": ["dom", "esnext"],
        "jsx": "preserve",
        "target": "es2016",
        "module": "esnext",
        "moduleResolution": "node",
        "preserveConstEnums": true,
        "removeComments": false,
        "sourceMap": true,
        "strict": true
      },
      "include": ["./**/*.ts", "./**/*.tsx"]
    }
    
    そして最後に、私たちのwebpack configを修正してくださいbabel-loader JSファイルとTSファイルを受け付けます.あまりにもwebpackエントリファイルの拡張子を変更することを忘れないでください!
    @@ -13,7 +13,7 @@ module.exports = (env, options) => ({
         ]
       },
       entry: {
    -    app: './assets/js/app.js'
    +    app: './assets/js/app.tsx'
       },
       output: {
         filename: 'app.js',
    @@ -22,7 +22,7 @@ module.exports = (env, options) => ({
       module: {
         rules: [
           {
    -        test: /\.js$/,
    +        test: /\.(js|jsx|ts|tsx)$/,
             exclude: /node_modules/,
             use: {
               loader: 'babel-loader'
    
    一旦あなたのBoilerPlateがセットアップされるならば、あなたのフェニックスプロジェクトのフォルダ構造は現在このように見えなければなりません.
    phoenix_react_playground/
    ├── assets/
    │   ├── js/
    │   │   ├── ...
    │   │   └── app.tsx
    │   ├── scss/
    │   │   ├── ...
    │   │   └── app.scss
    │   └── static/
    │       ├── images/
    │       │   └── ...
    │       ├── favicon.ico
    │       └── robots.txt
    ├── config/
    │   └── ...
    ├── lib/
    │   └── ...
    ├── priv/
    │   └── ...
    ├── test/
    │   └── ...
    ├── .gitignore
    ├── mix.exs
    ├── package.json
    ├── README.md
    ├── tsconfig.json
    └── webpack.config.js
    

    反応の設定


    今すぐフェニックスと正しい方法をフックしましょう.まず、もちろん、我々は反応をインストールする必要があります.
    $ yarn add react react-dom react-router-dom
    $ yarn add --dev @types/react @types/react-dom @types/react-router-dom
    
    それから、我々は我々のベース反応ボイラー板をセットアップすることができます.我々の資産フォルダでapp.js to app.tsx , ファイルを書き換える.assets/js/app.tsx
    import '../css/app.css'
    
    import 'phoenix_html'
    
    import * as React from 'react'
    import * as ReactDOM from 'react-dom'
    import Root from './Root'
    
    // This code starts up the React app when it runs in a browser. It sets up the routing
    // configuration and injects the app into a DOM element.
    ReactDOM.render(<Root />, document.getElementById('react-app'))
    
    assets/js/Root.tsx
    import * as React from 'react'
    import { BrowserRouter, Route, Switch } from 'react-router-dom'
    
    import Header from './components/Header'
    import HomePage from './pages'
    
    export default class Root extends React.Component {
      public render(): JSX.Element {
        return (
          <>
            <Header />
            <BrowserRouter>
              <Switch>
                <Route exact path="/" component={HomePage} />
              </Switch>
            </BrowserRouter>
          </>
        )
      }
    }
    
    assets/js/components/Header.tsx
    import * as React from 'react'
    
    const Header: React.FC = () => (
      <header>
        <section className="container">
          <nav role="navigation">
            <ul>
              <li>
                <a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a>
              </li>
            </ul>
          </nav>
          <a href="http://phoenixframework.org/" className="phx-logo">
            <img src="/images/phoenix.png" alt="Phoenix Framework Logo" />
          </a>
        </section>
      </header>
    )
    
    export default Header
    
    assets/js/components/Main.tsx
    import * as React from 'react'
    
    const Main: React.FC = ({ children }) => (
      <main role="main" className="container">
        {children}
      </main>
    )
    
    export default Main
    
    assets/js/pages/index.tsx
    import * as React from 'react'
    import { RouteComponentProps } from 'react-router-dom'
    import Main from '../components/Main'
    
    const HomePage: React.FC<RouteComponentProps> = () => <Main>HomePage</Main>
    
    export default HomePage
    
    そうするべきだ.
    さて、プロジェクトのrouter.ex フォルダー、および"/" スコープは次のとおりです.
    -    get "/", PageController, :index
    +    get "/*path", PageController, :index
    
    それから、我々の反応コードを適切にロードするように、我々のテンプレートファイルを修正してください.ベースのレイアウトテンプレートでは、我々はすべての内部<body> スクリプトでタグを付けます.templates/layout/app.html.eex
      <body>
        <%= render @view_module, @view_template, assigns %>
        <script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
      </body>
    
    インデックスページテンプレート.必ず設定してくださいid 指定したアプリケーションエントリポイントとして設定したものに属性を指定しますapp.tsx .templates/page/index.html.eex
    <div id="react-app"></div>
    

    健全性検査


    今、我々はすべてが動作するかどうかチェックするつもりです.ランmix deps.get and npm install もう一度確認して、もう一度実行するmix ecto.setup データベースを構築するには(設定されていれば).その後、実行mix phx.server , WebPackのプロセスが完了するまで待ち、次にlocalhost:4000 .
    それが動作し、あなたのウェブページを読み込むことができます、おめでとう、おめでとう!空想的な部分に移りましょう.

    追加のページを作成する


    今私たちの基本的なフェニックスサーバーを実行している、いくつかの例を作成しましょう.人々が反応の機能を示すときに最も一般的な例は、カウンタアプリです.
    まず、カウンタールートを我々に加えますRoot.tsx ファイル.
     import * as React from 'react'
     import { BrowserRouter, Route, Switch } from 'react-router-dom'
    
     import Header from './components/Header'
     import HomePage from './pages'
    +import CounterPage from './pages/counter'
    
     export default class Root extends React.Component {
       public render(): JSX.Element {
         return (
           <>
             <Header />
             <BrowserRouter>
               <Switch>
                 <Route exact path="/" component={HomePage} />
    +            <Route path="/counter" component={CounterPage} />
               </Switch>
             </BrowserRouter>
           </>
         )
       }
     }
    
    それから、我々は加えますCounter コンポーネント.assets/js/pages/counter.tsx
    import * as React from 'react'
    import { Link } from 'react-router-dom'
    
    import Main from '../components/Main'
    
    // Interface for the Counter component state
    interface CounterState {
      currentCount: number
    }
    
    const initialState = { currentCount: 0 }
    
    export default class CounterPage extends React.Component<{}, CounterState> {
      constructor(props: {}) {
        super(props)
    
        // Set the initial state of the component in a constructor.
        this.state = initialState
      }
    
      public render(): JSX.Element {
        return (
          <Main>
            <h1>Counter</h1>
            <p>The Counter is the simplest example of what you can do with a React component.</p>
            <p>
              Current count: <strong>{this.state.currentCount}</strong>
            </p>
            {/* We apply an onClick event to these buttons to their corresponding functions */}
            <button className="button" onClick={this.incrementCounter}>
              Increment counter
            </button>{' '}
            <button className="button button-outline" onClick={this.decrementCounter}>
              Decrement counter
            </button>{' '}
            <button className="button button-clear" onClick={this.resetCounter}>
              Reset counter
            </button>
            <br />
            <br />
            <p>
              <Link to="/">Back to home</Link>
            </p>
          </Main>
        )
      }
    
      private incrementCounter = () => {
        this.setState({
          currentCount: this.state.currentCount + 1
        })
      }
    
      private decrementCounter = () => {
        this.setState({
          currentCount: this.state.currentCount - 1
        })
      }
    
      private resetCounter = () => {
        this.setState({
          currentCount: 0
        })
      }
    }
    
    今すぐ行くlocalhost:4000/counter そして、あなたの創造をテストします.それが働くならば、我々は次の部分を続けることができます.

    APIを取得する


    前に述べたように、ほとんどすべての反応+フェニックスのチュートリアルでは、インターネット上で私はこれまでのところ、単一の反応コンポーネントをレンダリングとしてのみ行った.彼らはお互いに反応することができるように適切に両方の反応とフェニックスを作る方法を説明するように見えることはありません.うまくいけば、これはすべてを説明するでしょう.
    開始する前に、必ず確認してくださいrouter.ex , あなたは"/api" スコープは/*path 宣言.まじめに.私はAPIのルートが動作していない理由を把握する1週間を費やし、その後、私はルーティングの宣言を持っていたことを最近実現した.router.ex
      # ...
    
      scope "/api", PhoenixReactPlaygroundWeb do
        pipe_through :api
    
        # ...your API endpoints
      end
    
      # ...
    
      scope "/", PhoenixReactPlaygroundWeb do
        pipe_through :browser # Use the default browser stack
    
        # This route declaration MUST be below everything else! Else, it will
        # override the rest of the routes, even the `/api` routes we've set above.
        get "/*path", PageController, :index
      end
    
    すべてを設定すると、サンプルデータの新しいコンテキストを作成します.
    $ mix phx.gen.json Example Language languages name:string proverb:string
    
    router.ex
        scope "/api", PhoenixReactPlaygroundWeb do
          pipe_through :api
    
    +     resources "/languages", LanguageController, except: [:new, :edit]
        end
    
    事前にデータを事前に読み込むには、データベースシードを作成することもできます.これを行う方法の詳細についてはElixir Casts course .
    別の健全性チェックのための時間!フェニックスサーバを走らせてくださいlocalhost:4000/api/languages . すべてが正しく動作している場合は、空の、または格納されているJSONのどちらかを見る必要があります.

    すべてがうまくいくならば、我々は現在我々の構成要素に進むことができます.Root.tsx
     import * as React from 'react'
     import { BrowserRouter, Route, Switch } from 'react-router-dom'
    
     import Header from './components/Header'
     import HomePage from './pages'
     import CounterPage from './pages/counter'
    +import FetchDataPage from './pages/fetch-data'
    
     export default class Root extends React.Component {
       public render(): JSX.Element {
         return (
           <>
             <Header />
             <BrowserRouter>
               <Switch>
                 <Route exact path="/" component={HomePage} />
                 <Route path="/counter" component={CounterPage} />
    +            <Route path="/fetch-data" component={FetchDataPage} />
               </Switch>
             </BrowserRouter>
           </>
         )
       }
     }
    
    pages/fetch-data.tsx
    import * as React from 'react';
    import { Link } from 'react-router-dom';
    
    import Main from '../components/Main';
    
    // The interface for our API response
    interface ApiResponse {
      data: Language[];
    }
    
    // The interface for our Language model.
    interface Language {
      id: number;
      name: string;
      proverb: string;
    }
    
    interface FetchDataExampleState {
      languages: Language[];
      loading: boolean;
    }
    
    export default class FetchDataPage extends React.Component<
      {},
      FetchDataExampleState
    > {
      constructor(props: {}) {
        super(props);
        this.state = { languages: [], loading: true };
    
        // Get the data from our API.
        fetch('/api/languages')
          .then(response => response.json() as Promise<ApiResponse>)
          .then(data => {
            this.setState({ languages: data.data, loading: false });
          });
      }
    
      private static renderLanguagesTable(languages: Language[]) {
        return (
          <table>
            <thead>
              <tr>
                <th>Language</th>
                <th>Example proverb</th>
              </tr>
            </thead>
            <tbody>
              {languages.map(language => (
                <tr key={language.id}>
                  <td>{language.name}</td>
                  <td>{language.proverb}</td>
                </tr>
              ))}
            </tbody>
          </table>
        );
      }
    
      public render(): JSX.Element {
        const content = this.state.loading ? (
          <p>
            <em>Loading...</em>
          </p>
        ) : (
          FetchData.renderLanguagesTable(this.state.languages)
        );
    
        return (
          <Main>
            <h1>Fetch Data</h1>
            <p>
              This component demonstrates fetching data from the Phoenix API
              endpoint.
            </p>
            {content}
            <br />
            <br />
            <p>
              <Link to="/">Back to home</Link>
            </p>
          </Main>
        );
      }
    }
    
    すべて良い!今すぐ行くlocalhost:4000/fetch-data 試してみてください.

    結果


    あなたがまだここにいるならば、おめでとう、あなたのセットアップは完了です!ランmix phx.server 再び、すべてを通過します.すべてがうまくいけば、ダブルおめでとう!
    これで、次の反応+フェニックスのアプリケーションを構築するには、この知識を使用することができます.このガイドの最終結果は入手可能ですhere 誰もが試してみる.
    グッドラック!ご質問がございましたらお気軽に.
    ありがとう~selsky このポストを校正する彼らの助けのために!