原文地址:ericwindmill.com/posts/inher…git
[譯者注:InheritedWidget在Flutter框架裏面是一個很是重要的Widget,至關多的機制都是經過這個Widget來實現;好比:Material庫裏面的主題管理(Theme), Scoped Model, Redux都是使用這個機制來實現]github
若是你以前使用過Flutter,那麼你可能已經使用過不少類裏面的'of'方法redux
Theme.of(context).textTheme
MediaQuery.of(context).size
複製代碼
這些Widget(Theme, MediaQuery)都是Inherited Widgets。 在你的App的任何地方你都能訪問到你的theme(主題對象),由於他們都是繼承了的(繼承至InheritedWidget)。 在Flutter中,sdk的每一個部分都向開發人員公開,所以你能夠本身利用inherited widget(譯註: 使用了InheritedWidget的widget) 的優勢。你可使用定製的InheritedWidget來管理Flutter內置的state,相似於Redux的Store或者Vue的Vuex Store。 設置了相似於這樣的Store以後,你就能夠像這樣子使用:bash
class RedText extends StatelessWidget {
// ...
Widget build(BuildContext context) {
var state = StateContainer.of(context).state;
return new Text(
state.user.username,
style: const TextStyle(color: Colors.red),
);
// ...
複製代碼
當你使用InheritedWidget做爲你的狀態管理工具時,你就要依賴這種‘狀態提高’的架構模式。架構
咱們來改造一下,建立Flutter新工程時候的啓動模板工程(計數器App)。若是你要把這個App分拆分紅兩個頁面,一個用來顯示計數,一個用來改變計數器的數字。出乎意料,這個簡單的App有點讓人迷惑。每次你改變路由(routes)(譯註:改變頁面)時,你都得把這個狀態(計數,Counter),來回傳遞。app
InheritedWidget經過賦予整顆Widget樹訪問同一個狀態(state)的權限,來解決這個問題。框架
觀看 Brian Egan在DartConf 2018的演講,能夠了解到很是多優秀的Flutter架構概念,不要看太多,否則你就被說服去使用 flutter_redux了,而後你就再也不關心這篇文章了😭。相對於使用像Redux這樣的框架,使用狀態提高的優點在於:InheritedWidget很是容易理解和使用。less
PS: 我是Redux、Vuex等全部'ux'相關架構模式的粉絲,這只是你的Flutter工具箱裏面的另外一個工具;若是Redux超出了你的須要你就可使用這個。ide
這個時候你可能會問,你爲何會須要使用InheritedWidget。爲何不能在你的app的根widget使用 stateful widget (譯註:Flutter最底層的狀態管理API, flutter.io/docs/develo…) ?工具
確實,這就是咱們這裏要作的。inherited widget跟stateful widget結合,容許你將狀態傳遞到他的全部的祖先節點(widget)。這是一個很是方便的widget, 因此你不須要在每一個類裏面去寫代碼把狀態傳遞給他的孩子節點
在這個例子中,咱們編寫一個這樣簡單的App:
簡單來講,這個App的狀態是由根Widget提高的,當你點擊提交表單,它在inherited widgets調用setState通知主頁面有新的信息須要渲染。
這只是你的Flutter App的標準配置
void main() {
runApp(new UserApp());
}
class UserApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new HomeScreen(),
);
}
}
複製代碼
到如今爲止,這都是很是基礎的。在好東西登場以前,先跟着這些往下走吧
class HomeScreen extends StatefulWidget {
@override
HomeScreenState createState() => new HomeScreenState();
}
class HomeScreenState extends State<HomeScreen> {
Widget get _logInPrompt {
return new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Text(
'Please add user information',
style: const TextStyle(fontSize: 18.0),
),
],
),
);
}
// All this method does is bring up the form page.
void _updateUser(BuildContext context) {
Navigator.push(
context,
new MaterialPageRoute(
fullscreenDialog: true,
builder: (context) {
return new UpdateUserScreen();
},
),
);
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Inherited Widget Test'),
),
body: _logInPrompt,
floatingActionButton: new FloatingActionButton(
onPressed: () => _updateUser(context),
child: new Icon(Icons.edit),
),
);
}
}
複製代碼
到目前爲止,這個表單頁面什麼也沒作。
class UpdateUserScreen extends StatelessWidget {
static final GlobalKey<FormState> formKey = new GlobalKey<FormState>();
static final GlobalKey<FormFieldState<String>> firstNameKey =
new GlobalKey<FormFieldState<String>>();
static final GlobalKey<FormFieldState<String>> lastNameKey =
new GlobalKey<FormFieldState<String>>();
static final GlobalKey<FormFieldState<String>> emailKey =
new GlobalKey<FormFieldState<String>>();
const UpdateUserScreen({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Edit User Info'),
),
body: new Padding(
padding: new EdgeInsets.all(16.0),
child: new Form(
key: formKey,
autovalidate: false,
child: new ListView(
children: [
new TextFormField(
key: firstNameKey,
style: Theme.of(context).textTheme.headline,
decoration: new InputDecoration(
hintText: 'First Name',
),
),
new TextFormField(
key: lastNameKey,
style: Theme.of(context).textTheme.headline,
decoration: new InputDecoration(
hintText: 'Last Name',
),
),
new TextFormField(
key: emailKey,
style: Theme.of(context).textTheme.headline,
decoration: new InputDecoration(
hintText: 'Email Address',
),
)
],
),
),
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.add),
onPressed: () {
final form = formKey.currentState;
if (form.validate()) {
var firstName = firstNameKey.currentState.value;
var lastName = lastNameKey.currentState.value;
var email = emailKey.currentState.value;
// Later, do some stuff here
Navigator.pop(context);
}
},
),
);
}
}
複製代碼
新建一個文件命名爲state_container.dart,全部事情都將在這裏發生。 第一步,在那個文件中建立一個叫User的類。在真實的App裏面這個多是一個很是大的類叫AppState,在這裏你保存了全部你的App須要訪問的屬性。
class User {
String firstName;
String lastName;
String email;
User(this.firstName, this.lastName, this.email);
}
複製代碼
InheritedWidget經過StatefulWidget進行鏈接成爲Store。因此你的StateContainer實際上是有3個類:
class StateContainer extends StatefulWidget
class StateContainerState extends State<StateContainer>
class _InheritedStateContainer extends InheritedWidget
複製代碼
InheritedWidget和StateContainer都是最簡單的設置,他們一旦被設置就不會被改變。邏輯主要存放在StateContainerState。先寫前面兩個類:
class _InheritedStateContainer extends InheritedWidget {
// Data 是你整個的狀態(state). 在咱們的例子中就是 'User'
final StateContainerState data;
// 必須傳入一個 孩子widget 和你的狀態.
_InheritedStateContainer({
Key key,
@required this.data,
@required Widget child,
}) : super(key: key, child: child);
// 這個一個內建方法能夠在這裏檢查狀態是否有變化. 若是沒有變化就不須要從新建立全部Widget.
@override
bool updateShouldNotify(_InheritedStateContainer old) => true;
}
class StateContainer extends StatefulWidget {
// You must pass through a child.
final Widget child;
final User user;
StateContainer({
@required this.child,
this.user,
});
// 這個是全部一切的祕訣. 寫一個你本身的'of'方法,像MediaQuery.of and Theme.of
// 簡單的說,就是:從指定的Widget類型獲取data.
static StateContainerState of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(_InheritedStateContainer)
as _InheritedStateContainer).data;
}
@override
StateContainerState createState() => new StateContainerState();
}
複製代碼
那個'of'方法不該該作任何其餘事情。事實上,這兩個類能夠永遠單獨存在。
你全部的邏輯和狀態都存放在這裏,對於這個App來講,你將在這裏簡單的封裝和存儲User對象
class StateContainerState extends State<StateContainer> {
// Whichever properties you wanna pass around your app as state
User user;
// You can (and probably will) have methods on your StateContainer
// These methods are then used through our your app to
// change state.
// Using setState() here tells Flutter to repaint all the
// Widgets in the app that rely on the state you've changed. void updateUserInfo({firstName, lastName, email}) { if (user == null) { user = new User(firstName, lastName, email); setState(() { user = user; }); } else { setState(() { user.firstName = firstName ?? user.firstName; user.lastName = lastName ?? user.lastName; user.email = email ?? user.email; }); } } // Simple build method that just passes this state through // your InheritedWidget @override Widget build(BuildContext context) { return new _InheritedStateContainer( data: this, child: widget.child, ); } } 複製代碼
若是你使用過Redux,你能夠看到這裏涉及到的概念很是少。更少的概念意味着,潛在的Bug也會少,可是對於一個簡單的App來講,這樣子很是的實際。這就是創建你的Store所須要作的全部工做。接下來,你只須要在你的類中加入須要的方法和屬性。
首先用StateContainer封裝你的App:
void main() {
runApp(new StateContainer(child: new UserApp()));
}
複製代碼
就這樣,如今你能在你整個App裏面訪問你的Store了。能夠這樣作:
// main.dart
// ...
class HomeScreenState extends State<HomeScreen> {
// Make a class property for the data you want
User user;
// This Widget will display the users info:
Widget get _userInfo {
return new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
// This refers to the user in your store
new Text("${user.firstName} ${user.lastName}",
style: new TextStyle(fontSize: 24.0)),
new Text(user.email, style: new TextStyle(fontSize: 24.0)),
],
),
);
}
Widget get _logInPrompt {
// ...
}
void _updateUser(BuildContext context) {
// ...
}
@override
Widget build(BuildContext context) {
// This is how you access your store. This container
// is where your properties and methods live
final container = StateContainer.of(context);
// set the class's user user = container.user; var body = user != null ? _userInfo : _logInPrompt; return new Scaffold( appBar: new AppBar( title: new Text('Inherited Widget Test'), ), // The body will rerender to show user info // as its updated body: body, floatingActionButton: new FloatingActionButton( onPressed: () => _updateUser(context), child: new Icon(Icons.edit), ), ); } } 複製代碼
很是簡單的變化。表單頁面也沒什麼不一樣:
// form_page.dart
// ...
class UpdateUserScreen extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
// get reference to your store
final container = StateContainer.of(context);
return new Scaffold(
// the form is the same until here:
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.add),
onPressed: () {
final form = formKey.currentState;
if (form.validate()) {
var firstName = firstNameKey.currentState.value;
var lastName = lastNameKey.currentState.value;
var email = emailKey.currentState.value;
// This is a hack that isn't important // To this lesson. Basically, it prevents // The store from overriding user info // with an empty string if you only want // to change a single attribute if (firstName == '') { firstName = null; } if (lastName == '') { lastName = null; } if (email == '') { email = null; } // You can call the method from your store, // which will call set state and rerender // the widgets that rely on the user slice of state. // In this case, thats the home page container.updateUserInfo( firstName: firstName, lastName: lastName, email: email, ); Navigator.pop(context); } }, ), ); } } 複製代碼
就是這樣!InheritedWidget很簡單,對於簡單的App、原型等來講是一個很是好的選擇。