一文掌握vue3基础,适合自学入门案例丰富

发布于:2025-05-21 ⋅ 阅读:(20) ⋅ 点赞:(0)

Vue3

本文从Vue3的基础语法出发,全面系统的介绍了Vue3的核心概念与应用,旨在帮助自学者更轻松地掌握Vue3。文章内容由浅入深,从通过CDN引入Vue3开始,逐步介绍了组合式API、模块化开发、以及常见的Vue3指令和功能并从单个html的使用过渡到vite项目工程,案例丰富,总结编者学习经验,适合自学vue3。


渐进式的javascript框架,我们可以逐步引入vue的功能

通过cdn引用vue

你可以借助 script 标签直接通过 CDN 来使用 Vue:

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

通过 CDN 使用 Vue 时,不涉及“构建步骤”。这使得设置更加简单,并且可以用于增强静态的 HTML 或与后端框架集成。但是,你将无法使用单文件组件 (SFC) 语法。

组合式学习

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="vue.global.js"></script>
</head>
<body>
    <div id="app">
        <h1>{{ message }}</h1>
        <p>{{ web.title }} {{ web.url }}</p>
    </div>

    <script>
        Vue.createApp({
            // setup选项,用于设置响应式数据和方法等
            setup(){
                const message = Vue.ref('Hello Vue!')
                // 这是个对象,可以直接在模板中使用
                const web = Vue.reactive({
                    title: 'Vue.js',
                    url: 'https://cn.vuejs.org/'    
                })
                return {
                    message: message,
                    // web: web
                    web
                }
            }
        }).mount('#app')
    </script>

</body>
</html>

模块化开发

使用es

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    
</head>
<body>
    <div id="app">
        <h1>{{ message }}</h1>
        <p>{{ web.title }} {{ web.url }}</p>
    </div>

    <script type="module">
        import { createApp , reactive} from './vue.esm-browser.js'
        createApp({
            // setup选项,用于设置响应式数据和方法等
            setup(){
                
                // 这是个对象,可以直接在模板中使用
                const web = reactive({
                    title: 'Vue.js',
                    url: 'https://cn.vuejs.org/'    
                })
                return {
                    message: 'Hello Vue.js',
                    // web: web
                    web
                }
            }
        }).mount('#app')
    </script>

</body>
</html>

ref 与 reactive

在这里插入图片描述
ref是一个引用,修改使用.value, 而reactive则直接修改对应属性即可

ref也可以存储数组

绑定事件 v-on 简写 @

在这里插入图片描述

显示和隐藏 v-show

<p v-show="show">...</p>
当show为false就隐藏

条件渲染 v-if

<p v-if="show"> ... </p>
不适用于频繁切换显示状态

以下是更好的用法
<p v-if="show < 1000">用户较少</p>
<p v-else-if="show > 1000 && show < 10000 ">用户较多</p>
<p v-else>用户很多</p>

动态属性绑定v-bind

<input type="text" :value="web.url">
这样文本框的值就是web.url的值
简写直接用:
这个绑定可以用在class, img, src等用法,实现其值的动态绑定

放在li中为每行元素设置title和key

:title=“value.name”

:key=“value.id”

v-for 遍历数组和对象

<p>遍历数组或对象</p>
<ul>
         <li v-for="value in arr">{{ value }}</li>
</ul>
const arr = Vue.reactive([1,2,3,4,5])

双向数据绑定 v-model

单向数据绑定:当数据发生改变时,视图会自动更新,但是用户手动修改input的值,数据不会自动更新

双向数据绑定会自动更新数据的值

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="vue.global.js"></script>
</head>

<body>
    <div id="app">
        <h3>文本框 {{ data.text }}</h3>
        <h3>单选框 {{ data.radio }}</h3>
        <h3>多选框 {{ data.checkbox }}</h3>
        <h3>下拉框 {{ data.select }}</h3>
        <h3>记住密码 {{ data.remember }}</h3>

        双向数据绑定 <input type="text" v-model="data.text">
        <br>
        <input type="radio" value="male" v-model="data.radio"><input type="radio" value="female" v-model="data.radio"><br>
        <input type="checkbox" value="apple" v-model="data.checkbox"> 苹果
        <input type="checkbox" value="banana" v-model="data.checkbox"> 香蕉
        <input type="checkbox" value="orange" v-model="data.checkbox"> 橘子
        <br>
        <select v-model="data.select">
            <option value="">请选择</option>
            <option value="apple">苹果</option>
            <option value="banana">香蕉</option>
            <option value="orange">橘子</option>
        </select>
        <br>
        <input type="checkbox" v-model="data.remember"> 记住密码
    </div>

    <script>
        Vue.createApp({
            // setup选项,用于设置响应式数据和方法等
            setup() {
                const data = Vue.reactive({
                    text: 'hello',
                    radio: '',
                    checkbox: [],
                    select: '',
                    remember: false
                })

                return {
                    data
                }
            }
        }).mount('#app')
    </script>

</body>

</html>

v-model修饰符

之前的双向数据绑定是实时渲染的(默认形式),现在我们不需要实时渲染,只要按enter等才改

  • 在失去焦点或enter后修改 v-model.lazy
  • 输入框的值转为数字类型 v-model.number
  • 去除首尾空格 v-model.trim

以上是常用的三种

渲染数据 v-text 和 v-html

v-html可以将数据解析为html格式,而v-text会解析为纯文本格式

计算属性computed

在这里插入图片描述
可以避免重复计算

侦听器watch

监听元素的变化

setup(){
    const date = reactive({
        year:2023,
        month:10
    })
    watch(date.year, (newValue, oldValue))=>{
        console.log("new:", newValue, "old:", oldValue);
    }
}
// 注意,json中对象和数组是通过引用传递的,所以old也是修改后的值,如果是普通变量,则会通过赋值传递,会保留old值

自动监听watchEffect
在这里插入图片描述
将不需要手动设定监听的对象

图片轮播案例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="vue.global.js"></script>
</head>

<body>
    <div id="app">
        <h1>{{number}}</h1>
        <h1>imageSrc: {{imageSrc}}</h1>
        <img :src="imageSrc" style="width: 300px;"> <br>
        <button @click="next">+</button>
        <button @click="prev">-</button>
        <ul>
            <li v-for="value in 6">
                <button  @click="jump(value)">{{value}}</a>
            </li>
        </ul>
    </div>

    <script>
        Vue.createApp({
            // setup选项,用于设置响应式数据和方法等
            setup() {

                const number = Vue.ref(1)
                // 这是个对象,可以直接在模板中使用
                const next = () => {
                    number.value++
                    if (number.value == 6) {
                        number.value = 1
                    }
                }
                const prev = () => {
                    number.value--
                    if (number.value == 1) {
                        number.value = 6
                    }
                }

                const web = Vue.reactive({
                    title: 'Vue.js',
                    url: 'https://cn.vuejs.org/'
                })

                const imageSrc = Vue.computed(() => {
                    return `image/${number.value}.jpg`
                })

                const jump = (value) => {
                    number.value = value
                }

                return {
                    number,
                    next,
                    // web: web
                    imageSrc,
                    prev,
                    jump,
                    web
                }
            }
        }).mount('#app')
    </script>
    </script>
</body>

</html>

记事本案例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>记事本</title>
    <script src="vue.global.js"></script>
</head>
</head>
<body>
    <div id="app">
        <input type="text" v-model="data.content">
        <button @click="add">添加</button>
        
        <ul>
            <li v-for="(value, index) in data.list" :key="index">{{value}}
                <button @click="remove(index)">删除</button>
            </li>
            {{data.list.length}}
            <button @click="clean">清空</button>
        </ul>
    </div>
    <script>
        Vue.createApp({
            setup(){
                const data = Vue.reactive({
                    content: '',
                    list: ["· 吃饭", "· 睡觉", "· 打豆豆"]
                })

                const add = () =>{
                    data.list.push(data.content)
                    console.log(data.list)
                }
                const remove = (index) =>{
                    data.list.splice(index, 1)
                    console.log(data.list)
                }
                const clean = () =>{
                    data.list = []
                    console.log(data.list)
                }


                return {
                    data,
                    add,
                    remove,
                    clean
                }
            }


        }).mount('#app')



    </script>
</body>
</html>

change事件&&购物车案例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>购物车</title>
    <script src="vue.global.js"></script>
</head>

<body>
    <div id="app">
        <table>
            <thead>
                <tr>
                    <td><input type="checkbox" v-model="data.seleted" @change="selectAll"></td>
                    <td>商品名称</td>
                    <td>商品价格</td>
                    <td>库存</td>
                    <td colspan="2">操作</td>
                </tr>
            </thead>
            <tbody>
                <tr v-for="(item, index) in data.list">
                    <td><input type="checkbox" :value="item" v-model="data.checkboxList" @change="checkSelected"></td>
                    <td>{{item.name}}</td>
                    <td>{{item.price}}</td>
                    <td>{{item.stock}}</td>
                    <td><button @click="add(index)">+</button></td>
                    <td>{{item.number}}</td>
                    <td><button @click="reduce(index)">-</button></td>
                    <td><button @click="del(item.id)">删除</button></td>
                </tr>
            </tbody>
            <tfoot>
                <tr>
                    <td>总价 {{totalPrice()}}</td>
                </tr>
            </tfoot>
        </table>
    </div>

    <script>
        Vue.createApp({
            setup() {


                const data = Vue.reactive({
                    seleted: false,
                    checkboxList: [],
                    list: [{
                        id: 1,
                        name: '商品1',
                        price: 100,
                        number: 0,
                        stock: 3
                    },
                    {
                        id: 2,
                        name: '商品2',
                        price: 200,
                        number: 0,
                        stock: 2
                    },
                    {
                        id: 3,
                        name: '商品3',
                        price: 300,
                        number: 0,
                        stock: 1
                    },
                    {
                        id: 4,
                        name: '商品4',
                        price: 400,
                        number: 0,
                        stock: 4
                    },
                    {
                        id: 5,
                        name: '商品5',
                        price: 500,
                        number: 0,
                        stock: 5
                    }
                    ]

                })

                const selectAll = () => {
                    console.log(data.seleted)
                    if (data.seleted) {
                        data.checkboxList = data.list
                    } else {
                        data.checkboxList = []
                    }
                    console.log(data.checkboxList)
                }
                const checkSelected = () => {
                    if (data.checkboxList.length == data.list.length && data.list.length != 0) {
                        data.seleted = true
                    } else {
                        data.seleted = false
                    }
                }
                const totalPrice = () => {
                    let total = 0;
                    for (let i = 0; i < data.checkboxList.length; i++) {
                        total += data.list[i].price * data.list[i].number
                    }
                    return total
                }
                const add = (index) => {
                    if (data.list[index].number < data.list[index].stock)
                        data.list[index].number++;
                }
                const reduce = (index) => {
                    if (data.list[index].number > 0) {
                        data.list[index].number--;
                    }
                }
                const del = (id) => {
                    for (let i = 0; i < data.list.length; i++) {
                        if (data.list[i].id == id) {
                            data.list.splice(i, 1)
                            break;
                        }
                    }
                    for(let i = 0; i < data.checkboxList.length; i++){
                        if(data.checkboxList[i].id == id){
                            data.checkboxList.splice(i, 1)
                            break;
                        }
                    }
                    checkSelected()
                }
                return {
                    data,
                    selectAll,
                    checkSelected,
                    totalPrice,
                    add,
                    reduce,
                    del
                }

            }
        }).mount('#app')
    </script>
</body>

</html>

购物车案例优化

使用侦听器watch来实现全选和取消全选

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>购物车</title>
    <script src="vue.global.js"></script>
</head>

<body>
    <div id="app">
        <table>
            <thead>
                <tr>
                    <td><input type="checkbox" v-model="data.seleted"></td>
                    <td>商品名称</td>
                    <td>商品价格</td>
                    <td>库存</td>
                    <td colspan="2">操作</td>
                </tr>
            </thead>
            <tbody>
                <tr v-for="(item, index) in data.list">
                    <td><input type="checkbox" :value="item" v-model="data.checkboxList"></td>
                    <td>{{item.name}}</td>
                    <td>{{item.price}}</td>
                    <td>{{item.stock}}</td>
                    <td><button @click="add(index)">+</button></td>
                    <td>{{item.number}}</td>
                    <td><button @click="reduce(index)">-</button></td>
                    <td><button @click="del(item.id)">删除</button></td>
                </tr>
            </tbody>
            <tfoot>
                <tr>
                    <td>总价 {{totalPrice()}}</td>
                </tr>
            </tfoot>
        </table>
    </div>

    <script>
        Vue.createApp({
            setup() {


                const data = Vue.reactive({
                    seleted: false,
                    checkboxList: [],
                    list: [{
                        id: 1,
                        name: '商品1',
                        price: 100,
                        number: 0,
                        stock: 3
                    },
                    {
                        id: 2,
                        name: '商品2',
                        price: 200,
                        number: 0,
                        stock: 2
                    },
                    {
                        id: 3,
                        name: '商品3',
                        price: 300,
                        number: 0,
                        stock: 1
                    },
                    {
                        id: 4,
                        name: '商品4',
                        price: 400,
                        number: 0,
                        stock: 4
                    },
                    {
                        id: 5,
                        name: '商品5',
                        price: 500,
                        number: 0,
                        stock: 5
                    }
                    ]

                })

                // const selectAll = () => {
                //     console.log(data.seleted)
                //     if (data.seleted) {
                //         data.checkboxList = data.list
                //     } else {
                //         data.checkboxList = []
                //     }
                //     console.log(data.checkboxList)
                // }
                // const checkSelected = () => {
                //     if (data.checkboxList.length == data.list.length && data.list.length != 0) {
                //         data.seleted = true
                //     } else {
                //         data.seleted = false
                //     }
                // }
                let flag = true;
                Vue.watch(() => data.seleted, (newVal, oldVal) => {
                    console.log(newVal, oldVal)
                    if (newVal) {
                        data.checkboxList = data.list
                    } else {
                        if (flag) {
                            data.checkboxList = []
                        }
                    }
                })
                Vue.watch(() => data.checkboxList, (newVal, oldVal) => {
                    console.log(newVal, oldVal)
                    if (newVal.length == data.list.length && data.list.length != 0) {
                        data.seleted = true
                        flag = true;
                    } else {
                        data.seleted = false
                        flag = false;
                    }
                })
                const totalPrice = () => {
                    let total = 0;
                    for (let i = 0; i < data.checkboxList.length; i++) {
                        total += data.list[i].price * data.list[i].number
                    }
                    return total
                }
                const add = (index) => {
                    if (data.list[index].number < data.list[index].stock)
                        data.list[index].number++;
                }
                const reduce = (index) => {
                    if (data.list[index].number > 0) {
                        data.list[index].number--;
                    }
                }
                const del = (id) => {
                    for (let i = 0; i < data.list.length; i++) {
                        if (data.list[i].id == id) {
                            data.list.splice(i, 1)
                            break;
                        }
                    }
                    for (let i = 0; i < data.checkboxList.length; i++) {
                        if (data.checkboxList[i].id == id) {
                            data.checkboxList.splice(i, 1)
                            break;
                        }
                    }
                    // checkSelected()
                }
                return {
                    data,
                    // selectAll,
                    // checkSelected,
                    totalPrice,
                    add,
                    reduce,
                    del
                }

            }
        }).mount('#app')
    </script>
</body>

</html>

Axios获取后端数据

以下给出示例代码

axios.get{'url'}.then(response =>{
	console.log("get.data", response.data)
}).catch(error =>{
	console.log("get.error", error)
})


let param = {
    title: data.content,
    ...
}
axios.post{'url', param}.then(response =>{
	console.log("get.data", response.data)
}).catch(error =>{
	console.log("get.error", error)
})

基于vite创建vue3项目

在你的项目文件夹下打开cmd

需要下载node.js

npm create vite@latest

在这里插入图片描述
在这里插入图片描述

最后按照提示,以下是解释:

cd vite-vuedemo  // 进入你刚创建好的项目目录
npm install  // 根据你的选择开始下载项目文件
npm run dev  // 开启端口运行网站

以下是一些好用的vue vscode扩展

如果你没有用vscode,请自行安装
提供语法高亮,语法提示
在这里插入图片描述
ai代码工具,方便好用
在这里插入图片描述

vue组件学习

在Vue 3中,组件是构建用户界面的基本单位。每个组件可以看作是一个封装了 HTML、CSS 和 JavaScript 逻辑的小模块,负责页面的一部分。组件的主要作用是提高代码的复用性和可维护性

删除无关内容,将main.js删减到以下形式(空项目:

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

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

在这里插入图片描述
我们定义子组件footer.vue和header.vue,并编写以下内容:

<script setup>


</script>

<template>
    <h3>Footer</h3>
</template>

<style scoped>

</style>

然后在父组件app.vue中渲染以上页面

注意这里Header 和 Footer必须首字母大写才能被识别为模板

<script setup>
    import Header from './components/header.vue';
    import Footer from './components/footer.vue';
</script>

<template>
    <Header/>
    <div>Test Content</div>
    <Footer/>
</template>

<style scoped></style>

在这里插入图片描述

父传子defineprops

我们可以在<Header\>标签上定义一些属性来传递给子组件

<Header name="huohuopro" url="https://blog.csdn.net/2401_87092242"

然后在header.vue中接收:

<script setup>
    const props = defineProps(["name", "url"])
    confirm(props.name)
    confirm(props.url)
</script>

这便是最简单的传递。

当然,我们也可以使用对象的方式来传递,比如以下:

使用reactive定义对象,这里的reactive是响应式数据,咱们template中可以直接使用{{}}的形式来访问传递的数据,比如传递的userData有num属性,然后在子组件header中被{{props.userdata.num}}渲染,那么这里的num更新后(比如有一个按钮点一下就加一)会自动更新页面渲染,如果是普通数据,那么渲染好的页面不会再更新num

<script setup>
    import Header from './components/header.vue';
    import Footer from './components/footer.vue';
    import { reactive } from 'vue';

    const userData = reactive({
        name: 'huohuopro',
        url: 'https://blog.csdn.net/2401_87092242'
    });
</script>

<template>
    <Header :userData="userData"/>  // :即v-bind
    <div>Test Content</div>
    <Footer/>
</template>

<style scoped></style>

使用defineProps接收对象

<script setup>
const props = defineProps({
    userData: {
        type: Object, // 设置传递的参数类型,这里传来的一个属性是对象
        required: true // 必须传递
    }
});
alert(props.userData.name)
</script>

<template>
    <h3>Header</h3>
</template>

<style scoped></style>

子传父defineEmits

defineEmits传递自定义事件的名称,然后定义一个常量来接收,这样我们就可以将子组件定义的事件发送给父组件

在header.vue中编写

<script setup>
    const emits = defineEmits(['getWeb'])
    emits('getWeb', {name:"huohuopro", age:18})
</script>

然后在父组件中的Header接收,使用@传递的名称="别名"来接收

<Header @getWeb="emitsGetWeb"/>

最后再在父组件中定义这个方法,在这个方法内便有接收到的参数

    const emitsGetWeb = (web) => {
        alert(web.name);
        alert(web.age);
    }

接下来我们在子组件定义一个方法, 点击就给父组件的userData.num加10,注意按钮和方法定义在子组件,点击向父组件传递10, 即emits(‘addNum’, 10)

<script setup>
    const emits = defineEmits(['getWeb', 'addNum'])
    emits('getWeb', {name:"huohuopro", age:18})
    const add = () =>{
        emits('addNum', 10)
    }
</script>

<button @click="add">addNum</button>

在父组件调用并定义方法:

    const emitsAddNum = (data) => {
        userData.num += data;
    }


<Header @addNum="emitsAddNum"/>

这里的基本流程是,子组件定义事件addNum,定义方法add,点击子组件的add后触发emits。父组件监听到子组件触发的 addNum 事件。这个事件的回调函数是 emitsAddNum,它会接收子组件传递的数据(即 10)。当 addNum 事件触发时,父组件会调用 emitsAddNum 方法,传递的数据 10 被作为参数传递给 emitsAddNum

跨组件通信-依赖注入

之前我们只是父组件传递给子组件,现在我们可以在父组件将内容传递给子组件的子组件

但是不能子传父

我们先分别在app.vue,header.vue中加入以下内容:

<script setup>
    import Header from './components/header.vue';
    import { reactive, ref } from 'vue';

    const web = reactive({
        name: "huohuo",
        url: "https://www.baidu.com"
    });

    const user = ref(0)

</script>

<template>
    <h3>App.vue-Top</h3>
    user:  {{ user }}

    <!-- 子组件 -->
    <Header />
    
</template>

<style scoped>

</style>
<script setup>
    import Nav from './nav.vue';
</script>

<template>
    <h3>header.vue-Middle</h3>
    <!-- 子组件 -->
    <Nav />
</template>

<style scoped>

</style>

然后新建nav.vue,作为孙子组件

<script setup>

</script>
<template>
    <h3>这是nav,最底部的组件</h3>

</template>
<style scoped>

</style>

打开页面,应该是这样的:
在这里插入图片描述
在app中添加如下内容:

const web = reactive({
    name: "huohuo",
    url: "https://www.baidu.com"
});
const user = ref(0)
// provied可以将数据传递给所有的子组件,子组件通过inject来获取数据
provide('provideWeb', web);
provide('provideUser', user);
const userAdd = () => {
    user.value += 1;
}
provide('userAdd', userAdd);



user: {{ user }}

然后header中就可以访问传递的方法和数据了

<script setup>
    import { inject } from 'vue';
    import Nav from './nav.vue';

    const web = inject('provideWeb');
    const funcUserAdd = inject('userAdd');
    console.log(web);
    console.log(funcUserAdd);
</script>

<template>
    <h3>header.vue-Middle</h3>
    <!-- 注入的provideWeb对象 -->
    <h4>这是子组件收到的web对象</h4>
    {{ web.name }}
    <button @click="funcUserAdd()">添加用户</button>
    <!-- 子组件 -->
    <Nav />
</template>

<style scoped></style>

如果我们想要将数据进一步传递给nav,可以在子组件继续provide,就可以传递了。

匿名插槽和具名插槽

插槽的作用:将父组件定义的模板片段插入到子组件的特定位置。之前我们使用的如

都是全部插入,现在我们可以进行选择插入,比如通过匿名插槽来插入到子组件

App.vue:

<script setup>
    import Header from './components/header.vue'
    import Footer from './components/footer.vue'
</script>

<template>
    <h3>父组件</h3>
    <!-- <Header /> 
    <Footer /> -->

    <!-- 以下是匿名插槽的写法 -->
    <Header>
        <h3>这是给header的匿名插槽</h3>
    </Header>

    <!-- 以下是具名插槽的写法 v-slot定义插槽名为footerSlot -->
    <Footer>
        <!-- <template v-slot:footerSlot> 简写为# -->
        <template #footerSlot>
            <h3>这是给footer的具名插槽</h3>
        </template>
    </Footer>
</template>
<style scoped>

</style>

在header.vue中使用来使用匿名插槽

<template>
    <h3>header子组件</h3>
    <slot/>
</template>

footer.vue中 来使用具名插槽

<template>
    <h3>footer子组件</h3>
    <slot name="footerSlot"/>
</template>

作用域插槽

子组件向父组件传递数据,并在父组件定义的模板中渲染。

在footer.vue中的后可以跟定义的数据

比如:

<slot name="footerSlot" title="huohuo" user="1000"/>

然后在app.vue中就可以接收这些参数:

    <Footer>
        <!-- <template v-slot:footerSlot> 简写为# -->
        <template #footerSlot="data">
            <h3>这是给footer的具名插槽</h3>
            {{ data.title }}
            {{ data.user }}
        </template>
    </Footer>

这里的data就接收到了所有传递的数据,我们还可以直接使用title和user访问(解构的形式)

实例:开发一个表格组件,展示用户列表。管理员能看到“编辑”按钮,普通用户看不到

app.vue

<script setup>
import { ref } from 'vue'
import Table from './components/table.vue'

const users = ref([
  { id: 1, name: '张三', isAdmin: true },
  { id: 2, name: '李四', isAdmin: false }  
])

// 定义编辑用户的方法
const editUser = (user) => {
  alert('编辑用户:' + user.name);
}
</script>

<template>
  <Table :data="users">
    <!-- 作用域插槽 'header' 用于自定义表头内容 -->
    <template #header>
      <th>ID</th>
      <th>姓名</th>
      <th>操作</th>
    </template>

    <!-- 作用域插槽 'row' 用于自定义表格行的内容 -->
    <!-- 'item' 是当前行数据的变量,通过作用域插槽传递 -->
    <template #row="{ item }">
      <td>{{ item.id }}</td>
      <td>{{ item.name }}</td>
      <td>
        <!-- 只有当用户是管理员时,才显示编辑按钮 -->
        <button v-if="item.isAdmin" @click="editUser(item)">编辑</button>
      </td>
    </template>
  </Table>
</template>

<style scoped></style>

table.vue

<script setup>
    import { defineProps } from 'vue';
    const props = defineProps({
        data:{
            type:Array,
            required:true,
        },
    });
</script>

<template>
    <table>
        <thead>
            <tr>
                <slot name="header"></slot>
            </tr>
        </thead>
        <tbody>
            <tr v-for="(item, index) in data" :key="index">
                <slot name="row" :item="item"></slot>
            </tr>
        </tbody>
    </table>
</template>

<style scoped>

</style>

这里的张三是管理员,所以他能看到编辑按钮并进行编辑:
在这里插入图片描述

生命周期函数

生命周期函数是组件实例从创建到销毁过程中不同时间点自动调用的函数

介绍:DOM:Document Object Model(文档对象模型)

使用两个标准的获取方法:
(1)getElementById (通过标签的id获取标签,相对效率、准确率高;但不能精准选择元素)
(2)getElementsByTagName (通过标签明获取标签,获取到的元素是一个集合,也就是获取的是一个伪数组,即使一个标签获取的也一样是个集合)

除了document用于上面两种方法,其他的节点也一样拥有这两种方法;
制定了两个子规范:

(1)DOM Core(核心部分): 把xml文档设计为树形节点结构,并为这种结构的运行机制制订了一套规范化标准。同时定义了创建、编辑、操纵这些文档结构的属性和方法。(核心思想:树形节点结构、相关的属性与方法)

(2)DOM HTML: 针对HTML文档、标签集合,以及与个别HTML标签相关的元素定义了对象、属性和方法。

在这里插入图片描述
我们可以将生命周期划分为三个阶段:

  1. 挂载阶段
    1. onBeforeMount
      • 在组件实例即将被挂载到DOM树之前调用
      • 此时模板还未编译或渲染到DOM,通常用于执行初始化操作
      • 如获取异步数据,设置初始属性值等
    2. onMounted
      • 组件成功挂载到DOM并完成首次渲染后调用
      • 此时可以访问和操作DOM元素并执行与页面交互相关的逻辑
  2. 更新阶段
    1. onBeforeUpdate
      • 在组件更新之前即将重新渲染时调用
      • 可以根据新的参数判断是否需要特殊处理
      • 甚至可以选择阻止这次的更新
    2. onUpdated
      • 在组件完成更新并重新渲染后调用
      • 可以基于新的渲染结果并处理更新后的数据
  3. 卸载阶段
    1. onBeforeUnmount
      • 在组件从DOM中销毁之前调用
      • 用于释放资源,如清理计时器,解绑事件监听器等
    2. onUnmounted
      • 在组件已经从DOM中移除并销毁后调用
      • 确保组件所占用的所有资源都被正确释放
  4. 错误处理
    1. onErrorCaptured
      • 在捕获到组件中的错误时调用
      • 用于处理错误如记录错误日志等

挂载过程:

  • 模板编译
    • 将组件的模板转化为js代码
  • 渲染
    • 在模板编译后生成的JS代码渲染到页面上
    • 生成虚拟DOM
  • 挂载
    • 在渲染完成后将虚拟的DOM挂载到DOM树上
    • 使其在页面上显示出来

toRef 和 toRefs

toRefs是将一个响应式对象的所有属性转换为ref对象

toRef是将一个响应式对象的某个属性转换为ref变量

toRef 和 toRefs 主要用于需要保持解构后变量响应性的特定场景

Pinia简介与安装

Pinia是一个轻量级的状态管理库

状态管理库是用于管理应用全局状态的工具

以登陆为例

使用Pinia创建一个userStore来集中管理用户的登录状态和过期时间

当用户登录成功时:

​ 设置userStore中用户的登录状态为已登录,并设置过期时间

当用户退出登录时:

​ 修改userStore中用户的登录状态为未登录,并删除过期时间

Pinia和组件通信的区别

虽然Vue提供的父传子和子传父以及跨组件通信也可用于状态共享,但在大型项目中,随着组件数量的增加,会导致以下问题:

1.组件之间传递大量的props会使项目变得非常繁琐和难以维护

2.非父子组件间过度依赖provide/inject使状态散落在各个组件之间

Pinia可以解决以下问题

  1. 全局状态管理
    • 所有组件都可以访问和修改状态,而不用在每个组件内进行状态管理
  2. 简化组件之间的通信
    • 避免在组件间传递大量的props
  3. 状态持久化
    • 可以将应用程序的状态保存到本地存储中
    • 在应用程序重启后会保留登录状态,对于登录等场景非常有用

Pinia和localStorage的区别

LocalStorage

  • 只能存储字符串类型
  • 有大小限制,通常为5mb

Pinia

  • 可以存储任何类型的数据,包括对象,数组等
  • 没有大小限制,可以存储大量数据

总的来说,对于复杂的状态管理需求,Pinia是比较好的选择。

项目内使用npm install pinia 安装

npm install pinia

安装后在项目的package.json中可以看到pinia的版本等信息

然后在main.js中使用

import { createPinia } from 'pinia'

导入pinia实例,注册到vue应用中:

此处替换create的内容

import { createPinia } from 'pinia'
const pinia = createPinia()
createApp(App).use(pinia).mount('#app')

定义和使用Store

store是用来集中存储和管理组件之间共享状态的仓库

在src目录下新建一个文件夹stores,新建一个js文件。要创建仓库,首先要导入pinia的defineStore

// 从 Pinia 导入 defineStore 函数,用于定义一个 store
import { defineStore } from "pinia";
import { reactive, ref } from "vue";

// 使用 defineStore 定义一个名为 'web' 的 store。
// 第一个参数是 store 的名称,第二个参数是一个工厂函数,返回 store 的状态、方法等。
export const useWebStore = defineStore('web', () => {
    const web = reactive({
        title: "huohuo",  
        url: "https://blog.csdn.net/2401_87092242/article/details/147802631?spm=1001.2014.3001.5501" 
    });
    const users = ref(1000);
    
    const userAdd = () => {
        users.value += 1;  
    };

    // 返回一个对象,其中包含需要暴露给组件的响应式数据和方法
    return {
        web,        
        users,      
        userAdd    
    }
});

以上我们就定义好了一个仓库并使其暴露在外,其中useWebStore是约定俗成的命名规范。

在app.vue中添加如下:

<script setup>
    import { useWebStore } from './store/web.js'
    const webStore = useWebStore()
    console.log(webStore.web)
    console.log(webStore.users)
    
</script>

<template>
    {{  webStore.web.url }}
    <p></p>
    {{  webStore.users }}
    <p></p>
    {{  webStore.web.title }}
    <button @click="webStore.userAdd">添加用户</button>
</template>

<style scoped>


</style>

现在已经可以使用并修改库中的值了

Pinia持久化存储插件

pinia持久化插件也是存储到localStorage中,但是它可以自动状态同步并且更加易用。

这个插件可以自动将pinia的状态存储到localStorage中,无需手动的处理状态的读取和写入,并且和vue的数据完美结合,当状态改变时,依赖这些状态的组件会自动更新视图,更加灵活和强大。

官网地址:Home | pinia-plugin-persistedstate (prazdevs.github.io)

使用以下命令安装:

npm i pinia-plugin-persistedstate

同样在package.json中可以查看插件状态

在app.vue中导入并注册:

// 导入持久化插件
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'


// 注册持久化插件, 并传入pinia实例
pinia.use(piniaPluginPersistedstate)

随后我们将之前添加的库web.js的useWebStore后添加一个小参数就可以开启持久化存储:
注意注释标注的添加位置:

import { defineStore } from "pinia";
import { reactive, ref } from "vue";
export const useWebStore = defineStore('web', () => {
    const web = reactive({
        title:"huohuo",
        url:"https://blog.csdn.net/2401_87092242/article/details/147802631?spm=1001.2014.3001.5501"
    });

    const users = ref(1000);

    const userAdd = () => {
        users.value += 1;
    }

    return {
        web,
        users,
        userAdd
    }
},
// 在这将persist设置为true就可以将这个对象设置为持久化存储
{
    persist: true,
})

end

鸣谢

【2024最新版】3小时学会Vue3,小白零基础视频教程,web前端快速入门实战课程
邓瑞老师


网站公告

今日签到

点亮在社区的每一天
去签到