尚硅谷Vue2.0+Vue3.0全套教程视频笔记 + 代码 [P051-100]

 视频链接:尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通_哔哩哔哩_bilibili


P1-50:尚硅谷Vue2.0+Vue3.0全套教程视频笔记 + 代码 [P001-050]_小白桶子的博客-CSDN博客

P51-100:当前页面

P101-135:尚硅谷Vue2.0+Vue3.0全套教程视频笔记 + 代码 [P101-135]_小白桶子的博客-CSDN博客

P51-60:

- P51 - 生命周期_销毁流程

课堂笔记:

(1)vm.$destroy() 问题:

(官方文档说明如下图)

①为什么vm销毁了,页面上还有内容?

        vm确实销毁了,但是vm销毁之前的工作成果还在。只不过vm销毁之后,并没有东西去管理页面上的内容了。

②官网中 “清理它与其它实例的连接” 这句话并没有问题。因为Vue的官方文档始终站在组件化编码的思维基础上,给予提示,而目前还没接触到组件,所以还无法印证。

③为什么destroy之后,add事件还能打印?

        官网中所谓的 “移除了所有的事件监听器” ,这个事件指的是自定义事件,而不是原生的DOM事件。

(2)在beforeDestroy中,data、methods、指令等都处于可用状态。虽然能访问到数据,也能调用到方法,但是所有对数据的修改(如本节中的 add )不会再触发更新了。

本节代码(直接复制到空白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>
    <script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body>
    <div id="root">
        <h2 v-text="n"></h2>
        <h2> 当前的n值是:{
   
   {n}}</h2>
        <button @click="add">点我n+1</button>
        <button @click="bye">点我销毁vm</button>
    </div>

    <script type="text/javascript">
        Vue.config.productionTip = false

        const vm = new Vue({
            el:'#root',
            // template:`
            // <div>
            //     <h2> 当前的n值是:{
   
   {n}}</h2>
            //     <button @click="add">点我n+1</button> 
            // </div>
            // `,
            data:{
                n:1
            },
            methods: {
                add(){
                    console.log('add')
                    this.n ++
                },
                bye(){
                    console.log('bye')
                    this.$destroy()
                }
            },
            watch: {
                n(){
                    console.log('n变了')
                }
            },
            beforeCreate() {
                console.log('beforeCreate')
            },
            created() {
                console.log('created')
            },
            beforeMount() {
                console.log('beforeMount')
            },
            mounted() {
                console.log('mounted')
            },
            beforeUpdate() {
                console.log('beforeUpdate')
            },
            updated() {
                console.log('updated')
            },
            beforeDestroy() {
                console.log('beforeDestroy')
            },
            destroyed() {
                console.log('destroyed')
            },

        })
    </script>
</body>
</html>

- P52 - 生命周期_总结

课堂笔记:

(1)生命周期类比图

(2)如果多个地方需要用到同个东西(如本节中,mounted和methods中都需要用到定时器),可以直接用 this.xxx 。

老师总结:

常用的生命周期钩子:

        1.mounted:发送Ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。

        2.beforeDestroy:清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。

关于销毁Vue实例

        1.销毁后借助Vue开发者工具看不到任何信息。

        2.销毁后自定义事件会失效,但原生DOM事件依然有效。

        3.一般不会再beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。

本节代码(直接复制到空白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>
    <script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body>
    <div id="root">
        <h2 :style="{opacity}">欢迎学习Vue</h2>
        <button @click="opacity = 1">透明度设置为1</button>
        <button @click="stop">点我停止变换</button>
    </div>

    <script type="text/javascript">
        Vue.config.productionTip = false

        const vm = new Vue({
            el:'#root',
            data:{
                opacity:1
            },
            methods: {
                stop(){
                    this.$destroy()
                }
            },
            // Vue完成模板的解析并把真实的DOM元素放入页面后(挂载完毕)调用mounted
            mounted(){
                console.log('mounted')
                this.timer = setInterval(() => {
                    console.log('setInterval')
                    this.opacity -= 0.01
                    if(this.opacity <= 0) this.opacity = 1
                },16) 
            },
            beforeDestroy() {
                console.log('vm即将驾鹤西游了')
                clearInterval(this.timer)
            },
        })
    </script>
</body>
</html>

- P53 - 对组件的理解

课堂笔记:

(1)老师的PPT图:

老师总结:

模块与组件、模块化与组件化:

        1.模块:

                (1)理解:向外提供特定功能的js程序,一般就是一个js文件

                (2)为什么:js文件很多很复杂

                (3)作用:复用js,简化js的编写,提高js运行效率

        2.组件:

                (1)理解:用来实现局部(特定)功能效果的代码集合(html/css/js/image...)

                (2)为什么:一个界面的功能很复杂

                (3)作用:复用编码,简化项目编码,提高运行效率

        3.模块化:

                当应用中的js都以模块来编写,那这个应用就是一个模块化的应用。

        4.组件化:

                当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用。

- P54 - 非单文件组件

老师总结:

Vue中使用组件的三大步骤:

一、定义组件(创建组件)

二、注册组件

三、使用组件(写组件标签)

一、如何定义一个组件?

        使用Vue.extend(options)创建,其中 options 和 new Vue(options)时传入的那个options几乎一样,但区别如下:

                1.el不要写,为什么?——最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。

                2.data必须写成函数,为什么?——避免组件被复用时,数据存在引用关系。

        备注:使用template可以配置组件结构。

二、如何注册组件?

        1.局部注册:靠 new Vue的时候传入components选项

        2.全局注册:靠Vue.component('组件名',组件)

三、编写组件标签:

        <school></school>

本节代码(直接复制到空白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>
    <script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body>
    <div id="root">
        <hello></hello>
        <hr>
        <h1>{
   
   {msg}}</h1>
        <hr>
        <!-- 第三步:编写组件标签 -->
        <school></school>
        <hr>
        <!-- 第三步:编写组件标签 -->
        <student></student>
    </div>

    <div id="root2">
        <hello></hello>
    </div>

    <script type="text/javascript">
        Vue.config.productionTip = false

        // 第一步:创建school组件
        const school = Vue.extend({
            // el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
            template:`
                <div>
                    <h2>学校名称:{
   
   {shcoolName}}</h2>
                    <h2>学校地址:{
   
   {address}}</h2>
                    <button @click="showName">点我提示学校名</button>
                </div>
            `,
            data(){
                return{
                    shcoolName:'尚硅谷',
                    address:'北京昌平'
                }
            },
            methods: {
                showName(){
                    alert(this.shcoolName)
                }
            },
        })

        // 第一步:创建student组件
        const student = Vue.extend({
            template:`
                <div>
                    <h2>学生姓名:{
   
   {studentName}}</h2>
                    <h2>学生年龄:{
   
   {age}}</h2>
                </div>
            `,
            data(){
                return{
                    studentName:'张三',
                    age:18
                }
            }
        })

        // 第一步:创建hello组件
        const hello = Vue.extend({
            template:`
                <div>
                    <h2>你好啊!{
   
   {name}}</h2>
                </div>
            `,
            data(){
                return{
                    name:'Tom'
                }
            }
        })
        
        // 第二步:全局注册组件
        Vue.component('hello',hello)

        // 创建vm
        new Vue({
            el:'#root',
            data:{
                msg:'你好啊!'
            },
            // 第二步:注册组件(局部注册)
            components: {
                school,
                student
            }
        })

        new Vue({
            el:'#root2'
        })
    </script>
</body>
</html>

- P55 - 组件的几个注意点

课堂笔记:

(1)本节中,有个简写方式。为什么简写成对象了,Vue依旧能访问到?

        不是说简写中没有调用 Vue.extend ,最终就没有调用。而是在Vue底层(components)中写了判断,如果传入的值是对象(如本节的 s ),那么在components中会帮你执行 Vue.extend 。也就是说,写与不写都行,Vue最终会有判断,写了Vue就不管,没写会补上。

老师总结:

几个注意点:

1.关于组件名:

        一个单词组成:

                第一种写法(首字母小写):school

                第二种写法(首字母大写):School

        多个单词组成:

                第一种写法(kebab-case命名):my-school

                第二种写法(CamelCase命名):MySchool(需要Vue脚手架支持)

        备注:

                (1)组件名尽可能回避HTML中已有的9元素名称,例如:h2、H2都不行。

                (2)可以使用name配置项指定组件在开发者工具中呈现的名字。

2.关于组件标签:

        第一种写法:<school></school>

        第二种写法:<school/>

        备注:不适用脚手架时,<school/>会导致后续组件不能渲染。

3.一个简写方式:

        const school = Vue.extend(options) 可简写为:const school = options

本节代码(直接复制到空白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>
    <script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body>
    <div id="root">
        <h1>{
   
   {msg}}</h1>
        <school></school>
        <!-- <school/> -->
    </div>

    <script type="text/javascript">
        Vue.config.productionTip = false
        
        // 定义组件
        const s = {
            name:'atguigu',
            template:`
                <div>
                    <h2>学校名称:{
   
   {name}}</h2>
                    <h2>学校地址:{
   
   {address}}</h2>
                </div>
            `,
            data(){
                return{
                    name:'尚硅谷',
                    address:'北京'
                }
            }
        }
        
        new Vue({
            el:'#root',
            data:{
                msg:'欢迎学习Vue!'
            },
            components:{
                // 'my-school':s
                school:s
            }
        })
    </script>
</body>
</html>

- P56 - 组件的嵌套

本节代码(直接复制到空白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>
    <script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body>
    <div id="root">
        
    </div>

    <script type="text/javascript">
        Vue.config.productionTip = false

        // 定义student组件
        const student  = Vue.extend({
            name:'student',
            template:`
                <div>
                    <h2>学生姓名:{
   
   {name}}</h2>
                    <h2>学生年龄:{
   
   {age}}</h2>
                </div>
            `,
            data(){
                return{
                    name:'尚硅谷',
                    age:18
                }
            }
        })
        
        // 定义school组件
        const school  = Vue.extend({
            name:'school',
            template:`
                <div>
                    <h2>学校名称:{
   
   {name}}</h2>
                    <h2>学校地址:{
   
   {address}}</h2>
                    <student></student>
                </div>
            `,
            data(){
                return{
                    name:'尚硅谷',
                    address:'北京'
                }
            },
            // 注册组件(局部)
            components:{
                student
            }
        })

        // 定义hello组件
        const hello = Vue.extend({
            template:`<h1>{
   
   {msg}}</h1>`,
            data(){
                return{
                    msg:'欢迎来到尚硅谷学习!'
                }
            }
        })
        
        // 定义app组件
        const app = Vue.extend({
            template:`
                <div>
                    <hello></hello>
                    <school></school>
                </div>
            `,
            components:{
                school,
                hello
            }
        })

        // 创建vm
        new Vue({
            template:`<app></app>`,
            el:'#root',
            // 注册组件(局部)
            components:{app}
        })
    </script>
</body>
</html>

- P57 - VueComponent构造函数

课堂笔记:

(1)关于老师说的,每次调用返回的都是新的VueComponent,弹幕中看到有人说的不错:

        就是东西都是一样的,但由于你所给的参数不同,最终所拿到的东西也不同。

老师总结:

关于VueComponent:

        1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.exend生成的。

        2.我们只需要写或,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。

        3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!

        4.关于this指向:

                (1)组件配置中:

                        data函数、methods中的函数、watch中的函数、computed中的函数,他们的this均是【VueComponent实例对象】

                (2)new Vue()配置中:

                        data函数、methods中的函数、watch中的函数、computed中的函数,他们的this均是【Vue实例对象】

        5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。

                Vue的实例对象,以后简称vm。

(这节代码跟上面两节差不多,就不放上来了。自己一定要去验证、写写看)

- P58 - Vue实例与组件实例

课堂笔记:

(1)既然vc和vm中的内容一样,为什么不能把vc和vm直接划上等号?

        vc是由VueComponent缔造的,vm是由Vue缔造的。这两个个缔造的过程肯定不可能完全一样。

        比如说在创建组件实例对象、传入配置项的时候,是不可以写el的(回顾可看P54),但是在创建vm的时候,配置对象中就可以写el。也就是说vm可以写自己为哪个容器服务,但是vc不能指定服务容器,只能跟着vm走。

        再比如说,vc的数据配置项(data)就必须写成函数。

        可以说,它们俩身上99%的东西一样,但是有这1%就是不一样。就像是两个双胞胎,长得一模一样,但是不能说他们是同个人。

        打开官网——学习——教程——组件基础——基本实例中,也有说到:组件是可复用的Vue实例......因为组件是可复用的Vue实例,所以它们与new Vue接收相同的选项......

- P59 - 一个重要的内置关系

课堂笔记:

(1)本节学习中,需要有一定原型的基础。如果这里不太理解,可以去补一下原型和原型链的理解。可以看这个:js原型和原型链的理解(透彻)_lixiaonaaa的博客-CSDN博客_js原型和原型链的理解

(2)分析Vue与VueComponent的关系图。

(3)实例的隐式原型属性,永远指向自己缔造者的原型对象。

老师总结:

        1.一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype

        2.为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性、方法。

本节代码(直接复制到空白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>
    <script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body>
    <div id="root">
        <school></school>
    </div>

    <script type="text/javascript">
        Vue.config.productionTip = false
        Vue.prototype.x = 99

        // 定义school组件
        const school  = Vue.extend({
            name:'school',
            template:`
                <div>
                    <h2>学校名称:{
   
   {name}}</h2>
                    <h2>学校地址:{
   
   {address}}</h2>
                    <button @click="showX">点我输出x</button>
                </div>
            `,
            data(){
                return{
                    name:'尚硅谷',
                    address:'北京'
                }
            },
            methods: {
                showX(){
                    console.log(this.x)
                }
            },
        })

        // 创建一个vm
        new Vue({
            el:'#root',
            data:{
                msg:'你好'
            },
            components:{
                school
            }
        })

        // console.log(school.prototype.__proto__ === Vue.prototype)

        // 定义一个构造函数
        /* function Demo(){
            this.a = 1
            this.b = 2
        }
        // 创建一个Demo的实例对象
        const d = new Demo()

        console.log(Demo.prototype) //显示原型属性
        console.log(d.__proto__) //隐式原型属性

        console.log(Demo.prototype === d.__proto__)

        //程序员通过显示原型属性操作原型对象,追加一个x属性,值为99
        Demo.prototype.x = 99

        console.log('@',d.x) */
        
    </script>
</body>
</html>

- P60 - 单文件组件

课堂笔记:

(1)给 .vue 文件起名字的规则,和给组件起名的规则是一样的。

(2)老师安装的插件:Vetur,版本自己选择。

(3)如果ES6模块化、模块暴露不熟的同学,需要去补一下。可以看这个:es6中的模块化_我不是你不是我的博客-CSDN博客_es6模块化

(4)看到弹幕有一条说的不错,可以帮助理解结构:

        main:领导,App:包工头,其他组件:工人。

(本节代码在不放入脚手架前都是无用的,目前老师只是展示一个简单的结构,所以这里不贴代码了。)

P61-70:

- P61 - 创建Vue脚手架

课堂笔记:

(1)CLI就是 command line interface 的缩写。Vue CLI官网:Vue CLI

(2)安装过程:

        (PS:

                ①老师已经提前安装过node.js了,没有安装的可以打开这个:Download | Node.js

                ②已经安装的可以检查node.js和npm版本及安装情况:

                        检查nodejs : node -v

                        检查npm : npm -v (这两个指令均出现版本号为安装成功)

        )

        ①配置npm淘宝镜像:npm config set registryhttps://registry.npm.taobao.org

                (这一步可以让vue的安装更加快速,可有可无,但是建议还是安装一下)

        ②第一步(仅第一次执行),全局安装@vue/cli : npm install -g @vue/cli

        ③第二步,切换到你要创建项目的目录,然后使用命令创建项目 : vue create xxxx (xxxx为项目名,最好不要取vue、jQuery这种名字)

        ④第三步,启动项目 : npm run serve

- P62 - 分析脚手架结构

课堂笔记:

(1)目录结构:

┌── node_modules (依赖包)

 |

├── public ──┬── favicon.ico(网站的页签图标)

 |                      └── index.html(vue是单页面应用,这就是总的入口文件)

 |

 |                 ┌── assets(一般用来放静态资源)

├── src ──┼── components(所有组件都往这里放)

 |                 ├── App.vue(大哥)

 |                 └── main.js (非常重要!该文件是整个项目的入口文件)

 |

├── .gitignore (git的配置文件)

├── babel.config.js (babel的控制文件,虽然很重要,但是和我们没啥关系,不需要动,主要工作于 ES6 => ES5,如果想要配置的话,可以参考babel官网)

├── package-lock.json (包版本控制文件)

├── package.json (只要打开的功能是符合npm规范的,那么就一定会有这个文件,类似于包的说明书。其中会配置包的名字(name)、版本(version)、采用的依赖(dependencies)等。还有常用的命令:serve开始的时候配置全部;build所有工程完成之后转换;lint语法检查,几乎不用)

└── README.md (对整个工程进行说明、描述)

(2)整个流程:

        执行npm run serve,随后来到src中,找到main.js,这个页面中引入了Vue、App.vue、关闭了提示等。

        继续找到app.vue页面,看到这个页面中引入了school和student,于是就到components文件夹中找到这两个并执行,执行最终汇总到了App.vue页面。

        再回到main.js页面,把App组件放入容器中。

        再找到index.html,把东西放到这个里面。

        至此完成了整个运行流程。

(3)我自己这边报了一个错:

                Component name "School" should always be multi-word

        看到弹幕上很多人也有这个问题,这个是说组件名最好由多个单词组成,可以找到vue.config.js文件,在module.exports里面添加 lintOnSave:false (关闭语法检查)即可。

        完整的可以看:关于Vue报错“Component name “School“ should always be multi-word”的解决方法_指尖世界 £的博客-CSDN博客

        或者自己按照规范重新命名。

- P63 - render函数

课堂笔记:

(1)在main.js中引入的vue是残缺版的。

(2)理解render这行:

render(createElement){
    return createElement('h1','你好啊')
}
// ↑ 也就可以简写成 ↓
render:q => q('h1','你好啊')

(3)为什么不直接引入vue.js,反而要vue文件夹那么多东西?

        vue中包含了两种东西,核心(比如说生命周期、处理事件等)和模板解析器。如果没有其它的文件,只把vue放到一个页面中,可能会出现问题。因为vue中有三分之一都是模板解析器,之后webpack打包完成后,会生成一个非常大的文件,这个时候vue的模板解析器就不适合出现在这里,没有作用。(vue文件中,带有runtime的都表示运行时的vue,带有common的就是走commonJS)

        简而言之,没有了模板解析器的vue体积很小,打包之后能够更加轻量,代价就是写的时候要用那行render去写。

        老师的比喻:

老师总结:

关于不同版本的Vue:

        1. vue.js 与 vue.runtime.xxx.js 的区别:

                (1)vue.js 是完整版的Vue,包含:核心功能+模板解析器。

                (2)vue.runtime.xxx.js 是运行版的Vue,只包含核心功能,没有模板解析器。

        2.因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createrElement函数去指定具体内容。

- P64 - 修改默认配置

课堂笔记:

(1)Vue 脚手架隐藏了所有 webpack 相关的配置,即使不填写,也会有默认的配置。

(2)五个默认不能改的:public文件夹、favicon.ico、index.html、src文件夹、main.js

(3)如果还是要修改的话,打开Vue CLI官网——配置参考——左侧栏都是能改的。

(4)项目开发中需要关闭语法检查(具体的根据公司要求定),在 module.exports 里面添加 lintOnSave:false (详细的可以看62节我也有说)

(5)看md文件推荐使用Typroa。

老师总结:

1.脚手架文件结构:

(我在62节写了,老师这个会更精简一点,我写的那个包含了老师课上说的其它内容,如果要直接参考查看的话还是老师这个好,补充知识可以看我那个)

2.关于不同版本的Vue

(这个我也在63节写了,这里不弄上去了)

3. vue.config.js 配置文件

        使用 vue inspect > output.js 可以查看到Vue脚手架的默认配置。

        使用 vue.config.js 可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh

- P65 - ref属性

课堂笔记:

(1)对于传统的HTML而言,id和ref确实没有什么差别,但是对于组件来说就不一样了。

        给组件加id,打印出获取的结果为组件所对应的完整DOM结构。

        给组件加ref,打印出获取的结果就是VueComponent实例。

老师总结:

ref属性:

1.被用来给元素或子组件注册引用信息(id的代替者)

2.应用在html标签上获取的是真实的DOM元素,应用在组件标签上是组件实例对象(vc)

3.使用方式:

        打标识:<h1 ref="xxx">  ... </h1>

        获取: this.$refs.xxx

本节部分代码:

main.js页面:

// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 关闭Vue的生产提示
// Vue.config.productionTip = false

// 创建vm
new Vue({
    el:'#app',
    render: h => h(App),
})

App.vue页面:

<template>
    <div>
        <h1 v-text="msg" ref="title"></h1>
        <button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
        <School ref="sch"/>
    </div>
</template>

<script>
// 引入School组件
import School from './components/School.vue'
export default {
    name: "App",
    components: { School },
    data() {
        return {
            msg:'欢迎学习Vue!'
        }
    },
    methods: {
        showDOM(){
            console.log(this.$refs.title) //真实DOM元素
            console.log(this.$refs.btn) //真实DOM元素
            console.log(this.$refs.sch) //School组件的实例对象(vc)

        }
    },
}
</script>

- P66 - props配置

老师总结:

配置项propos

        功能:让组件接收外部传过来的数据

                (1)传递数据:

                        <Demo name="xxx"/>

                (2)接收数据:

                        第一种方式(只接收):

                                props:['name']

                        第二种方式(限制类型):

                                props:{

                                        name:Number

                                }

                        第三种方式(限制类型、限制必要性、指定默认值):

                                props:{

                                        name:{

                                                type:String, // 类型

                                                required:true, // 必要性

                                                default:'老王' // 默认值

                                        }

                                }

        备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

本节部分代码:

App.vue页面:

<template>
    <div>
        <Student name="李四" sex="女" :age="18"/>
    </div>
</template>

<script>
import Student from './components/Student.vue'
export default {
    name: "App",
    components: { Student },
}
</script>

Student.vue页面:

<template>
    <div>
        <h1>{
   
   {msg}}</h1>
        <h2>学生姓名:{
   
   {name}}</h2>
        <h2>学生性别:{
   
   {sex}}</h2>
        <h2>学生年龄:{
   
   {myAge + 1}}</h2>
        <button @click="updateAge">尝试修改收到的年龄</button>
    </div>
</template>

<script>
export default {
    name:'Student',
    data() {
        return {
            msg:'我是一个尚硅谷的学生',
            myAge:this.age
        }
    },
    methods: {
        updateAge(){
            this.myAge++
        }
    },
    // 简单声明接收
    props:['name','sex','age'] 

    // 接收的同时对数据进行类型限制
    /* props:{
        name:String,
        age:Number,
        sex:String
    } */
    

    // 接收的同时对数据进行类型限制 + 默认值的指定 + 必要性的限制
    /* props:{
        name:{ 
            type:String, // name的类型是字符串
            required:true // name是必要的
        },
        age:{
            type:Number,
            default:99 // 默认值
        },
        sex:{ 
            type:String, 
            required:true 
        },
    } */
}
</script>

- P67 - mixin混入

课堂笔记:

(1)mixin.js页面中用了ES6模块化的分别暴露,可以看下这个:es6中的模块化_我不是你不是我的博客-CSDN博客_es6模块化

(2)mixin中的data数据会与页面中的data数据进行整合,如若有冲突的话,则以页面为准。

(3)mixin和页面中的生命周期钩子不以任何为主,都要。

老师总结:

mixin(混入)

        功能:可以把多个组件共用的配置提取成一个混入对象

        使用方式:

                第一步定义混合,例如:

                        {

                                data(){ ... }

                                methods:{ ... }

                                ...

                        }

                第二步使用混入,例如:

                       (1)全局混入:Vue.mixin(xxx)

                       (2)局部混入:mixins:['xxx']

本节部分代码:

mixin.js页面:

export const mixin = {
    methods: {
        showName(){
            alert(this.name)
        }
    },
    mounted() {
        console.log('你好啊!')
    },
}

export const mixin2 = {
    data(){
        return{
            x:100,
            y:200
        }
    }
}

main.js页面:

// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
import {mixin,mixin2} from './mixin'
// 关闭Vue的生产提示
Vue.config.productionTip = false

Vue.mixin(mixin)
Vue.mixin(mixin2)

// 创建vm
new Vue({
    el:'#app',
    render: h => h(App),
})

Student.vue页面:

<template>
    <div>
        <h2 @click="showName">学生姓名:{
   
   {name}}</h2>
        <h2>学生性别:{
   
   {sex}}</h2>
    </div>
</template>

<script>
// 引入了一个混合
import {mixin,mixin2} from '../mixin'
export default {
    name:'Student',
    data() {
        return {
            name:'张三',
            sex:'男'
        }
    },
    mixins:[mixin,mixin2]
}
</script>

- P68 - 插件

老师总结:

插件:

        功能:用于增强Vue

        本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。

        定义插件:

                对象.install = fuction (Vue, options){

                        // 1.添加全局过滤器

                        Vue.filter( ... )

                        // 2.添加全局指令

                        Vue.directive( ... )

                }

本节部分代码:

pugins.js页面:

export default {
    install(Vue){
        console.log('111',Vue)
        // 全局过滤器
        Vue.filter('mySlice',function(value){
            return value.slice(0,4)
        })
        
        // 定义全局指令
        Vue.directive('fbind',{
            // 指令与元素成功绑定时(一上来)
            bind(element,binding){
                element.value = binding.value
            },
            // 指令所在元素被插入页面时
            inserted(element,binding){
                element.focus()
            },
            // 指令所在模板被重新解析时
            update(element,binding){
                element.value = binding.value
            }
        })
        
        // 定义混入
        Vue.mixin({
            data(){
                return{
                    x:100,
                    y:200
                }
            }
        })

        // 给Vue原型上添加一个方法(vm和vc就都能用了)
        Vue.prototype.hello = ()=>{alert('你好啊!')}
    }
}

main.js页面:

// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 引入插件
import plugins from './pugins'
// 关闭Vue的生产提示
Vue.config.productionTip = false
// 应用插件
Vue.use(plugins)
// 创建vm
new Vue({
    el:'#app',
    render: h => h(App),
})

School.vue页面(我将两个页面的引用插件都整合在一起了,比较方便看)

<template>
    <div>
        <h2>学校名称:{
   
   {name | mySlice}}</h2>
        <h2>学校地址:{
   
   {address}}</h2>
        <input type="text" v-fbind:value="name">
        <button @click="test">点我测试一个hello方法</button>
    </div>
</template>

<script>
export default {
    name:'School',
    data() {
        return {
            name:'尚硅谷atguigu',
            address:'北京'
        }
    },
    methods: {
        test(){
            this.hello()
        }
    }
}
</script>

- P69 - scoped样式

课堂笔记:

(1)各个组件虽然样式是在自己的页面上写的,可是最后编译起来都会汇总到一起。

(2)脚手架在解析Vue文件的时候,顺序是:最先扫描import引入、然后再读取配置项、最后才解析模板。

(3)scoped原理:

        其实就是在外层的div中,加了一个特殊的标签(随机生成值),通过标签+属性选择器,完成控制指定的div。

(4)App组件不适合用。

(5)安装less指令:npm i less-loader

        查看less版本指令:npm view less-loader versions (less-loader可替换成别的,查看别的版本)

        安装指定版本的less:npm i less-loader@7

        (我自己是没指定版本就安装好了,依据个人情况定)

老师总结:

scoped样式

        作用:让样式在局部生效,放置冲突。

        写法:<style scoped>

- P70 - 静态

课堂笔记:

(1)老师的工作经验:如果你在拆分组件取名字的时候,发现很难取,说不定是因为你拆的不合理。

        比如说课上的,将input框和list中的item放一起,就难命名,显然就是拆分不合理。

(2)把旧代码改成组件的流程:

        ①一个个div折叠,通过折叠判断哪个是一块儿的,然后剪切到新建组件中。

        ②剪切完之后,马上在剪切的地方写上新建组件,以免到时候忘记、搞混了。

        ③一个个组件分好之后,确认哪些样式是需要公用的,哪些样式是单独的,分配好。

        (老师讲的流程虽然比较理想化,现实中需要整理的代码可能会更乱,但是这个流程确实是比较靠谱的,实际工作中就在这个流程上调整即可,多写注释造福你我他)

老师总结:

组件化编码流程(通用)

        1.实现静态组件:抽取组件,使用组件实现静态页面效果

        2.展示动态数据:

                (1)数据的类型、名称是什么?

                (2)数据保存在哪个组件?

        3.交互——从绑定事件监听开始

P71-80:

- P71 - TodoList案例_初始化列表

课堂笔记:

(1)如何在Vue中,让一个标签动态拥有某个属性?

        如本节中的checked,通过 v-bind:cheked=" "(true or false)完成动态展示。

(全部代码在77节)

- P72 - TodoList案例_添加

课堂笔记:

(1)uuid标准:用于生成全球唯一的字符串编码。

        (因为这个包太大了,所以本节中用的是uuid的精简版,nanoid)

(2)nanoid安装指令:npm i nanoid

        nanoid的库用了分别暴露,所以需要这样引用:

import {nanoid} from 'nanoid'

(3)子组件传值父组件简单原理:

        ①父亲先传给儿子一个函数。

        ②儿子在合适的时候调用,调用的时候父亲就能收到参数。

(4)整个添加的流程:

        首先在MyHeader中,添加用了add方法,add方法调用 this.addTodo() 将 todoObj的值传给了App。

        传给App后,addTodo方法获取值,操作data中的todos。

        Vue收到todos变了,就开始重新解析模板。

        重新解析的时候,显然todos已经变了,则MyList中收到的也会跟着变化。

        MyList收到数据变化,也开始重新解析模板,通过比较数据,虚拟DOM增加,于是MyItem增加。

(全部代码在77节)

- P73 - TodoList案例_勾选

课堂笔记:

(1)数据在哪个页面,那么对数据的操作就应该在哪个页面。

(2)本节中的第二个做法:

<input type="checkbox" v-model="todo.done"/>

        ①因为v-model是双向绑定数据,所以修改有效。

        ②什么叫props被修改了?

                本节中的举例:

                        Vue不能监测到的修改:let obj = {a:1,b:2},修改成 obj.a = 666

                        Vue能监测到的修改:let obj = {a:1,b:2},修改成 obj = {x:100,y:200}

                也就是说Vue监视的是浅层次的修改。

        ③不建议这样做,因为这样会违反原则修改props,虽然并没有被Vue监测到。

(全部代码在77节)

- P74 - TodoList案例_删除

课堂笔记:

(1)在浏览器的Console中,可能会有未展示的消息。

        在Default levels中选择需要看到的。老师建议勾选:Info、Warnings、Errors。

(全部代码在77节)

- P75 - TodoList案例_底部统计

课堂笔记:

(1)ES6中的reduce,条件统计,不熟的可以看:Array.prototype.reduce() - JavaScript | MDN

(全部代码在77节)

- P76 - TodoList案例_底部交互

课堂笔记:

(1)计算属性是允许套娃的。一个计算属性可以通过其它的两个或以上计算属性再进行计算。

(全部代码在77节)

- P77 - TodoList案例_总结

课堂笔记:

(1)看md文件,除了使用Typroa外,还可以在VScode中有个预览按钮可以查看,或者安装VScode插件,老师推荐的是Open in External App。

老师总结:

总结TodoList案例

1.组件化编码流程:

        (1)拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。

        (2)实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

                ①一个组件在用:放在组件自身即可。

                ②一些组件在用:放在他们共同的父组件上(状态提升)

        (3)实现交互:从绑定事件开始。

2.props适用于:

        (1)父组件 ==> 子组件 通信

        (2)子组件 ==> 父组件 通信 (要求父先给子一个函数)

3.使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!

4.props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

本节部分代码:

App.vue页面:

<template>
    <div class="todo-container">
        <div class="todo-wrap">
            <MyHeader :addTodo="addTodo"/>
            <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
            <MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
        </div>
    </div>
</template>

<script>
import MyHeader from './components/MyHeader'
import MyList from './components/MyList'
import MyFooter from './components/MyFooter'

export default {
    name: "App",
    components: {
        MyHeader,
        MyList,
        MyFooter
    },
    data() {
        return {
            todos:[
                {id:'001', title:'抽烟', done:true},
                {id:'002', title:'喝酒', done:false},
                {id:'003', title:'开车', done:true}
            ]
        }
    },
    methods: {
        // 添加一个todo
        addTodo(todoObj){
            this.todos.unshift(todoObj)
        },
        // 勾选or取消勾选一个todo
        checkTodo(id){
            this.todos.forEach((todo) => {
                if(todo.id === id) todo.done = !todo.done
            });
        },
        // 删除一个todo
        deleteTodo(id){
            this.todos = this.todos.filter(todo=> todo.id !== id )
        },
        // 全选or取消全选
        checkAllTodo(done){
            this.todos.forEach((todo)=>{
                todo.done = done
            })
        },
        // 清楚所有已经完成的todo
        clearAllTodo(){
            this.todos = this.todos.filter((todo)=>{
                return !todo.done
            })
        }
    },
}
</script>

<style>
/* base */
body {
    background-color: #fff;
}
.btn {
    display: inline-block;
    padding: 4px 12px;
    margin-bottom: 0;
    font-size: 14px;
    line-height: 20px;
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.2), 0 1px 2px rgba(0, 0,0,0.05);
    border-radius: 4px;
}
.btn-danger{
    color: #fff;
    background-color: #da4f49;
    border: 1px solid #bd362f;
}
.btn-danger:hover{
    color: #fff;
    background-color: #bd362f;
}
.btn:focus{
    outline: none;
}
.todo-container{
    width: 600px;
    margin: 0 auto;
}
.todo-container .todo-wrap{
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
}
</style>

MyHeader.vue页面:

<template>
    <div class="todo-header">
        <input 
            type="text" 
            placeholder="请输入你的任务名称,按回车键确认" 
            @keyup.enter="add"
            v-model="title"
        >
    </div>
  
</template>

<script>
import {nanoid} from 'nanoid'
export default {
    name:'MyHeader',
    props:['addTodo'],
    data() {
        return {
            title:''
        }
    },
    methods: {
        add(){
            // 校验数据
            if(!this.title.trim()) return alert('输入不能为空')
            // 将用户的输入包装成一个todo对象
            const todoObj = {
                id:nanoid(),
                title:this.title,
                done:false
            }
            // 通知App组件去添加一个todo对象
            this.addTodo(todoObj)
            // 清空输入
            this.title = ''
        }
    },
}
</script>

<style scoped>
/* header */
.todo-header input{
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px 7px;
}
.todo-header input:focus{
    outline: none;
    border-color: rgba(82,168,236,0.8);
    box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);
}

</style>

MyItem.vue页面:

<template>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
            <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,修改了props -->
            <!-- <input type="checkbox" v-model="todo.done"/> -->
            <span>{
   
   {todo.title}}</span>
        </label>
        <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
    </li>
</template>

<script>
export default {
    name:'MyItem',
    // 声明接收todo对象
    props:['todo','checkTodo','deleteTodo'],
    methods: {
        // 勾选or取消勾选
        handleCheck(id){
            // 通知App组件将对应的todo对象的done值取反
            this.checkTodo(id)
        },
        // 删除
        handleDelete(id){
            if(confirm('确定删除吗?')){
                this.deleteTodo(id)
            }
        }
    },
}
</script>

<style scoped>
/* item */
li {
    list-style: none;
    height: 36px;
    line-height: 36px;
    padding: 0 5px;
    border-bottom: 1px solid #ddd;
}
li label {
    float: left;
    cursor: pointer;
}
li label li input {
    vertical-align: middle;
    margin-right: 6px;
    position: relative;
    top: -1px;
}
li button {
    float: right;
    display: none;
    margin-top: 3px;
}
li:before {
    content: initial;
}
li:last-child {
    border-bottom: none;
}
li:hover {
    background-color: #ddd;
}
li:hover button {
    display: block;
}
</style>

MyList.vue页面:

<template>
  <ul class="todo-main">
      <MyItem 
        v-for="todoObj in todos" 
        :key="todoObj.id" 
        :todo="todoObj" 
        :checkTodo="checkTodo"
        :deleteTodo="deleteTodo"
    />
  </ul>
</template>

<script>
import MyItem from './MyItem'
export default {
    name:'MyList',
    components:{MyItem},
    props:['todos','checkTodo','deleteTodo']
}
</script>

<style scoped>
/* main */
.todo-main{
    margin-left: 0px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
}
.todo-empty{
    height: 40px;
    line-height: 40px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding-left: 5px;
    margin-top: 10px;
}
</style>

MyFooter.vue页面:

<template>
  <div class="todo-footer" v-show="total > 0">
      <label>
          <!-- 全选的第一种做法 -->
          <!-- <input type="checkbox" :checked="isAll" @change="checkAll" /> -->
          <!-- 全选的第二种做法 -->
          <input type="checkbox" v-model="isAll" />
      </label>
      <span>
          <span>已完成{
   
   {doneTotal}}</span>  /  全部 {
   
   {total}}
      </span>
      <button class="btn btn-danger" @click="clearAll">清楚已完成任务</button>
  </div>
</template>

<script>
export default {
    name:'MyFooter',
    props:['todos','checkAllTodo','clearAllTodo'],
    computed:{
        total(){
            return this.todos.length
        },
        doneTotal(){
            // 老师写的第一种土方法
            /* let i =0
            this.todos.forEach((todo) => {
                if(todo.done) i++
            });
            return i */

            // 高端方法
            /* const x = this.todos.reduce((pre,current)=>{
                console.log('@',pre,current)
                return pre + (current.done ? 1 : 0)
            },0) */
            return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
        },
        isAll:{
            get(){
                return this.doneTotal === this.total && this.total > 0
            },
            // 全选的第二种做法
            set(value){
                this.checkAllTodo(value)
            }
        }
    },
    methods: {
        // 全选的第一种做法
        /* checkAll(e){
            this.checkAllTodo(e.target.checked)
        } */
        clearAll(){
            this.clearAllTodo()
        }
    },

}
</script>

<style scoped>
/* footer */
.todo-footer {
    height: 40px;
    line-height: 40px;
    padding-left: 6px;
    margin-top: 5px;
}
.todo-footer label {
    display: inline-block;
    margin-right: 20px;
    cursor: pointer;
}
.todo-footer label input{
    position: relative;
    top: -1px;
    vertical-align: middle;
    margin-right: 5px;
}
.todo-footer button{
    float: right;
    margin-top: 5px;
}
</style>

- P78 - 浏览器本地存储

老师总结:

webStorage

        1.存储内容大小一般支持5MB左右(不同浏览器可能还不一样)

        2.浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。

        3.相关API:

                (1)xxxxxStorage.setItem( 'key', 'value' );

                        该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。

                (2)xxxxxStorage.getItem( 'person' );

                        该方法接受一个键名作为参数,返回键名对应的值。

                (3)xxxxxStorage.removeItem( 'key' );

                        该方法接受一个键名作为参数,并把该键名从存储中删除。

                (4)xxxxxStorage.clear()

                        该方法会清空存储中的所有数据。

        4.备注:

                (1)SessionStorage存储的内容会随着浏览器窗口关闭而消失。

                (2)LocalStorage存储的内容,需要手动清楚才会消失。

                (3)xxxxxStorage.getItem( xxx ) 如果xxx对应的value获取不到,那么getItem的返回值是null。

                (4)JSON.parse(null)的结果依然是null。

- P79 - TodoList_本地存储

本节App.vue页面部分代码:

export default {

    ......

    data() {
        return {
            todos:JSON.parse(localStorage.getItem('todos')) || []
        }
    },

    ......

    watch: {
        todos:{
            deep:true,
            handler(value){
                localStorage.setItem('todos',JSON.stringify(value))
            }
        }
    }
}

- P80 - 组件自定义事件_绑定

课堂笔记:

(1)本节在接收多个参数时,用到了ES6的解构赋值,可以看这个:解构赋值_永远的大白的博客-CSDN博客_解构赋值

(全部代码在82节)

P81-90:

- P81 - 组件自定义事件_解绑

(全部代码在82节)

- P82 - 组件自定义事件_总结

课堂笔记:

(1)this.$refs.student.$on问题:

        ①为什么在App.vue页面中,写如下的内容,打印出来的this却是Student组件的实例呢?

this.$refs.student.$on('atguigu', function(name,...params){
    console.log('App收到了学生名:', name, params)
    console.log(this)
})

                因为Vue底层逻辑设计了,谁触发的那个atguigu事件,那么这个事件回调中的this就是谁。

        ②那为什么在写 this.$refs.student.$on('atguigu',this.getStudentName) 的时候,this能获取到App的方法呢?

                因为Vue给过承诺,如果这个函数写在methods里面,并且是普通函数,那么这个this就会是App的实例。

老师总结:

组件的自定义事件

        1.一种组件间通信的方式,适用于:子组件 ==> 父组件。

        2.使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

        3.绑定自定义事件:

                (1)第一种方式,在父组件中:

<Demo @atguigu="test"/> 或 <Demo v-on:atguigu="test"/>

                (2)第二种方式,在父组件中:

<Demo ref="demo">

......

mounted(){

        this.$refs.xxx.$on('atguigu',this.test)

}

                (3)若想让自定义事件只能触发一次,可以使用 once 修饰符或 $once 方法。

        4.触发自定义事件:this.$emit('atguigu',数据)

        5.解绑自定义事件:this.$off('atguigu')

        6.组件上也可以绑定原生DOM事件,需要使用 native 修饰符。

        7.注意:通过 this.$refs.xxx.$on('atguigu',回调) 绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

本节部分代码:

App.vue页面:

<template>
    <div class="app">
        <h1>{
   
   {msg}} 学生姓名是:{
   
   {stdentName}}</h1>

        <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
        <School :getSchoolName="getSchoolName"/>

        <!-- 通过父组件给子组件绑定一个自定义实现:子给父传递数据(第一种写法,使用@或v-on) -->
        <!-- <Student @atguigu="getStudentName" @demo="m1"/> -->

        <!-- 通过父组件给子组件绑定一个自定义实现:子给父传递数据(第二种写法,使用ref) -->
        <Student ref="student" @click.native="show"/>
    </div>
</template>

<script>
import { NONAME } from 'dns'
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {
    name: "App",
    components: { Student, School },
    data() {
        return {
            msg:'你好啊!',
            stdentName:''
        }
    },
    methods: {
        getSchoolName(name){
            console.log('App收到了学校名:', name)
        },
        getStudentName(name,...params){
            console.log('App收到了学生名:', name, params)
            this.stdentName = name
        },
        m1(){
            console.log('demo事件被触发了')
        },
        show(){
            alert(123)
        }
    },
    mounted() {
        // this.$refs.student.$on('atguigu', this.getStudentName) //绑定自定义事件
       this.$refs.student.$on('atguigu', (name,...params)=>{
            console.log('App收到了学生名:', name, params)
            this.stdentName = NONAME
       })
       // this.$refs.student.$once('atguigu', this.getStudentName) //绑定自定义事件(一次性)

    },
}
</script>

<style scoped>
.app{
    background-color: gray;
}
</style>

Student.vue页面:

<template>
    <div class="student">
        <h2>学生姓名:{
   
   {name}}</h2>
        <h2>学生性别:{
   
   {sex}}</h2>
        <h2>当前求和为:{
   
   {number}}</h2>
        <button @click="add">点我number++</button>
        <button @click="sendStudentName">把学生名给App</button>
        <button @click="unbind">解绑atguigu事件</button>
        <button @click="death">销毁当前Student组件的实例(vc)</button>
    </div>
</template>

<script>
export default {
    name:'Student',
    data() {
        return {
            name:'张三',
            sex:'男',
            number:0
        }
    },
    methods: {
        add(){
            console.log('add回调被调用了')
            this.number++
        },
        sendStudentName(){
            // 触发Student组件实例身上的atguigu事件
            this.$emit('atguigu',this.name,666,888,999)
            // this.$emit('demo')

        },
        unbind(){
            this.$off('atguigu') //解绑一个自定义事件
            // this.$off(['atguigu','demo']) //解绑多个自定义事件
            // this.$off() //解绑所有的自定义事件
        },
        death(){
            this.$destroy() //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效。
        }
    },
}
</script>

<style scoped>
    .student{
        background-color: pink;
        padding: 5px;
        margin-top: 30px;
    }
</style>

- P83 - TodoList案例_自定义事件

(这节太简单了不贴代码,最后所有的TodoList统一放在P90)

- P84 - 全局事件总线1

- P85 - 全局事件总线2

课堂笔记:

(1)对以下代码的理解:

const Demo = Vue.extend({}) 
const d = new Demo()
Vue.prototype.x = d

        第一行(回顾54节)创建Demo全局组件。

        第二行,获取组件的实例对象。

        因为需要写了标签才能使用组件,然而不能在main.js中引用标签。所以这一行亲自new一个。

        第三行(回顾59节)获得了原型vc。

(2)对以下代码的理解:

new Vue({
    ......
    beforeCreate() {
        Vue.prototype.$bus = this //安装全局事件总线
    }
})

        第三行(回顾49节)此时beforeCreate时无法访问到data中的数据的,只是初始化。

        第四行,已知这个时候vm原型还未生成,所以不能直接等于vm,但是Vue中有个this本身就是vue实例。$bus为总线。

(3)为什么bus需要销毁,自定义事件不用销毁?

        举个例子,比如说Student组件被销毁了,那么整个vc都没有了,所以自定义事件就直接没有了。

        但是bus不是这样的,就算Student组件被销毁了,bus也会一直在,所以bus用完了最好要销毁。

老师总结:

全局事件总线(GlobalEventBus)

        1.一种组件通信的方式,适用于任意组件间通信。

        2.安装全局事件总线。

new Vue({
    ......
    beforeCreate() {
        Vue.prototype.$bus = this //安装全局事件总线
    }
})

        3.使用事件总线:

                (1)接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

methods(){
    demo(data){......}
}
......
moutnted(){
    this.$bus.$on('xxx',数据)
}

                (2)提供数据:this.$bus.$emit( 'xxx', 数据 )

        4.最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

本节部分代码:

main.js页面:

// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 关闭Vue的生产提示
Vue.config.productionTip = false

// 创建vm
new Vue({
    el:'#app',
    render: h => h(App),
    beforeCreate() {
        Vue.prototype.$bus = this //安装全局事件总线
    }
})

School.vue页面:

<template>
    <div class="school">
        <h2>学校名称:{
   
   {name}}</h2>
        <h2>学校地址:{
   
   {address}}</h2>
    </div>
</template>

<script>
export default {
    name:'School',
    data() {
        return {
            name:'尚硅谷',
            address:'北京'
        }
    },
    mounted() {
        // console.log('School',this)
        this.$bus.$on('hello',(data)=>{
            console.log('我是School组件,收到了数据',data)
        })
    },
    beforeDestroy() {
        this.$bus.$off('hello')
    },
}
</script>

<style scoped>
    .school{
        background-color: skyblue;
        padding: 5px;
    }
</style>

Student.vue页面:

<template>
    <div class="student">
        <h2>学生姓名:{
   
   {name}}</h2>
        <h2>学生性别:{
   
   {sex}}</h2>
        <button @click="sendStudentName">把学生名给School组件</button>
    </div>
</template>

<script>
export default {
    name:'Student',
    data() {
        return {
            name:'张三',
            sex:'男'
        }
    },
    methods: {
        sendStudentName(){
            this.$bus.$emit('hello',this.name)
        }
    },
}
</script>

<style scoped>
    .student{
        background-color: pink;
        padding: 5px;
        margin-top: 30px;
    }
</style>

- P86 - TodoList案例_事件总线

课堂笔记:

(1)如果在Vue控制台的事件中,看到触发事件是 by 的话,那么多半意味着这是个事件总线。

(这节太简单了不贴代码,最后所有的TodoList统一放在P90)

- P87 - 消息订阅与发布_pubsub

课堂笔记:

(1)老师的比喻:

(2)对以下代码的理解:

mounted() {
    this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
        console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
    })
},
beforeDestroy() {
    pubsub.unsubscribe(this.pubId)
},

        pubsub在取消订阅的时候,需要通过Id去取消。

        但是beforeDestroy不能直接获取到methods中的id,所以这里需要用this.id。

        又因为pubsub是外部函数,所以这里使用箭头函数。

老师总结:

消息订阅与发布(pubsub)

        1.一种组件通信的方式,适用于任意组件间通信。

        2.使用步骤:

                (1)安装pubsub: npm i pubsub-js

                (2)引入:import pubsub from 'pubsub-js'

                (3)接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

methods: {
    demo(msgName,data){......}
},
mounted() {
    this.pubId = pubsub.subscribe('xxx',this.demo) //订阅消息
},

                (4)提供数据:pubsub.publish( 'xxx',数据 )

                (5)最好在beforeDestroy钩子中,用 pubsub.unsubscribe(pid) 去取消订阅。

本节部分代码:

School.vue页面:

<template>
    <div class="school">
        <h2>学校名称:{
   
   {name}}</h2>
        <h2>学校地址:{
   
   {address}}</h2>
    </div>
</template>

<script>
import pubsub from 'pubsub-js'
export default {
    name:'School',
    data() {
        return {
            name:'尚硅谷',
            address:'北京'
        }
    },
    methods: {
        demo(msgName,data){
            console.log('hello消息收到了',data)
        }
    },
    mounted() {
        // this.$bus.$on('hello',(data)=>{
        //     console.log('我是School组件,收到了数据',data)
        // })
        this.pubId = pubsub.subscribe('hello',this.demo)
    },
    beforeDestroy() {
        // this.$bus.$off('hello')
        pubsub.unsubscribe(this.pubId)
    },
}
</script>

<style scoped>
    .school{
        background-color: skyblue;
        padding: 5px;
    }
</style>

Student.vue页面部分代码:

    methods: {
        sendStudentName(){
            // this.$bus.$emit('hello',this.name)
            pubsub.publish('hello',666)
        }
    },

- P88 - TodoList案例_pubsub

(这节太简单了不贴代码,最后所有的TodoList统一放在P90)

- P89 - TodoList案例_编辑

课堂笔记:

(1)hasOwnProperty,判断一个属性定义在对象本身而不是继承原型链的方法,主要用于判断某个对象中是否有某个属性,返回值为布尔值 。

(全部代码在P90)

- P90 - $nextTick

课堂笔记:

(1)为什么直接写无效?

        因为Vue需要等整个函数走完,才去重新渲染DOM,这就和之前一样(回顾46节),还没渲染就focus,显然是无效的。

老师总结:

nextTick

        1.语法:this.$nextTick( 回调函数 )

        2.作用:在下一次DOM更新结束后执行其指定的回调。

        3.什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

本节部分代码:

main.js页面:

// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 关闭Vue的生产提示
Vue.config.productionTip = false

// 创建vm
new Vue({
    el:'#app',
    render: h => h(App),
    beforeCreate() {
        Vue.prototype.$bus = this
    }
})

App.vue页面:

<template>
    <div class="todo-container">
        <div class="todo-wrap">
            <MyHeader @addTodo="addTodo"/>
            <MyList :todos="todos"/>
            <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
        </div>
    </div>
</template>

<script>
import pubsub from 'pubsub-js'
import MyHeader from './components/MyHeader'
import MyList from './components/MyList'
import MyFooter from './components/MyFooter'

export default {
    name: "App",
    components: {
        MyHeader,
        MyList,
        MyFooter
    },
    data() {
        return {
            todos:JSON.parse(localStorage.getItem('todos')) || []
        }
    },
    methods: {
        // 添加一个todo
        addTodo(todoObj){
            this.todos.unshift(todoObj)
        },
        // 勾选or取消勾选一个todo
        checkTodo(id){
            this.todos.forEach((todo) => {
                if(todo.id === id) todo.done = !todo.done
            });
        },
        // 更新一个todo
        updateTodo(id,title){
            this.todos.forEach((todo) => {
                if(todo.id === id) todo.title = title
            });
        },
        // 删除一个todo
        deleteTodo(_,id){
            this.todos = this.todos.filter(todo=> todo.id !== id )
        },
        // 全选or取消全选
        checkAllTodo(done){
            this.todos.forEach((todo)=>{
                todo.done = done
            })
        },
        // 清楚所有已经完成的todo
        clearAllTodo(){
            this.todos = this.todos.filter((todo)=>{
                return !todo.done
            })
        }
    },
    watch: {
        todos:{
            deep:true,
            handler(value){
                localStorage.setItem('todos',JSON.stringify(value))
            }
        }
    },
    mounted() {
        this.$bus.$on('checkTodo',this.checkTodo)
        this.$bus.$on('updateTodo',this.updateTodo)
        // this.$bus.$on('deleteTodo',this.deleteTodo)
        this.pubId = pubsub.subscribe('deleteTodo',this.deleteTodo)
    },
    beforeDestroy() {
        this.$bus.$off('checkTodo')
        this.$bus.$off('updateTodo')
        // this.$bus.$off('deleteTodo')
        pubsub.unsubscribe(this.pubId)
    },
}
</script>

<style>
/* base */
body {
    background-color: #fff;
}
.btn {
    display: inline-block;
    padding: 4px 12px;
    margin-bottom: 0;
    font-size: 14px;
    line-height: 20px;
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.2), 0 1px 2px rgba(0, 0,0,0.05);
    border-radius: 4px;
}
.btn-danger{
    color: #fff;
    background-color: #da4f49;
    border: 1px solid #bd362f;
}
.btn-danger:hover{
    color: #fff;
    background-color: #bd362f;
}
.btn-edit{
    color: #fff;
    background-color: skyblue;
    border: 1px solid rgb(103,159,180);
    margin-right: 5px;
}
.btn-edit:hover{
    color: #fff;
    background-color: rgb(103,159,180);
}
.btn:focus{
    outline: none;
}
.todo-container{
    width: 600px;
    margin: 0 auto;
}
.todo-container .todo-wrap{
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
}
</style>

MyHeader.vue页面:

<template>
    <div class="todo-header">
        <input 
            type="text" 
            placeholder="请输入你的任务名称,按回车键确认" 
            @keyup.enter="add"
            v-model="title"
        >
    </div>
  
</template>

<script>
import {nanoid} from 'nanoid'
export default {
    name:'MyHeader',
    data() {
        return {
            title:''
        }
    },
    methods: {
        add(){
            // 校验数据
            if(!this.title.trim()) return alert('输入不能为空')
            // 将用户的输入包装成一个todo对象
            const todoObj = {
                id:nanoid(),
                title:this.title,
                done:false
            }
            // 通知App组件去添加一个todo对象
            this.$emit('addTodo',todoObj)
            // 清空输入
            this.title = ''
        }
    },
}
</script>

<style scoped>
/* header */
.todo-header input{
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px 7px;
}
.todo-header input:focus{
    outline: none;
    border-color: rgba(82,168,236,0.8);
    box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);
}

</style>

MyList.vue页面:

<template>
  <ul class="todo-main">
      <MyItem 
        v-for="todoObj in todos" 
        :key="todoObj.id" 
        :todo="todoObj" 
    />
  </ul>
</template>

<script>
import MyItem from './MyItem'
export default {
    name:'MyList',
    components:{MyItem},
    props:['todos']
}
</script>

<style scoped>
/* main */
.todo-main{
    margin-left: 0px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
}
.todo-empty{
    height: 40px;
    line-height: 40px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding-left: 5px;
    margin-top: 10px;
}
</style>

MyItem.vue页面:

<template>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
            <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,修改了props -->
            <!-- <input type="checkbox" v-model="todo.done"/> -->
            <span v-show="!todo.isEdit">{
   
   {todo.title}}</span>
            <input 
                type="text"
                v-show="todo.isEdit" 
                :value="todo.title" 
                @blur="handleBlur(todo,$event)"
                ref="inputTitle"
            >
        </label>
        <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
        <button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">编辑</button>
   </li>
</template>

<script>
import pubsub from 'pubsub-js'
export default {
    name:'MyItem',
    // 声明接收todo对象
    props:['todo'],
    methods: {
        // 勾选or取消勾选
        handleCheck(id){
            // 通知App组件将对应的todo对象的done值取反
            this.$bus.$emit('checkTodo',id)
        },
        // 删除
        handleDelete(id){
            if(confirm('确定删除吗?')){
                // 通知App组件将对应的todo对象删除
                // this.deleteTodo(id) //其中的deleteTodo是父组件传来的函数
                // this.$bus.$emit('deleteTodo',id) //其中的deleteTodo是自定义事件的时间名
                pubsub.publish('deleteTodo',id) //其中的deleteTodo是订阅消息的消息名
            }
        },
        // 编辑
        handleEdit(todo){
            if(todo.hasOwnProperty('isEdit')){
                todo.isEdit = true
            }else{
                this.$set(todo,'isEdit',true)
            }
            this.$nextTick(function(){
               this.$refs.inputTitle.focus() 
            })
        },
        // 失去焦点回调(真正执行修改逻辑)
        handleBlur(todo,e){
            todo.isEdit = false
            if(!e.target.value.trim()) return alert('输入不能为空')
            this.$bus.$emit('updateTodo',todo.id,e.target.value)
        }
    },
}
</script>

<style scoped>
/* item */
li {
    list-style: none;
    height: 36px;
    line-height: 36px;
    padding: 0 5px;
    border-bottom: 1px solid #ddd;
}
li label {
    float: left;
    cursor: pointer;
}
li label li input {
    vertical-align: middle;
    margin-right: 6px;
    position: relative;
    top: -1px;
}
li button {
    float: right;
    display: none;
    margin-top: 3px;
}
li:before {
    content: initial;
}
li:last-child {
    border-bottom: none;
}
li:hover {
    background-color: #ddd;
}
li:hover button {
    display: block;
}
</style>

MyFooter.vue页面:

<template>
  <div class="todo-footer" v-show="total > 0">
      <label>
          <!-- 全选的第一种做法 -->
          <!-- <input type="checkbox" :checked="isAll" @change="checkAll" /> -->
          <!-- 全选的第二种做法 -->
          <input type="checkbox" v-model="isAll" />
      </label>
      <span>
          <span>已完成{
   
   {doneTotal}}</span>  /  全部 {
   
   {total}}
      </span>
      <button class="btn btn-danger" @click="clearAll">清楚已完成任务</button>
  </div>
</template>

<script>
export default {
    name:'MyFooter',
    props:['todos'],
    computed:{
        total(){
            return this.todos.length
        },
        doneTotal(){
            // 老师写的第一种土方法
            /* let i =0
            this.todos.forEach((todo) => {
                if(todo.done) i++
            });
            return i */

            // 高端方法
            /* const x = this.todos.reduce((pre,current)=>{
                console.log('@',pre,current)
                return pre + (current.done ? 1 : 0)
            },0) */
            return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
        },
        isAll:{
            get(){
                return this.doneTotal === this.total && this.total > 0
            },
            // 全选的第二种做法
            set(value){
                this.$emit('checkAllTodo',value)
            }
        }
    },
    methods: {
        // 全选的第一种做法
        /* checkAll(e){
            this.checkAllTodo(e.target.checked)
        } */
        clearAll(){
            this.$emit('clearAllTodo')
        }
    },

}
</script>

<style scoped>
/* footer */
.todo-footer {
    height: 40px;
    line-height: 40px;
    padding-left: 6px;
    margin-top: 5px;
}
.todo-footer label {
    display: inline-block;
    margin-right: 20px;
    cursor: pointer;
}
.todo-footer label input{
    position: relative;
    top: -1px;
    vertical-align: middle;
    margin-right: 5px;
}
.todo-footer button{
    float: right;
    margin-top: 5px;
}
</style>

P91-100:

- P91 - 动画效果

本节部分代码:

Test.vue页面:

<template>
  <div>
      <button @click="isShow = !isShow">显示/隐藏</button>
      <transition name="hello" appear>
        <h1 v-show="isShow">你好啊!</h1>
      </transition>
  </div>
</template>

<script>
export default {
    name:'Test',
    data() {
        return {
            isShow:true
        }
    },
}
</script>

<style scoped>
h1{
    background-color: orange;
}
.hello-enter-active{
    animation: atguigu 0.5s linear;
}
.hello-leave-active{
    animation: atguigu 0.5s linear reverse;
}
@keyframes atguigu {
    from{
        transform: translateX(-100%);
    }
    to{
        transform: translateX(0px);

    }
}
</style>

- P92 - 过度效果

(代码在P93)

- P93 - 多个元素过度

本节部分代码:

Test2.vue页面:

<template>
  <div>
      <button @click="isShow = !isShow">显示/隐藏</button>
      <transition-group name="hello" appear>
        <h1 v-show="!isShow" key="1">你好啊!</h1>
        <h1 v-show="isShow" key="2">尚硅谷!</h1>
      </transition-group>
  </div>
</template>

<script>
export default {
    name:'Test',
    data() {
        return {
            isShow:true
        }
    },
}
</script>

<style scoped>
h1{
    background-color: orange;
    transition: 0.5s linear;
}
/* 进入的起点、离开的终点 */
.hello-enter , .hello-leave-to{
    transform: translateX(-100%);
}
/* .hello-enter-active , .hello-leave-active{
    transition: 0.5s linear;
} */
/* 进入的终点、离开的起点 */
.hello-enter-to , .hello-leave{
    transform: translateX(0);
}
</style>

- P94 - 集成第三方动画

课堂笔记:

(1)npm官网:npm

        Animate官网:Animate.css | A cross-browser library of CSS animations.

(2)Animate安装指令:npm i animate (我从npm上复制来的,跟老师的不太一样,哪种都行)

本节部分代码:

Tset3.vue页面:

<template>
    <div>
        <button @click="isShow = !isShow">显示/隐藏</button>
        <transition-group
            appear
            name="animate__animated animate__bounce"
            enter-active-class="animate__swing"
            leave-active-class="animate__backOutUp"
        >
            <h1 v-show="!isShow" key="1">你好啊!</h1>
            <h1 v-show="isShow" key="2">尚硅谷!</h1>
        </transition-group>
    </div>
</template>

<script>
import 'animate'
export default {
    name:'Test',
    data() {
        return {
            isShow:true
        }
    },
}
</script>

<style scoped>
h1{
    background-color: orange;
    transition: 0.5s linear;
}
</style>

- P95 - 总结过度与动画

老师总结:

Vue封装的过度与动画

        1.作用:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名。

        2.图示:

        3.写法:

                (1)准备好样式:

                        • 元素进入的样式:

                                ①v-enter:进入的起点

                                ②v-enter-active:进入的过程

                                ③v-enter-to:进入的终点

                        • 元素离开的样式:

                                ①v-leave:进入的起点

                                ②v-leave-active:进入的过程

                                ③v-leave-to:进入的终点

                (2)使用<transition>包裹要过度的元素,并配置name属性:

<transition name="hello">
    <h1 v-show="isShow">你好啊!</h1>
</transition>

                (3)备注:若有多个元素需要过度,则需要使用:,且每个元素都要指定key值。

(MyList.vue页面的代码不贴了,跟Test2的一样,稍微修改一下就好了)

- P96 - 配置代理_方式一

课堂笔记:

(1)回顾几种请求:

        ① xhr:也就是 new XMLHttpRequest() ,用于与服务器交互数据,是ajax功能实现所依赖的对象。

        ② JQuery:是对 xhr 的封装:$.get 、$.post。

        ③ axios:也是对 xhr 的封装。

        ④ fetch:在window的内置对象中直接有的方法,兼容性稍差。

(2)axios安装指令:npm i axios

(3)常见的跨域解决:

        ① cors:其实就是给你加几个特殊的响应头。拓展链接:CORS解决跨域问题_亦木丶的博客-CSDN博客_cors跨域

        ② jsonp:借助了script标签的src属性,在引入外部资源的时候,不受同源限制的特点。需要前后端都调整,且只能解决get请求(平时很少用,但是面试常问到)。拓展链接:使用jsonp解决跨域问题_是啥也不会酱的博客-CSDN博客_jsonp解决跨域问题

        ③配置代理服务器

(4)Vue CLI官网——配置参考——devServer.proxy:配置参考 | Vue CLI

(5)public文件夹就相当于8080服务器的根路径。public文件夹里有的东西都算是8080中有的。

- P97 - 配置代理_方式二

课堂笔记:

(1)方法二部分分析:

        '/foo' 是在 '/api' 的基础上精简而来的。

        pathRewrite:官网上没有加,需要自己加上去,重写路径。

        ws:用于支持websocket。

老师总结:

Vue脚手架配置代理

方法一:

        在vue.config.js中添加如下配置:

devServer: {
    proxy: 'http://localhost:5000'
}

说明:

        1.优点:配置简单,请求资源时直接发给前端(8080)即可。

        2.缺点:不能配置多个代理,不能灵活的控制请求是否走代理。

        3.工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器(优先匹配前端资源)

方法二:

        编写vue.config.js配置具体代理规则:

module.exports = {
  devServer: {
    proxy: {
      '/api1': { //匹配所有以'/api1'开头的请求路径
        target: 'http://localhost:5000', //代理目标的基础路径
        changeOrigin: true,
        pathRewrite:{'^/api1':''}
      },
      '/api2': {  //匹配所有以'/api2'开头的请求路径
        target: 'http://localhost:5001' //代理目标的基础路径
        changeOrigin: true,
        pathRewrite:{'^/api2':''}
      }
    }
  }
}
/* 
    changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
    changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
    changeOrigin默认值为true
*/

说明:

        1.优点:可以配置多个代理,且可以灵活的控制请求是否走代理。

        2.缺点:配置略微繁琐,请求资源时必须加前缀。

本节部分代码:

vue.config.js页面:

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave:false, //关闭语法检测
  // 开启代理服务器(方式一)
  /* devServer: {
    proxy: 'http://localhost:5000'
  } */
  // 开启代理服务器(方式二)
  devServer: {
    proxy: {
      '/atguigu': {
        target: 'http://localhost:5000',
        pathRewrite:{'^/atguigu':''},
        // ws: true, //用于支持websocket
        // changeOrigin: true //用于控制请求头中host值
      },
      '/demo': {
        target: 'http://localhost:5001',
        pathRewrite:{'^/demo':''},
      }
    }
  }
})

- P98 - github案例_静态组件

课堂笔记:

(1)bootstrap官网:Bootstrap中文网

(2)vue中引入样式的两种方法:

        ①在src文件夹中新建assets文件夹,再新建css文件夹,放入css文件,在.vue页面中import。缺点就是,Vue会逐条检查,如果有本节类似缺失字体部分,就很麻烦。

        ②在public文件夹中新建css文件夹,放入css文件,在index.html页面中用link引入。

<!-- 引入第三方样式 -->
<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">

(代码在P100)

- P99 - github案例_列表展示

课堂笔记:

(1)课件提供的接口地址:

https://api.github.com/search/users?q=xxx

(2)写请求的时候提到了ES6中的模板字符串,可以看下:什么是模板字符串?_紫微前端的博客-CSDN博客_模板字符串

(代码在P100)

- P100 - github案例_完善案例

本节部分代码:

App.vue页面:

<template>
    <div class="container">
        <Search/>
        <List/>
    </div>
</template>

<script>
import Search from './components/Search.vue'
import List from './components/List.vue'
export default {
    name:'App',
    components:{Search,List}
}
</script>

Search.vue页面:

<template>
  <section class="jumbotron">
      <h3 class="jumbotron-heading">Search Github Users</h3>
      <div>
          <input 
            type="text" 
            placeholder="enter the name you search"
            v-model="keyWord"
          />&nbsp;
          <button @click="searchUsers">Search</button>
      </div>
  </section>
</template>

<script>
import axios from 'axios'
export default {
    name:'Search',
    data() {
        return {
            keyWord:''
        }
    },
    methods: {
        searchUsers(){
            // 请求前更新List的数据
            this.$bus.$emit('updateListData',{
                isFirst:false, //是否为第一次展示
                isLoading:true, //是否处于加载中
                errMsg:'', //报错信息
                users:[]
            })
            axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
                response => {
                    console.log('请求成功了')
                    // 请求成功后更新List的数据
                    this.$bus.$emit('updateListData',{ isLoading:false, errMsg:'', users:response.data.items})
                },
                error => {
                    // 请求失败后更新List的数据
                    console.log('请求失败了',error.message)
                    this.$bus.$emit('updateListData',{isLoading:false, errMsg:error.message, users:[]})
                }
            )
        }
    }
}
</script>

<style>

</style>

List.vue页面:

<template>
    <div class="row">
        <!-- 展示用户列表 -->
        <div v-show="info.users.length" class="card" v-for="user in info.users" :key="user.login">
            <a :href="user.html_url" target="_blank">
                <img :src="user.avatar_url" style="width:100px" />
            </a>
            <p class="card-text">{
   
   {user.login}}</p>
        </div>
        <!-- 展示欢迎词 -->
        <h1 v-show="info.isFirst">欢迎使用!</h1>
        <!-- 展示加载中 -->
        <h1 v-show="info.isLoading">Loading···</h1>
        <!-- 展示错误信息 -->
        <h1 v-show="info.errMsg">{
   
   {info.errMsg}}</h1>
    </div>
</template>

<script>
export default {
    name:'List',
    data() {
        return {
            info:{
                isFirst:true, //是否为第一次展示
                isLoading:false, //是否处于加载中
                errMsg:'', //报错信息
                users:[]
            }
        }
    },
    mounted() {
        this.$bus.$on('updateListData',(dataObj)=>{
            this.info = {...this.info,...dataObj}
        })
    },
}
</script>

<style scoped>
.album {
    min-height: 50rem; /* Can be removed */
    padding-top: 3rem;
    padding-bottom: 3rem;
    background-color: #f7f7f7;
}
.card {
    float: left;
    width: 33.333%;
    padding: .75rem;
    margin-bottom: 2rem;
    border: 1px solid #efefef;
    text-align: center;
}
.card > img {
    margin-bottom:  .75rem;
    border-radius: 100px;
}
.card-text {
    font-size: 85%;
}
</style>

——————————————————————————————————————

——————————————原创不易,转载请声明——————————————

猜你喜欢

转载自blog.csdn.net/weixin_44830518/article/details/125108739