Live Demo---GitHub Repojavascript
我是一個Ruby程序員,最近開始學習Elixir。我驚歎於Elixir和Phoenix展示的技術魅力,並很快喜歡上了這個新東西。就像Rails之於Ruby同樣,Phoenix使得Elixir變得流行起來,緣由就在於Elixir使得開發人員可以高效的編寫出性能優秀,穩定性好的應用程序,而且很容易使用這些應用處理實時數據。html
寫這篇博文時,我只有大約一週的Phoenix使用經驗。我寫這篇博文的目的就是趨勢本身從不一樣的角度思考正在解決的問題,以期得到對這門語言和框架更深刻的理解。若是你在代碼中發現任何錯誤或有改進的建議,歡迎給我來信或者提交pull request!前端
若是你和我同樣也是個Elixir新手,我推薦你閱讀 Programming Elixir 和 Programming Phoenix ,這兩本書全面的闡述了Elixir和Phoenix的基本要點。java
爲了向我喜好的聊天應用Slack致敬,我將打造一個高仿版的Slack,我叫它Sling。爲了讓這篇博文簡潔、完整、可讀,我只實現Slack的部分功能,可是要實現的這部分功能足夠咱們習得Phoenix的基本原理。react
Slack有team的概念,而且每一個team有若干個channel。team成員可以加入到channel,channel就是聊天的地方。簡化起見,咱們就不建立team這個功能了,取而代之的是咱們將建立room,每一個註冊用戶都能加入到room中,而且在room中聊天。git
咱們會使用牛x的Phoenix Presence Module展現當前room中的在線用戶。程序員
我將盡量詳盡的展現出我是如何構建這個應用的,爲此我會保持小增量的git commit。而且在每次提交後留下git diff的連接。github
查看當前代碼web
服務器端sql
前端
因爲個人技術經驗是使用Ruby構建web應用,因此熟悉Rails的讀者對於我寫的東西會更易於理解。我假定你熟悉JavaScript和ES6。因爲這不是React 教程,因此我會盡可能解釋React組件相關的邏輯,可是不會深究。
若是你尚未安裝Elixir或者Phoenix,請看這裏
對於咱們要構建的應用而言,真實項目通常會建立兩個獨立的代碼倉庫,一個用於放置Phoenix API,另外一個用於放置React App。可是爲了使咱們的博文清楚明瞭,我將代碼放置在同一個倉庫中。目錄結構以下:
sling/ |--- api/ (phoenix app) |--- web/ (react app)
開始吧!少年。
建立一個新的文件夾做爲咱們的代碼倉庫
mkdir sling cd sling
生成新的Phoenix應用,咱們使用Phoenix直接做爲JSON API。因此不須要默認安裝的asset manager, 使用參數--no-brunch
;也不須要html模板和瀏覽器端的路由,使用參數--no-html
mix phoenix.new sling --no-html --no-brunch mv sling api
使用 create-react-app初始化React App,這是一個強大的工具,零配置搭建咱們的前端應用。
安裝 create-react-app
命令行工具
npm i -g create-react-app
建立React App
create-react-app sling mv sling web
牛叉吧,咱們已經初始化好了後端的Phoenix API和前端的React App。
咱們第一個正式的提交 init commit
首先配置數據庫,開發環境下默認的數據庫配置位於該文件sling/api/config/dev.exs
, PostgreSQL默認用戶密碼均爲 postgres 。安全起見咱們新建一個文件dev.secret.exs
,用於存放私人的數據庫配置信息,覆蓋掉默認的數據庫鏈接配置。這樣一來也便於別人使用咱們的代碼。將dev.secret.exs
加入到.gitignore中(因爲新建的配置文件是私有信息因此沒必要提交),內容以下:
sling/api/config/dev.secret.exs
use Mix.Config config :sling, Sling.Repo, username: "your_postgres_user", password: "your_postgres_password"
在dev.exs
的末尾添加 import "dev.secret.exs"
,這樣咱們的私有配置才能生效。
sling/api/config/dev.exs
# contents above import_config "dev.secret.exs"
建立數據庫(當前所在路徑爲sling/api
)
mix ecto.create
數據庫建立完成後,啓動Phoenix App 。
mix phoenix.server
訪問 http://localhost:4000, 正常狀況會報錯,緣由在於咱們的Phoenix App 只用作API,沒有配置網頁瀏覽相關的路由。
Phoenix已經配置完成,接下來咱們配置 React App。
create-react-app已近初始化了一個可運行的app, npm start
, 訪問 http://localhost:3000,就能看到初始化的react app。咱們要配置本身的redux react-router, 因此刪除掉web/src
目錄下的全部文件。
另,咱們將使用最新的JavaScript依賴管理工具Yarn, 安裝指南在此.
在這個前端應用中有許多第三方庫咱們須要使用,一次性所有將其安裝(當前目錄是sling/web
)
yarn add aphrodite lodash md5 moment phoenix react-redux react-router@4.0.0-alpha.5 redux redux-form redux-thunk
你應該注意到咱們使用v4-alpha版本的react-router, 其相較於v2版本的react-router有不少重大的改變。借這個機會咱們一併學一學v4-alpha版react-router, 期待v4正式版儘快發佈,若有變化到時我會更新博文。
我將使用Airbnb's styleguide,其中用到了eslint和flow,因此接下來咱們安裝開發環境下用到的第三方庫。
yarn add babel-eslint eslint eslint-config-airbnb eslint-plugin-flowtype eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react --dev
Linting rules的配置徹底依賴於我的的喜愛,下面是我本身用的.eslintrc
文件。(react/no-unused-prop-types 規則被disable掉了,緣由是和flowtype有衝突)
sling/web/.eslintrc
{ "parser": "babel-eslint", "plugins": ["react", "flowtype"], "extends": ["airbnb", "plugin:flowtype/recommended"], "rules": { "react/jsx-filename-extension": 0, "import/prefer-default-export": 0, "react/no-unused-prop-types": 0, "camelcase": 0 }, "globals": { "fetch": true, "window": true, "document": true, "localStorage": true } }
React項目有各類各樣的目錄結構,固然都是基於應用場景權衡的結果。就咱們的項目而言,建立containers目錄用於存放和redux store 鏈接相關的組件。建立components目錄,存放其餘組件。建立actions和reducers目錄分別用於存放action和reducer相關的文件。建立store目錄存放redux store相關的配置文件。咱們着手開始吧。
建立app的入口文件 sling/web/src/index.js, 這個文件須要導入redux store配置文件(稍後建立), App容器組件,並掛載到 index.html 的<div id="root" />
節點下。
sling/web/src/index.js
import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import store from './store'; import App from './containers/App'; ReactDOM.render( <Provider store={store}> <App /> </Provider> , document.getElementById('root') );
建立上面提到的redux store配置文件,引入reducers文件(稍後建立),使用redux-thunk 處理異步操做和Promises。
sling/web/src/store/index.js
import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import reducers from '../reducers'; const middleWare = [thunk]; const createStoreWithMiddleware = applyMiddleware(...middleWare)(createStore); const store = createStoreWithMiddleware(reducers); export default store;
建立reducer根文件sling/web/src/reducers/index.js,這個文件用於彙總其餘的reducer文件,可是如今咱們只須要用redux-form使reducer 能正常工做便可。咱們不直接返回配置參數的combineReducers函數,相反,當logout時咱們強制返回帶undefined參數的appReducer,這樣就會強制初始化全部reducer的state(也就是強制清理登出用戶的redux state,不會讓其污染下一個login用戶的state)
sling/web/src/reducers/index.js
import { combineReducers } from 'redux'; import { reducer as form } from 'redux-form'; const appReducer = combineReducers({ form, }); export default function (state, action) { if (action.type === 'LOGOUT') { return appReducer(undefined, action); } return appReducer(state, action); }
如今咱們來建立App組件,在這個組件中咱們要到了v4版的react-router 來配置頁面路由。目前咱們只有兩個路由,一個是Home路由,另外一個是404頁面。
sling/web/src/containers/App/index.js
// @flow import React, { Component } from 'react'; import { BrowserRouter, Match, Miss } from 'react-router'; import Home from '../Home'; import NotFound from '../../components/NotFound'; class App extends Component { render() { return ( <BrowserRouter> <div> <Match exactly pattern="/" component={Home} /> <Miss component={NotFound} /> </div> </BrowserRouter> ); } } export default App;
目前咱們的Home頁面只是簡單的組件
sling/web/src/containers/Home/index.js
// @flow import React from 'react'; const Home = () => (<div>Home</div>); export default Home;
NotFound組件
sling/web/src/components/NotFound/index.js
// @flow import React from 'react'; import { Link } from 'react-router'; const NotFound = () => <div style={{ margin: '2rem auto', textAlign: 'center' }}> <p>Page not found</p> <p><Link to="/">Go to the home page →</Link></p> </div>; export default NotFound;
好,redux的基本配置已經完成。
到目前爲止,咱們的前端App和後端API還沒法通信,不過也好,本篇博文就此結束。下篇博文咱們將實現前端和後端的通信,而且添加用戶帳戶和用戶身份認證。