TOC
CHAT

Vue3渐进式框架教程

Vue3是Vue.js的最新版本,新增了很多特性。

  1. Performance:性能比Vue2更强。
  2. Tree shaking support:可以将无用模块进行“剪辑”,仅打包需要的。
  3. Composition API:组合API。
  4. Fragment, Teleport, Suspense:三个新组件。
  5. Better TypeScript support:更优秀的TS支持。
  6. Custom Renderer API:自定义渲染API。

官网:cn.vuejs.org

1 API风格

1.1 选项式API

使用选项式API,可以用包含多个选项的对象来描述组件的逻辑,例如data、methods和mounted。选项所定义的属性都会暴露在函数内部的this上,它会指向当前的组件实例。

<template>
    <button @click="increment">Count is: {{ count }}</button>
</template>

<script>
    export default {
        data() {
            return {
                count: 0
            }
        },
        methods: {
            increment() {
                this.count++
            }
        },
        mounted() {
            // 该函数会在组件挂载完成后被调用
            // 一般进行网络请求,重新渲染数据
        }
    }
</script>

HyplusMD编辑器bug:该代码块语言选择html会导致整篇文章渲染异常,暂设为xml

1.2 组合式API

通过组合式API,可以使用导入的API函数来描述组件逻辑。在单文件组件中,组合式API通常会与<script setup>搭配使用。这个setup attribute是一个标识,告诉Vue需要在编译时进行一些处理,使得可以更简洁地使用组合式API。比如,<script setup>中的导入和顶层变量/函数都能够在模板中直接使用。

下面是使用了组合式API与<script setup>改造后和上面的模板完全一样的组件:

<template>
    <button @click="increment">Count is: {{ count }}</button>
</template>

<script setup>
    import { ref, onMounted } from 'vue'

    // 响应式状态
    const count = ref(0)

    // 用来修改状态、触发更新的函数
    function increment() {
        count.value++
    }

    // 生命周期钩子
    onMounted(() => {
        console.log(`The initial count is ${count.value}.`)
    })
</script>

2 Vue指令

2.1 {{ }} 文本插值

{{ }}用于文本替换,内部可以计算数值。

<template>
    <h2>{{ message }}plasma!</h2>      <!-- Hyperplasma! -->
    <h2>{{ age + 1 }}</h2>    <!-- 13 -->
</template>
<script>
    export default {
        data() {
            return {
                message: "Hyper",
                age: 12
            }
        }
    }
</script>

2.2 v-html 标签元素

v-html指令用于设置元素的innerHTML

  • 内容中有html结构会被解析为标签。
  • 解析文本使用{{ }},需要解析html结构才用v-html
<template>
    <p v-html="message"></p>  <!-- <p><a href='https://www.hyperplasma.top'>Hyplus</a></p> -->
</template>
<script>
    export default {
        data() {
            return {
                message: "<a href='https://www.hyperplasma.top'>Hyplus</a>"
            }
        }
    }
</script>

2.3 v-on 绑定事件

v-on指令用于为元素绑定事件

  • 可用的事件:blurfocusfocusinfocusoutloadresizescrollunloadclickdblclickmousedownmouseupmousemovemouseovermouseoutmouseentermouseleavechangeselectsubmitkeydownkeypresskeyuperror
  • 可以简写为@,如v-on:click="func()"等价于@click="func()"
  • 绑定的方法定义在methods属性中,方法内部通过this关键字可以访问定义在data中的数据。
<template>
    <p>{{message}}</p>
    <input type="button" value="触发事件1" v-on:click="func">
    <input type="button" value="触发事件2" @click="func2('Akira')">
</template>
<script>
    export default {
        data() {
            return {
                message: "Hello,World!"
            }
        },
        methods: {
            func() {
                alert(this.message);
            },
            func2(name) {
                alert("你好," + name);
            }
        }
    }
</script>

2.4 v-show 隐藏元素

v-show指令用于根据真假切换元素的显示状态

  • 原理是修改元素的display值,实现显示和隐藏。其值最终会解析为布尔值 (true元素显示,false元素隐藏)。
  • 数据改变之后,对应元素的显示状态会同步更新。
<template>
    <div v-show="isShow">模块一</div>
    <div v-show="num > 10">模块二</div>
</template>
<script>
    export default {
        data() {
            return {
                isShow: false,
                num: 18
            }
        }
    }
</script>

2.5 v-if 消除元素

v-if指令用于根据表达式的真假切换元素的显示状态

  • 后续新版本中存在与之配合的指令:v-else-ifv-else
  • 本质是通过操纵dom元素来切换显示状态。表达式值为true时,元素存在于dom树中;值为false时,从dom树中移除。
  • 频繁切换使用v-show,反之使用v-if,前者的切换消耗小。
<template>
    <div v-if="isShow">模块一</div>
    <div v-if="num > 10">模块二</div>
</template>
<script>
    export default {
        data() {
            return {
                isShow: false,
                num: 18
            }
        }
    }
</script>

2.6 v-bind 属性渲染

v-bind用于为元素绑定属性

  • 可简写为:,如v-bind:src="imgSrc"等价于:src="imgSrc"
  • 需要动态增删class时,建议使用对象的方式。
<template>
    <a v-bind:href="myHref">Hyplus</a>
    <input type="button" :value="myVal">
</template>
<script>
    export default {
        data() {
            return {
                myHref: "https://www.hyperplasma.top",
                myVal: "我的按钮"
            }
        }
    }
</script>

2.7 v-for 列表渲染

v-for用于根据数据生成列表结构,经常和数组结合使用。

  • 常用语法:item in arr(item, index) in arr
  • 当数组长度发生改变时,更新会响应式地同步到页面上。但默认使用“就地更新”策略,即如果数据项的顺序被改变,Vue不会移动DOM元素来匹配数据项的顺序。为了让Vue能跟踪每个节点的身份,从而重新排序现有元素,需要为v-for渲染的元素添加一个唯一的key值 (可以使用index,但建议使用动态数据的唯一id)。
<template>
    <ul>
        <li v-for="item in vegetables">菜品:{{ item.name }}, 价格:{{ item.price }}</li>
    </ul>
    <p v-for="(it, index) in arr" :key="it.id | index">{{ index+1 }}.{{ it }}</p>
</template>
<script>
    export default {
        data() {
            return {
                arr:["北京","上海","广州","深圳"],
                vegetables:[
                    {name:"西兰花炒蛋", price:20},
                    {name:"蛋炒西蓝花", price:30}
                ]
            }
        }
    }
</script>

2.8 v-model 数据双向绑定

v-model用于便捷地设置和获取表单元素的值,绑定的数据会和表单元素值相关联。

<template>
    <input type="text" v-model="message">
    <p>动态修改massage值: {{ message }}</p>
</template>
<script>
    export default {
        data() {
            return {
                message:"Welcome to Hyperplasma!!!"
            }
        }
    }
</script>

3 组件

3.1 组件组合

单文件组件(Single File Component,SFC,又名*.vue文件) 是一种特殊的文件格式,允许将Vue组件的模板、逻辑与样式封装在单个文件中。

<template>
    <h2>我的单文件组件</h2>
</template>

<script>
    export default {
        name: "MyComponent"
    }
</script>

<style scoped>  <!-- scoped表示只改变该文件的模板样式 -->
    h2 {
        color: red;
    }
</style>

加载组件:

  1. 第一步:引入组件 import MyComponent from './components/MyComponent.vue'
  2. 第二步:挂载组件 components: { MyComponent }
  3. 第三步:显示组件 <MyComponent />
<template>
    <h2>APP.vue的二级标题</h2>
    <MyComponent />
</template>

<script>
    import MyComponent from './components/MyComponent.vue';
    export default {
        name: "APP",
        components: {
            MyComponent
        }
    }
</script>

3.2 Props组件交互

设置props来实现父组件向子组件传递数据。

  • 可以传递任意类型的参数
  • 当数据类型为数组或者对象的时候,默认值需要返回工厂模式使用函数返回,如下所示)。
<!-- 父组件:App.vue -->
<template>
    <h2>APP.vue的二级标题</h2>
    <MyComponent :title="title" :arr="arr"/>
</template>

<script>
    import MyComponent from './components/MyComponent.vue';
    export default {
        name: "APP",
        data() {
            return {
                title: "传递字符串",
                arr: ["zhangsan", "lisi", "wangwu"]
            }
        },
        components: {
            MyComponent
        }
    }
</script>
<!-- 子组件:MyComponent.vue -->
<template>
    <h2>Props组件交互</h2>
    <p>{{ title }}</p>
    <p v-for="(item,index) in arr" :key="index">{{ item }}</p>
</template>

<script>
    export default {
        name: "MyComponent",
        props: {
            title: {
                type: String,   //数据类型
                default: ""   //默认值
            },
            arr: {
                type: Array,
                default: function() {
                    return [];
                }
            }
        }
    }
</script>

3.3 自定义事件组件交互

组件向父组件传递数据,自定义事件可以在组件中反向传递数据。

<!-- 子组件:MyComponent.vue -->
<template>
    <h2>自定义事件组件交互</h2>
    <input type="button" value="发送数据" @click="sendData">
</template>

<script>
    export default {
        name: "MyComponent",
        methods: {
            sendData() {
                // 参数一: 接收数据的事件名称,参数二: 发送的数据
                this.$emit("onEvent", {name:"akira",age:132});
            }
        }
    }
</script>
<!-- 父组件:App.vue -->
<template>
    <h2>APP.vue的二级标题</h2>
    <MyComponent @onEvent="getData"/>
</template>

<script>
    import MyComponent from './components/MyComponent.vue';
    export default {
        name: "APP",
        components: {
            MyComponent
        },
        methods: {
            getData(student) {
                alert("姓名:" + student.name + ",年龄:" + student.age);
            }
        }
    }
</script>

3.4 组件生命周期

每个组件在被创建时都要经过一系列的初始化过程。例如,需要设置数据监听、编译模板、将实例挂载到DOM并在数据变化时更新DOM等。同时该过程中也会运行称为生命周期钩子的函数,使得用户能够在不同阶段添加代码。

钩子的分类:

  • 创建时:beforeCreatecreated
  • 渲染时:beforeMountmounted
  • 更新时:beforeUpdateupdated
  • 卸载时:beforeUnmountunmounted

4 Swiper

Swiper(官方文档:swiperjs.com/vue)是纯JavaScript打造的滑动特效插件,面向手机、平板电脑等移动终端,能实现触屏焦点图、触屏Tab切换、触屏轮播图切换等常用效果。

安装:npm install --save swiper@latest

基础实现:

<template>
    <div class="hello">
        <swiper class="mySwiper" :modules="modules" :pagination="{ clickable: true }">
            <swiper-slide><img src="../assets/1.png"></swiper-slide>
            <swiper-slide><img src="../assets/2.png"></swiper-slide>
            <swiper-slide><img src="../assets/3.png"></swiper-slide>
        </swiper>
    </div>
</template>

<script>
    import { Swiper, SwiperSlide } from 'swiper/vue'; //核心组件
    import { Pagination } from 'swiper'; //指示器组件
    import 'swiper/css';
    import 'swiper/css/pagination';

    export default {
        name: "HelloWorld",
        data() {
            return {
                modules: [Pagination]
            }
        },
        components: {
            Swiper,
            SwiperSlide
        }
    }
</script>

<style scoped>
    img {
        width: 100%;
    }
</style>

5 Axios网络请求库

Axios官网:github.com/axios/axios
中文说明:看云

5.1 安装依赖

除了安装axios基本模块外,还需安装用于处理post请求参数的querystring,参数转换格式形如qs.stringify({ ... })

npm install --save axios

# 用于处理post请求参数
npm install --save querystring

5.2 GET与POST

组件中引入:import axios from "axios"

GET请求的两种方式:

axios({
    method: "get",
    url: "https://www.hyperplasma.top/api/url/example.php"
}).then(res => {
    console.log(res.data);
})
axios.get("https://www.hyperplasma.top/api/url/example.php")
    .then(res => {
    console.log(res.data);
})

POST请求的两种方式:

axios({
    method: "post",
    url: "https://www.hyperplasma.top/api/url/example.php",
    data: qs.stringify({
        user_id: "akira37@foxmail.com",
        password: "unipswd4hyplus",
        verification_code: "hypress"
    })
}).then(res => {
    console.log(res.data);
})
axios.post("https://www.hyperplasma.top/api/url/example.php", qs.stringify({
    user_id: "akira37@foxmail.com",
    password: "unipswd4hyplus",
    verification_code: "hypress"
})).then(res => {
    console.log(res.data);
})

5.3 网络请求封装

在实际应用过程中,一个项目中的网络请求会很多,此时一般采取的方案是将网络请求封装起来。

步骤如下:

  1. 创建src/utils/request.js文件,用于存储网络请求对象axios
import axios from "axios";
import qs from "querystring";

const errorHandle = (status, info) => {
    switch (status) {
        case 400: console.log("语义有误"); break;
        case 401: console.log("服务器认证失败"); break;
        case 403: console.log("服务器拒绝访问"); break;
        case 404: console.log("地址错误"); break;
        case 500: console.log("服务器遇到意外"); break;
        case 502: console.log("服务器无响应"); break;
        default: console.log(info); break;
    }
}

const instance = axios.create({
    timeout: 5000 // 网络请求的公共配置: 请求超时时间
})

// 发送数据前的拦截器
instance.interceptors.request.use(
    config => {
        if (config.method === "post") {
            config.data = qs.stringify(config.data);
        }
        return config; // 包括网络请求的所有信息
    },
    error => Promise.reject(error)
)

// 获取数据前的拦截器
instance.interceptors.response.use(
    response => response.status === 200 ? Promise.resolve(response) : Promise.reject(response),
    error => {
        const { response } = error;
        errorHandle(response.status, response.info);
    }
)

export default instance;
  1. 创建src/api/path.js,用于存放网络请求路径
const base = {
    baseUrl: "https://www.hyperplasma.top",
    exampleName: "/api/url/example.php"
}
export default base
  1. 创建src/api/index.js,用于存放网络请求路径
import path from "./path";
import axios from "../utils/request";

export default {
    getExample() {
        return axios.get(path.baseUrl + path.exampleName);
    }
}
  1. 在组件中直接调用网络请求
    <script>
    import api from './api/index';
    export default {
        name: "APP",
        mounted() {
            api.getExample().then(res => {
                console.log(res.data);
            })
        }
    }
    </script>

5.4 网络请求跨域解决方案

JS使用同源策略。同源策略是浏览器的一项安全策略,浏览器只允许JS代码请求当前所在服务器的域名、端口、协议相同的数据接口上的数据。即当协议、域名、端口任意一个不相同时,都会产生跨域问题。

目前主流的跨域解决方案有两种:后台解决(cors)、前台解决(proxy

实现:找到父工程下的vue.config.jsvite.config.js(对应项目创建方式)文件,向其defineConfig项中添加以下配置

// vue.config.js配置
devServer: {
    proxy: {
        '/api': {
            target: 'http://xxxxxxxxxxx.xxx/api',
            changOrigin: true,
            pathRewrite: {
                '^/api': ''
            }
        }
    }
}
// vite.config.js配置
server: {
    proxy: {
        '/api': {
            target: 'http://iwenwiki.com/api',    // 凡是遇到`/api`路径的请求,都映射到target属性
            changeOrigin: true,
            rewrite: path => path.replace(/^\/api/, '')    // 重写`/api`为空来去掉它
        }
    }
}

访问跨域地址(解决跨域配置问题后记得重启服务器):

mounted() {
    // 使用`/url`代替域名部分即可
    axios.get("/api/another/example.php")
        .then(res => {
            console.log(res.data);
        })
}

6 Vue Router

官方文档:router.vuejs.org/zh

在Vue中,可以通过vue-router路由管理页面之间的关系。Vue Router是Vue.js的官方路由。它与Vue.js核心深度集成,让 Vue.js构建单页应用变得轻而易举。

6.1 引入路由

步骤如下:

  1. 安装路由:npm install --save vue-router
  2. 配置独立的路由文件src/router/index.js
import { createRouter, createWebHashHistory } from 'vue-router';
import Home from '../views/Home.vue';

const routes = [
    {
        path: '/',
        name: 'home',
        component: Home
    },
    {
        path: '/about',
        name: 'about',
        component: () => import('../views/About.vue') //异步加载方式
    }
]

const router = createRouter({
    /**
     * createWebHashHistory
     *      home: http://localhost:5176/#/
     *      about: http://localhost:5176/#/about
     * 原理: a标签锚点链接
     *
     * createWebHistory 此种方式需要后台配合做重定向,否则会出现404问题
     *      home: http://localhost:5176/
     *      about: http://localhost:5176/about
     * 原理: HTML5的pushState()
     */
    history: createWebHashHistory(),
    routes
})

export default router
  1. 引入路由到项目,在src/main.js中添加use(router)
import './assets/main.css';

import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

createApp(App).use(router).mount('#app')
  1. 指定路由显示入口、路由跳转
<template>
    <nav>
        <RouterLink to="/">主页</RouterLink> | 
        <router-link to="/about">关于</router-link>
    </nav>
    <RouterView></RouterView> <!-- 路由跳转入口 -->
</template>

6.2 根据URL参数路由

页面跳转过程中可以携带参数,常应用于分页功能、商品项详情等。

步骤如下:

  1. 在路由配置中指定参数的key
{
    path: '/news/details/:name',
    name: 'newsdetails',
    component: () => import('../views/NewsDetails.vue') //对应新闻详情页
}
  1. 在跳转过程中携带参数
<template>
    <h1>News页面</h1>
    <ul>
        <li><RouterLink to="/news/details/百度">百度新闻</RouterLink></li>
        <li><RouterLink to="/news/details/新浪">新浪新闻</RouterLink></li>
        <li><RouterLink to="/news/details/网易">网易新闻</RouterLink></li>
    </ul>
</template>
  1. 在详情页面读取路由携带的参数
<template>
    <h1>新闻详情</h1>
    <p>{{ $route.params.name }}</p>
</template>

6.3 多级路由嵌套

路由嵌套常用于二级导航。

步骤如下:

  1. 创建子路由要加载显示的页面。【例】“关于我们”src/views/aboutsub/AboutUs.vue、“更多信息”src/views/aboutsub/AboutInfo.vue
  2. 在路由配置文件中添加子路由配置
{
    path: '/about',
    name: 'about',
    redirect: '/about/us', //重定向到指定的子路由
    component: () => import('../views/About.vue'),
    children: [
        {
            path: 'us', //子路由不加'/'
            component: () => import('../views/aboutsub/AboutUs.vue')
        },
        {
            path: 'info',
            component: () => import('../views/aboutsub/AboutInfo.vue')
        }
    ]
}
  1. 指定子路由显示位置、跳转链接
<template>
    <h1>About页面</h1>
    <RouterLink to="/about/us">关于我们</RouterLink> | 
    <RouterLink to="/about/info">更多信息</RouterLink>
    <RouterView></RouterView>
</template>

7 Vuex状态管理

官方文档:vuex.vuejs.org/zh/index.html

Vuex是一个专为Vue.js应用程序开发的状态管理模式+库,采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

状态管理可以理解成为了更方便的管理组件之间的数据交互,提供一个集中式的管理方案,任何组件都可以按照指定的方式进行读取和改变数据。

7.1 引入Vuex

步骤如下:

  1. 安装Vuex:npm install --save vuex
  2. 配置Vuex文件,创建src/store/index.js
import { createStore } from 'vuex';

// Vuex管理组件之间的状态
export default createStore({
    state: {
        // 存储所有状态(数据)
    },
    getters: {
        // 通过getter方法进行数据过滤
    },
    mutations: {
        // 数据更改
    },
    actions: {
        // 数据异步更改
    }
})
  1. main.js主文件中引入Vuex
import store from './store';

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

7.2 State数据存储

所有数据都存储在state中:

state: {
    num: 13
}

在组件中读取数据:

<p>{{ $store.state.num }}</p>

或者

<template>
    <p>{{ num }}</p>
</template>

<script>
    import { mapState } from 'vuex';
    export default {
        name: "APP",
        computed: {
            ...mapState(['num'])
        }
    }
</script>

7.3 Getters数据过滤

getters中的方法对Vuex中的数据进行过滤:

getters: {
    getNum(state) {
        return state.num>18 ? state.num : '未成年';
    }
}

在组件中获取过滤后的数据:

<p>{{ $store.getters.getNum }}</p>

或者

<template>
    <p>{{ num }}</p>
    <p>{{ getNum }}</p>
</template>

<script>
    import { mapState, mapGetters } from 'vuex';
    export default {
        name: "APP",
        computed: {
            ...mapState(['num']),     // 直接获取数据
            ...mapGetters(['getNum']) // 获取过滤数据
        }
    }
</script>

7.4 Mutations数据更改

Vuex中的mutation非常类似于事件。在mutations中,每个mutation都有一个类型为字符串的事件类型(type)和一个回调函数 (handler)。该回调函数就是实际进行状态更改的地方,并且会接受state作为第一个参数。

更改Vuex的$store中的状态的唯一方法是提交mutation,如下定义:

mutations: {
    incrNum(state) {
        state.num++;
    },
    addNum(state, n) {
        state.num += n;
    }
}

在组件中通过触发事件来修改数据:

<template>
    <button @click="onEvent">修改数据</button>
    <button @click="onEvent2">修改数据2</button>
</template>

<script>
    export default {
        name: "APP",
        methods: {
            onEvent() {
                this.$store.commit('incrNum')
            },
            onEvent2() {
                this.$store.commit('incrNum', 10)
            }
        }
    }
</script>

或者

<template>
    <button @click="onEvent3">修改数据3</button>
</template>

<script>
    import { mapState, mapGetters, mapMutations } from 'vuex';
    export default {
        name: "APP",
        methods: {
            ...mapMutations(['addNum']),
            onEvent3() {
                this.addNum(20)
            }
        }
    }
</script>

7.5 Actions异步更改

action类似于mutation,实际上actions中提交的就是mutation,但不是直接变更状态。action可以包含任意异步操作。

对Vuex中的数据进行异步更改:

mutations: {
    addNum(state,n) {
        state.num += n
    }
},
actions: {
    asyncAddNum({ commit }) {
        axios.get('http://iwenwiki.com/api/generator/list.php')
            .then(res => {
                commit('addNum', res.data[0]) // 调用mutations中的方法
            })
    }
}

在组件中通过触发事件来进行异步修改:

<template>
    <button @click="asyncEvent">异步修改</button>
</template>

<script>
    export default {
        name: "APP",
        methods: {
            asyncEvent() {
                this.$store.dispatch('asyncAddNum')
            }
        }
    }
</script>

或者

<template>
    <button @click="asyncEvent2">异步修改2</button>
</template>

<script>
    import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
    export default {
        name: "APP",
        methods: {
            ...mapActions(['asyncAddNum']),
            asyncEvent2() {
                this.asyncAddNum()
            }
        }
    }
</script>

8 Vue3组合式API

相比于Vue2,Vue3新增了组合式API。

8.1 ref、reactive

ref通常用于定义基本类型数据,reactive通常用于定义对象或数组类型数据。

ref也可以定义对象或者数组类型的数据,内部会通过reactive转为代理对象。

<template>
    <div id="app">
        <p>{{ message }}</p>
        <p v-for="(item, index) in cities.arr" :key="index">{{ item }}</p>
        <p v-for="(item, index) in list" :key="index">{{ item }}</p>
        <p>{{ citys.age }}</p>
    </div>
</template>

<script>
    import { ref, reactive } from 'vue';
    export default {
        setup() {
            const message = ref('张三')
            const list = ref(['a', 'b', 'c'])
            const cities = reactive({
                arr: ['北京', '上海', '广州', '深圳'],
                age: 18
            })
            return {
                message,
                cities,
                list
            }
        }
    }
</script>

8.2 setup()定义方法

setup()中不仅可以定义数据,还可以定义方法(使用lambda表达式)。注意在setup()中定义的数据和方法都需要使用return返回才能被调用。

<template>
    <div id="app">
        <button @click="onEvent">不带参数</button>
        <button @click="onEvent2('Akira')">携带参数</button>
        <button @click="changeData">修改数据</button>
    </div>
</template>

<script>
    import { ref, reactive } from 'vue';
    export default {
        setup() {
            const onEvent = () => {
                alert('触发事件');
            }
            const onEvent2 = (arg) => {
                alert(arg);
            }
            const changeData = () => {
                message.value = 'KINA'
            }
            return {
                onEvent,
                onEvent2,
                changeData
            }
        }
    }
</script>

8.3 props、context

在Vue2中,组件的方法中可以通过this获取到当前组件的实例,并执行data变量的修改,方法的调用,组件的通信等等。

在Vue3中,setup()在beforeCreate和created的时机就已调用,无法使用Vue2的方式,因此可通过接收setup(props, ctx)来获取当前组件的实例和props(组件交互中的基本用法详见3.2)。

父组件发送数据:

<template>
    <div id="app">
        <Home title="Yohane"/>
    </div>
</template>

<script>
    import Home from './views/Home.vue';
    export default {
        components: { Home }
    }
</script>

子组件接收数据(注意setup(props)):

<template>
    <h1>Home页面</h1>
    <p>{{ title }}</p>
</template>

<script>
    export default {
        name: 'home',
        props: {
            title: String
        },
        setup(props) {
            const title = props.title;
            return {
                title
            }
        }
    }
</script>

子组件发送数据(注意setup(props, ctx),相较于Vue2取消了this,变为使用context):

<template>
    <h1>Home页面</h1>
    <input type="button" value="发送数据" @click="sendData">
</template>

<script>
    export default {
        name: 'home',
        setup(props,ctx) {
            const sendData = () => {
                ctx.emit("onEvent", {name: "Teresa", age: 17});
            }
            return {
                sendData
            }
        }
    }
</script>

父组件接收数据:

<template>
    <div id="app">
        <Home @onEvent="getData"/>
    </div>
</template>

<script>
    import Home from './views/Home.vue';
    export default {
        components: { Home },
        setup() {
            const getData = (student) => {
                alert("姓名:" + student.name + ",年龄:" + student.age);
            }
            return {
                getData
            };
        }
    }
</script>

8.4 provide()、inject()

provide()inject()可以实现嵌套组件之间的数据传递,这两个函数只能在setup()函数中使用。

  • 父级组件中使用provide()函数向下传递数据;子级组件中使用inject()获取上层传递过来的数据。
  • 不限层级。
// 父组件
import {provide} from 'vue';
import Home from './views/Home.vue';
export default {
    components: { Home },
    setup() {
        provide('num', 18);
    }
}
// 子组件
import { inject } from 'vue';
export default {
    setup() {
        const num = inject('num')
        return {
            num
        }
    }
}

8.5 setup()中的生命周期钩子

如下表所示:

生命周期 setup()中的生命周期钩子
beforeCreate
created
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted

setup()中可以定义多个相同的生命周期函数,并且在周期内都会执行,示例如下:

export default {
    setup() {
        onMounted(() => {
            console.log('业务一')
        })
        onMounted(() => {
            console.log('业务二')
        })
    }
}

9 Element-plus

官网:Element Plus

安装 Element-Plus:npm install element-plus --save

9.1 加载组件

方式一:完整引用。占用空间较大。

// main.js
import './assets/main.css'

import { createApp } from 'vue';
import App from './App.vue';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';

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

方式二:按需导入。更推荐这种方式,步骤如下——

  1. 安装unplugin-vue-componentsunplugin-auto-import两款插件
npm install -D unplugin-vue-components unplugin-auto-import
  1. 修改vue.config.jsvite.config.js配置文件
// vue.config.js
const { defineConfig } = require('@vue/cli-service')
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')

module.exports = defineConfig({
    transpileDependencies: true,
    configureWebpack: {
        plugins: [
            AutoImport({
                resolvers: [ElementPlusResolver()]
            }),
            Components({
                resolvers: [ElementPlusResolver()]
            })
        ]
    }
})
// vite.config.js
import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        vue(),
        AutoImport({
            resolvers: [ElementPlusResolver()]
        }),
        Components({
            resolvers: [ElementPlusResolver()]
        })
    ],
    resolve: {
        alias: {
            '@': fileURLToPath(new URL('./src', import.meta.url))
        }
    }
})

9.2 加载字体图标

步骤如下:

  1. 安装icons字体图标:npm install @element-plus/icons-vue
  2. 全局注册,创建src/plugins/icons.js文件,添加以下内容
import * as components from "@element-plus/icons-vue";
export default {
    install: (app) => {
        for (const key in components) {
            const componentConfig = components[key];
            app.component(componentConfig.name, componentConfig);
        }
    }
};
  1. 引入文件,在main.js中引入icons.js文件
import elementIcon from "./plugins/icons";
createApp(App).use(elementIcon).mount('#app')
  1. 参阅官网文档,直接复制所需的组件代码,示例如下
<el-icon><CirclePlus /></el-icon>

发表评论