數(shù)據(jù)傳遞/狀態(tài)管理 一InheritedWidget
InheritedWidget是一個(gè)特殊的widget,可以存儲(chǔ)和獲取數(shù)據(jù),子組件可以獲取到存儲(chǔ)的數(shù)據(jù),常用的 MediaQuery、Theme 就是繼承了 InheritedWidget。先通過(guò)MediaQuery來(lái)學(xué)習(xí)InheritedWidget。
- MediaQuery
class MediaQuery extends InheritedWidget {
const MediaQuery({
Key key,
@required this.data,
@required Widget child,
}) : assert(child != null),
assert(data != null),
super(key: key, child: child);
///...
final MediaQueryData data;
///...
static MediaQueryData of(BuildContext context, { bool nullOk = false }) {
///...
}
}
通過(guò) MediaQuery.of(context) 獲取到MediaQueryData對(duì)象,然后獲取到設(shè)備屏幕相關(guān)的信息。
///獲取屏幕寬度
MediaQuery.of(context).size.width;
代碼查找,下面這些地方創(chuàng)建了MediaQuery,

在app.dart里面可以發(fā)現(xiàn) WidgetsApp 如下代碼,build 方法返回一個(gè)DefaultFocusTraversal組件,而DefaultFocusTraversal 組件的 child 就是 MediaQuery。
@override
Widget build(BuildContext context) {
///...
return DefaultFocusTraversal(
policy: ReadingOrderTraversalPolicy(),
child: MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window),
child: Localizations(
locale: appLocale,
delegates: _localizationsDelegates.toList(),
child: title,
),
),
);
}
繼續(xù)通過(guò)代碼查找在 MaterialApp 的源碼里面找到了 WidgetsApp ,在build方法里面返回了ScrollConfiguration類,ScrollConfiguration的child就是WidgetsApp。
@override
Widget build(BuildContext context) {
Widget result = WidgetsApp(
key: GlobalObjectKey(this),
navigatorKey: widget.navigatorKey,
navigatorObservers: _navigatorObservers,
pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) =>
MaterialPageRoute<T>(settings: settings, builder: builder),
home: widget.home,
///...
);
return ScrollConfiguration(
behavior: _MaterialScrollBehavior(),
child: result,
);
}
通過(guò)這些代碼,可以推斷出 MaterialApp 的home是屬于MediaQuery的子組件,所有home下面所有組件都能夠通過(guò) MediaQuery.of(context) 來(lái)獲取其共享的數(shù)據(jù)。但是注意并不是任何組件的任何地方,只有當(dāng)前組件執(zhí)行完 didChangeDependencies 方法之后才能正確的獲取到 InheritedWidget 對(duì)象的數(shù)據(jù)。
-
示例
完成下面的示例,頁(yè)面進(jìn)來(lái)加載用戶信息,保存和展示,然后在另外的頁(yè)面修改編輯后返回,展示的數(shù)據(jù)也改變了:

完成上面的示例首先創(chuàng)建一個(gè)數(shù)據(jù)實(shí)體類,
///共享的數(shù)據(jù)
class InheritedWidgetData {
String userName;
int userAge;
InheritedWidgetData({this.userName = "", this.userAge = 0});
void setUser(String userName, int userAge) {
this.userName = userName;
this.userAge = userAge;
}
}
InheritedWidgetDemo 繼承 InheritedWidget ,data 存放著用戶的數(shù)據(jù)。提供一個(gè)of的靜態(tài)方法供子組件調(diào)用,復(fù)寫updateShouldNotify 方法判斷是否需要更新。
///主Widget 繼承InheritedWidget 存放數(shù)據(jù)
class InheritedWidgetDemo extends InheritedWidget {
final InheritedWidgetData data;
InheritedWidgetDemo({@required this.data, @required Widget child})
: super(child: child);
@override
bool updateShouldNotify(InheritedWidgetDemo oldWidget) {
return data != oldWidget.data;
}
static InheritedWidgetData of(BuildContext context) {
final InheritedWidgetDemo user =
context.inheritFromWidgetOfExactType(InheritedWidgetDemo);
return user.data;
}
}
數(shù)據(jù)展示W(wǎng)idget,在 didChangeDependencies 方法里面獲取用戶數(shù)據(jù)。頁(yè)面初始化開啟模擬網(wǎng)絡(luò)請(qǐng)求等待3秒后,調(diào)用 setUser 方法修改 InheritedWidgetDemo 中的數(shù)據(jù),然后重建界面。當(dāng)用戶信息為空的話頁(yè)面顯示加載等待組件。
class CenterWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() => _CenterWidgetState();
}
class _CenterWidgetState extends State<CenterWidget> {
InheritedWidgetData _userData;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_userData = InheritedWidgetDemo.of(context);
_getUserData();
}
_getUserData() async {
///模擬網(wǎng)絡(luò)請(qǐng)求 等待五秒
await Future.delayed(Duration(seconds: 3));
InheritedWidgetDemo.of(context).setUser("李華", 18);
///setState 觸發(fā)頁(yè)面刷新
setState(() {});
}
@override
Widget build(BuildContext context) {
return new Center(
/// 數(shù)據(jù)為空顯示加載圓圈
child: _userData.userName == ""
? CircularProgressIndicator()
: new UserInfoWidget(),
);
}
}
用戶信息展示W(wǎng)idget,和上面的Widget一樣,在 didChangeDependencies 方法里面獲取用戶數(shù)據(jù)并且展示。
///用戶信息Widget
class UserInfoWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() => _UserInfoState();
}
class _UserInfoState extends State<UserInfoWidget> {
InheritedWidgetData _userData;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_userData = InheritedWidgetDemo.of(context);
}
@override
Widget build(BuildContext context) {
return new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new Text(_userData.userName),
new Text(_userData.userAge.toString())
],
),
);
}
}
用戶資料修改界面,和上面一樣在 didChangeDependencies 方法里面拿到數(shù)據(jù),點(diǎn)擊保存按鈕的時(shí)候 InheritedWidgetDemo.of(context).setUser(_userName, userAge) 修改數(shù)據(jù),當(dāng)返回展示界面的時(shí)候,界面會(huì)拿到新數(shù)據(jù)重建。
class EditUserPageWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("修改數(shù)據(jù)"),
),
body: new EditUserWidget(),
);
}
}
class EditUserWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() => _EditUserState();
}
class _EditUserState extends State<EditUserWidget> {
InheritedWidgetData _userData;
String _userName;
String _userAge;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_userData = InheritedWidgetDemo.of(context);
_userName = _userData.userName;
if (_userData.userAge > 0) {
_userAge = _userData.userAge.toString();
} else {
_userAge = "";
}
}
@override
Widget build(BuildContext context) {
return new Center(
child: new Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new TextField(
///文本內(nèi)容控制器
controller: TextEditingController.fromValue(new TextEditingValue(
text: _userName,
///光標(biāo)移動(dòng)到最后面
selection: TextSelection.fromPosition(new TextPosition(
affinity: TextAffinity.downstream, offset: _userName.length)),
)),
decoration: new InputDecoration(labelText: "輸入姓名"),
///內(nèi)容變化監(jiān)聽
onChanged: (value) {
_userName = value;
},
),
new TextField(
///文本內(nèi)容控制器
controller: TextEditingController.fromValue(new TextEditingValue(
text: _userAge,
///光標(biāo)移動(dòng)到最后面
selection: TextSelection.fromPosition(new TextPosition(
affinity: TextAffinity.downstream, offset: _userAge.length)),
)),
decoration: new InputDecoration(labelText: "輸入年齡"),
///內(nèi)容變化監(jiān)聽
onChanged: (value) {
_userAge = value;
},
),
new FlatButton(
onPressed: () {
int userAge;
try {
///保存當(dāng)前修改的值
userAge = int.parse(_userAge);
InheritedWidgetDemo.of(context).setUser(_userName, userAge);
///關(guān)閉當(dāng)前界面
Navigator.pop(context);
} catch (e) {}
},
child: new Text("保存"))
],
),
);
}
}
由于用戶數(shù)據(jù)需要在全局共享,所以 InheritedWidgetDemo 要在頂層入口MaterialApp之上,當(dāng)然在日常開發(fā)并不是所有的數(shù)據(jù)都是全局保存的,每個(gè)子頁(yè)面都會(huì)管理自己所屬的數(shù)據(jù),只需要把 InheritedWidget 放在子頁(yè)面的最外層。
class AppPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new InheritedWidgetDemo(
data: new InheritedWidgetData(),
child: new MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: new Scaffold(
appBar: new AppBar(
title: new Text("數(shù)據(jù)傳遞"),
),
floatingActionButton: new Builder(builder: (context) {
return new FloatingActionButton(
child: new Icon(Icons.edit),
onPressed: () {
///push到編輯頁(yè)面
Navigator.of(context).push(
new MaterialPageRoute(builder: (BuildContext context) {
return EditUserPageWidget();
}));
});
}),
body: new CenterWidget(),
),
));
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
///使用
return AppPage();
}
}
-
關(guān)于
didChangeDependencies的回調(diào),在生命周期提到過(guò),解釋是在State對(duì)象的依賴發(fā)生變化時(shí)會(huì)被調(diào)用,結(jié)合InheritedWidget看下面這個(gè)demo。Jun-15-2019 14-44-43兩個(gè)Text,一個(gè)依賴了
InheritedWidget,一個(gè)沒(méi)有。點(diǎn)擊按鈕修改InheritedWidget的值,上面的text會(huì)根據(jù)值的變化刷新重建,并回調(diào)了didChangeDependencies,下面的text由于沒(méi)有數(shù)據(jù)依賴,所以只有初始化的時(shí)候回調(diào)了一次didChangeDependenciesclass InheritedRely extends StatefulWidget { @override State<StatefulWidget> createState() { return new _InheritedRelyState(); } } class _InheritedRelyState extends State<InheritedRely> { int _state = 0; @override Widget build(BuildContext context) { return new InheritedTestWidget( data: _state, child: new MaterialApp( title: "Inherited數(shù)據(jù)依賴", home: new Scaffold( body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new TextWidget(), new Text1Widget() ], ), ), floatingActionButton: new FloatingActionButton( onPressed: () { setState(() { ///修改state _state++; }); }, child: new Icon(Icons.add), ), ), )); } } class Text1Widget extends StatefulWidget { @override State<StatefulWidget> createState() { return new _Text1WidgetState(); } } ///這個(gè)text沒(méi)有使用InheritedWidget的數(shù)據(jù) class _Text1WidgetState extends State<Text1Widget> { @override Widget build(BuildContext context) { return Text("這個(gè)text沒(méi)有使用InheritedWidget的數(shù)據(jù)"); } @override void didChangeDependencies() { super.didChangeDependencies(); print("_Text1------>>>>>>didChangeDependencies"); } } ///這個(gè)text使用了InheritedWidget的數(shù)據(jù) class TextWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _TextWidgetState(); } } class _TextWidgetState extends State<TextWidget> { @override Widget build(BuildContext context) { ///InheritedTestWidget.of(context) 依賴了InheritedWidget的數(shù)據(jù) return Text("這個(gè)text使用了InheritedWidget的數(shù)據(jù)" + InheritedTestWidget.of(context).toString()); } @override void didChangeDependencies() { super.didChangeDependencies(); print("_Text------>>>>>>didChangeDependencies"); } } class InheritedTestWidget extends InheritedWidget { final int data; InheritedTestWidget({@required this.data, @required Widget child}) : super(child: child); @override bool updateShouldNotify(InheritedTestWidget oldWidget) { return data != oldWidget.data; } static int of(BuildContext context) { final InheritedTestWidget user = context.inheritFromWidgetOfExactType(InheritedTestWidget); return user.data; } }代碼和演示清楚的解釋了,當(dāng)
InheritedWidget的State對(duì)象發(fā)生變化時(shí),它下面子組件只要是對(duì)InheritedWidget有依賴的都會(huì)調(diào)didChangeDependencies和build進(jìn)行刷新重建。 -
scoped_model
上面的例子展示了如何使用
InheritedWidget,有個(gè)問(wèn)題就是在數(shù)據(jù)變化之后每次都需要手動(dòng)調(diào)動(dòng)setState方法才會(huì)進(jìn)行重建。如何讓組件自動(dòng)刷新不需要我們自己手動(dòng)調(diào)用setState方法呢。這需要使用到
scoped_model庫(kù) ,scoped_model是基于InheritedWidget實(shí)現(xiàn)的,源碼稍后分析,先通過(guò)剛剛用戶資料編輯的demo來(lái)學(xué)習(xí)如何使用這個(gè)庫(kù),展示效果和上面的demo一樣。因?yàn)槭堑谌綆?kù)第一步肯定是添加依賴
scoped_model: ^1.0.1。文檔在這里(需要梯子)。用戶model,繼承
Model提供一個(gè)of方法供組件調(diào)用獲取model,注意rebuildOnChange參數(shù)是可選的,默認(rèn)為fales,為false的時(shí)候獲取的model數(shù)據(jù)變化之后不會(huì)刷新界面。class UserMode extends Model { String userName; int userAge; UserMode({this.userName, this.userAge}); void setUserInfo(String userName, int userAge) { this.userName = userName; this.userAge = userAge; ///修改完數(shù)據(jù) 調(diào)用刷新方法 notifyListeners(); } ///rebuildOnChange 此方法拿到的model對(duì)象修改后是否需要刷新 static UserMode of(BuildContext context) => ScopedModel.of<UserMode>(context, rebuildOnChange: true); }展示組件,邏輯基本和
InheritedWidget的一樣,初始化的時(shí)候模擬請(qǐng)求,延遲3秒修改數(shù)據(jù),當(dāng)沒(méi)有數(shù)據(jù)的時(shí)候加載等待小圓圈,不同的是獲取數(shù)據(jù)的方法,這里變成通過(guò)model的of方法。class CenterWidget extends StatefulWidget { @override State<StatefulWidget> createState() => _CenterWidgetState(); } class _CenterWidgetState extends State<CenterWidget> { @override void initState() { super.initState(); _getUserData(); } _getUserData() async { ///模擬網(wǎng)絡(luò)請(qǐng)求 等待五秒 await Future.delayed(Duration(seconds: 3)); UserMode.of(context).setUserInfo("李華", 22); } @override Widget build(BuildContext context) { return new Center( /// 數(shù)據(jù)為空顯示加載圓圈 child: UserMode.of(context).userName == null ? CircularProgressIndicator() : new UserInfoWidget()); } }除了
ScopedModel.of方法還可以通過(guò)ScopedModelDescendant來(lái)獲取,在需要獲取數(shù)據(jù)的組件外加一層ScopedModelDescendant,用戶信息使用此方法。///用戶信息Widget class UserInfoWidget extends StatefulWidget { @override State<StatefulWidget> createState() => _UserInfoState(); } class _UserInfoState extends State<UserInfoWidget> { @override Widget build(BuildContext context) { return new ScopedModelDescendant<UserMode>(builder: (context, child, model) { return new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: <Widget>[ ///可以直接通過(guò)mode拿到數(shù)據(jù) new Text(model.userName), new Text(model.userAge.toString()) ], ), ); }); } }信息編輯頁(yè)面邏輯也一樣,獲取和修改數(shù)據(jù)的方式不一樣。
class EditUserPageWidget extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("修改數(shù)據(jù)"), ), body: new EditUserWidget(), ); } } class EditUserWidget extends StatefulWidget { @override State<StatefulWidget> createState() => _EditUserState(); } class _EditUserState extends State<EditUserWidget> { UserMode _userMode; String _userName; String _userAge; @override Widget build(BuildContext context) { _userMode = UserMode.of(context); _userName = _userMode.userName; _userAge = _userMode.userAge.toString(); return new Center( child: new Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new TextField( ///文本內(nèi)容控制器 controller: TextEditingController.fromValue(new TextEditingValue( text: _userName, ///光標(biāo)移動(dòng)到最后面 selection: TextSelection.fromPosition(new TextPosition( affinity: TextAffinity.downstream, offset: _userName.length)), )), decoration: new InputDecoration(labelText: "輸入姓名"), ///內(nèi)容變化監(jiān)聽 onChanged: (value) { _userName = value; }, ), new TextField( ///文本內(nèi)容控制器 controller: TextEditingController.fromValue(new TextEditingValue( text: _userAge, selection: TextSelection.fromPosition(new TextPosition( affinity: TextAffinity.downstream, offset: _userAge.length)), )), decoration: new InputDecoration(labelText: "輸入年齡"), ///內(nèi)容變化監(jiān)聽 onChanged: (value) { _userAge = value; }, ), new FlatButton( onPressed: () { int userAge; try { ///保存當(dāng)前修改的值 userAge = int.parse(_userAge); _userMode.setUserInfo(_userName, userAge); ///關(guān)閉當(dāng)前界面 Navigator.pop(context); } catch (e) {} }, child: new Text("保存")) ], ), ); } }主Widget,和
InheritedWidget一樣需要在最外層,使用ScopedModel。class ScopedModelDemo extends StatefulWidget { @override State<StatefulWidget> createState() { return new _ScopedModelDemoState(); } } class _ScopedModelDemoState extends State<ScopedModelDemo> { @override Widget build(BuildContext context) { return new ScopedModel<UserMode>( model: UserMode(), child: new MaterialApp( theme: ThemeData( primarySwatch: Colors.blue, ), home: new Scaffold( appBar: new AppBar( title: new Text("ScopedModelDemo"), ), floatingActionButton: new Builder(builder: (context) { return new FloatingActionButton( child: new Icon(Icons.edit), onPressed: () { ///push到編輯頁(yè)面 Navigator.of(context).push( new MaterialPageRoute(builder: (BuildContext context) { return EditUserPageWidget(); })); }); }), body: new CenterWidget(), ), )); } }效果和上面一樣,不上圖了。代碼中沒(méi)有任何地方使用到
setState數(shù)據(jù)更新之后,界面自動(dòng)刷新重建。上面說(shuō)到了這個(gè)庫(kù)也是基于InheritedWidget,具體如何實(shí)現(xiàn)的,通過(guò)源碼來(lái)分析。首先看下Model的源碼,
abstract class Model extends Listenable { final Set<VoidCallback> _listeners = Set<VoidCallback>(); int _version = 0; int _microtaskVersion = 0; /// [listener] will be invoked when the model changes. @override void addListener(VoidCallback listener) { _listeners.add(listener); } /// [listener] will no longer be invoked when the model changes. @override void removeListener(VoidCallback listener) { _listeners.remove(listener); } /// Returns the number of listeners listening to this model. int get listenerCount => _listeners.length; /// Should be called only by [Model] when the model has changed. @protected void notifyListeners() { // We schedule a microtask to debounce multiple changes that can occur // all at once. if (_microtaskVersion == _version) { _microtaskVersion++; scheduleMicrotask(() { _version++; _microtaskVersion = _version; // Convert the Set to a List before executing each listener. This // prevents errors that can arise if a listener removes itself during // invocation! _listeners.toList().forEach((VoidCallback listener) => listener()); }); } } }Model繼承至
Listenable,維護(hù)著一個(gè)VoidCallback類型的集合,/// Signature of callbacks that have no arguments and return no data. typedef VoidCallback = void Function();VoidCallback就是一個(gè)沒(méi)有返回值的方法,notifyListeners方法里面遍歷調(diào)用了所有集合里面的方法。再看入口
ScopedModel,class ScopedModel<T extends Model> extends StatelessWidget { /// The [Model] to provide to [child] and its descendants. final T model; /// The [Widget] the [model] will be available to. final Widget child; ScopedModel({@required this.model, @required this.child}) : assert(model != null), assert(child != null); @override Widget build(BuildContext context) { return AnimatedBuilder( animation: model, builder: (context, _) => _InheritedModel<T>(model: model, child: child), ); } static T of<T extends Model>( BuildContext context, { bool rebuildOnChange = false, }) { final Type type = _type<_InheritedModel<T>>(); Widget widget = rebuildOnChange ? context.inheritFromWidgetOfExactType(type) : context.ancestorWidgetOfExactType(type); if (widget == null) { throw new ScopedModelError(); } else { return (widget as _InheritedModel<T>).model; } } }代碼很簡(jiǎn)單
of方法為Model方法提供獲取值的方法,而且是通過(guò)inheritFromWidgetOfExactType和上面InheritedWidget的of差不多,這里也可以解釋了上面Model里面為什么rebuildOnChange為fals的時(shí)候不會(huì)更新界面的原因。然后查看
bulid方法返回的是一個(gè)AnimatedBuilder,兩個(gè)參數(shù),model和_InheritedModel,查看_InheritedModel,果然,繼承了InheritedWidget。并且存放了model和一個(gè)int類型的version通過(guò)version來(lái)判斷是否更新。class _InheritedModel<T extends Model> extends InheritedWidget { final T model; final int version; _InheritedModel({Key key, Widget child, T model}) : this.model = model, this.version = model._version, super(key: key, child: child); @override bool updateShouldNotify(_InheritedModel<T> oldWidget) => (oldWidget.version != version); }回到
AnimatedBuilder查看它的代碼class AnimatedBuilder extends AnimatedWidget { /// Creates an animated builder. /// /// The [animation] and [builder] arguments must not be null. const AnimatedBuilder({ Key key, @required Listenable animation, @required this.builder, this.child, }) : assert(animation != null), assert(builder != null), super(key: key, listenable: animation); /// Called every time the animation changes value. final TransitionBuilder builder; final Widget child; @override Widget build(BuildContext context) { return builder(context, child); } }看不出有什么重要的代碼,點(diǎn)擊
super往上走看AnimatedWidget的代碼,abstract class AnimatedWidget extends StatefulWidget { ///... } class _AnimatedState extends State<AnimatedWidget> { @override void initState() { super.initState(); widget.listenable.addListener(_handleChange); } @override void didUpdateWidget(AnimatedWidget oldWidget) { super.didUpdateWidget(oldWidget); if (widget.listenable != oldWidget.listenable) { oldWidget.listenable.removeListener(_handleChange); widget.listenable.addListener(_handleChange); } } @override void dispose() { widget.listenable.removeListener(_handleChange); super.dispose(); } void _handleChange() { setState(() { // The listenable's state is our build state, and it changed already. }); } @override Widget build(BuildContext context) => widget.build(context); }看到這里就很清晰了,通過(guò)觀察者模式,
_InheritedModel里的數(shù)據(jù)也就是Model是被觀察者,在_AnimatedState的initState和dispose方法來(lái) 訂閱和取消,Model發(fā)生變化時(shí),去調(diào)用notifyListeners來(lái)通知所有觀察者執(zhí)行setState刷新重構(gòu)造界面。還有一個(gè)
ScopedModelDescendant,class ScopedModelDescendant<T extends Model> extends StatelessWidget { /// Called whenever the [Model] changes. final ScopedModelDescendantBuilder<T> builder; /// An optional constant child that does not depend on the model. This will /// be passed as the child of [builder]. final Widget child; /// An optional constant that determines whether the final bool rebuildOnChange; /// Constructor. ScopedModelDescendant({ @required this.builder, this.child, this.rebuildOnChange = true, }); @override Widget build(BuildContext context) { return builder( context, child, ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange), ); } }
本質(zhì)也是ScopedModel.of 方法拿到數(shù)據(jù)的。
初學(xué)小白的個(gè)人學(xué)習(xí)筆記,歡迎大佬檢查,如有不對(duì)請(qǐng)指出。
