在flutter中我們經常會使用到這樣的代碼
//打開一個新的頁面 Navigator.of(context).push //打開Scaffold的Drawer Scaffold.of(context).openDrawer //獲取display1樣式文字主題 Theme.of(context).textTheme.display1
那么這個of(context)到底是個什么呢。我們這里以Navigator打開新頁面為例。
static NavigatorState of( BuildContext context, { bool rootNavigator = false, bool nullOk = false, }) { //關鍵代碼-----------------------------------------v final NavigatorState navigator = rootNavigator ? context.rootAncestorStateOfType(const TypeMatcher<NavigatorState>()) : context.ancestorStateOfType(const TypeMatcher<NavigatorState>()
); //關鍵代碼----------------------------------------^ assert(() { if (navigator == null && !nullOk) { throw FlutterError( 'Navigator operation requested with a context that does not include a Navigator.\n' 'The context used to push or pop routes from the Navigator must be that of a ' 'widget that is a descendant of a Navigator widget.' ); } return true; }()); return navigator; }
可以看到,關鍵代碼部分通過context.rootAncestorStateOfType向上遍歷 Element tree,並找到最近匹配的 NavigatorState。也就是說of實際上是對context跨組件獲取數據的一個封裝。
而我們的Navigator的 push操作就是通過找到的 NavigatorState 來完成的。
不僅如此,BuildContext還有許多方法可以跨組件獲取對象
ancestorInheritedElementForWidgetOfExactType(Type targetType) → InheritedElement ancestorRenderObjectOfType(TypeMatcher matcher) → RenderObject ancestorStateOfType(TypeMatcher matcher) → State ancestorWidgetOfExactType(Type targetType) → Widget findRenderObject() → RenderObject inheritFromElement(InheritedElement ancestor, { Object aspect }) → InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) → InheritedWidget rootAncestorStateOfType(TypeMatcher matcher) → State visitAncestorElements(bool visitor(Element element)) → void visitChildElements(ElementVisitor visitor) → void
需要注意的是,在 State 中 initState階段是無法跨組件拿數據的,只有在didChangeDependencies之后才可以使用這些方法。
回顧問題
我們現在再來看看之前遇到的 當前 context 不包含 Navigator 這個問題是不是很簡單了呢。
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Center( child: FlatButton( onPressed: () { Navigator.of(context).push( MaterialPageRoute(builder: (context) => SecondPage())); }, child: Text('跳轉')), ), ), ); } }
當我們在 build 函數中使用Navigator.of(context)的時候,這個context實際上是通過 MyApp 這個widget創建出來的Element對象,而of方法向上尋找祖先節點的時候(MyApp的祖先節點)並不存在MaterialApp,也就沒有它所提供的Navigator。
所以當我們把Scaffold部分拆成另外一個widget的時候,我們在FirstPage的build函數中,獲得了FirstPage的BuildContext,然后向上尋找發現了MaterialApp,並找到它提供的Navigator,於是就可以愉快進行頁面跳轉了。