
By: justine warrington – CC BY 2.0
React on rails
React on railsは、RailsからReact.jsを使いやすいようにしたgem
webpackerでいいとも思えるけど、サーバーサイドレンダリング(SSR)が簡単にできたりする.
インストール
webpackerのインストール
Gemfileに、 gem 'webpacker' を加えてbundle.
1 2 | bundle exec rails webpacker:install bundle exec rails webpacker:install:react |
react_on_railsのインストール
Gemfileに、 gem 'react_on_rails' を加えてbundle.
gitのステージをクリーンにしておく(git commitとか).
reduxバージョンもインストールできる.
1 2 3 4 5 | bundle exec rails g react_on_rails:install or bundle exec rails g react_on_rails:install --redux |
react on railsで生成されるファイル
Procfile.dev
Procfileが生成されるので、foremanなどでまとめてサーバー起動できる.
1 | foreman start -f Procfile.dev |
routes.rb
まず、routes.rbに/hello_worldが加えられる.
1 | get 'hello_world', to: 'hello_world#index' |
hello_world_controller.rb
hello_worldコントローラーをみると、indexアクションが定義されてる.
1 2 3 4 5 6 7 | class HelloWorldController < ApplicationController layout "hello_world" def index @hello_world_props = { name: "Stranger" } end end |
layouts/hello_world.html.erb
layoutの方をみると、webpackのjavascriptが読み込まれてる.
1 2 3 4 5 6 7 8 9 10 11 12 | <!DOCTYPE html> <html> <head> <title>ReactOnRailsWithWebpacker</title> <%= csrf_meta_tags %> <%= javascript_pack_tag 'hello-world-bundle' %> </head> <body> <%= yield %> </body> </html> |
index.html.erb
つぎに、index.html.erbの方をみると、react_componentメソッドが使われている.
サーバーサイドレンダリングは、ここでprerender: trueにすればよい.
propsで、react.jsのpropsに値を渡すことができる.
1 2 | <h1>Hello World</h1> <%= react_component("HelloWorld", props: @hello_world_props, prerender: false) %> |
app/javascripts
javascripts以下はこんな感じ.
1 2 3 4 5 6 7 8 9 | app/javascript/ ├── bundles │ └── HelloWorld │ └── components │ └── HelloWorld.jsx └── packs ├── application.js ├── hello-world-bundle.js └── hello_react.jsx |
hello-world-bundle.js
react_on_railsで設定したエントリポイントはhello-world-bundle.js
そして、ここで、ReactOnRails.registerしてるので、ビューヘルパーのreact_componentメソッドで”HelloWorld”を見つけられる.
1 2 3 4 5 6 7 8 | import ReactOnRails from 'react-on-rails'; import HelloWorld from '../bundles/HelloWorld/components/HelloWorld'; // This is how react_on_rails can see the HelloWorld in the browser. ReactOnRails.register({ HelloWorld, }); |
HelloWorld.jsx
Reactコンポーネントの実体はこちら
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | import PropTypes from 'prop-types'; import React from 'react'; export default class HelloWorld extends React.Component { static propTypes = { name: PropTypes.string.isRequired, // this is passed from the Rails view }; /** * @param props - Comes from your rails view. */ constructor(props) { super(props); // How to set initial state in ES6 class syntax // https://reactjs.org/docs/state-and-lifecycle.html#adding-local-state-to-a-class this.state = { name: this.props.name }; } updateName = (name) => { this.setState({ name }); }; render() { return ( <div> <h3> Hello, {this.state.name}! </h3> <hr /> <form > <label htmlFor="name"> Say hello to: </label> <input id="name" type="text" value={this.state.name} onChange={(e) => this.updateName(e.target.value)} /> </form> </div> ); } } |
material-uiをつかう
material-uiは、マテリアルデザインを実装したReactコンポーネント.
バージョン1以降は、次のようにインストールできる.
1 2 | yarn add @material-ui/core yarn add @material-ui/icons |
こんな感じでReactコンポーネントが使える.
AppBar
bodyのマージンを0にしておく.
1 2 3 | <body style="margin: 0"> <%= yield %> </body> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import React, { Component } from 'react' import AppBar from '@material-ui/core/AppBar' import Toolbar from '@material-ui/core/Toolbar' import Typography from '@material-ui/core/Typography' const Navbar = ()=> { return ( <div> <AppBar position="static"> <Toolbar> <Typography variant="title" color="inherit"> React & Material-UI Sample Application </Typography> </Toolbar> </AppBar> </div> ) } export default Navbar |
class化して、メニュー用のハンバーガーアイコンを追加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class Navbar extends Component { render(){ return ( <div> <AppBar position="static"> <Toolbar> <IconButton color="inherit" aria-label="Menu" > <MenuIcon /> </IconButton> <Typography variant="title" color="inherit"> React & Material-UI Sample Application </Typography> </Toolbar> </AppBar> </div> ) } } |
Fab
Fabは右下に置きたい.
withStylesでコンポーネントにスタイルを適用できる.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import React, { Component } from 'react' import Button from '@material-ui/core/Button' import AddIcon from '@material-ui/icons/Add' import { withStyles } from '@material-ui/core/styles' const styles = theme => ({ fab: { position: 'absolute', bottom: theme.spacing.unit * 2, right: theme.spacing.unit * 2 } }) const FloatingActionButton = (props)=> { const {classes} = props return ( <Button variant="fab" color="secondary" className={classes.fab}> <AddIcon /> </Button> ) } // export default FloatingActionButton export default withStyles(styles)(FloatingActionButton) |
Dialog
FabからDialogを起動させてみる.
自分で作ったコンポーネントにonClickを設定しても反応しない.
そこで、handleClickOpen関数をpropsで子にわたす.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | import React from 'react'; import Dialog from '@material-ui/core/Dialog'; import DialogTitle from '@material-ui/core/DialogTitle'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import IconButton from '@material-ui/core/IconButton'; import Typography from '@material-ui/core/Typography'; import CloseIcon from '@material-ui/icons/Close'; import Button from '@material-ui/core/Button'; import Slide from '@material-ui/core/Slide'; import FloatingActionButton from './FloatingActionButton' function Transition(props) { return <Slide direction="up" {...props} />; } class FullScreenDialog extends React.Component { state = { open: false } handleClickOpen = () => { this.setState({open: true}) } handleClose = () => { this.setState({open: false}) } render() { return( <div> <FloatingActionButton handleClickOpen={this.handleClickOpen} /> <Dialog fullScreen open={this.state.open} onClose={this.handleClose} TransitionComponent={Transition} > <AppBar> <Toolbar> <IconButton color="inherit" onClick={this.handleClose} aria-label="Close" > <CloseIcon /> </IconButton> <Typography variant="title" color="inherit"> Sound </Typography> <Button color="inherit" onClick={this.handleClose}> save </Button> </Toolbar> </AppBar> aiueo </Dialog> </div> ) } } export default FullScreenDialog |
で、Fabの方にonClickメソッドを設定する.
1 2 3 4 5 6 7 8 9 | const {classes} = this.props return ( <Button variant="fab" color="secondary" className={classes.fab} onClick={this.props.handleClickOpen} > <AddIcon /> </Button> ) |
Drawer
ハンバーガーメニューをクリックすると、ドロワーメニューが出てくるやつ.
HeaderというコンポーネントでNavbarとDrawerをくくって状態管理.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import React, { Component } from "react"; import Navbar from "./NavBar"; import MenuDrawer from "./MenuDrawer" class Header extends Component { state = { open: false } handleMenuDrawer = () => { this.setState({open: !this.state.open}) } render() { return( <div> <MenuDrawer open={this.state.open} handleMenuDrawer={this.handleMenuDrawer} /> <Navbar handleMenuDrawer={this.handleMenuDrawer} /> </div> ) } } export default Header |
で、ドロワーのコンポーネントをつくる.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import React, { Component } from 'react' import Drawer from '@material-ui/core/Drawer' class MenuDrawer extends Component { render() { return ( <div> <Drawer open={this.props.open} onClose={this.props.handleMenuDrawer}> <div tabIndex={0} role="button" onClick={this.props.handleMenuDrawer} onKeyDown={this.props.handleMenuDrawer} > aiueo </div> </Drawer> </div> ) } } export default MenuDrawer |
最後に、NavBarの方にクリックイベントを設定する
6 | onClick={this.props.handleMenuDrawer} |
React Developer Tools
Reactのデバッグ用のChromeエクステンション.
情報
安定のドットインストール.
英語だけど、おすすめ.一番わかりやすかった.
reduxとredux-reactを分けて1から説明してくれるのでわかりやすい.