热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

数据传递/状态管理一InheritedWidget使用示例

介绍:在Theme.of(context)、MediaQuery.of(context)等实现代码都能看到InheritedWidget的身影:

介绍:

在Theme.of(context)、 MediaQuery.of(context)等实现代码都能看到InheritedWidget的身影:

  • Theme:Theme.of(context)->_InheritedTheme->InheritedTheme->InheritedWidget
  • MediaQuery: MediaQuery.of(context)->MediaQuery->InheritedWidget
    其实Theme和MediaQuery最终都是通过InheritedWidget实现的

InheritedWidget是Flutter中非常重要的一个功能型组件,它提供了一种数据在widget树中从上到下传递、共享的方式,比如我们在应用的根widget中通过InheritedWidget共享了一个数据,那么我们便可以在任意子widget中来获取该共享的数据!这个特性在一些需要在widget树中共享数据的场景中非常方便!

InheritedWidget使用步骤:

1、首先继承InheritedWidget,并实现自定义of静态方法和重写updateShouldNotify方法,如下:

/* *

数据共享InheritedWidget

* * @author 许志新 * @version 1.0 (2020-06-14) */ import 'package:flutter/material.dart'; class ShareDataInheritedWidget extends InheritedWidget { final String name; //用于区分子widget调用的是哪个ShareDataInheritedWidget //共享数据 final InheritedShareModel shareModel; //点击+号的方法 final Function() increment; //点击-号的方法 final Function() reduce; ShareDataInheritedWidget( this.name, { Key key, @required this.shareModel, @required this.increment, @required this.reduce, @required Widget child, }) : super(key: key, child: child); ///定义一个便捷方法,方便子树中的widget获取共享数据--InheritedWidget变化时会通知子widget的didChangeDependencies static ShareDataInheritedWidget of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(); } ///定义一个便捷方法,方便子树中的widget获取共享数据--InheritedWidget变化时不会通知子widget的didChangeDependencies static ShareDataInheritedWidget ofNotNoticeDependence(BuildContext context) { return context .getElementForInheritedWidgetOfExactType() ?.widget; } ///该回调决定当data发生变化时,是否通知子树中依赖data的Widget @override bool updateShouldNotify(ShareDataInheritedWidget oldWidget) { //如果返回true,则子树中依赖(build函数中有调用)本widget //的子widget的`state.didChangeDependencies`会被调用 return (null != shareModel?.count && null != oldWidget?.shareModel?.count && shareModel.count != oldWidget.shareModel.count); } ///用于展示信息 String showInfo() { return '来自${name ?? ''} InheritedWidget:ncount值为${shareModel?.count ?? ''}'; } } class InheritedShareModel { final int count; const InheritedShareModel(this.count); }

说明:

  • 自定义静态of方法:提供给子树中的widget来访问我们自定义InheritedWidget,进而可以访问InheritedWidget共享的数据或方法,如上面共享的shareModel或increment等;
  • 重写updateShouldNotify方法:根据自己的业务,决定当共享发生变化时,是否通知子树中依赖共享数据的Widget;如果返回true,则子树中依赖本InheritedWidget的子widget的state.didChangeDependencies会被调用;返回false,则不会调用;

2、子widget使用自定义InheritedWidget的共享数据或方法,如下:

class DependenceWidgetA extends StatefulWidget { @override _DependenceWidgetAState createState() => new _DependenceWidgetAState(); } class _DependenceWidgetAState extends State { @override Widget build(BuildContext context) { //使用InheritedWidget中的共享数据 final shareDataInheritedWidget = ShareDataInheritedWidget.of(context); return Padding( padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0), child: Text( 'DependenceWidgetA : ${shareDataInheritedWidget?.showInfo() ?? 'null'}',textAlign: TextAlign.center, style: TextStyle(fontSize: 20.0), ), ); } @override void didChangeDependencies() { super.didChangeDependencies(); //父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。 //如果build中没有依赖InheritedWidget,则此回调不会被调用。 print("DependenceWidgetA didChangeDependencies"); } }

说明:

  • 访问方式:子widget通过InheritedWidget的自定义静态of方法来访问InheritedWidget共享的数据和方法;
  • 有效前提条件:子widget必须在InheritedWidget的子树里,才能访问到InheritedWidget共享的数据和方法;否则通过静态of方法获取到的InheritedWidget实例为空;

3、InheritedWidget状态变化通知子widget,如下:

class DependenceWidgetA extends StatefulWidget { @override _DependenceWidgetAState createState() => new _DependenceWidgetAState(); } class _DependenceWidgetAState extends State { @override Widget build(BuildContext context) { //使用InheritedWidget中的共享数据 final shareDataInheritedWidget = ShareDataInheritedWidget.of(context); return Padding( padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0), child: Text( 'DependenceWidgetA : ${shareDataInheritedWidget?.showInfo() ?? 'null'}',textAlign: TextAlign.center, style: TextStyle(fontSize: 20.0), ), ); } @override void didChangeDependencies() { super.didChangeDependencies(); //父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。 //如果build中没有依赖InheritedWidget,则此回调不会被调用。 print("DependenceWidgetA didChangeDependencies"); } }

说明:

  • didChangeDependencies:当子widget依赖的InheritedWidget变化时,将会调用子widget的didChangeDependencies来通知子widget;
  • 有效提前条件:
    (1) 、InheritedWidget的updateShouldNotify返回true;
    (2) 、InheritedWidget自定of静态方法里面实现调用的是dependOnInheritedWidgetOfExactType而不是getElementForInheritedWidgetOfExactType;

InheritedWidget完整使用示例:

自定义InheritedWidget->ShareDataInheritedWidget,用于共享数据和方法,如下:

class ShareDataInheritedWidget extends InheritedWidget { final String name; //用于区分子widget调用的是哪个ShareDataInheritedWidget //共享数据 final InheritedShareModel shareModel; //点击+号的方法 final Function() increment; //点击-号的方法 final Function() reduce; ShareDataInheritedWidget( this.name, { Key key, @required this.shareModel, @required this.increment, @required this.reduce, @required Widget child, }) : super(key: key, child: child); ///定义一个便捷方法,方便子树中的widget获取共享数据--InheritedWidget变化时会通知子widget的didChangeDependencies static ShareDataInheritedWidget of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(); } ///定义一个便捷方法,方便子树中的widget获取共享数据--InheritedWidget变化时不会通知子widget的didChangeDependencies static ShareDataInheritedWidget ofNotNoticeDependence(BuildContext context) { return context .getElementForInheritedWidgetOfExactType() ?.widget; } ///该回调决定当data发生变化时,是否通知子树中依赖data的Widget @override bool updateShouldNotify(ShareDataInheritedWidget oldWidget) { //如果返回true,则子树中依赖(build函数中有调用)本widget //的子widget的`state.didChangeDependencies`会被调用 return (null != shareModel?.count && null != oldWidget?.shareModel?.count && shareModel.count != oldWidget.shareModel.count); } ///用于展示信息 String showInfo() { return '来自${name ?? ''} InheritedWidget:ncount值为${shareModel?.count ?? ''}'; } } class InheritedShareModel { final int count; const InheritedShareModel(this.count); }

说明:定义name共享变量主要是为了区别子widget访问到的共享数据来自哪个InheritedWidget

自定义InheritedWidget->TestInheritedWidget,用于理解InheritedWidget在树中的位置对子widget访问InheritedWidget的影响,如下:

class TestInheritedWidget extends InheritedWidget { final String name; //用于区分子widget调用的是哪个TestInheritedWidget TestInheritedWidget( this.name, { Key key, @required Widget child, }) : super(key: key, child: child); static TestInheritedWidget of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(); } ///是否重建widget就取决于数据是否相同 @override bool updateShouldNotify(InheritedWidget oldWidget) { return true; } ///用于展示信息 String showInfo() { return '来自${name ?? ''} TestInheritedWidget '; } }

各种widget访问自定义InheritedWidget里的共享数据和方法,如下:

class IncreaseWidget extends StatelessWidget { @override Widget build(BuildContext context) { final shareDataInheritedWidget = ShareDataInheritedWidget.of(context); // print('IncreaseWidget : ${shareDataInheritedWidget?.showInfo()??'null'}'); return Padding( padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0), child: RaisedButton( textColor: Colors.black, child: Text( '+', textAlign: TextAlign.center, ), onPressed: shareDataInheritedWidget?.increment), ); } } class DataWidget extends StatelessWidget { @override Widget build(BuildContext context) { final shareDataInheritedWidget = ShareDataInheritedWidget.of(context); final inheritedShareModel = shareDataInheritedWidget?.shareModel; // print('DataWidget : ${shareDataInheritedWidget?.showInfo() ?? ''}'); return Padding( padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0), child: Text( '来自${shareDataInheritedWidget?.name ?? ''} InheritedWidget:ncount值为${inheritedShareModel?.count ?? ''}', textAlign: TextAlign.center, style: TextStyle(fontSize: 20.0), ), ); } } class ReduceWidget extends StatelessWidget { @override Widget build(BuildContext context) { final shareDataInheritedWidget = ShareDataInheritedWidget.of(context); // print('ReduceWidget : ${shareDataInheritedWidget?.showInfo() ?? ''}'); return Padding( padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0), child: RaisedButton( textColor: Colors.black, child: Text('-', textAlign: TextAlign.center), onPressed: shareDataInheritedWidget?.reduce), ); } } class DependenceWidgetA extends StatefulWidget { @override _DependenceWidgetAState createState() => new _DependenceWidgetAState(); } class _DependenceWidgetAState extends State { @override Widget build(BuildContext context) { //使用InheritedWidget中的共享数据 final shareDataInheritedWidget = ShareDataInheritedWidget.of(context); return Padding( padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0), child: Text( 'DependenceWidgetA : ${shareDataInheritedWidget?.showInfo() ?? 'null'}', textAlign: TextAlign.center, style: TextStyle(fontSize: 20.0), ), ); } @override void didChangeDependencies() { super.didChangeDependencies(); //父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。 //如果build中没有依赖InheritedWidget,则此回调不会被调用。 print("DependenceWidgetA didChangeDependencies"); } } class DependenceWidgetB extends StatefulWidget { @override _DependenceWidgetBState createState() => new _DependenceWidgetBState(); } class _DependenceWidgetBState extends State { @override Widget build(BuildContext context) { //使用InheritedWidget中的共享数据 final shareDataInheritedWidget = ShareDataInheritedWidget.ofNotNoticeDependence(context); return Padding( padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0), child: Text( 'DependenceWidgetB : ${shareDataInheritedWidget?.showInfo() ?? 'null'}', textAlign: TextAlign.center, style: TextStyle(fontSize: 20.0), ), ); } @override void didChangeDependencies() { super.didChangeDependencies(); //父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。 //如果build中没有依赖InheritedWidget,则此回调不会被调用。 print("DependenceWidgetB didChangeDependencies"); } }

在app层使用InheritedWidget,如下:

void main() => runApp(App()); class App extends StatefulWidget { @override _AppState createState() => _AppState(); } class _AppState extends State { InheritedShareModel _inheritedShareModel; @override void initState() { super.initState(); _initData(); } _initData() { _inheritedShareModel = InheritedShareModel(0); //初始为0 } _incrementCount() { setState(() { _inheritedShareModel = InheritedShareModel(_inheritedShareModel.count + 2); //增幅度为2 }); } _reduceCount() { setState(() { _inheritedShareModel = InheritedShareModel(_inheritedShareModel.count - 2); //减幅度为2 }); } @override Widget build(BuildContext context) { return ShareDataInheritedWidget( 'app层-在MaterialApp上', shareModel: _inheritedShareModel, increment: _incrementCount, reduce: _reduceCount, child: MaterialApp( title: 'InheritedWidget使用示例', theme: ThemeData( primarySwatch: Colors.blue, ), home: TestInheritedWidget('app层-在MaterialApp里面', child: HomePage()), ), /* child: TestInheritedWidget('app层-在MaterialApp上',child: MaterialApp( title: 'InheritedWidget使用示例', theme: ThemeData( primarySwatch: Colors.blue, ), home: TestInheritedWidget('app层-在MaterialApp里面', child: HomePage()), ),),*/ ); } }

在home页使用InheritedWidget,如下:

class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State { @override Widget build(BuildContext context) { return HomePageWidget(); } @override void deactivate() { super.deactivate(); print('HomePage page deactivate'); } @override void dispose() { super.dispose(); print('HomePage page dispose'); } } class HomePageWidget extends StatefulWidget { @override State createState() { return _HomePageWidgetState(); } } class _HomePageWidgetState extends State { CiteModel _testCiteModel; InheritedShareModel _inheritedShareModel; @override void initState() { super.initState(); _initData(); _testCiteModel = CiteModel(1); } @override void deactivate() { super.deactivate(); print('test page deactivate ${_testCiteModel ?? 'null'}'); } _initData() { _inheritedShareModel = InheritedShareModel(1); //初始化为1 } _incrementCount() { setState(() { _inheritedShareModel = InheritedShareModel(_inheritedShareModel.count + 1); //增幅度为1 }); } _reduceCount() { setState(() { _inheritedShareModel = InheritedShareModel(_inheritedShareModel.count - 1); //减幅度为1 }); } @override Widget build(BuildContext context) { return ShareDataInheritedWidget('home', shareModel: _inheritedShareModel, increment: _incrementCount, reduce: _reduceCount, child: Scaffold( appBar: AppBar( title: Text('InheritedWidget使用示例'), ), body: Container( child: SingleChildScrollView( child: Container( /* width: double.infinity, height: double.infinity,*/ child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox( height: 20, ), IncreaseWidget(), DataWidget(), ReduceWidget(), DependenceWidgetA(), DependenceWidgetB(), SizedBox( height: 20, ), RaisedButton( child: Text("下一页"), onPressed: () { Navigator.push( //跳转到第二个界面 context, MaterialPageRoute(builder: (context) { return NextPage(); }), ); }, ), SizedBox( height: 20, ), RaisedButton( child: Text("home context"), onPressed: () { final ShareDataInheritedWidget shareDataInheritedWidget = ShareDataInheritedWidget.of(context); //home context print( 'home context ShareDataInheritedWidget : ${shareDataInheritedWidget?.showInfo() ?? 'null'}'); final TestInheritedWidget testInheritedWidget = TestInheritedWidget.of(context); print( 'home context TestInheritedWidget: ${testInheritedWidget?.showInfo() ?? 'null'}'); }, ), SizedBox( height: 20, ), Builder(builder: (BuildContext context) { //context变成home 子widget context return RaisedButton( child: Text("home 子widget context"), onPressed: () { final ShareDataInheritedWidget shareDataInheritedWidget = ShareDataInheritedWidget.of( context); //home 子widget context print( 'home 子widget context ShareDataInheritedWidget: ${shareDataInheritedWidget?.showInfo() ?? 'null'}'); final TestInheritedWidget testInheritedWidget = TestInheritedWidget.of(context); print( 'home 子widget context TestInheritedWidget: ${testInheritedWidget?.showInfo() ?? 'null'}'); }, ); }), SizedBox( height: 20, ), RaisedButton( child: Text("测试type作为map key"), onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) { return TestTypePage(); }), ); }, ), SizedBox( height: 20, ), RaisedButton( child: Text("测试引用计数"), onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) { return TestModelCitePage(_testCiteModel); }), ); }, ), Padding( padding: const EdgeInsets.only( left: 10.0, top: 10.0, right: 10.0), child: Text( 'Theme.of(context).textThemenMediaQuery.of(context).size等n就是通过InheritedWidget实现的', style: TextStyle(fontSize: 20.0), ), ), ], ), ), ), width: double.infinity, height: double.infinity, ), )); } }

从home页用路由跳到下一页使用InheritedWidget,如下:

class NextPage extends StatefulWidget { @override _NextPageState createState() => _NextPageState(); } class _NextPageState extends State { @override void initState() { super.initState(); //直接在iniState使用InheritedWidget会报错 /*ShareDataInheritedWidget shareDataInheritedWidget = ShareDataInheritedWidget.of(context); print( 'next page initState shareDataInheritedWidget: ${shareDataInheritedWidget?.showInfo() ?? 'null'}');*/ Future.delayed(Duration.zero, () { final ShareDataInheritedWidget shareDataInheritedWidget = ShareDataInheritedWidget.of(context); print( 'next page initState delayed shareDataInheritedWidget: ${shareDataInheritedWidget?.showInfo() ?? 'null'}'); }); } @override Widget build(BuildContext context) { final TestInheritedWidget testInheritedWidget = TestInheritedWidget.of(context); print( 'next page build TestInheritedWidget: ${testInheritedWidget?.showInfo() ?? 'null'}'); final ShareDataInheritedWidget shareDataInheritedWidget = ShareDataInheritedWidget.of(context); print( 'next page build ShareDataInheritedWidget: ${shareDataInheritedWidget?.showInfo() ?? 'null'}'); return Scaffold( appBar: AppBar( title: Text('下一页'), ), body: Container( width: double.infinity, height: double.infinity, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ IncreaseWidget(), DataWidget(), ReduceWidget(), ], ), ), ); } }

操作如下:

数据传递/状态管理 一InheritedWidget使用示例
效果.gif

示例说明:

  • 自定义InheritedWidget通过name参数来区分InheritedWidget的类别和相同类别InheritedWidget在widget中的位置;
  • 界面展示的是子widget访问到的InheritedWidget是在widget树中离它最近的父InheritedWidget;
    +界面展示的是路由页面之前的InheritedWidget不能共享,除非InheritedWidget为MaterialApp祖先;
  • 更多内容请结合代码注释和demo理解;

详细信息请运行demo体验:InheritedWidget_Sample

从示例中可以得出以下结论:

  • 子widget通过自定义InheritedWidget提供的of静态方法来访问自定义InheritedWidget的共享数据和方法;
  • 子widget和InheritedWidget必须在同一棵树上,并且InheritedWidget为子widget的祖先,这样子widget才能访问到InheritedWidget共享的数据和方法;
  • 当widget树中存在多个相同类别的InheritedWidget,子widget访问到的是离它最近的父InheritedWidget;
  • InheritedWidget状态变化时,只有当InheritedWidget的of静态方法是dependOnInheritedWidgetOfExactType实现,并且updateShouldNotify返回true,才会通知子widget的didChangeDependencies;
  • 用路由切换的页面,由于不在同一棵widget上,所以InheritedWidget在路由页面之间不起作用;
  • 只有作为MaterialApp祖先的InheritedWidget,才能在路由页面之间共享;

InheritedWidget内部原理分析:

InheritedWidget原理分析请移步:InheritedWidget内部实现原理浅析


推荐阅读
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 在Kubernetes上部署JupyterHub的步骤和实验依赖
    本文介绍了在Kubernetes上部署JupyterHub的步骤和实验所需的依赖,包括安装Docker和K8s,使用kubeadm进行安装,以及更新下载的镜像等。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 本文介绍了在多平台下进行条件编译的必要性,以及具体的实现方法。通过示例代码展示了如何使用条件编译来实现不同平台的功能。最后总结了只要接口相同,不同平台下的编译运行结果也会相同。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • CEPH LIO iSCSI Gateway及其使用参考文档
    本文介绍了CEPH LIO iSCSI Gateway以及使用该网关的参考文档,包括Ceph Block Device、CEPH ISCSI GATEWAY、USING AN ISCSI GATEWAY等。同时提供了多个参考链接,详细介绍了CEPH LIO iSCSI Gateway的配置和使用方法。 ... [详细]
author-avatar
莫轻松
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有