Vue 基本使用知识点整理

Vue 基本使用

指令,插值

  • 插值,表达式
  • 指令,动态属性
  • v-html:会有 XSS 风险,会覆盖子组件
<template>
    <div>
        <p>文本插值 {{message}}</p>
        <p>JS 表达式 {{ flag ? 'yes' : 'no' }} (只能是表达式,不能是 js 语句)</p>

        <p :id="dynamicId">动态属性 id</p>

        <hr/>
        <p v-html="rawHtml">
            <span>有 xss 风险</span>
            <span>【注意】使用 v-html 之后,将会覆盖子元素</span>
        </p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            message: 'hello vue',
            flag: true,
            rawHtml: '指令 - 原始 html <b>加粗</b> <i>斜体</i>',
            dynamicId: `id-${Date.now()}`
        }
    }
}
</script>

computed 和 watch

  • computed 有缓存,data 不变则不会重新计算
  • computed 可以设置 set 和 get 方法
  • watch 深度监听
    • watch 默认不是深度监听
    • 启动深度监听的话需要设置 deep 为 true
  • watch 监听引用类型,拿不到 old value

computed demo

<template>
    <div>
        <p>num {{num}}</p>
        <p>double1 {{double1}}</p>
        <input v-model="double2"/>
    </div>
</template>

<script>
export default {
    data() {
        return {
            num: 20
        }
    },
    computed: {
        double1() {
            return this.num * 2
        },
        double2: {
            get() {
                return this.num * 2
            },
            set(val) {
                this.num = val/2
            }
        }
    }
}
</script>

watch demo

<template>
    <div>
        <input v-model="name"/>
        <input v-model="info.city"/>
    </div>
</template>

<script>
export default {
    data() {
        return {
            name: '吉良吉影',
            info: {
                city: '上海'
            }
        }
    },
    watch: {
        name(oldVal, val) {
            console.log('watch name', oldVal, val) // 值类型,可正常拿到 oldVal 和 val
        },
        info: {
            handler(oldVal, val) {
                console.log('watch info', oldVal, val) // 引用类型,拿不到 oldVal 。因为指针相同,此时已经指向了新的 val
            },
            deep: true // 深度监听
        }
    }
}
</script>

class 和 style

注意事项

  • 使用动态属性
  • 使用驼峰式写法

代码演示

<template>
    <div>
        <p :class="{ black: isBlack, yellow: isYellow }">使用 class</p>
        <p :class="[black, yellow]">使用 class (数组)</p>
        <p :style="styleData">使用 style</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            isBlack: true,
            isYellow: true,

            black: 'black',
            yellow: 'yellow',

            styleData: {
                fontSize: '40px', // 转换为驼峰式
                color: 'red',
                backgroundColor: '#ccc' // 转换为驼峰式
            }
        }
    }
}
</script>

<style scoped>
    .black {
        background-color: #999;
    }
    .yellow {
        color: yellow;
    }
</style>

条件渲染

  • v-if , v-else 的用法,可使用变量,也可使用 === 表达式
  • v-if 和 v-show 的区别?
    • v-if 会直接销毁和加载 DOM
    • v-show 修改 display 属性为显示或隐藏
  • v-if 和 v-show 的使用场景?
    • 频繁切换显示隐藏场景使用 v-show
    • 非频繁切换显示隐藏场景(如切换注册/登录)可使用 v-if

代码演示

<template>
    <div>
        <p v-if="type === 'a'">A</p>
        <p v-else-if="type === 'b'">B</p>
        <p v-else>other</p>

        <p v-show="type === 'a'">A by v-show</p>
        <p v-show="type === 'b'">B by v-show</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            type: 'a'
        }
    }
}
</script>

循环(列表)渲染

注意事项

  • v-for 可遍历对象
  • key 不可乱写(如 random 或者 index)
  • v-for 和 v-if 不建议一起使用!

代码演示

<template>
    <div>
        <p>遍历数组</p>
        <ul>
            <li v-for="(item, index) in listArr" :key="item.id">
                {{index}} - {{item.id}} - {{item.title}}
            </li>
        </ul>

        <p>遍历对象</p>
        <ul >
            <li v-for="(val, key, index) in listObj" :key="key">
                {{index}} - {{key}} -  {{val.title}}
            </li>
        </ul>
    </div>
</template>

<script>
export default {
    data() {
        return {
            flag: false,
            listArr: [
                { id: 'a', title: '标题1' }, // 数据结构中,最好有 id ,方便使用 key
                { id: 'b', title: '标题2' },
                { id: 'c', title: '标题3' }
            ],
            listObj: {
                a: { title: '标题1' },
                b: { title: '标题2' },
                c: { title: '标题3' },
            }
        }
    }
}
</script>

事件

  • event参数,自定义参数
    • 在不传自定义参数的情况下,event 对象可以直接在函数形参获取
    • 传入自定义参数情况下,需要将 $event 传入
    • 此 event 是原生的 event 对象
  • 事件修饰符,按键修饰符
    • stop 阻值点击事件继续传播
    • prevent 提交事件不再重载页面
    • v-on:click.stop.prevent 修饰符可以串联使用
  • 【观察】事件被绑定到哪里?
    • Vue 的事件是被挂载到当前元素的

代码演示

<template>
    <div>
        <p>{{num}}</p>
        <button @click="increment1">+1</button>
        <button @click="increment2(2, $event)">+2</button>
    </div>
</template>

<script>
export default {
    data() {
        return {
            num: 0
        }
    },
    methods: {
        increment1(event) {
            console.log('event', event, event.__proto__.constructor) // 是原生的 event 对象
            console.log(event.target)
            console.log(event.currentTarget) // 注意,事件是被注册到当前元素的,和 React 不一样
            this.num++

            // 1. event 是原生的
            // 2. 事件被挂载到当前元素
            // 和 DOM 事件一样
        },
        increment2(val, event) {
            console.log(event.target)
            this.num = this.num + val
        },
        loadHandler() {
            // do some thing
        }
    },
    mounted() {
        window.addEventListener('load', this.loadHandler)
    },
    beforeDestroy() {
        //【注意】用 vue 绑定的事件,组建销毁时会自动被解绑
        // 自己绑定的事件,需要自己销毁!!!
        window.removeEventListener('load', this.loadHandler)
    }
}
</script>

表单

  • v-model 双向数据绑定
  • 常见表单项 textarea checkbox radio select
  • 修饰符 lazy(类似防抖效果) number(只能输入数字) trim(去除两边空格)

代码演示

<template>
    <div>
        <p>输入框: {{name}}</p>
        <input type="text" v-model.trim="name"/>
        <input type="text" v-model.lazy="name"/>
        <input type="text" v-model.number="age"/>

        <p>多行文本: {{desc}}</p>
        <textarea v-model="desc"></textarea>
        <!-- 注意,<textarea>{{desc}}</textarea> 是不允许的!!! -->

        <p>复选框 {{checked}}</p>
        <input type="checkbox" v-model="checked"/>

        <p>多个复选框 {{checkedNames}}</p>
        <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
        <label for="jack">Jack</label>
        <input type="checkbox" id="john" value="John" v-model="checkedNames">
        <label for="john">John</label>
        <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
        <label for="mike">Mike</label>

        <p>单选 {{gender}}</p>
        <input type="radio" id="male" value="male" v-model="gender"/>
        <label for="male">男</label>
        <input type="radio" id="female" value="female" v-model="gender"/>
        <label for="female">女</label>

        <p>下拉列表选择 {{selected}}</p>
        <select v-model="selected">
            <option disabled value="">请选择</option>
            <option>A</option>
            <option>B</option>
            <option>C</option>
        </select>

        <p>下拉列表选择(多选) {{selectedList}}</p>
        <select v-model="selectedList" multiple>
            <option disabled value="">请选择</option>
            <option>A</option>
            <option>B</option>
            <option>C</option>
        </select>
    </div>
</template>

<script>
export default {
    data() {
        return {
            name: '吉良吉影',
            age: 33,
            desc: '自我介绍',

            checked: true,
            checkedNames: [],

            gender: 'male',

            selected: '',
            selectedList: []
        }
    }
}
</script>

其他

  • v-once 让某元素标签只渲染一次
  • ref 写在标签上可以通过 this.$refs 获取操作 DOM 节点
    • ref 还可以写在子组件上,从而获取子组件的引用

组件

props 和 $emit

  • props 父组件向子组件传递信息
  • $emit 子组件向父组件触发一个事件
  • 实现父子组件之间通讯

父组件

<template>
    <div>
        <Input @add="addHandler"/>
        <List :list="list" @delete="deleteHandler"/>
    </div>
</template>

<script>
import Input from './Input'
import List from './List'

export default {
    components: {
        Input,
        List
    },
    data() {
        return {
            list: [
                {
                    id: 'id-1',
                    title: '标题1'
                },
                {
                    id: 'id-2',
                    title: '标题2'
                }
            ]
        }
    },
    methods: {
        addHandler(title) {
            this.list.push({
                id: `id-${Date.now()}`,
                title
            })
        },
        deleteHandler(id) {
            this.list = this.list.filter(item => item.id !== id)
        }
    },
    created() {
        console.log('index created')
    },
    mounted() {
        console.log('index mounted')
    },
    beforeUpdate() {
        console.log('index before update')
    },
    updated() {
        console.log('index updated')
    },
}
</script>

Input 组件

<template>
    <div>
        <input type="text" v-model="title"/>
        <button @click="addTitle">add</button>
    </div>
</template>

<script>
import event from './event'

export default {
    data() {
        return {
            title: ''
        }
    },
    methods: {
        addTitle() {
            // 调用父组件的事件
            this.$emit('add', this.title)

            // 调用自定义事件
            event.$emit('onAddTitle', this.title)

            this.title = ''
        }
    }
}
</script>

List 组件

<template>
    <div>
        <ul>
            <li v-for="item in list" :key="item.id">
                {{item.title}}

                <button @click="deleteItem(item.id)">删除</button>
            </li>
        </ul>
    </div>
</template>

<script>
import event from './event'

export default {
    // props: ['list']
    props: {
        // prop 类型和默认值
        list: {
            type: Array,
            default() {
                return []
            }
        }
    },
    data() {
        return {

        }
    },
    methods: {
        deleteItem(id) {
            this.$emit('delete', id)
        },
        addTitleHandler(title) {
            console.log('on add title', title)
        }
    },
    created() {
        console.log('list created')
    },
    mounted() {
        console.log('list mounted')

        // 绑定自定义事件
        event.$on('onAddTitle', this.addTitleHandler)
    },
    beforeUpdate() {
        console.log('list before update')
    },
    updated() {
        console.log('list updated')
    },
    beforeDestroy() {
        // 及时销毁,否则可能造成内存泄露
        event.$off('onAddTitle', this.addTitleHandler)
    }
}
</script>

自定义事件

  • 创建一个 event.js (如下图)
  • 通过在 event 上绑定自定义事件,调用自定义事件实现兄弟组件之间通讯(上图代码所示)
// event.js
import Vue from 'vue'

export default new Vue()

生命周期

单个组件

生命周期图示

带有父子组件的生命周期

  • 初始化父子组件执行顺序

先触发父元素,然后子元素先进行,父元素收尾。

父beforeCreate => 父created => 父beforeMount => 子beforeCreate => 子created => 子beforeMount

=> 子mounted => 父mounted

先父组件创建虚拟dom,再子组件创建虚拟dom 。初始化先保证父组件初始化完再初始化子组件。(外 => 内)

然后子组件渲染,再父组件渲染 。渲染先保证子组件渲染完再渲染父组件 (内 => 外)

最先开始创建的是最外层组件,但是最先创建完的是最内层组件。 => 类似先进后出(栈)。

  • 删除子组件触发生命周期顺序

先触发父元素,然后子元素先进行,父元素收尾。

父beforeUpdate => 子beforeDestroy => 子destroyed => 父updated

  • 更新子组件触发生命周期顺序

先触发父元素,然后子元素先进行,父元素收尾。

父beforeUpdate => 子beforeUpdate => 子updated => 父updated

首先是修改父组件的data,所以触发父组件的beforeUpdate,它要把更新后数据再传递给给子组件,子组件更新props,因此再触发子组件的beforeUpdate,紧接着渲染页面触发子组件的updated,只有子组件渲染完页面后,这个时候父组件也就渲染完,触发父组件的updated。

Non-Props

  • 父组件向子组件传值,子组件没有通过 props 接收,那么子组件会把该属性变成子组件最外层的 DOM 上的一个属性
  • 如果不希望发生此特性那么可以在子组件上加 一条属性:inheritAttrs: false
  • 子组件可以通过 this.$attrs 手动获取 non-props 属性

插槽

  • slot 中使用的作用域的问题

    • 父模板里调用的数据属性,使用的都是父模板里的数据
    • 子模板里调用的数据属性,使用的都是子模板里的数据
  • 具名插槽

    • 父组件传递时加上属性 v-slot="..." 可简写为 #...
    • 子组件<slot name="..."></slot>
  • 作用域插槽

    • 代码演示:

    • const app = Vue.createApp({
          template: `
              <List v-slot="slotProps">   // 可简写为 ES6 解构形式 v-slot="{ item }", 则下面直接用 item 即可
                  <span>{{ slotProps.item }}</span>   // 2. slotProps.item 就是子组件传入的数据
              </List>
          `
      })
      
      app.component('List', {
          data() { return { list: [1, 2, 3] } },
          template: `
              <div>
                  <slot v-for="item in list" :item=item />    // 1. 将item传给父组件
              </div>
          `
      })
      
    • 分析上面代码:

    • 父组件调子组件的时候传入 slot 进来

    • 子组件通过 :item=item 把数据传给父组件

    • 作用域插槽解决了什么问题?

      • 当子组件渲染的内容要由父组件决定的情况
      • 通过作用域插槽,可以让使父组件调用子组件里的数据
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容