普通视图

发现新文章,点击刷新页面。
今天 — 2026年4月21日首页

VUE开发环境配置基础(构建工具→单文件组件SFC→css预处理器sass→eslint)及安装脚手架

作者 RONIN
2026年4月20日 17:44

VUE开发环境配置基础(构建工具→单文件组件SFC→css预处理器sass→eslint)

一、构建工具

作用:

打包压缩、转换(.vue文件转换成浏览器能识别的html、css、js)

内置了web服务器可进行热更新

  • webpack构建工具
  • vite构建工具

使用:

  1. npm init -y初始化项目,生成package.json文件
  2. npm i vite -D(npm install vite -D)安装vite构建工具(-S生产环境,-D局部安装/开发环境,-G全局安装)

安装之后可以使用vite命令启动内置web服务器

但开发中一般会在package.json文件中配置dev、build

"scripts": {
    "dev": "vite --host",
    "build": "vite build",
    "preview": "vite preview",
    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
  },

 

  1. 执行npm run dev命令启动内置web服务器
  2. 执行npm run build打包项目,打包后会在根目录下生成一个dist文件夹,该文件夹下存放的就是打包压缩后的包

二、单文件组件SFC(.vue结尾的文件)包括<template><script><style>

  1. npm i @vitejs/plugin-vue -S 安装vite构建工具解析.vue文件的插件

根目录下创建vite.config.js文件,配置集成该插件

import vue from '@vitejs/plugin-vue' // vite构建工具解析 .vue文件的插件
import {defineConfig} from 'vite'//defineConfig方法,编写代码会有提示
export default defineConfig({
    plugins:[vue()] // 集成插件
})

2. npm i vue -S 安装vue框架

main.js

// import {createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import { createApp } from 'vue'

// import App from './App.js'
import App from './App.vue'

createApp(App).mount('#app')

App.vue

<!-- 模板 -->
<template>
    <div class="g-wrapper">
        <h2>单文件组件 SFC</h2>
        <p>{{message}}</p>
        <table>
            <tr>
                <th>序号</th>
                <th>名称</th>
                <th>价格</th>
            </tr>

            <!--  绑定key目的: 虚拟dom diff算法能够快速找到列表项,对列表项进行高效操作 -->
            <tr v-for="item,index in list" :key="item.id">
                 <td>{{item.id}}</td>
                 <td>{{item.name}}</td>
                 <td>{{item.price}}</td>
            </tr>
        </table>
        <ComA></ComA>
    </div>
</template>

<!-- js代码 -->
<script>
import ComA from './components/ComA.vue'
export default {
    components:{
        ComA
    },
    data() {
        return {
            message:'根组件App.vue',
            list:[
                {id:1001,name:'javascript编程',price:99.89},
                {id:1002,name:'css编程',price:89.89},
                {id:1003,name:'vue编程',price:178.88},
            ]
        }
    }
}
</script>

<!-- css样式 -->
<style scoped>
.g-wrapper{
    width: 1200px;
    margin: 100px auto;
}

.g-wrapper table{
    width: 100%;
    text-align: center;
}

.g-wrapper table tr td,th{
    border-bottom: 1px dotted gray;
    line-height: 40px;
}
</style>

ComA.vue

<template>
  <div class="g-wrapper">
    <h2>组件ComA</h2>
  </div>
</template>

<script>
export default {};
</script>

<!-- scoped :样式作用域只在当前组件生效 -->
<style scoped>
h2 {
  color: red;
}
</style>

三、css预处理器less,sass,stylus

sass(两个版本:sass、scss。scss是sass的升级版,完全兼容css)

官网:www.sass.hk/

npm i sass -D 安装sass(vite构建工具内置了sass库,安装后不需要配置)

1. 导入样式可以在main.js入口文件中导入,也可以在某个模块中导入

scss文件导入总结:

  • main.js入口文件:import  ‘./text.scss’
  • .vue文件:@import  url(./text.scss)
  • .scss同级文件:@import  ‘./text.scss’

main.js

import { createApp } from 'vue'
// 模块化导入样式
import "./assets/scss/text.scss";

import App from './App.vue'
createApp(App).mount('#app')

.vue文件

<style scoped>
// 导入样式
@import url(../assets/sass/test.scss);
</style>

2. scss语法

变量$btn、混合器@mixin可单独封装一个文件,便于维护

1>.定义变量:

$变量名:值

$c: blue; // scss定义变量
$h: 200px;
$btnH: 40px;
2>.嵌套语法:
//css写法
// .g-container {
//   background-color: pink;
//   height: $h;
// }

// .g-container h2 {
//     font-size: 18px;
//     color: $c;
// }

// 嵌套语法
.g-container {
  background-color: pink;
  height: $h;
  h2 {
    font-size: 18px;
    color: $c;
  }
}
3>.混合器:样式封装

定义混合器@mixin 混合器名{}(相当于函数function 函数名)

@mixin btn1{
    display: inline-block;
    //封装样式
    width: 100px;
    height: $btnH;
    text-align: center;
    line-height: $btnH;
    border: none;
    outline: none;
    background-color: skyblue;
    border-radius: 5px;
}

使用封装的混合器(@include 混合器名)

.m-a1 {
    color: blue;
    margin: 10px;
    @include btn1;
  }
4>.鼠标悬停(伪类&)
//css写法
// .m-a1:hover{
//   background-color: #3eb8e9;
// }

.m-a1 {
  color: blue;
  margin: 10px;
  @include btn1;
  &:hover {
    background-color: #3eb8e9;
  }
}
5>.控制指令@if

@if 表达式返回值不是false或者null时,条件成立,输出(}内的代码。

@if 声明后面可以跟多个@else if 声明,或者一个@else 声明。

$type: monster;

P{
@if $type == ocean {color: blue;}
@else if $type == matador {color: red;}
@else if $type == monster {color: green;}
@else {color: black;}
}

更多语法参官网

3.单文件组件中使用scss(lang=”scss”)

<style lang="scss" scoped>
// 模块化导入样式
/*@import url(../assets/sass/test.scss); */

.g-container{
  background-color: pink;
  h2{
    color:red;
  }
  div{
    width: 100px;
    height: 40px;
    background-color: skyblue;
  }
}
</style>

四、eslint一个语法规则和代码风格的检查工具,保证写出语法正确、风格统一的代码

官网:eslint.nodejs.cn/

手动集成:

  1. npm i eslint -D(yarn add eslint -D) 安装eslint
  2. npx eslint --init 初始化项目eslint,生成.eslintrc.js配置文件
/* eslint-disable no-undef */

module.exports = {
    "env": {
        "browser"true,
        "es2021"true
    },

    "extends": [
        "eslint:recommended",
        "plugin:vue/vue3-essential"
    ],

    "overrides": [
    ],

    "parserOptions": {
        "ecmaVersion""latest",
        "sourceType""module"
    },

    "plugins": [
        "vue"
    ],

    "rules": {//自定义规则
        // semi: ['error', 'never'],  // 使用分号结束报错
        // quotes: ['error', 'single'],  // 使用单引号报错
        // eqeqeq: ['error', 'always'],// 使用===,不能使用==
        // 'vue/no-unused-vars': 'error',
    }
}

3. npm i eslint-plugin-vue 安装检查单文件组件的插件 4. vscode搜索安装ESLint插件,自动检测,不符合规则会报错

  1. npm i pretter eslint-config-prettier -D(yarn add pretter eslint-config-prettier -D)安装eslint格式化插件,格式化时自动改正
  2. 根目录下创建配置.prettierrc.json格式化规则文件
{
    "tabWidth": 4,
    "useTabs": false,
    "semi": false,
    "singleQuote": true,
    "TrailingComma": "all",
    "bracketSpacing": true,
    "jsxBracketSameLine": false,
    "arrowParens": "avoid"
}

脚手架(create-vite、vue-cli、create-vue、quasar-cli)

1. create-vite

npm create vite@latest(yarn create vite)安装脚手架命令

2. vue-cli

npm i -g @vue-cli安装脚手架命令

vue create project1创建项目

3. create-vue(vue官方的项目脚手架工具,内置了vite构建工具)项目开发中使用的脚手架

npm init vue@latest安装脚手架命令,根据预设生成相应的配置文件

npm install(npm i) 安装依赖

image.png npm run dev运行

目录结构介绍

image.png

4. quasar-cli项目开发中使用的脚手架

关于quasar要求:

  • Node 12+用于Quasar CLI与Webpack,Node 14+用于Quasar CLI与Vite。
  • Yarn v1(强烈推荐),PNPM,或NPM。

npm i -g @quasar/cli 安装脚手架命令

npm init quasar 初始化quasar根据预设生成相应的配置文件 image.png 此时回车,会生成项目文件和目录 image.png 提示安装项目依赖,选择yes回车 image.png quasar dev(npm run dev)运行

vue2、vue3区别之混入mixins和过滤器filter

作者 RONIN
2026年4月20日 17:17

一、混入mixins

一个包含组件选项的对象数组(可复用),这些选项都将被混入到当前组件的实例中

属性相同时,原组件中的属性会覆盖混入的属性。

vue2多使用

作用:将组件公共的数据方法和生命周期函数提取出来,封装到一个独立对象中,被其它所有组件共享。

实现:

1.MyMixins.js定义混入对象(1.定义混入对象 2.在vue组件中通过mixins选项接收要混入的对象数组 3.使用)

export const mixins1 = {
  data() {
      return {
          message:'这是混入的message'
      }
  },

  methods: {
      plus(){
          console.log('这是混入的plus >>>>')
      }
  },
}

2.App.js引入接收使用

import { mixins1 } from "./mixins/MyMixins.js";

export default {
  mixins: [mixins1],

  data() {
    return {
      title"混入技术",
      vcolor"red",
      message:'这是组件app中message'
    };
  },

  methods: {
    bindUpdateColor() {
      this.vcolor = "blue";
    },
  },

  /*html*/

  template: `<div>
                <h2>{{title}}</h2>
                <p>{{ message }}</p>
                <button @click="plus">确定</button>
            </div>
            `,
};

二、过滤器filter

全局方法,本质是一个函数。

vue2中使用,vue3没有filter过滤器

注册:Vue.filter(过滤器名称,过滤器函数)

调用:  <p>{{  参数|过滤器名称 }}</p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>过滤器</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <!-- <script src="./vue.js"></script> -->
</head>

<body>
    <div id="app"></div>
    <script>
        const root = {
            el:'#app',
            data: {
                title'过滤器'
            },

            /*html 调用*/
            template:`<div>
                    {{title}}
                    <p>{{ title|msgFilter }}</p>  
                 </div>`
        }

         //注册
         Vue.filter('msgFilter',(t)=>{
            const data =  new Date()
            return data.toLocaleTimeString()
        })

        // 创建vue实例
        new Vue(root)
    </script>
</body>
</html>

属性透传attribute、vue实例对象方法$nextTick()、虚拟dom与浏览器渲染机制

作者 RONIN
2026年4月20日 17:10

一、属性透传attribute

  • 指的是传递给一个组件,但没有通过props或emits接收,常见的如class、style、id
  • 透传的样式会自动被添加到子组件根元素上,如果子组件已经有一个样式,透传的样式会和已有样式合并。
  • 如果透传的是一个点击事件,也会自动被添加到子组件根元素上,如果子组件自身也有点击事件,点击时透传过来的事件和其本身的事件都会触发。
  • 透传的属性可以通过{{$attrs}}拿到
  • 属性透传只会透传到根元素上,如果有多个根节点,vue不知道要透传到哪个节点,需要通过v-bind=”$attrs”绑定到要透传到的那个节点上,否则会抛出警告。
  • 属性透传是可以禁用的,通过inheritAttrs: false可以阻止透传

例:

目录

image.png

1.新建assets样式文件夹,样式文件style.css

.large{
    color: red;
}
.small{
    background-color: pink;
}

2.在index.html中引入样式文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>首页</title>
    <link rel="stylesheet" href="./assets/css/style.css">
</head>

<body>
    <div id="app"></div>
    <script type="module" src="./src/main.js"></script>
</body>
</html>

父组件App.js

import Child from "./Child.js";
export default {
  components: {
    Child,
  },

  data() {
    return {
      title: "attribute属性透传",
      count:0
    };
  },

  /*html*/
  template: `<div style="width:400px;height:400px;background-color:skyblue;">
                <h2>{{title}}</h2>
                <p>{{count}}</p>
                 <Child class="large" @click="count++" ></Child>
            </div>`,
};

子组件Child.js

export default {
  data() {
    return {
      num10,
    };
  },

  inheritAttrs: true, // 阻止透传

  /*html*/
  template: `
              <button @click="num++" class="small">子按钮{{num}}</button>
              <main v-bind="$attrs">多个根节点</main>
          `,
};


二、vue实例对象方法$nextTick()

可以通过this.$nextTick(()=>{})访问,回调函数会在dom节点渲染完成后执行

如:正常操作dom节点是在mounted生命周期中操作,nextTick()方法可以实现在created生命周期中操作dom节点

export default {
  data() {
    return {
      title"vue实例对象的 $nextTick()",
      count:10
    };
  },

  created() {
      this.$nextTick(()=>{
        //回调函数,模板界面异步更新完成后执行
        const pEle = document.querySelector('#countP')
        console.log('bindPlus >> ',pEle.innerHTML);
      })
  },

  methods: {
    bindPlus(){
      this.count++
      // 验证, count数据变化,通知依赖更新界面是一个异步过程
      const pEle = document.querySelector('#countP')
      console.log('bindPlus >> ',pEle.innerHTML);//是更新之前的值,说明是异步更新的

      //模板界面异步更新完成后执行nextTick回调函数中代码
      this.$nextTick(()=>{
        const pEle = document.querySelector('#countP')
        console.log('bindPlus >> ',pEle.innerHTML); //是更新之后的值
      })
    }
  },

  /*html*/
  template: `<div>
                <h2>{{title}}</h2>
                <p id="countP">{{count}}</p>
                <button @click="bindPlus">加一 </button>
            </div>`,
};

三、虚拟dom、浏览器渲染机制

整个html文档是一个dom对象,整个html由dom节点对象构成。

浏览器渲染机制:

  1. 解析HTML生成dom树,同时解析CSS文档构建CSSOM树
  2. DOM树和CSSOM树关联起来生成渲染树(RenderTree)
  3. 浏览器按照渲染树进行重排重绘

重绘:CSS 样式改变(如:visibility,背景色的改变),使浏览器需要根据新的属性进行绘制

重排:对DOM的修改引发了DOM几何元素的变化(如:改变元素高度),渲染树需要重新计算,重新生成布局,重新排列元素。

重绘不一定导致重排,但重排一定会导致重绘

 

操作真实dom会引起重排重绘,vue框架操作的是虚拟dom(本质上就是一个普通的JS对象。是模拟真实dom得到的一个JS对象)

将真实dom多次操作在虚拟dom上完成,再将虚拟dom映射到真实dom,完成一次重排重绘,提高渲染效率。

我们在vue中写的template模板,vue编译过程中,会调用render()函数将其编译成虚拟dom树,后映射挂载成真实dom,然后重排重绘显示给用户

vue提供了一个h()方法,用于创建虚拟dom(vnode)

import { h } from 'vue'
render(){
  return[

        h('div')

        h('div', { id"foo" })

        h('div', { id"foo", class"bar", style: { color'red' }, onClick: () => { } },'标  题', [/*child*/])

  ]
}

vue自定义指令与自定义插件

作者 RONIN
2026年4月20日 16:57

一、自定义指令

  • vue内置指令:指带有v-前缀的特殊属性
  • 自定义指令:指包含类似组件生命周期钩子函数的特殊对象(钩子函数会接收到指令所绑定元素作为其参数)

 

const mydirective = {
自定义指令钩子:

// 在绑定元素的 attribute 前,或事件监听器应用前调用
created(el,binding,vnode, prevvnode){},

// 在元素被插入到 DOM 前调用
beforeMount(el,binding, vnode, prevvnode){}

// 在绑定元素的父组件及他自己的所有子节点都挂载完成后调用
mounted(el,binding, vnode, prevvnode){},

// 绑定元素的父组件更新前调用
beforeupdate(el,binding, vnode, prevvnode){},

// 在绑定元素的父组件及他自己的所有子节点都更新后调用
updated(el,binding, vnode, prevvnode){},

// 绑定元素的父组件卸载前调用
beforeUnmount(el,binding,vnode,prevvnode){}

// 绑定元素的父组件卸载后调用
unmounted(el,binding, vnode, prevvnode){}
}

自定义指令钩子函数参数说明:

el :指令绑定到的元素。可以用于直接操作 DOM。

binding :一个对象,包含属性:

value :传递给指令的值。

oldvalue :之前的值,仅在 beforeupdate 和 updated 中可用。无论值是否更改,它都可用。

arg:传递给指令的参数(如果有的话)。

modifiers :一个包含修饰符的对象(如果有的话)。

instance :使用该指令的组件实例。

dir :指令的定义对象。

vnode:代表绑定元素的底层VNode。

prevNode :之前的渲染中代表指令所绑定元素的VNode。仅在 beforeupdate 和 updated 钩子中可用.。

实现:

1.App.js(1.定义指令对象 2.通过directive注册指令(全局注册,局部注册) 3.使用)

/**
 * 自定义指令
 *    指令: v-特殊属性
 *          vue内置指令:  v-html  v-text v-pre v-bind v-on v-if v-show
 *    自定义指令: 包含组件生命周期函数的特殊对象
 *               1. 特定对象
 *               2. 组件生命周期函数
 *  
 *   实现:
 *      1. 定义指令对象
 *        const foucs = {
 *              created(el,binding){},
 *              mounted(el,binding){},
 *              unmounted(el,binding){}
 *         }
 *      2. 注册指令
 *          全局注册
 *             指令可以整个应用所有标签使用
 *             const app = creatApp()
 *             app.directive('foucs',focus)
 *          局部注册
 *             只在当前注册的组件标签中使用
 *             const App = {
 *                  components:{}
 *                  directives:{
 *                      foucs:foucs
 *                  }
 *              }
 *
 */

// v-focus自动获取焦点
const focus = {
  mounted(el, binding) {
    el.focus();
  },
};

// v-red使作用的元素内容为红色

const red = {
  mounted(el, binding) {
    el.style.color = "red";
  },
};

// v-color根据指令值,设置指令作用元素内容颜色
const color = {
  mounted(el, binding) {
    el.style.color = binding.value;
  },

  updated (el,binding) {
    el.style.color = binding.value;
  }
}

export default {
//局部注册
  directives: {
    focus,
    red,
    color,
  },

  data() {
    return {
      title: "自定义指令",
      vcolor: "red",
    };
  },

  methods: {
    bindUpdateColor() {
      this.vcolor = "blue";
      console.log(this.vcolor);
    },
  },

  /*html*/
  template: `<div>
                <h2>{{title}}</h2>
                <input type="text" v-focus>
                
                <p v-red>内容</p>

                <p v-color="vcolor">v-color指令内容</p>

                <!--<p v-color="’blue’">v-color指令内容</p>-->

                <button @click="bindUpdateColor">确定</button>
            </div>
            `,
};

main.js

// 使用vue3 ES模块构建版本
import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
// 导入根组件
import App from './App.js'
// createApp(App).mount('#app')
const app = createApp(App)

// v-focus自动获取焦点
const focus = {
  mounted(el, binding) {
    el.focus();
  },
};

//全局注册
app.directive('focus',focus)
app.mount('#app')

二、自定义插件

  • 自定义插件指拥有install()方法的对象,是一种为vue添加全局功能的工具代码。可以在里面注册全局的组件或指令,然后集成到vue全局对象中,全局使用。

 

install(app, options){}方法参数说明:

app: vue应用实例

options: 可选参数对象

 

实现:

main.js导入集成插件

// 使用vue3 ES模块构建版本

import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import { Myplugin } from "./plugins/MyPlugin.js";

// 导入根组件
import App from './App.js'
// createApp(App).mount('#app')
const app = createApp(App);

app.use(Myplugin); //集成插件
app.mount("#app")

MyPlugin.js定义插件(1.定义插件 2.通过app.use集成插件 3.使用)

export const Myplugin = {
    // app: vue应用实例
    // options: 可选参数对象

    install(app, options) {
      // 封装插件功能
      // 封装全局组件, 封装全局指令

      // 注册组件
      // const ButtonCouter = {
      //  data() {
      //     return {
      //       title: "按钮",
      //     };
      //   },
      //   template: `<button>{{title}}</button>`,
      // }
      // app.component("ButtonCouter",ButtonCouter)

      // 注册组件
      app.component("ButtonCouter", {
        data() {
          return {
            title"按钮",
          };
        },
        template`<button>{{title}}</button>`,
      });

      //注册指令
      app.directive("color", {
        mounted(el, binding) {
          el.style.color = binding.value;
        },

        updated(el, binding) {
          el.style.color = binding.value;
        },
      });
 
      //使一个资源可被注入整个应用app.provide()
    },
  };

App.js使用

export default {
  data() {
    return {
      title: "自定义指令",
    };
  },

  /*html*/
  template: `<div>
                <h2>{{title}}</h2>
                <button-couter></button-couter>
                <p v-color="'blue'">插件定义的指令内容</p>
            </div>
            `,
};

属性透传attribute与性能优化组件(component、异步组件、keep-alive/Suspense/Teleport/Transition)

作者 RONIN
2026年4月20日 16:40

一、属性透传attribute

  • 指的是传递给一个组件,但没有通过props或emits接收,常见的如class、style、id
  • 透传的样式会自动被添加到子组件根元素上,如果子组件已经有一个样式,透传的样式会和已有样式合并。
  • 如果透传的是一个点击事件,也会自动被添加到子组件根元素上,如果子组件自身也有点击事件,点击时透传过来的事件和其本身的事件都会触发。
  • 透传的属性可以通过{{$attrs}}拿到
  • 属性透传只会透传到根元素上,如果有多个根节点,vue不知道要透传到哪个节点,需要通过v-bind=”$attrs”绑定到要透传到的那个节点上,否则会抛出警告。
  • 属性透传是可以禁用的,通过inheritAttrs: false可以阻止透传

例:

目录

image.png

1.新建assets样式文件夹,样式文件style.css

.large{
    color: red;
}
.small{
    background-color: pink;
}

2.在index.html中引入样式文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>首页</title>
    <link rel="stylesheet" href="./assets/css/style.css">
</head>

<body>
    <div id="app"></div>
    <script type="module" src="./src/main.js"></script>
</body>
</html>

父组件App.js

import Child from "./Child.js";
export default {
  components: {
    Child,
  },

  data() {
    return {
      title: "attribute属性透传",
      count:0
    };
  },

  /*html*/
  template: `<div style="width:400px;height:400px;background-color:skyblue;">
                <h2>{{title}}</h2>
                <p>{{count}}</p>
                 <Child class="large" @click="count++" ></Child>
            </div>`,
};

子组件Child.js

export default {
  data() {
    return {
      num10,
    };
  },

  inheritAttrs: true, // 阻止透传

  /*html*/
  template: `
              <button @click="num++" class="small">子按钮{{num}}</button>
              <main v-bind="$attrs">多个根节点</main>
          `,
};

二、性能优化组件

详细介绍六个性能优化组件,其他组件官网自行学习

1.动态组件(<component :is=””></component>改变is属性绑定的值即可)

例如tab栏切换

父组件App.js

import Home from "./Home.js";
import Category from "./Category.js";
import Cart from "./Cart.js";
import My from "./My.js";
/*
 *  点击tab选项 内容区域切换为对应组件
 *    1. tab选项绑定点击事件
 *    2. 切换组件
 */

export default {
  components: {
    Home,
    Category,
    Cart,
    My,
  },

  data() {
    return {
      title: "动态组件",
      currentTab:'home',
      list:[
        {name:'home',title:'首页'},
        {name:'category',title:'分类'},
        {name:'cart',title:'购物车'},
        {name:'my',title:'我的'},
      ]
    };
  },

  methods: {
    onTabChange(tabName){
      this.currentTab = tabName
    }
  },

  /*html*/
  template: `<div class="g-container">
                 <div class="g-content">
                      <component :is="currentTab"></component>
                 </div>

                 <ul class="g-footer">
                    <li v-for="item in list" @click="onTabChange(item.name)" :class="{active:currentTab==item.name}">{{item.title}}</li>
                 </ul>
            </div>`,
};

样式文件style.css

*{padding: 0;margin: 0;}

ul,li{
    list-style: none;
}

.g-container{
    height: 100vh;
    width: 100%;
    display: flex;
    flex-direction: column;
}

.g-container .g-content{
    flex:1
}

.g-container .g-footer{
    height: 60px;
    background-color: skyblue;
    display: flex;
    justify-content: space-around;
    align-items: center;
}

.active{
   color: red;    
}

子组件Home.js(其他子组件同此组件)

export default {
  data() {
    return {
      title"首页",
    };
  },

  /*html*/
  template: `<div>
                  <h2>{{title}}</h2>
              </div>`,
};

2.异步组件(服务端定义的组件,通过网络异步获取到前端,再注册使用。通过defineAsyncComponent方法获取组件)

App.js

import { defineAsyncComponent } from "https://unpkg.com/vue@3/dist/vue.esm-browser.js";

/**
 *   异步组件: 服务端定义的组件,通过网络异步获取到前端,再注册使用
 *   同步组件: 前端客户端定义组件
 */

const AsyncChild = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    //模拟网络接口
    setTimeout(() => {
      //异步获取后端定义的异步组件
      const asyncComponent = {
        template`<p>我是异步组件</p>`,
      };

      resolve(asyncComponent);
    }, 2000);
  });
});

export default {
  components: {
    AsyncChild,
  },

  data() {
    return {
      title"异步组件",
    };
  },

  methods: {},

  /*html*/
  template: `<div style="width:400px;height:400px;background-color:skyblue;">
                <h2>{{title}}</h2>

                <p>---异步组件----</p>
                <async-child></async-child>
            </div>`,
};

3.内置组件--缓存组件keep-alive/Suspense/传送门Teleport/过渡动画Transition

直接使用,无需注册

1>. 缓存组件,结合动态组件使用(<keep-alive include=””></keep-alive> 通过 include可以配置哪些组件需要缓存,需要注意的是,include中填写的并不是组件注册时的名称,是定义组件时name选项定义的组件名称)

注:存组件添加之后组件生命周期钩子函数也不会执行了,但是有另外两个钩子函数会执行:activated激活deactivated失活

使用示例:b组件,a组件会销毁,再次切换到a组件时组件会重新创建,但有时是不需要重新创建的,即切换回来原数据还存在。

父组件App.js

import Home from "./components/Home.js";
import Category from "./components/Category.js";
import Cart from "./components/Cart.js";
import My from "./components/My.js";
/**
 *  点击tab选项 内容区域切换为对应组件
 *    1. tab选项绑定点击事件
 *    2. 切换组件
 */
export default {
  name:'App',
  components: {
    Home,
    Category,
    Cart,
    My,
  },

  data() {
    return {
      title: "动态组件",
      currentTab: "home",
      list: [
        { name: "home", title: "首页" },
        { name: "category", title: "分类" },
        { name: "cart", title: "购物车" },
        { name: "my", title: "我的" },
      ],
    };
  },

  methods: {
    onTabChange(tabName) {
      this.currentTab = tabName;
    },
  },

  /*html*/
  template: `<div class="g-container">
                 <div class="g-content">
                      <!--数组写法-->
                      <!--<keep-alive :include="['Home','Category','Cart']">-->

                      <!--字符串写法-->
                      <keep-alive include="Home,Category,Cart">
                          <component :is="currentTab"></component>
                      </keep-alive>
                 </div>

                 <ul class="g-footer">
                    <li v-for="item in list" @click="onTabChange(item.name)" :class="{active:currentTab==item.name}">{{item.title}}</li>
                 </ul>
            </div>`,
};

子组件Home.js

export default {
  name:'Home',
  data() {
    return {
      title"首页",
    };
  },

  created() {
    console.log("home created ");
  },

  mounted() {
    console.log("home mounted ");
  },

  activated() {
    console.log("home activated ");
  },

  deactivated() {
    console.log("home deactivated ");
  },

  unmounted() {
    console.log("home unmounted ");
  },

  /*html*/

  template: `<div>
                  <h2>{{title}}</h2>
                  <input type="text" name="message">
              </div>`,

};

子组件Category.js

export default {
  name:'Category',
  data() {
    return {
      title"分类",
      list: [],
    };
  },
  
  created() {
    console.log("category created ");
  },

  mounted() {
    console.log("category mounted ");
  },

  activated() {
    console.log("category activated ");

    // 调用接口获取分类列表数据 fetch返回promise对象,

    fetch("https://api.yuguoxy.com/api/shop/list?pageSize=4")
      .then((response) => {
        return response.json();
      })
      .then((data) => {
        console.log(data);
        this.list = data.resultInfo.list;
      });
  },

  deactivated() {
    console.log("category deactivated ");
  },

  unmounted() {
    console.log("category unmounted ");
  },

  /*html*/
  template: `<div>
                  <h2>{{title}}</h2>
                  <ul>
                    <li v-for="item in list">
                       <img :src="item.picture" style="width:100px;"/>
                       <p>{{item.shop}}</p>
                    </li>
                  </ul>
              </div>`,
};

2>.Suspense组件,结合插槽使用。(当网络请求时间较长,请求的内容暂时没有获取到。等待时渲染一个加载状态,获取到之后展示获取到的内容)

App.js

import { defineAsyncComponent } from "https://unpkg.com/vue@3/dist/vue.esm-browser.js";

/**
 *   异步组件: 服务端定义的组件,通过网络异步获取到前端,再注册使用
 *   同步组件: 前端客户端定义组件
 */

const AsyncChild = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    //模拟网络接口
    setTimeout(() => {
      //异步获取后端定义的异步组件
      const asyncComponent = {
        template: `<p>我是异步组件</p>`,
      };
      resolve(asyncComponent);
    }, 2000);
  });
});
 
export default {
  components: {
    AsyncChild,
  },

  data() {
    return {
      title: "异步组件",
    };
  },

  methods: {},
  /*html*/
  template: `<div style="width:400px;height:400px;background-color:skyblue;">
                <h2>{{title}}</h2>

                <p>---异步组件----</p>
                <!-- Suspense 作用: 首先显示 名为fallback的插槽内容,当异步组件加载完成后,显示异步组件  -->

                <Suspense>
                     <async-child></async-child>

                     <template #fallback>
                         <p>加载中...</p>
                     </template>
                </Suspense>
            </div>`,
};

3>.传送门Teleport

实际应用场景:没有传送门时嵌套的css样式太深(#app div box model),可以使用传送门减少嵌套层数(#app model)

父组件App.js

import Dialog from "./Dialog.js";

export default {
  components: {
    Dialog,
  },

  data() {
    return {
      title: "父组件",
      show: false, // 控制对话框隐藏显示
    };
  },

  /*html*/
  template: `<div style="width:400px;height:400px;background-color:skyblue;">
                <h2>{{title}}</h2>
                <button @click="show=true">添加用户</button>

                <!--传送到body标签下面-->
                <Teleport to="body">
                    <Dialog v-if="show" @closeDialog="show=false"></Dialog>
                </Teleport>
            </div>
            `,
};

子组件Dialog.js

export default {
  emits: ["closeDialog"], // 接收事件
  data() {
    return {};
  },

  methods: {
    bindConfirm(){
       // 1. 获取表单输入框内容
       // 2. 调用添加用户接口,保存用户数据到服务端
       // 3. 保存用户成功,关闭对话框
          this.$emit('closeDialog')
    }
  },

  /*html*/
  template: `<div class="box">
              <div class="modal">
                  <!-- header -->
                  <div class="header">
                      <p class="title">标题</p>
                      <p class="close" @click="$emit('closeDialog')">x</p>
                  </div>

                  <!-- 内容区域 -->
                  <div class="content">
                      <form>
                          <input type="text" name="username" placeholder="请输入用户名">  
                          <input type="text" name="password" placeholder="请输入密码">  
                      </form>
                  </div>

                  <!-- 底部区域 -->
                  <div class="footer">
                      <button @click="bindConfirm">确定</button>
                  </div>
              </div>
            </div>`,
};

4>.过渡动画<Transition name=””>

没有定义name,样式默认v-;定义了name=”a”,样式为a-

.v-enter-active,

.v-leave-active {transition: opacity 0.5s ease;}

.v-enter-from,

.v-leave-to {opacity: 0;}

父组件App.js

import Dialog from "./Dialog.js";

export default {
  components: {
    Dialog,
  },

  data() {
    return {
      title: "父组件",
      show: true, // 控制对话框隐藏显示
    };
  },

  /*html*/
  template: `<div style="width:400px;height:400px;background-color:skyblue;">
                <h2>{{title}}</h2>
                <button @click="show=!show">切换</button>

                <!--过渡动画效果-->
                <Transition> 
                   <p v-if="show">过度动画效果</p>
                </Transition> 

                <Transition name="fade"> 
                   <Dialog v-if="show" @closeDialog="show=false"></Dialog>
                </Transition> 
            </div>
            `,
};

子组件Dialog.js

export default {
  emits: ["closeDialog"], // 接收事件
  data() {
    return {};
  },

  methods: {
    bindConfirm(){
       // 1. 获取表单输入框内容
       // 2. 调用添加用户接口,保存用户数据到服务端
       // 3. 保存用户成功,关闭对话框
          this.$emit('closeDialog')
    }
  },

  /*html*/
  template: `<div class="box">
              <div class="modal">

                  <!-- header -->
                  <div class="header">
                      <p class="title">标题</p>
                      <p class="close" @click="$emit('closeDialog')">x</p>
                  </div>

                  <!-- 内容区域 -->

                  <div class="content">
                      <form>
                          <input type="text" name="username" placeholder="请输入用户名">  
                          <input type="text" name="password" placeholder="请输入密码">  
                      </form>

                  </div>

                  <!-- 底部区域 -->
                  <div class="footer">
                      <button @click="bindConfirm">确定</button>
                  </div>
              </div>
            </div>`,
};

样式style.css

/* 下面我们会解释这些 class 是做什么的 */

.v-enter-active,

.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,

.v-leave-to {
  opacity: 0;
}


.fade-enter-active,

.fade-leave-active {
  transition: opacity 1s ease;
}

.fade-enter-from,

.fade-leave-to {
  opacity: 0;
}
❌
❌