Flutter之TabBarView组件项目实战示例

目录
  • TabBarView
  • TabBar
  • TabBarView+项目实战
    • 1 构建导航头部搜索框
    • 2 构建导航头部TabBar
    • 3 构建导航底部TabBarView容器
    • 4 构建导航底部结构填充
    • 5 构建导航底部结构轮播图
    • 6 构建导航底部结构信息流
  • 总结

TabBarView

TabBarView 是 Material 组件库中提供了 Tab 布局组件,通常和 TabBar 配合使用。

TabBarView 封装了 PageView,它的构造方法:

 TabBarView({
  Key? key,
  required this.children, // tab 页
  this.controller, // TabController
  this.physics,
  this.dragStartBehavior = DragStartBehavior.start,
})

TabController 用于监听和控制 TabBarView 的页面切换,通常和 TabBar 联动。如果没有指定,则会在组件树中向上查找并使用最近的一个 DefaultTabController

TabBar

TabBar 为 TabBarView 的导航标题,如下图所示

TabBar 有很多配置参数,通过这些参数我们可以定义 TabBar 的样式,很多属性都是在配置 indicator 和 label,拿上图来举例,Label 是每个Tab 的文本,indicator 指 “新闻” 下面的白色下划线。

const TabBar({
  Key? key,
  required this.tabs, // 具体的 Tabs,需要我们创建
  this.controller,
  this.isScrollable = false, // 是否可以滑动
  this.padding,
  this.indicatorColor,// 指示器颜色,默认是高度为2的一条下划线
  this.automaticIndicatorColorAdjustment = true,
  this.indicatorWeight = 2.0,// 指示器高度
  this.indicatorPadding = EdgeInsets.zero, //指示器padding
  this.indicator, // 指示器
  this.indicatorSize, // 指示器长度,有两个可选值,一个tab的长度,一个是label长度
  this.labelColor,
  this.labelStyle,
  this.labelPadding,
  this.unselectedLabelColor,
  this.unselectedLabelStyle,
  this.mouseCursor,
  this.onTap,
  ...
})

TabBar 通常位于 AppBar 的底部,它也可以接收一个 TabController ,如果需要和 TabBarView 联动, TabBarTabBarView 使用同一个 TabController 即可,注意,联动时 TabBarTabBarView 的孩子数量需要一致。如果没有指定 controller,则会在组件树中向上查找并使用最近的一个 DefaultTabController 。另外我们需要创建需要的 tab 并通过 tabs 传给 TabBar, tab 可以是任何 Widget,不过Material 组件库中已经实现了一个 Tab 组件,我们一般都会直接使用它:

const Tab({
  Key? key,
  this.text, //文本
  this.icon, // 图标
  this.iconMargin = const EdgeInsets.only(bottom: 10.0),
  this.height,
  this.child, // 自定义 widget
})

注意,textchild 是互斥的,不能同时制定。

全部代码:

import 'package:flutter/material.dart';
/// @Author wywinstonwy
/// @Date 2022/1/18 9:09 上午
/// @Description:
class MyTabbarView1 extends StatefulWidget {
  const MyTabbarView1({Key? key}) : super(key: key);
  @override
  _MyTabbarView1State createState() => _MyTabbarView1State();
}
class _MyTabbarView1State extends State<MyTabbarView1>with SingleTickerProviderStateMixin {
  List<String> tabs =['头条','新车','导购','小视频','改装赛事'];
  late TabController tabController;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    tabController = TabController(length: tabs.length, vsync: this);
  }
  @override
  void dispose() {
    tabController.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TabbarView',textAlign: TextAlign.center,),
        bottom:TabBar(
            unselectedLabelColor: Colors.white.withOpacity(0.5),
            labelColor: Colors.white,
            // indicatorSize:TabBarIndicatorSize.label,
            indicator:const UnderlineTabIndicator(),
            controller: tabController,
            tabs: tabs.map((e){
              return Tab(text: e,);
            }).toList()) ,
      ),
      body: Column(
      children: [
        Expanded(
          flex: 1,
          child:  TabBarView(
            controller: tabController,
            children: tabs.map((e){
              return Center(child: Text(e,style: TextStyle(fontSize: 50),),);
            }).toList()),)
      ],),
    );
  }
}

运行效果:

滑动页面时顶部的 Tab 也会跟着动,点击顶部 Tab 时页面也会跟着切换。为了实现 TabBar 和 TabBarView 的联动,我们显式创建了一个 TabController,由于 TabController 又需要一个 TickerProvider (vsync 参数), 我们又混入了 SingleTickerProviderStateMixin;

由于 TabController 中会执行动画,持有一些资源,所以我们在页面销毁时必须得释放资源(dispose)。综上,我们发现创建 TabController 的过程还是比较复杂,实战中,如果需要 TabBar 和 TabBarView 联动,通常会创建一个 DefaultTabController 作为它们共同的父级组件,这样它们在执行时就会从组件树向上查找,都会使用我们指定的这个 DefaultTabController。

我们修改后的实现如下:

class TabViewRoute2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    List tabs = ["新闻", "历史", "图片"];
    return DefaultTabController(
      length: tabs.length,
      child: Scaffold(
        appBar: AppBar(
          title: Text("App Name"),
          bottom: TabBar(
            tabs: tabs.map((e) => Tab(text: e)).toList(),
          ),
        ),
        body: TabBarView( //构建
          children: tabs.map((e) {
            return KeepAliveWrapper(
              child: Container(
                alignment: Alignment.center,
                child: Text(e, textScaleFactor: 5),
              ),
            );
          }).toList(),
        ),
      ),
    );
  }
}

可以看到我们无需去手动管理 Controller 的生命周期,也不需要提供 SingleTickerProviderStateMixin,同时也没有其它的状态需要管理,也就不需要用 StatefulWidget 了,这样简单很多。

TabBarView+项目实战

实现导航信息流切换效果并缓存前面数据:

1 构建导航头部搜索框

import 'package:flutter/material.dart';
import 'package:qctt_flutter/constant/colors_definition.dart';
enum SearchBarType { home, normal, homeLight }
class SearchBar extends StatefulWidget {
  final SearchBarType searchBarType;
  final String hint;
  final String defaultText;
  final void Function()? inputBoxClick;
  final void Function()? cancelClick;
  final ValueChanged<String>? onChanged;
  SearchBar(
      {this.searchBarType = SearchBarType.normal,
      this.hint = '搜一搜你感兴趣的内容',
      this.defaultText = '',
      this.inputBoxClick,
      this.cancelClick,
      this.onChanged});
  @override
  _SearchBarState createState() => _SearchBarState();
}
class _SearchBarState extends State<SearchBar> {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      height: 74,
      child: searchBarView,
    );
  }
  Widget get searchBarView {
    if (widget.searchBarType == SearchBarType.normal) {
      return _genNormalSearch;
    }
    return _homeSearchBar;
  }
  Widget get _genNormalSearch {
    return Container(
        color: Colors.white,
        padding: EdgeInsets.only(top: 40, left: 20, right: 60, bottom: 5),
        child: Container(
          height: 30,
          decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(6),
              color: Colors.grey.withOpacity(0.5)),
          padding: EdgeInsets.only(left: 5, right: 5),
          child: Row(
            children: [
              const Icon(
                Icons.search,
                color: Colors.grey,
                size: 24,
              ),
              Container(child: _inputBox),
              const Icon(
                Icons.clear,
                color: Colors.grey,
                size: 24,
              )
            ],
          ),
        ),);
  }
  //可编辑输入框
  Widget get _homeSearchBar{
    return  Container(
      padding: EdgeInsets.only(top: 40, left: 20, right: 40, bottom: 5),
      decoration: BoxDecoration(gradient: LinearGradient(
          colors: [mainColor,mainColor.withOpacity(0.2)],
          begin:Alignment.topCenter,
          end: Alignment.bottomCenter
      )),
      child: Container(
        height: 30,
        decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(6),
            color: Colors.grey.withOpacity(0.5)),
        padding: EdgeInsets.only(left: 5, right: 5),
        child: Row(
          children: [
            const Icon(
              Icons.search,
              color: Colors.grey,
              size: 24,
            ),
            Container(child: _inputBox),
          ],
        ),
      ),);
  }
 //构建文本输入框
  Widget get _inputBox {
    return Expanded(
      child: TextField(
        style: const TextStyle(
            fontSize: 18.0, color: Colors.black, fontWeight: FontWeight.w300),
        decoration: InputDecoration(
//                   contentPadding: EdgeInsets.fromLTRB(1, 3, 1, 3),
//                   contentPadding: EdgeInsets.only(bottom: 0),
            contentPadding:
                const EdgeInsets.symmetric(vertical: 0, horizontal: 12),
            border: InputBorder.none,
            hintText: widget.hint,
            hintStyle: TextStyle(fontSize: 15),
            enabledBorder: const OutlineInputBorder(
              // borderSide: BorderSide(color: Color(0xFFDCDFE6)),
              borderSide: BorderSide(color: Colors.transparent),
              borderRadius: BorderRadius.all(Radius.circular(4.0)),
            ),
            focusedBorder: const OutlineInputBorder(
                borderRadius: BorderRadius.all(Radius.circular(8)),
                borderSide: BorderSide(color: Colors.transparent))),
      ),
    );
    ;
  }
}

通常一个应该会出现多出输入框,但是每个地方的输入框样式和按钮功能类型会有一定的区别,可以通过初始化传参的方式进行区分。如上面事例中enum SearchBarType { home, normal, homeLight }枚举每个功能页面出现SearchBar的样式和响应事件。

2 构建导航头部TabBar

//导航tabar 关注 头条 新车 ,,。
_buildTabBar() {
  return TabBar(
      controller: _controller,
      isScrollable: true,//是否可滚动
      labelColor: Colors.black,//文字颜色
      labelPadding: const EdgeInsets.fromLTRB(20, 0, 10, 5),
      //下划线样式设置
      indicator: const UnderlineTabIndicator(
        borderSide: BorderSide(color: Color(0xff2fcfbb), width: 3),
        insets: EdgeInsets.fromLTRB(0, 0, 0, 10),
      ),
      tabs: tabs.map<Tab>((HomeChannelModel model) {
        return Tab(
          text: model.name,
        );
      }).toList());
}

因为Tabbar需要和TabBarView进行联动,需要定义一个TabController进行绑定

3 构建导航底部TabBarView容器

//TabBarView容器 信息流列表
_buildTabBarPageView() {
  return KeepAliveWrapper(child:Expanded(
      flex: 1,
      child: Container(
        color: Colors.grey.withOpacity(0.3),
        child: TabBarView(
          controller: _controller,
          children: _buildItems(),
        ),
      )));
}

4 构建导航底部结构填充

底部内容结构包含轮播图左右切换,信息流上下滚动,下拉刷新,上拉加载更多、刷新组件用到SmartRefresher,轮播图和信息流需要拼接,需要用CustomScrollView

代码如下:

_buildRefreshView() {
  //刷新组件
  return SmartRefresher(
    controller: _refreshController,
    enablePullDown: true,
    enablePullUp: true,
    onLoading: () async {
      page++;
      print('onLoading $page');
      //加载频道数据
      widget.homeChannelModel.termId == 0 ? _getTTHomeNews() : _getHomeNews();
    },
    onRefresh: () async {
      page = 1;
      print('onRefresh $page');
      //加载频道数据
      widget.homeChannelModel.termId == 0 ? _getTTHomeNews() : _getHomeNews();
    },
    //下拉头部UI样式
    header: const WaterDropHeader(
      idleIcon: Icon(
        Icons.car_repair,
        color: Colors.blue,
        size: 30,
      ),
    ),
    //上拉底部UI样式
    footer: CustomFooter(
      builder: (BuildContext context, LoadStatus? mode) {
        Widget body;
        if (mode == LoadStatus.idle) {
          body = const Text("pull up load");
        } else if (mode == LoadStatus.loading) {
          body = const CupertinoActivityIndicator();
        } else if (mode == LoadStatus.failed) {
          body = const Text("Load Failed!Click retry!");
        } else if (mode == LoadStatus.canLoading) {
          body = const Text("release to load more");
        } else {
          body = const Text("No more Data");
        }
        return Container(
          height: 55.0,
          child: Center(child: body),
        );
      },
    ),
    //customScrollview拼接轮播图和信息流。
    child: CustomScrollView(
      slivers: [
        SliverToBoxAdapter(
                child: _buildFutureScroll()
              ),
        SliverList(
          delegate: SliverChildBuilderDelegate((content, index) {
            NewsModel newsModel = newsList[index];
            return _buildChannelItems(newsModel);
          }, childCount: newsList.length),
        )
      ],
    ),
  );
}

5 构建导航底部结构轮播图

轮播图单独封装SwiperView小组件

//首页焦点轮播图数据获取
_buildFutureScroll(){
  return FutureBuilder(
      future: _getHomeFocus(),
      builder: (BuildContext context, AsyncSnapshot&lt;FocusDataModel&gt; snapshot){
        print('轮播图数据加载 ${snapshot.connectionState} 对应数据:${snapshot.data}');
        Container widget;
        switch(snapshot.connectionState){
          case ConnectionState.done:
            if(snapshot.data != null){
              widget = snapshot.data!.focusList!.isNotEmpty?Container(
                height: 200,
                width: MediaQuery.of(context).size.width,
                child: SwiperView(snapshot.data!.focusList!,
                    MediaQuery.of(context).size.width),
              ):Container();
            }else{
              widget = Container();
            }
            break;
          case ConnectionState.waiting:
            widget = Container();
            break;
          case ConnectionState.none:
            widget = Container();
            break;
          default :
            widget = Container();
            break;
        }
        return widget;
      });
}

轮播图组件封装,整体基于第三方flutter_swiper_tv

import "package:flutter/material.dart";
import 'package:flutter_swiper_tv/flutter_swiper.dart';
import 'package:qctt_flutter/http/api.dart';
import 'package:qctt_flutter/models/home_channel.dart';
import 'package:qctt_flutter/models/home_focus_model.dart';
class SwiperView extends StatelessWidget {
  // const SwiperView({Key? key}) : super(key: key);
  final double width;
  final List<FocusItemModel> items;
  const SwiperView(this.items,this.width,{Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Swiper(
      itemCount: items.length,
      itemWidth: width,
      containerWidth: width,
      itemBuilder: (BuildContext context,int index){
        FocusItemModel focusItemModel = items[index];
        return Stack(children: [
          Container(child:Image.network(focusItemModel.picUrlList![0],fit: BoxFit.fitWidth,width: width,))
        ],
        );
      },
      pagination: const SwiperPagination(),
      // control: const SwiperControl(),
    );
  }
}

6 构建导航底部结构信息流

信息流比较多,每条信息流样式各一,具体要根据服务端返回的数据进行判定。如本项目不至于22种样式,

  _buildChannelItems(NewsModel model) {
    //0,无图,1单张小图 3、三张小图 4.大图推广 5.小图推广 6.专题(统一大图)
// 8.视频小图,9.视频大图 ,,11.banner广告,12.车展,
// 14、视频直播 15、直播回放 16、微头条无图 17、微头条一图
// 18、微头条二图以上 19分组小视频 20单个小视频 22 文章折叠卡片(关注频道)
    switch (model.style) {
      case '1':
        return GestureDetector(
          child: OnePicArticleView(model),
          onTap: ()=>_jumpToPage(model),
        );
      case '3':
        return GestureDetector(
          child: ThreePicArticleView(model),
          onTap: ()=>_jumpToPage(model),
        );
      case '4':
        return GestureDetector(
          child: AdBigPicView(newsModel: model,),
            onTap: ()=>_jumpToPage(model),) ;
      case '9':
        return GestureDetector(
          child: Container(
          padding: const EdgeInsets.only(left: 10, right: 10),
          child: VideoBigPicView(model),
        ),
        onTap: ()=>_jumpToPage(model),
        );
      case '15':
        return GestureDetector(
          child: Container(
            width: double.infinity,
            padding: const EdgeInsets.only(left: 10, right: 10),
            child: LiveItemView(model),
          ),
          onTap: ()=>_jumpToPage(model),
        );
      case '16'://16、微头条无图
        return GestureDetector(
          child: Container(
            width: double.infinity,
            padding: const EdgeInsets.only(left: 10, right: 10),
            child: WTTImageView(model),
          ),
          onTap: ()=>_jumpToPage(model),
        );
      case '17'://17、微头条一图
        return GestureDetector(
          child: Container(
            width: double.infinity,
            padding: const EdgeInsets.only(left: 10, right: 10),
            child: WTTImageView(model),
          ),
          onTap:()=> _jumpToPage(model),
        );
      case '18'://18、微头条二图以上
        //18、微头条二图以上
        return GestureDetector(
          child: Container(
            width: double.infinity,
            padding: const EdgeInsets.only(left: 10, right: 10),
            child: WTTImageView(model),
          ),
          onTap: ()=>_jumpToPage(model),
        );
      case '19': //19分组小视频
        return Container(
          width: double.infinity,
          padding: const EdgeInsets.only(left: 10, right: 10),
          child: SmallVideoGroupView(model.videoList),
        );
      case '20':
      //20小视频 左上方带有蓝色小视频标记
        return Container(
          padding: const EdgeInsets.only(left: 10, right: 10),
          child: VideoBigPicView(model),
        );
      default:
        return Container(
          height: 20,
          color: Colors.blue,
        );
    }
  }

每种样式需要单独封装Cell组件视图。

通过_buildChannelItems(NewsModel model)方法返回的是单独的Cell视图,需要提交给对应的list进行组装:

SliverList(
  delegate: SliverChildBuilderDelegate((content, index) {
    NewsModel newsModel = newsList[index];
    return _buildChannelItems(newsModel);
  }, childCount: newsList.length),
)

这样整个App首页的大体结构就完成了,包含App顶部搜索,基于Tabbar的头部频道导航。TabbarView头部导航联动。CustomScrollView对轮播图信息流进行拼接,等。网络数据是基于Dio进行了简单封装,具体不在这里细说。具体接口涉及隐私,不展示。

至于底部BottomNavigationBar会在后续组件介绍的时候详细介绍到。

总结

本章主要介绍了TabBarView的基本用法以及实际复杂项目中TabBarView的组合使用场景,更多关于Flutter TabBarView组件的资料请关注我们其它相关文章!

时间: 2022-10-27

使用PlatformView将 Android 控件view制作成Flutter插件

目录 引言 1. FlutterPlugin 创建 2. 创建 Android 控件 3. 注册 Android 控件 4. 封装 Android 层通信交互 ‘CustomViewController’ 代码说明 5. 在 flutter 中如何使用已注册的 Android 控件(view) 代码说明 如何使用这个View 6. 附上 example 完整代码 引言 小编最近在项目中实现相机识别人脸的功能,将 Android 封装的控件 view 进行中转,制作成 FlutterPlugin

Flutter之PageView页面缓存与KeepAlive

目录 正文 构造函数 页面缓存 KeepAlive KeepAliveWrapper 总结 正文 如果要实现页面切换和 Tab 布局,我们可以使用 PageView 组件.需要注意,PageView 是一个非常重要的组件,因为在移动端开发中很常用,比如大多数 App 都包含 Tab 换页效果.图片轮动以及抖音上下滑页切换视频功能等等,这些都可以通过 PageView 轻松实现. 构造函数 PageView({ Key? key, this.scrollDirection = Axis.horiz

Android开发之Flutter与webview通信桥梁实现

前言 最近业务有需求需要在flutter内使用webview进行内嵌H5进行展示,此时需要涉及到H5与flutter之间额通信问题.比如发送消息或者H5调用Flutter的相机等等 webview选型 这里我们使用官方维护的插件webview_flutter 如何通信? webview在初始化的时候需要向容器内注册一个全局方法供H5进行调用 WebView( initialUrl: 'https://flutter.dev', javascriptMode: JavascriptMode.unr

Flutter开发Widgets 之 PageView使用示例

目录 构造方法以及参数: 基本用法 无限滚动 实现指示器 切换动画 总结: 构造方法以及参数: PageView可用于Widget的整屏滑动切换,如当代常用的短视频APP中的上下滑动切换的功能,也可用于横向页面的切换,如APP第一次安装时的引导页面,也可用于开发轮播图功能. PageView({ Key? key, this.scrollDirection = Axis.horizontal, // 设置滚动方向 垂直 / 水平 this.reverse = false, // 反向滚动 Pag

Flutter之 ListView组件使用示例详解

目录 ListView的默认构造函数定义 默认构造函数 ListView.builder ListView.separated 固定高度列表 ListView 原理 实例:无限加载列表 添加固定列表头 总结 ListView的默认构造函数定义 ListView是最常用的可滚动组件之一,它可以沿一个方向线性排布所有子组件,并且它也支持列表项懒加载(在需要时才会创建).我们看看ListView的默认构造函数定义: ListView({ ... //可滚动widget公共参数 Axis scrollD

Flutter 中 Dart的Mixin示例详解

原文在这里.写的不错,推荐各位看原文. 这里补充一下Mixin的定义: 只要一个类是继承自Object的而且没有定义构造方法,那么这个类可以是一个Mixin了.当然,如果你想让mixin的定义更加的清晰,可以使用mixin关键字开头来定义.具体请参考这里 原文截图体会一下风格. 正文 在经典的面向对象编程语言里一定会有常规的类,抽象类和接口.当然,Dart也有它自己的接口,不过那是另外的文章要说的.有的时候阴影里潜伏者另外的野兽:Mixin!这是做什么的,如何使用?我们来一起发现. 没有mixi

封装Vue Element的table表格组件的示例详解

在封装Vue组件时,我依旧会交叉使用函数式组件的方式来实现.关于函数式组件,我们可以把它想像成组件里的一个函数,入参是渲染上下文(render context),返回值是渲染好的HTML(VNode).它比较适用于外层组件仅仅是对内层组件的一次逻辑封装,而渲染的模板结构变化扩展不多的情况,且它一定是无状态.无实例的,无状态就意味着它没有created.mounted.updated等Vue的生命周期函数,无实例就意味着它没有响应式数据data和this上下文. 我们先来一个简单的Vue函数式组件

jQuery.Validate表单验证插件的使用示例详解

jQuery Validate 插件为表单提供了强大的验证功能,让客户端表单验证变得更简单,同时提供了大量的定制选项,满足应用程序各种需求. 请在这里查看示例 validate示例 示例包含 验证错误时,显示红色错误提示 自定义验证规则 引入中文错误提示 重置表单需要执行2句话 源码示例 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <

Angular2 组件交互实例详解

1. 组件通信 我们知道Angular2应用程序实际上是有很多父子组价组成的组件树,因此,了解组件之间如何通信,特别是父子组件之间,对编写Angular2应用程序具有十分重要的意义,通常来讲,组件之间的交互方式主要有如下几种: l 使用输入型绑定,把数据从父组件传到子组件 l 通过 setter 拦截输入属性值的变化 l 使用 ngOnChanges 拦截输入属性值的变化 l 父组件监听子组件的事件 l 父组件与子组件通过本地变量互动 l 父组件调用 ViewChild l 父组件和子组件通过服

.NetCore实现上传多文件的示例详解

本章和大家分享的是.NetCore的MVC框架上传文件的示例,主要讲的内容有:form方式提交上传,ajax上传,ajax提交+上传进度效果,Task并行处理+ajax提交+上传进度,相信当你读完文章内容后能后好的收获,如果可以不妨点个赞:由于昨天电脑没电了,快要写完的内容没有保存,今天早上提前来公司从头开始重新,断电这情况的确让人很头痛啊,不过为了社区的分享环境,这也是值得的,不多说了来进入今天的正篇环节吧: form方式上传一组图片 先来看看咋们html的代码,这里先简单说下要上传文件必须要

React学习笔记之列表渲染示例详解

前言 本文主要给大家介绍了关于React列表渲染的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 示例详解: 列表渲染也很简单,利用map方法返回一个新的渲染列表即可,例如: const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.map((number) => <li>{number}</li> ); ReactDOM.render( <ul>{listItems}<

vue组件与复用详解

一.什么是组件 组件 (Component) 是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码. 二.组件用法 组件需要注册后才可以使用,注册有全局注册和局部注册两种方式. 2.1 全局注册后,任何V ue 实例都可以使用.如: <div id="app1"> <my-component></my-component> </div> Vue.component('my-component',{ templ

vue 注册组件的使用详解

一.介绍 组件系统是Vue.js其中一个重要的概念,它提供了一种抽象,让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树 那么什么是组件呢? 组件可以扩展HTML元素,封装可重用的HTML代码,我们可以将组件看作自定义的HTML元素. 二.如何注册组件 Vue.js的组件的使用有3个步骤:创建组件构造器.注册组件和使用组件. 下面用代码演示这三步 <!DOCTYPE html> <html> <body> <div id=&q

vue 之 .sync 修饰符示例详解

在一些情况下,我们可能会需要对一个 prop (父子组件传递数据的属性) 进行"双向绑定". 在vue 1.x 中的 .sync 修饰符所提供的功能.当一个子组件改变了一个带 .sync 的prop的值时,这个变化也会同步到父组件中所绑定的值. 这很方便,但也会导致问题,因为它破坏了单向数据流.(数据自上而下流,事件自下而上走) 由于子组件改变 prop 的代码和普通的状体改动代码毫无区别,所以当你光看子组件的代码时,你完全不知道它合适悄悄地改变了父组件的状态. 这在 debug 复杂

vue动态注册组件实例代码详解

写本篇文章之前其实也关注过vue中的一个关于加载动态组件is的API,最开始研究它只是用来实现一个tab切换的功能,使用起来也蛮不错的. is 预期:string | Object (组件的选项对象) 用于动态组件且基于 DOM 内模板的限制来工作. 示例: <!-- 当 `currentView` 改变时,组件也跟着改变 --> <component v-bind:is="currentView"></component> 详见vue API中关于