Client Routing (โดยใช้ react-router) และ Server-Side Routing


108

ฉันคิดและสับสนกับการกำหนดเส้นทางระหว่างไคลเอนต์และเซิร์ฟเวอร์ สมมติว่าฉันใช้ ReactJS สำหรับการแสดงผลฝั่งเซิร์ฟเวอร์ก่อนที่จะส่งคำขอกลับไปที่เว็บเบราว์เซอร์และใช้ react-router เป็นเส้นทางฝั่งไคลเอ็นต์เพื่อสลับระหว่างเพจโดยไม่ต้องรีเฟรชเป็น SPA

สิ่งที่อยู่ในใจคือ:

  • เส้นทางตีความอย่างไร? ตัวอย่างเช่นคำขอจากหน้าแรก ( /home) ไปยังหน้าโพสต์ ( /posts)
  • การกำหนดเส้นทางไปที่ใดบนฝั่งเซิร์ฟเวอร์หรือไคลเอนต์
  • มันรู้ได้อย่างไรว่ามันถูกประมวลผลอย่างไร?

1
ฉันขอแนะนำให้อ่าน History API ในเบราว์เซอร์
WiredPrairie

คำตอบ:


137

หมายเหตุคำตอบนี้ครอบคลุม React Router เวอร์ชัน 0.13.x - เวอร์ชัน 1.0 ที่กำลังจะมาถึงดูเหมือนว่าจะมีรายละเอียดการใช้งานที่แตกต่างกันอย่างมาก

เซิร์ฟเวอร์

นี่เป็นขั้นต่ำที่server.jsมี 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})
  })
})

ที่routesโมดูลส่งออกรายการเส้นทาง:

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

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

ทุกครั้งที่มีการร้องขอไปยังเซิร์ฟเวอร์คุณจะสร้างRouterอินสแตนซ์แบบใช้ครั้งเดียวที่กำหนดค่าด้วย URL ขาเข้าเป็นตำแหน่งคงที่ซึ่งจะแก้ไขกับโครงสร้างของเส้นทางเพื่อตั้งค่าเส้นทางที่ตรงกันที่เหมาะสมเรียกกลับด้วยระดับบนสุด ตัวจัดการเส้นทางที่จะแสดงผลและบันทึกว่าเส้นทางย่อยใดที่ตรงกับแต่ละระดับ นี่คือสิ่งที่ได้รับการปรึกษาเมื่อคุณใช้<RouteHandler>ส่วนประกอบภายในองค์ประกอบการจัดการเส้นทางเพื่อแสดงเส้นทางลูกที่ตรงกัน

หากผู้ใช้ปิดใช้งาน JavaScript หรือโหลดช้าลิงก์ใด ๆ ที่คลิกจะเข้าสู่เซิร์ฟเวอร์อีกครั้งซึ่งได้รับการแก้ไขอีกครั้งตามด้านบน

ลูกค้า

นี่เป็นขั้นต่ำที่client.jsมี react-router (ใช้โมดูลเส้นทางเดิมซ้ำ):

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)
})

เมื่อคุณโทรRouter.run()มันจะสร้างอินสแตนซ์เราเตอร์สำหรับคุณอยู่เบื้องหลังซึ่งจะถูกนำมาใช้ใหม่ทุกครั้งที่คุณสำรวจรอบ ๆ แอพเนื่องจาก URL สามารถเป็นแบบไดนามิกบนไคลเอนต์ซึ่งตรงข้ามกับบนเซิร์ฟเวอร์ที่คำขอเดียวมี URL คงที่

ในกรณีนี้เรากำลังใช้สิ่งHistoryLocationที่ใช้HistoryAPIเพื่อให้แน่ใจว่าสิ่งที่ถูกต้องจะเกิดขึ้นเมื่อคุณกดปุ่มย้อนกลับ / ไปข้างหน้า นอกจากนี้ยังมีHashLocationการเปลี่ยนแปลง URL hashเพื่อสร้างรายการประวัติและรับฟังwindow.onhashchangeเหตุการณ์เพื่อทริกเกอร์การนำทาง

เมื่อคุณใช้<Link>ส่วนประกอบของ react-router คุณจะต้องให้toprop ซึ่งเป็นชื่อของเส้นทางรวมทั้งข้อมูลใด ๆparamsและqueryข้อมูลที่เส้นทางต้องการ การ<a>แสดงผลโดยคอมโพเนนต์นี้มีonClickตัวจัดการซึ่งท้ายที่สุดจะเรียกrouter.transitionTo()อินสแตนซ์ของเราเตอร์พร้อมกับอุปกรณ์ประกอบฉากที่คุณให้ลิงก์ซึ่งมีลักษณะดังนี้:

  /**
   * 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);
    }
  },

สำหรับลิงก์ปกติสิ่งนี้จะเรียกlocation.push()ว่าตำแหน่งประเภทใดที่คุณใช้ในที่สุดซึ่งจะจัดการรายละเอียดของการตั้งค่าประวัติดังนั้นการนำทางด้วยปุ่มย้อนกลับและปุ่มไปข้างหน้าจะใช้งานได้จากนั้นโทรกลับไปที่router.handleLocationChange()เพื่อแจ้งให้เราเตอร์ทราบว่าสามารถดำเนินการเปลี่ยนเป็น เส้นทาง URL ใหม่

จากนั้นเราเตอร์จะเรียกrouter.dispatch()เมธอดของตัวเองด้วย URL ใหม่ซึ่งจัดการรายละเอียดของการกำหนดว่าเส้นทางใดที่กำหนดค่าไว้ตรงกับ URL จากนั้นจึงเรียกใช้การเปลี่ยนแปลงที่มีอยู่สำหรับเส้นทางที่ตรงกัน คุณสามารถใช้ตะขอการเปลี่ยนเหล่านี้กับตัวจัดการเส้นทางใด ๆ ของคุณเพื่อดำเนินการบางอย่างเมื่อเส้นทางกำลังจะถูกนำออกจากหรือไปที่โดยสามารถยกเลิกการเปลี่ยนแปลงได้หากสิ่งต่างๆไม่เป็นที่ต้องการของคุณ

หากการเปลี่ยนแปลงไม่ถูกยกเลิกขั้นตอนสุดท้ายคือการโทรกลับที่คุณให้Router.run()ด้วยองค์ประกอบตัวจัดการระดับบนสุดและออบเจ็กต์สถานะพร้อมรายละเอียดทั้งหมดของ URL และเส้นทางที่ตรงกัน คอมโพเนนต์ตัวจัดการระดับบนสุดคือRouterอินสแตนซ์เองซึ่งจัดการการเรนเดอร์ตัวจัดการเส้นทางบนสุดที่ตรงกัน

กระบวนการข้างต้นจะถูกเรียกใช้ใหม่ทุกครั้งที่คุณไปที่ URL ใหม่บนไคลเอนต์

ตัวอย่างโครงการ


3
ดังนั้นฉันอาจพูดได้ว่าการกำหนดเส้นทางไคลเอนต์ถูกจัดการโดย javascript (ซึ่งเป็นรหัสตอบกลับเราเตอร์) หากมี เมื่อใดก็ตามที่ฉันกด Enter บนแถบที่อยู่ของเบราว์เซอร์หรือรีเฟรชหน้าหรือปิดใช้งาน JS ฝั่งเซิร์ฟเวอร์จะจัดการการกำหนดเส้นทาง ในทางกลับกันเมื่อจาวาสคริปต์พร้อมในหน้าปัจจุบันการกำหนดเส้นทางจะถูกจัดการโดยฝั่งไคลเอ็นต์ ฉันเข้าใจถูกต้องหรือไม่?
heartmon

9
สิ่งที่อยู่ในโมดูลเส้นทางvar routes = require('./routes')มันคือรายการเส้นทางหรือไม่? ฉันใช้เราเตอร์ Express แต่ตัวอย่างนี้ใน SO ดูเหมือนจะเป็นเพียงตัวอย่างเดียวของการตั้งค่าการแสดงผลฝั่งเซิร์ฟเวอร์ด้วย React Router ดังนั้นจะเป็นการดีถ้าเป็นตัวอย่างโค้ดแบบเต็ม
svnm

2
มันควรจะเป็นรายการเส้นทาง ฉันจะเพิ่มหมายเหตุเกี่ยวกับเรื่องนั้นและลิงก์ไปยังโครงการตัวอย่าง
Jonny Buchanan

2
ดังนั้นหาก react-router ดูแลการกำหนดเส้นทางฝั่งเซิร์ฟเวอร์แล้วใครจะพูดคุยกับฐานข้อมูล? เกิดอะไรขึ้นกับการกำหนดเส้นทางฝั่งเซิร์ฟเวอร์ ลองจินตนาการว่าเราต้องการให้ REST API สำหรับแอพมือถือที่มาพร้อมเครื่อง ใครดูแลที่?
Morteza Shahriari Nia

1
คำตอบล้าสมัยเนื่องจากreact-routerเวอร์ชันใหม่กว่า กรุณาอัปเดต
oleh.meleshko

26

ด้วย 1.0 React-Router ขึ้นอยู่กับโมดูลประวัติเป็นแบบ peerDependency โมดูลนี้เกี่ยวข้องกับการกำหนดเส้นทางในเบราว์เซอร์ โดยค่าเริ่มต้น React-Router ใช้ HTML5 History API ( pushState, replaceState) แต่คุณสามารถกำหนดค่าให้ใช้การกำหนดเส้นทางตามแฮชได้ (ดูด้านล่าง)

ตอนนี้การจัดการเส้นทางเสร็จสิ้นแล้วเบื้องหลังและ ReactRouter จะส่งอุปกรณ์ประกอบฉากใหม่ไปยังตัวจัดการเส้นทางเมื่อเส้นทางเปลี่ยนไป เราเตอร์มีการonUpdateเรียกกลับเสาใหม่ทุกครั้งที่มีการเปลี่ยนแปลงเส้นทางมีประโยชน์สำหรับการติดตามการดูหน้าเว็บหรืออัปเดต<title>ตัวอย่างเช่น

ไคลเอนต์ (การกำหนดเส้นทาง 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)

ไคลเอนต์ (การกำหนดเส้นทางตามแฮช)

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)

เซิร์ฟเวอร์

บนเซิร์ฟเวอร์เราสามารถใช้ได้ReactRouter.matchซึ่งนำมาจากคู่มือการแสดงผลเซิร์ฟเวอร์

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')
    }
  })
})
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.