vue基础

kitten 发布于 2024-02-04 204 次阅读


本Wiki将会介绍Vue的基础语法, 您在按照本教程学习之前建议掌握以下基础:

  1. HTML基础。 了解什么是HTML并会使用编译器编写html文件, 了解html中的基本标签
  2. CSS基础。 明白CSS的作用和语法并会简单的使用
  3. JavaScript基础。需要掌握基础关键字, 数据类型;知道DOM, BOM, 了解面向过程编程和面向对象编程的区别。还要了解异步JavaScript 和一些网络编程基础
  4. 最好但是不强求知道如何使用node.js 以及 npm包管理工具的使用

这部分内容会从vue指令讲起,到组件通信结束

v-bind

作用 : 动态设置html元素的标签属性   →    src    url    title    …

//语法:
v-bind:属性名="表达式"
//示例:
<div id="app">
    <img v-bind:src="imgUrl" v-bind:title="msg">
</div>
<script src="./vue.js"></script>
<script>
    const app = new Vue({
        el: "app",
        data: {
            imgUrl: 'img',
            msg: '小小kitten qvq'
        }
    })
</script>

v-for

作用 : 基于数据循环 , 多次渲染整个元素 → 数组 , 对象 , 数字。遍历数组示例 :

<div id="app">
    <ul>
        <li v-for="(item,index) in list" :key="item.id">
            {{ item.name }} → {{ item.id }}
            <button @click="del(item.id)">delete</button>
        </li>
    </ul>
</div>
<script src="./vue.js"></script>
<script>
    const app = new Vue({
        el: "app",
        data: {
            list:[
                {id:1,name:'西瓜'},
                {id:2,name:'苹果'},    
                {id:3,name:'芭娜娜'}
            ]
        },
        methods:{
            del(id){        //拿到数组中的每一项 → item
                this.list = this.list.filter(item => item.id !== id)
            }
        }
    })
</script>
v-for 中的 key

v-for 的默认行为会尝试原地修改元素

:key的作用是给每一项添加一个唯一标识(目的是为了高效地更新虚拟DOM) , 这里给第一个元素添加一个背景色 , 然后点击删除按钮 , 如果没有唯一标识 , 那么vue就不能正确识别 v-for 从数组中渲染的结构 , 此时新的第一个 li 标签仍然会有背景色

尽量不要使用索引值index作key值,一定要用唯一标识的值,如id等。因为若用数组索引index为key,当向数组中指定位置插入一个新元素后,因为这时候会重新更新index索引,对应着后面的虚拟DOM的key值全部更新了,这个时候还是会做不必要的更新

v-model

作用 : 给表单元素使用 , 双向绑定数据 → 可以快速 获取或设置 表单内容元素

本质上是一个语法糖。就是 value属性input事件 的和写

<div id="app">
    <input :value="msg" @input="msg = $event.target.value" type="text">
</div>

示例 (待办) :

<div id="app">
        <input type="text" placeholder="请输入今天要做的事" class="entering" v-model="todoName">
        <button class="add" @keyup.enter="add" @click="add">添加</button>

        <section class="sum" >合计:{{ list.length }}<span class="destroy" @click="clear">清空</span></section>
        <section class="main">
            <ul class="todoList">
                <li class="todo" v-for="(item,index) in list" :key="item.id">
                    <div class="view">
                        <!-- 建议使用index 而不用item.id是因为删除时序号的顺序会动态刷新 -->
                        <span class="index">{{ index + 1 }}.</span><label >{{ item.taskName }}</label>
                        <button class="del" @click="del(item.id)">删除</button>
                    </div>
                </li>
            </ul>
        </section>
</div>
<script src="./lib/vue.js"></script>
<script>
        //v-model 绑定输入框,实时获取表单元素内容
        const app = new Vue({
            el:"#app",
            data:{
                list:[
                    {id:1,taskName:"跑步十公里"},
                    {id:2,taskName:"跳绳一百下"},
                    {id:3,taskName:"买宝宝爽身粉"}
                ],
                todoName:'',
            },
            methods:{
                del(id){
                    // this.list = this.list.filter(function(item){
                    //     return item.id !== id
                    // })
                    this.list = this.list.filter(item => item.id !==id)
                },
                add(){
                    if(this.todoName.trim() === ''){
                        alert('Please input your task name')
                        return
                    }
                    //unshift()方法:将元素添加到数组的开头,并返回数组的长度
                    this.list.unshift({
                        id: +new Date(),
                        taskName: this.todoName,
                    })
                    //清空输入内容
                    this.todoName = ''
                },
                clear(){
                    if(confirm("确定清空?")){
                        this.list = []
                    } 
                }
            },
        })
</script>

指令的修饰符

  1. 按键修饰符 @keyup.enter → 键盘回车监听
  2. v-model修饰符 v-model.trim → 去除首尾空格 v-model.number → 转数字
  3. 事件修饰符 @事件名.stop → 阻止冒泡 @事件名.prevent → 阻止默认行为

修饰符就是对指令进行了一些封装 , 拿到事件对象 e , 这里以@keyup.enter为例 , 会监听键盘事件 , 如果 e.key === 'Enter' , 那么就触发事件

计算属性computed

概念 : 基于现有的数据 , 计算出来的新属性 。依赖的数据变化,自动重新计算 。

声明在 computed 配置项中 , 一个计算属性对应一个函数 , 函数里是计算逻辑

<div id="app">
    <ul>
        <li v-for="(item,index) in list" :key="item.id">
            {{ item.name }} → {{ item.id }} → {{ item.number }}
            <button @click="del(item.id)">delete</button>
        </li>
        <span>总共:{{ totalnumber }}个</span>
    </ul>
</div>
<script src="./vue.js"></script>
<script>
    const app = new Vue({
        el: "app",
        data: {
            list:[
                {id:1,name:'西瓜',number:4},
                {id:2,name:'苹果',number:7},    
                {id:3,name:'芭娜娜',number:9}
            ]
        },
        methods:{
            del(id){        //拿到数组中的每一项 → item
                this.list = this.list.filter(item => item.id !== id)
            }
        },
        computed: {
            totalnumber() {
                    return this.list.reduce((sum, item) => sum + item.number, 0)
            }
        }
    })
</script>

①computed 与 ②methods

  • ①作用 : 封装了一段对于数据的处理 , 求得一个结果 使用 : 写在 computed 配置项中 , 作为属性 , 直接使用 → this.计算属性    /    {{ 计算属性 }}
  • ②作用 : 给实例提供一个方法 , 调用以处理业务逻辑

computed计算属性具有缓存特性    会对计算出来的结果缓存 , 再次使用直接读取缓存 , 依赖项变化了 , 会自动重新计算→并再次缓存

计算属性的完整写法

//简单写法只配置了获取,我们无法修改计算属性的值,比如在methods里添加修改计算属性的方法就会报错,下面是完整写法
<script>
    const app = new Vue({
        el: "app",
        data: {},
        computed:{
            计算属性名:{
                get(){
                    //计算逻辑
                    return 结果
                },
                set(修改的值){
                    //修改逻辑
                }
            }
        }
    })
</script>
//示例
<div id="app">
        姓:<input type="text" v-model="firstname"> + 名:<input type="text" v-model="lastname"> = <span>{{ fullname }}</span>
        <button @click="changeName">改名卡</button>
</div>
<script src="./lib/vue.js"></script>
<script>
    const app = new Vue({
        el:"#app",
        data:{
            firstname:'刘',
            lastname:'备'
        },
        methods:{
            changeName(){
                //调用changeName()方法时,会修改fullname这个计算属性,那么就会调用其set()方法
                this.fullname = '吕小布'    
            }
        },
        computed:{
            fullname :{
                get(){
                    return this.firstname+this.lastname
                },
                set(value){
                    this.firstname =  value.slice(0,1)
                    this.lastname = value.slice(1)
                }
            }
        }
    })
</script>

watch侦听器

作用 : 监视数据变化 , 执行一些 业务逻辑 或 异步操作

<div id="app">
    <input type="text" v-model="words"/>
    <input type="text" v-model="obj.name"/>
</div>
<script src="./vue.js"></script>
<script>
    const app = new Vue({
        el: "app",
        data: {
            words:'小小kitten qvq',
            obj:{
                id:1,
                name:buddy
            }
        },
        watch: {
            //该方法会在数据变化时,触发执行
            数据属性名(newVal,oldVal){
                //一些业务逻辑 或 异步操作
            }
            '对象.属性名' (newVal,oldVal) {
                //一些业务逻辑 或 异步操作
            }
        }
    })
</script>

Vue生命周期 和 生命周期的四个阶段

vue生命周期 : 一个Vue实例从创建到销毁的整个过程

生命周期四个阶段 : 创建    挂载    更新    销毁

  1. 创建响应式数据
  2. 渲染模板 → 操作dom
  3. 数据修改,更新视图
  4. 销毁实例

Vue生命周期 过程中 , 会自动运行一些函数 , 被称为 生命周期钩子

  1. beforeCreate    
  2. created    
  3. beforeMount   
  4. mounted    
  5. beforeUpdate    →    数据修改了 , 视图还没更新
  6. updated    
  7. beforeDestory    
  8. destoryed
//示例: mounted应用    获取焦点
<div class="container" id="app">
    <div class="search-container">
      <div class="search-box">
        <input type="text" v-model="words" id="inp">
        <button>搜索一下</button>
      </div>
    </div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        words: ''
      },
      mounted() {
        document.querySelector('#inp').focus()
      }
    })
  </script>

工程化开发 & vue-cli(可以前置学习webpack)

传统开发模式是直接引入核心包 , 开发vue

工程化是基于构建工具 webpack 等开发 , 但是存在问题

  1. webpack的配置不简单
  2. 雷同的基础配置
  3. 缺乏统一标准

所以就需要一个统一的架子 , 可以快速开发Vue 项目 , 好处是

  1. 容易配置
  2. 内置babel等工具 → 打包处理高级请求
  3. 标准化
使用步骤:
全局安装: yarn global add @vue/cli    ||    npm i @vue/cli -g (node环境下npm指令在所有目录中生效)
查看vue版本:    vue --version
启动项目架子创建项目:    vue create project-name    项目名不能用中文
启动项目        yarn serve || npm run serve  (node环境下)

创建项目后,可以看到有很多文件,这里挑几个重点的说,其它自行了解

/**    main.js    **/
import Vue from 'vue'
import App from './App.vue'
//导入 核心包 和 App.vue根组件
//当前处于什么环境 false生产环境 true开发环境(具体区别可以看webpack)
Vue.config.productionTip = false
//vue实例化 render方法→基于App.vue创建结构渲染 index.html    →    将App.vue渲染到index.html中
new Vue({
  // el:'#app', 和下面的$mount 作用一致,用于指定Vue所管理容器
  render: h => h(App),  //这里是简写
  // render:(createElement) =>{
  //   return createElement(App)
  // }
}).$mount('#app')
<template>                    → 结构
    <div @click="fn">
        hello
    </div>
</template>
<script>                      → 行为
    export default{
        methods:{
            fn(){
                alert("hello")
            }
        },
        created(){
            console.log("hello")
        }
    }
</script>
<style>                       →样式
    div{
        width:200px;
        height:200px;
        border: 3px solid #4576f4;
    }
</style>
--------------------------------------
<!-- vue2中,template标签里 只能有一个根元素!
<style lang="less">    让组件支持less需要装包 → npm i less less-loader
vscode中可以装插件(vetur,或volar)使得能够自动识别vue文件(生产提示,字体颜色等等)
 -->

组件化开发

组件化:一个页面可以拆分成一个个组件 , 每个组件都有自己独立的结构 , 样式 , 行为

好处 : 便于维护, 利于复用 → 提升开发效率

局部组件的使用

1.创建组件    2.导入    3.使用

  1. 局部注册 : 只能在注册的组件内使用
  2. 全局注册 : 所有组件内都能使用

导入 : 在App.vue中import对应路径

使用 : 在App.vue中 使用

// 先在commponents 文件夹中创建组件headNav.vue
<template>
    <div class="head">
        The Summer Kitten    
    </div>
</template>
<script>
    export default{}
</script>
<style>
    .head{
        width: 600px;
        height: 100px;
        background-color: blueviolet;
        color: whitesmoke;
        text-align: center;
        font-size: 28px;
        line-height: 100px;
    }
</style>

// 再在App.vue中 导入和使用
<template>
    <div class="app">
        <Header></Header>
    </div>
</template>

<script>
//导入 , 得到对象
import Header from './components/headNav.vue';
export default{
    components:{
        //组件名 : 组件对象
        Header : Header
    }
}
</script>
<style></style>
//自行添加另外两个组件: bodyMain 和 footNav

全局组件的使用

示例: 全局注册一个组件greenButton

先在components目录下创建一个组件greenButton.vue,写好样式 , 然后在main.js里全局注册

/* main.js 文件 */
//这一段尽量写在 调整开发环境的上面 , 导入为先
//全局注册
import greenButton from "./components/greenButton.vue"
// 注册名 , 注册对象
Vue.component('greenButton',greenButton)

组件的样式冲突scoped

默认情况 : 写在组件中的样式会全局生效 → 容易造成多个组件之间的样式冲突问题

给组件中的<style>加上scoped属性 , 可以让样式局部生效

<style scoped>
    div{
        border: 3px solid blue;
        margin: 30px;
    }
/*   本样式只会对当前组件生效 , 不会影响页面中所有的div   */
<style>
<!--原理 : 本组件中的所有元素都会默认增加一个 data-v-hash 自定义属性,再将选择器更改
可在浏览器的开发者工具中看到:
div[data-v-...]{
    //样式
}
这里的...是一个随机值 -->

组件通信

data是一个函数

一个组件的data选项必须是一个函数 → 保证每个组件实例 , 维护独立的一份数据对象

每次创建新的组件实例 , 都会新执行一次data函数 , 得到一个新对象

示例 :

/* App.vue */
<template>
    <div class="app">
        <baseCount></baseCount>
        <baseCount></baseCount>
        <baseCount></baseCount>
    </div>
</template>
<script>
import baseCount from './components/BaseCount'
export default {
    components: {
        baseCount,
    },
}
</script>
<style></style>
/* baseCount.vue */
<template>
    <div class="base-count">
        <button @click="count--">-</button>
        <span>{{ count }}</span>
        <button @click="count++">+</button>
    </div>
</template>

<script>
export default {
    // data() {
    //   console.log('函数执行了')
    //   return {
    //     count: 100,
    //   }
    // },
    data: function () {
        return {
            count: 100,
        }
    },
}
</script>
<style>
.base-count {
    margin: 20px;
}
</style>

上述示例在App.vue中使用了basecount组件3次 , 三个组件中 每一个 都有一个data函数,函数中维护了 一个count 数据 , 但是这三个 组件维护的不是 同一个count , 而是分别属于他们自己的count , 因此在修改时 , 不会出现点击一个 +按钮 , 页面上三个数据同步修改的情况 , 这就体现了data 作为一个函数的好处

组件通信概念

指组件与组件之间的数据传递 , 因为组件的数据是独立的 , 所以无法访问其他组件的数据,这时就需要组件通信

不同组件关系 和 组件通信方案分类(3种)

组件关系 : 父子                                            非父子

通信方案 : props 和 $emit                         provide & inject   ||  eventbus     

通用解决方案 : vuex

1.props和 $emit

父组件通过props的方式向子组件传递数据,而通过$emit 子组件可以向父组件通信。

2.event bus 事件总线

作用 : 非父子组件之间 , 进行简单消息传递

  1. 创建 一个事件总线 (空 vue 实例) → utils/EventBus.js
  2. 监听方监听事件
  3. 发送方触发Bus实例 的事件

3.provide & inject

  1. 父组件provide提供数据
  2. 子/孙 inject 取值使用

示例 :

src目录下新建 utils 文件夹,里面新建EventBus.js文件

//1.创建一个都能访问的 事件总线
//得到Vue这个实例
import Vue from 'vue'
//
const Bus = new Vue()
//把当前实例导出
export default Bus
/* App.vue */
<template>
    <div id="app">
        <sonFirst :a="title[index]" :b="indexobj"  @changeTitle="handleChange" @changeBorder="handleChangeBorder"></sonFirst>
        <mainBody :b="index"></mainBody>
    </div>
</template>

<script>
import sonFirst from './components/sonFirst.vue';
import mainBody from './components/mainBody.vue';

export default {
    name: 'App',
    //父组件提供数据 , 子/孙/... 组件渲染
    provide(){
        return {
            index:this.index,    //传递自己本身有的数据,有两种方式,一种是简单数据类型,一种是复杂数据类型
            indexobj:this.indexobj
        }
    },
    data() {
        return {
            index:0,
            //演示provide和inject
            indexobj:{
                index:0
            },
            title:['Spring','Summer','Autumn','Winter'],
        }
    },
    methods:{
        handleChange(newTitleIndex){
            this.index = newTitleIndex
        },
        handleChangeBorder(newBorderIndex){
            this.indexobj.index = newBorderIndex
        }
    },
    components: {
        sonFirst: sonFirst,
        mainBody: mainBody,
    }
}
</script>
<style></style>
/* commponents 下面的 sonFirst.vue 文件 */
<template>
    <div class="main">
        <div 
            :class="{ spring: backgroundIndex[0], summer: backgroundIndex[1], autumn: backgroundIndex[2], winter: backgroundIndex[3] }">
            The {{ a }} Kitten</div>
        <button @click="change">更换季节</button>
    </div>
</template>

<script>
import Bus from '../utils/EventBus.js'

export default {
    data() {
        return { index: 0, backgroundIndex: [true, false, false, false] ,lock:true}
    },
    props: ['a','b'],
    methods: {
        //1.通过 $emit(),向父组件发送消息通知
        change() {
            if (this.lock) {
                this.index++
                if (this.index > 3) { this.index = 0 }  //这里必须放上面
                //不然 index++后,backIndex数组会自动扩充
                this.backgroundIndex.forEach((element, index) => {
                    this.backgroundIndex[index] = false
                });
                this.backgroundIndex[this.index] = true
                this.$emit("changeTitle", this.index)
                this.$emit("changeBorder",this.index)   //演示改变复杂类型
                this.lock = false
                setTimeout(()=>{this.lock = true},1500)
            }
        },
    },
    created(){
        //2.接收方 , 监听Bus的事件 , 监听后再回调函数
        Bus.$on('sendMsg',(msg)=>{
            alert(msg)
        })
    }
};
</script>

<style scoped>
div {
    margin: 0 auto;
    width: 900px;
    height: 100px;
    border-radius: 5px;
    color: aliceblue;
    font-size: 36px;
    text-align: center;
    line-height: 100px;
}

.spring {
    background-color: #3afe80;
}

.summer {
    background-color: #fe6a80;
}

.autumn {
    background-color: #e2e23f
}

.winter {
    background-color: blueviolet;
}

button {
    position: absolute;
    top: 10px;
    left: 0;
    margin: 0 auto;
    background-color: rgb(106, 246, 141);
    color: white;
    width: 75px;
    height: 30px;
    border: 0;
    font-size: 16px;
    box-sizing: content-box;
    border-radius: 5px;
}
</style> 
/* mainBody.vue 文件 , 里面有一个子组件 grandSon */
<template>
    <div class="main">
        <button @click="clicksend">查看天气情况</button>
        <grandSon></grandSon>
    </div>
</template>

<script>
import Bus from '../utils/EventBus.js'
import grandSon from './grandSon.vue'

export default {
    data(){
        return{
            alertText:["今天天气不错","今天有点热呢","今天很凉爽哇","今天有点冷哦"]
        }
    },
    components:{
        grandSon:grandSon
    },
    props:['b'],
    methods:{
        clicksend(){
            //  触发事件
            Bus.$emit('sendMsg',this.alertText[this.b])
        },
    }
}
</script>

<style scoped>
.main{
    margin: 10px auto;
    width: 900px;
    height: 600px;
    border-radius: 8px;
    background-color: burlywood;
}
button {
    display: block;
    margin: 10px auto;
    background-color: rgb(87, 175, 101);
    color: white;
    width: 100px;
    height: 45px;
    border: 0;
    color: ghostwhite;
    font-size: 16px;
    box-sizing: content-box;
    border-radius: 5px;
}

</style>
/* grandSon组件 */
<template>
    <div >
        <div class="sonMain" :style="{'border-color':borderColor[index]}"></div>
        <div class="sonMain" :style="{'border-color':borderColor[indexobj.index]}"></div>
    </div>
</template>

<script>
export default {
    //简单类型是非响应式的,不会随着父/爷 数据的更新而更新,复杂类型是响应式的,会更新
    inject:['index','indexobj'],
    data(){
        return{
            borderColor:['darkgreen','rebeccapurple','greenyellow','blueviolet']
        }
    }
}
</script>

<style scoped>
.sonMain{
    margin: 10px auto;
    border: 3px dotted darkgreen;
    width: 850px;
    height: 120px;
    text-align: center;
}
</style>

vue2的基础部分就到这里了,后面会介绍一些其它的东西