ข้ามไปยังเนื้อหา

Declarative Routing

navigation แบบ imperative ทำงานได้ดีจนกว่าคุณต้องทำ deep link เมื่อ user เปิด myapp.com/orders/42 จาก cold start ไม่มีลำดับการ push ที่เกิดขึ้นมาก่อน — คุณต้องประกอบ stack ทั้งหมด (Home → Orders → Order 42) ขึ้นมาใหม่จาก URL การทำแบบนั้นด้วยมือด้วย Navigator.push เจ็บปวดและผิดพลาดง่าย

ทางแก้คือทำให้ route stack เป็น ฟังก์ชันของ state แบบ declarative: กำหนด URL หรือ app state ปัจจุบันมา แล้วคำนวณ stack ออกมา นี่คือ Navigator 2.0 และเพราะ API ดิบ ๆ ของตัวเอง verbose เกือบทุกคนจึงใช้ go_router ที่เป็น package ทางการที่สร้างทับ Navigator 2.0 อีกที

flowchart LR
  state["app state / URL
/orders/42"] --> router["go_router
(route table)"]
  router --> stack["computed stack
Home then Orders then Order 42"]
declarative routing derive stack จาก state

คุณ declare route ครั้งเดียวในรูปของ data go_router map path ไป builder, รองรับ path parameter ที่มี type และ sync URL ของ browser ให้อัตโนมัติบนเว็บ

final router = GoRouter(
routes: [
GoRoute(path: '/', builder: (context, state) => const HomeScreen()),
GoRoute(
path: '/orders/:id', // typed path parameter
builder: (context, state) {
final id = state.pathParameters['id']!;
return OrderScreen(orderId: id);
},
),
],
);
// Navigate by path — no manual stack juggling:
context.go('/orders/42');

context.go('/orders/42') set location; router คำนวณ stack ใหม่ บนเว็บ URL bar อัปเดตให้ตรง และการ paste URL นั้นจะประกอบหน้าจอเดิมขึ้นมาใหม่ — ได้ deep linking มาฟรี

shell route ห่อชุดของ route ไว้ใน UI ร่วมกัน — แอปแบบ bottom-navigation คลาสสิกที่ tab bar อยู่คงที่ ส่วนเนื้อหาข้างในเปลี่ยนไป shell ยังคง mount อยู่ มีแต่ child route ที่สลับ

ShellRoute(
builder: (context, state, child) => ScaffoldWithNavBar(child: child),
routes: [
GoRoute(path: '/feed', builder: (context, state) => const FeedScreen()),
GoRoute(path: '/profile', builder: (context, state) => const ProfileScreen()),
],
);

redirect รันก่อน route จะ build และส่ง user ไปที่อื่นได้ — เป็นที่ที่เหมาะที่สุดสำหรับ authentication guard return path ใหม่เพื่อ redirect หรือ null เพื่อไปต่อ

GoRouter(
redirect: (context, state) {
final loggedIn = auth.isLoggedIn;
final goingToLogin = state.matchedLocation == '/login';
if (!loggedIn && !goingToLogin) return '/login'; // guard
if (loggedIn && goingToLogin) return '/'; // already in
return null; // no redirect
},
routes: [ /* ... */ ],
);
ทำไม navigation แบบ imperative push/pop ถึงมีปัญหากับ deep link?
แนวคิดหลักของ declarative routing คืออะไร?
shell route ใช้ทำอะไร?
ใน go_router ควรวาง authentication guard ไว้ที่ไหน?