Client Routing (menggunakan react-router) dan Server-Side Routing

108

Saya telah berpikir dan saya bingung dengan routing antara Client dan Server. Misalkan saya menggunakan ReactJS untuk rendering sisi server sebelum mengirim permintaan kembali ke browser web, dan menggunakan react-router sebagai perutean sisi klien untuk beralih antar halaman tanpa menyegarkan sebagai SPA.

Yang terlintas dalam pikiran adalah:

  • Bagaimana rute diinterpretasikan? Misalnya, permintaan dari halaman Beranda ( /home) ke halaman Posting ( /posts)
  • Ke mana perginya perutean, di sisi server atau klien?
  • Bagaimana cara mengetahui cara memprosesnya?
heartmon
sumber
1
Saya sarankan membaca History API di browser.
WiredPrairie

Jawaban:

137

Catatan, jawaban ini mencakup React Router versi 0.13.x - versi 1.0 yang akan datang sepertinya akan memiliki detail implementasi yang sangat berbeda

Server

Ini minimal server.jsdengan react-router:

var express = require('express')
var React = require('react')
var Router = require('react-router')

var routes = require('./routes')

var app = express()

// ...express config...

app.use(function(req, res, next) {
  var router = Router.create({location: req.url, routes: routes})
  router.run(function(Handler, state) {
    var html = React.renderToString(<Handler/>)
    return res.render('react_page', {html: html})
  })
})

Di mana routesmodul mengekspor daftar Rute:

var React = require('react')
var {DefaultRoute, NotFoundRoute, Route} = require('react-router')

module.exports = [
  <Route path="/" handler={require('./components/App')}>
    {/* ... */}
  </Route>
]

Setiap kali permintaan dibuat ke server, Anda membuat Routerinstance sekali pakai yang dikonfigurasi dengan URL masuk sebagai lokasi statisnya, yang diselesaikan terhadap pohon rute untuk menyiapkan rute yang cocok dan sesuai, memanggil kembali dengan level teratas penangan rute yang akan dirender dan catatan tentang rute turunan yang cocok di setiap tingkat. Inilah yang dikonsultasikan saat Anda menggunakan <RouteHandler>komponen dalam komponen penanganan rute untuk merender rute anak yang cocok.

Jika pengguna menonaktifkan JavaScript, atau lambat dalam memuat, tautan apa pun yang mereka klik akan masuk ke server lagi, yang diselesaikan lagi seperti di atas.

Klien

Ini minimal client.jsdengan react-router (menggunakan kembali modul routes yang sama):

var React = require('react')
var Router = require('react-router')

var routes = require('./routes')

Router.run(routes, Router.HistoryLocation, function(Handler, state) {
  React.render(<Handler/>, document.body)
})

Saat Anda memanggil Router.run(), itu membuat instance Router untuk Anda di belakang layar, yang digunakan kembali setiap kali Anda menavigasi aplikasi, karena URL dapat menjadi dinamis pada klien, bukan di server di mana satu permintaan memiliki URL tetap.

Dalam hal ini, kami menggunakan HistoryLocation, yang menggunakan HistoryAPI untuk memastikan hal yang benar terjadi saat Anda menekan tombol kembali / maju. Ada juga HashLocationyang mengubah URL hashuntuk membuat entri sejarah dan mendengarkan window.onhashchangeacara untuk memicu navigasi.

Bila Anda menggunakan bereaksi-router <Link>komponen, Anda memberikan toprop yang merupakan nama dari rute, ditambah paramsdan querydata kebutuhan rute. The <a>diberikan oleh komponen ini memiliki onClickhandler yang akhirnya panggilan router.transitionTo()pada contoh router dengan alat peraga yang Anda berikan link, yang terlihat seperti ini:

  /**
   * Transitions to the URL specified in the arguments by pushing
   * a new URL onto the history stack.
   */
  transitionTo: function (to, params, query) {
    var path = this.makePath(to, params, query);

    if (pendingTransition) {
      // Replace so pending location does not stay in history.
      location.replace(path);
    } else {
      location.push(path);
    }
  },

Untuk tautan biasa, ini pada akhirnya memanggil location.push()jenis Lokasi mana pun yang Anda gunakan, yang menangani detail pengaturan riwayat sehingga menavigasi dengan tombol kembali dan maju akan berfungsi, kemudian memanggil kembali ke router.handleLocationChange()untuk memberi tahu router bahwa ia dapat melanjutkan dengan transisi ke jalur URL baru.

Router kemudian memanggil router.dispatch()metodenya sendiri dengan URL baru, yang menangani detail penentuan rute yang dikonfigurasi mana yang cocok dengan URL, kemudian memanggil setiap hook transisi yang ada untuk rute yang cocok. Anda dapat menerapkan hook transisi ini pada salah satu penangan rute Anda untuk mengambil tindakan saat rute akan dinavigasi menjauh atau dinavigasi, dengan kemampuan untuk membatalkan transisi jika hal-hal tidak sesuai dengan keinginan Anda.

Jika transisi tidak dibatalkan, langkah terakhir adalah memanggil callback yang Anda berikan Router.run()dengan komponen pengendali level teratas dan objek status dengan semua detail URL dan rute yang cocok. Komponen pengendali tingkat atas sebenarnya adalah Routerinstance itu sendiri, yang menangani rendering pengendali rute paling atas yang cocok.

Proses di atas dijalankan ulang setiap kali Anda menavigasi ke URL baru di klien.

Contoh proyek

Jonny Buchanan
sumber
3
Jadi saya mungkin bisa mengatakan bahwa perutean klien ditangani oleh javascript (yang merupakan kode react-router) jika ada. Setiap kali saya menekan enter pada bilah alamat browser atau menyegarkan halaman atau menonaktifkan JS, sisi server akan menangani perutean. Sebaliknya, jika javascript sudah siap di halaman saat ini, perutean akan ditangani oleh sisi klien. Apakah saya mengerti dengan benar?
heartmon
9
Apa yang ada di modul routes var routes = require('./routes')Apakah ini daftar rute? Saya telah menggunakan router Express tetapi contoh ini di SO tampaknya menjadi satu-satunya contoh pengaturan rendering sisi server dengan React Router, jadi akan lebih baik jika itu adalah contoh kode lengkap
svnm
2
Ini harus menjadi daftar rute. Saya akan menambahkan catatan tentang itu dan beberapa tautan ke proyek contoh.
Jonny Buchanan
2
Jadi jika react-router menangani perutean sisi server, lalu siapa yang berbicara ke database? apa yang terjadi pada perutean sisi server? bayangkan kami ingin menyediakan REST API untuk aplikasi seluler asli. Siapa yang mengurus itu?
Morteza Shahriari Nia
1
Jawaban sudah usang karena react-routerversi yang lebih baru . Harap perbarui.
oleh.meleshko
26

Dengan 1.0, React-Router bergantung pada modul history sebagai peerDependency. Modul ini berhubungan dengan perutean di browser. Secara default, React-Router menggunakan API Riwayat HTML5 ( pushState, replaceState), tetapi Anda dapat mengkonfigurasinya untuk menggunakan perutean berbasis hash (lihat di bawah)

Penanganan rute sekarang dilakukan di belakang layar, dan ReactRouter mengirim props baru ke penangan Route saat rute berubah. Router memiliki onUpdatecallback prop baru setiap kali rute berubah, berguna untuk pelacakan tampilan halaman, atau memperbarui <title>, misalnya.

Klien (perutean HTML5)

import {Router} from 'react-router'
import routes from './routes'

var el = document.getElementById('root')

function track(){
  // ...
}

// routes can be children
render(<Router onUpdate={track}>{routes}</Router>, el)

Klien (perutean berbasis hash)

import {Router} from 'react-router'
import {createHashHistory} from 'history'
import routes from './routes'

var el = document.getElementById('root')

var history = createHashHistory()

// or routes can be a prop
render(<Router routes={routes} history={history}></Router>, el)

Server

Di server, kita bisa menggunakan ReactRouter.match, ini diambil dari panduan rendering server

import { renderToString } from 'react-dom/server'
import { match, RoutingContext } from 'react-router'
import routes from './routes'

app.get('*', function(req, res) {
  // Note that req.url here should be the full URL path from
  // the original request, including the query string.
  match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
    if (error) {
      res.status(500).send(error.message)
    } else if (redirectLocation) {
      res.redirect(302, redirectLocation.pathname + redirectLocation.search)
    } else if (renderProps) {
      res.status(200).send(renderToString(<RoutingContext {...renderProps} />))
    } else {
      res.status(404).send('Not found')
    }
  })
})
tom
sumber