R³: Rails, React, Redux

Son zamanlarda sürekli olarak bu stack üzerinde çalışıyorum. Ve şunu söylemeliyim ki üzerinde çalıştığım en stabil stack’lerden birisi. Bu stack’in her maddesine tek tek değinmek de istiyorum aslında.

Rails

Uzun zamandır çalıştığım web frameworkler arasında benim için en anlaşılır ve hızlı geliştirmeye açık olan framework bu oldu. Bir çok şeyi çok hızlı yapmanıza izin vermesi, scaffolding’in çok iyi olması Rails’i benim için bir adım öteye taşıyor. Özellikle sprockets ile sağladığı asset pipeline diğer bir çok asset pipelining araçları arasında karşılaştığım en iyilerden. Asset pipeline işini uzun süreler Grunt / Gulp üzerinden task’ler ile yapmış biri olarak, Sprockets bu işi tamamen tam olarak benim istediğim gibi, hatta çok daha ötesinde hallediyor diyebilirim.

React

JavaScript tarafında koyu bir Vanilla’cı olarak React’ı her ne kadar her proje için uygun görmesem de uygun olabilecek projeler için React gerçekten çok iyi bir araç. Bu stack’te de React’ın bir Rails projesine çok hızlı bir şekilde entegrasyonundan bahsedeceğiz.

Redux

Redux’un basitliği ve özellikle Redux DevTools ile birlikte kullanıldığında sağladığı rahatlık, state yönetimini mümkün olabilecek en kolay hale getirmesi kesinlikle yadsınamaz. Bu stack’te React’ı Redux ile birlikte kullanarak state yönetimimizi rahatlatacağız.


Artık kolları sıvayıp işe koyulma vakti geldi. Bu süreçte kullandığım araçların versiyonları şu şekilde:

  • Ruby 2.2.2
  • Rails 4.2.5

Diğer araçların versiyonlarına da o kısımları anlatırken değineceğim.

rails new r3

dedikten sonra r3 adında bir klasörümüz oluşacak. Gemfile’a react-rails gem’ini ekledikten sonra bundle install diyerek react-rails’in yüklenmesini sağlıyoruz.

cd r3
echo "gem 'react-rails'" >> Gemfile
bundle install

Daha sonra react-rails gem’inin sağladığı react generator’u ile react’ı entegre ediyoruz.

rails generate react:install

Bu, app/assets/javacripts klasörü altına components klasörü oluşturacaktır.

Aslında artık React ve Rails stack’ımız oluştu. Fakat bu stack’e Redux’ı da almak istiyoruz. Bu yüzden biraz NPM dünyasına karışmak gerekecek. Bunun için de browserify-rails paketini kullanıyoruz.

echo "gem 'browserify-rails'" >> Gemfile
bundle install
npm init --yes
npm install browserify browserify-incremental --save

Artık browserify’ı da Rails projemize eklemiş olduk. Sprockets ile birlikte rahatça CommonJS kullanabiliriz. Fakat burada Sprockets’in işini pek elinden almayıp Browserify’ı, NPM paketlerini rahatça kullanabilmek için bir aracı olarak kullanıyoruz. Sprockets, browserify’dan farklı olarak daha çok “concatination” temelli bir modül sistemine sahip.

app/assets/javascripts/vendors.js dosyası oluşturarak NPM’den almak istediğimiz paketleri buraya ekliyoruz.

window.jQuery = require("jquery");
window.React = require("react");
window.ReactDOM = require("react-dom");
window.Redux = require("redux");
window.ReactRedux = require("react-redux");

Burada jQuery, React, ReactDOM, Redux ve ReactRedux’u direkt olarak global scope’a ekliyoruz. Node’a alışmış olanlar “abi ne yaptın hepsini direkt window’a ekledin” diyebilir. Fakat bunların tarayıcı versiyonları zaten tam olarak bu şekilde çalışıyor. Ayrıca biz isomorphic bir yapı kurmadığımız için, bunların Node tarafını düşünmemiz gerekmiyor.

Bu vendors.js dosyası bizim NPM’den içeriye almak istediğimiz paketlerin global olarak belirlendiği yer olarak kalacak.

Bu yüzden hemen aşağıdaki komutu çalıştırıp NPM’den gerekli paketleri yüklüyoruz.

npm install jquery react react-dom redux react-redux --save

Rails’in oluşturduğu application.js içerisini de biraz toparlıyoruz ve sprockets’in hangi dosyaları alacağını belirtiyoruz:

//= require ./vendors
//= require jquery_ujs
//= require react_ujs
//= require ./actions
//= require ./reducers
//= require ./store
//= require_tree ./components/
//= require ./base/application
//= require ./base/root

Gördüğünüz gibi en başa ./vendors dosyasını ekledik. Daha sonra _ujs ile biten Rails’in ve react-rails’in gerektirdiği dosyaları ekledik.

Sonrasında ise henüz oluşturmadığımız 3 farklı dosyayı çağırdık. Bu dosyalar Redux için anlam ifade edecek. require_tree ile components klasörü altındaki tüm component’leri aldık. Ve yine henüz var olmayan base/application ve base/root dosyalarını çağırdık.

Şimdi bu dosyalarımızı oluşturalım ve ne işe yaradıklarına bakalım.

Bu dosyaların tamamını .jsx uzantısı ile kaydediyorum. Böylece ES6'nın bazı ufak faydalarını kazanmış olacağız.

Aksiyonlar (javascripts/actions.jsx)

Bu dosyada aksiyonlarımız olacak. Redux’ı anlatmayacağım için bildiğinizi varsayarak uzatmayacağım.

Aksiyonlar temelde bir nesne dönen küçük fonksiyonlar. Bu nesnelerin de iki key’i mevcut: type ve data.

function nextScreen() {
return {
type: "changeScreen",
data: 1
}
}
function prevScreen() {
return {
type: "changeScreen",
data: -1
}
}

Burada iki basit aksiyon oluşturduk. Bunları Redux component’leri altından çağıracağız.

Reducer’lar (javascripts/reducers.jsx)

Redux’ın temeli olan reducer’lar bu dosyada saklanıyor. Reducer’ların işi, gelen aksiyonları alarak global state’i farklılaştırarak sunmak. Böylece state bu reducer’lardan geçiyor ve farklı bir hale geliyor.

function activeScreen(state = 1, action) {
switch (action.type) {
case "changeScreen":
if (state == 1 && action.data < 0) {
return state
}
state += action.data
break
case "goToScreen":
state = action.data
break
}
return state
}
const REDUCERS = Redux.combineReducers({
activeScreen
})

Store (javascripts/store.jsx)

Store, tüm state’imizin durduğu yer. Burada çok karmaşık bir işlem yok. Genel olarak middleware’lar burada yönetiliyor. Burada Redux DevTools’u store’umuza bağlıyoruz, böylece state’imizi çok rahat şekilde Chrome DevTools ile beraber debug edebilir hale geliyoruz.

function ReduxDevToolsMiddleware() {
var ext = "devToolsExtension"
return (window[ext] ? window[ext]() : f => f);
}
var createStore = Redux.compose(
ReduxDevToolsMiddleware()
)(Redux.createStore);
const STORE = createStore(REDUCERS)

Redux Provider Component (javascripts/base/root.jsx)

Redux’ı React’e bağlamamız için gerekli olan Provider’ın kullanıldığı Root component’imizi oluşturalım.

class Root extends React.Component {
render() {
let {Provider} = ReactRedux
return <Provider store={STORE}><Application /></Provider>
}
}

Base Application Component (javascript/base/application.jsx)

Bu dosyamız ise artık uygulamamızı yazacağımız ana component. Tüm uygulamamız burada yürüyecek. Diğer parçaladığınız componentler ise javascripts/components altına istediğiniz gibi yerleştirebilirsiniz. sprockets require_tree ile buradaki tüm dosyaları alacak.

class Application extends React.Component {
render () {
let {dispatch} = this.props
return <div>
<button onClick={() => dispatch(nextScreen())}>Next</button>
<button onClick={() => dispatch(prevScreen())}>Prev</button>
</div>
}
}
Application = ReactRedux.connect(state => state)(Application);

Tüm setup tamamlandığına göre artık uygulamamızı çalıştırabiliriz. Ama öncelikle Rails’te ana controller’ı oluşturup routes.rb’den root view’ı ana controller’a ayarlayalım ve bir view oluşturalım:

rails generate controller index

komutunu çalıştırdıktan sonra routes.rb dosyasına root “index#index” tanımını ekliyoruz.

Rails.application.routes.draw do
root "index#index"
#...
end

Yeni controller’ımıza index_controller.rb dosyasına boş bir index action’u ekliyoruz:

class IndexController < ApplicationController
def index
end
end

Son olarak index action’u için bir view oluşturalım:

app/views/index/index.html.erb dosyasını oluşturup, altına:

<%= react_component "Root" %>

kodunu yazıyoruz. react_component react-rails gem’i tarafından sağlanan güzel bir view helper.

Artık sunucumuz çalışmaya hazır.

rails server

dedikten sonra, http://localhost:3000 adresine giderek çalışan sunucunuzu kontrol edebilirsiniz.

Bu koda github/f/r3 adresinden ulaşabilirsiniz.