Vue学习笔记(二)

Vue学习笔记(二)

个人博客(欢迎光临)Vue学习笔记(二)(赤蓝紫)

单页面应用程序SPA,指的是一个Web网站中只有唯一一个HTML页面,所有的功能和交互都在这个唯一的页面内完成。

1. vue-cli

vue-cli是Vue.js开发的标准工具。

1.1 安装

npm install -g @vue/cli

1.2 vue项目的部分文件功能

vue通过main.js把App.vue渲染到index.html的指定区域中。

  • App.vue用来编写待渲染的模板结构
  • index.html需要预留一个el区域等待渲染
  • main.js把App.vue渲染到index.html预留的区域中

$(mount)用法

import Vue from 'vue'
import MyCom from './components/myComponent'

Vue.config.productionTip = false

// new Vue({
//   el: '#app',
//   render: h => h(MyCom),
// })

new Vue({
  render: h => h(MyCom)
}).$mount('#app') //$(mount)和上面的el属性用法一样

2. 组件

组件化开发:把页面上可复用的UI结构封装成组件,从而方便项目的开发和维护

vue支持组件化开发。组件的后缀名是**.vue**

每个.vue组件可有三个部分组成:

  • template:组件的模板结构(必须包含), 只能有一个根节点
  • script:组件的Javascript行为
  • style:组件的样式
<template>
    <div>
        <h2>用户名:{{ username }}</h2>
    </div>
</template>

<script>
export default {
    // data: {
    //     username: 'admin'
    // }        //.vue文件中的数据源不可以用对象形式,而应是函数形式

    data() {
        return {
            username: 'admin'
        }
    }
}
</script>

<style>
    div>h2 {
        color: red;
    }
</style>

在vue中使用less

<style lang="less">
    .box {
        background-color: pink;
        h2 {
            color: red;
        }
    }
</style>

2.1 组件使用的三个步骤

  1. 使用import语法导入要用的组件
  2. 在components节点注册组件
  3. 直接把组件当成标签在要渲染的地方使用

2.2 注册全局组件

上面的注册组件的方法是私有注册,即每一个需要用到经常用的组件,都需要反复注册。这个时候,可以直接在main.js中进行全局组件的注册

import Vue from 'vue'
import App from './App.vue'
import Test from './components/Test.vue' //导入要注册的全局组件

Vue.config.productionTip = false

Vue.component("Mytest", Test) //使用Vue.component()来进行全局组件的注册,第一个参数是要注册的组件的名称,第二个参数是要注册的组件

new Vue({
  render: h => h(App),
}).$mount('#app')

2.3 组件的props

props是组件的自定义属性,在封装通用组件时,合理使用props可以提高组件的复用性,允许使用者通过自定义属性,为当前组件指定初始值

props是只读的

要修改的话,可以把得到的初始值赋给data中的属性,再进行修改,props中的属性的值会一直是初始值

default属性、type属性和required属性:如果使用者使用使用组件时,没有传递init属性, 则默认值生效

使用语法示例:

props: {
    init: {
        default: 0,  //用default属性定义属性的默认值
        type: Number,  //限定传递的属性的类型,不匹配会报错
        required: true,  //设置为true时,必须要传递参数,否则即使有默认值,也会报错
    }
},

2.4 组件之间的样式冲突

默认情况下,写在**.vue组件中的样式会全局生效**,所以很容易造成多个组件之间的样式冲突问题

导致组件之间的样式冲突的原因:

  1. 单页面应用程序中,所有的组件的DOM结构都是基于唯一的index.html页面呈现的
  2. 每个组件中的样式都会影响到整个index.html页面中的所有DOM元素

通过给要设置样式的组件的style标签中添加"scoped"属性,可以实现不影响到其他组件的样式

原理:给组件里的所有标签都来一个自定义样式,然后通过属性选择器实现样式只会影响到该组件

上面使用"scoped"的代码,不使用"scoped"属性实现:

<template>
  <div class="left-container" data-d-001>
    <h3 data-d-001>Left 组件</h3>
    <MyCount :init="5" data-d-001></MyCount>  
  </div>
</template>

<script>
export default {
}
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
h3[data-d-001] {
  color: red;
}

</style>

只是使用"scoped"的话,无法实现单独修改用上的其他组件的样式

如果想要让某些样式对子组件生效,可以使用**/deep/ 深度选择器**

通过浏览器查看生效样式:

属性选择器使用不一定需要依靠其他选择器,单独使用表示选择所有有对应属性的元素

3. 组件的生命周期

生命周期是指一个组件从创建->运行->销毁的整个阶段,强调的是一个时间段

生命周期函数:由vue框架提供的内置函数,会伴随组件的生命周期,自动按次序执行

生命周期强调的是时间段,生命周期函数强调的是时间点

组件生命周期函数的分类

生命周期图示

3.1 组件创建阶段

3.1.1 beforeCreate()

组件的propsdatamethods还没有被创建,都处于不可用状态

<template>
  <div>
      <h2>test组件</h2>
  </div>
</template>

<script>
export default {
    props: ['info'],
    data() {
        return {
            message: 'message'
        }
    },
    methods: {
        show() {
            console.log('show');
        }
    },
    beforeCreate() {
        // console.log(this.info);
        // console.log(this.message);
        this.show();
    }

}
</script>

<style>

</style>

3.1.2 created()

组件的propsdatamethods已经创建完,处于可用状态。

created方法很重要,经常在里面调用methods的方法,请求服务器的数据,并把请求到的数据转存到data中,供渲染时使用,因为应该尽可能早的请求数据。

<template>
  <div>
      <h2>共有 {{ books.length }} 本书</h2>
  </div>
</template>

<script>
export default {
    props: ['info'],
    data() {
        return {
            message: 'message',
            books: [],
        }
    },
    methods: {
        show() {
            console.log('show');
        },
        initBooklist() {
            const xhr = new XMLHttpRequest();
            xhr.open('get', 'http://www.liulongbin.top:3006/api/getbooks');
            xhr.send(null);
            xhr.addEventListener('load', () => {
                const result = JSON.parse(xhr.responseText);
                this.books = result.data;
            })	//使用ajax获取图书信息示例
        }
    },
    created() {
        this.initBooklist();
    }

}
</script>

<style>

</style>

组件的模板结构还没生成(DOM节点现在不能操作)

3.1.3 beforeMount()

将要把内存中编译好的HTML结构渲染到当前组件的DOM结构

3.1.4 mounted()

重要,要操作当前组件的DOM,最早只能在mounted阶段,已经把内存中的HTML结构成功的渲染到了浏览器中,此时已经包含了当前组件的DOM结构

3.2 组件的运行阶段

3.2.1 beforeUpdate()

将要根据变化过后、最新的数据,重新渲染到组件的模板结构。(即现在data里的数据已经变化了,但是DOM元素里的数据还没来得及变)

<template>
  <div>
      <h3>
          用于测试运行阶段的message: 
          <span>{{ message }}</span>
      </h3>
      <button @click="datachange">更改数据</button>
  </div>
</template>

<script>
export default {
    props: ['info'],
    data() {
        return {
            message: 'Hello',
        }
    },
    methods: {
        datachange() {
            this.message += '!';
        }
    },
    beforeUpdate() {
        const span = document.querySelector('h3 span');

        console.log('data里的数据:' + this.message);
        console.log('DOM中的数据:' + span.innerHTML);
    }

}
</script>

<style>

</style>

3.2.2 updated()

已经完成了组件DOM结构的重新渲染

数据发生变化时,如果要操作重新渲染过的DOM,应在updated()中执行

3.3 组件销毁阶段

3.3.1 beforeDestroy()

将要销毁组件,组件还处于正常工作的状态

3.3.2 destroyed()

DOM结构已经完全销毁

4. 组件间的数据共享

4.1 父组件向子组件传递数据

父组件

<template>
  <div class="app-container">
    <h1>父组件数据: {{ message }} <br>
       {{ present }}</h1>
    <Test :msg="message" :pst="present"></Test>
  </div>
</template>

<script>
import Test from '@/components/Test.vue'

export default {
  data() {
    return {
      message: 'Hello Son!',
      present: {
        fruit: 'apple',
        toy: 'car'
      }
    }
  },
  components: {
    Test
  }
}
</script>

<style lang="less">
</style>

子组件

<template>
  <div class="test">
      <h3 class="de">
          子组件
      </h3>
      <p>接收父组件传来的message: {{msg }}</p>
      <p>接收父组件传来的present: {{pst }}</p>
  </div>
</template>

<script>
export default {
    props: ["msg", "pst"],
    data() {
        return {
            message: 'Hello',
        }
    },
    methods: {
        datachange() {
            this.message += '!';
        }
    },

}
</script>

<style scope>
.de {
    color: red
}
</style>

实现父组件向子组件传递数据,主要依靠

  • 在子组件自定义属性
  • 在父组件通过v-bind传递数据给子组件的自定义属性

通过上面的方法,传递给子组件的数据在props的属性中,只读,所以需要修改的话,要把接收的数据赋给子组件data中的元素

4.2 子组件向父组件传递数据

通过在父组件处自定义事件,和在子组件处通过$.emit()方法触发自定义事件来实现子组件向父组件传递数据

4.3 兄弟组件组件的数据共享

兄弟组件之间的数据共享方案是EventBus

步骤:

  1. 创建eventBus.js文件,向外共享一个Vue的实例对象(用法相当于中转站)
  2. 在数据发送方,调用bus.$emit(‘事件名称’, 要发送的数据)方法触发自定义事件
  3. 在数据接收方,调用bus.$on(‘事件名称’, 事件处理函数)方法注册一个自定义事件

5. ref引用

ref用来辅助开发者在不依赖jQuery的情况下,获取DOM元素或组件的引用。

每个vue的组件实例上,都包含一个** r e f s 对 象 ∗ ∗ , 里 面 存 储 着 对 应 的 D O M 元 素 或 组 件 的 引 用 。 默 认 情 况 下 , ∗ ∗ 组 件 的 refs对象**,里面存储着对应的DOM元素或组件的引用。默认情况下,**组件的 refsDOMrefs指向一个空对象**。

使用ref引用DOM元素

所以上面要操作DOM元素可以通过this.$refs.myh3来修改,如:

使用ref引用组件实例

控制文本框和按钮的按需切换:(点击按钮,按钮隐藏,文本框显示;文本框失去焦点,按钮显示,文本框隐藏;文本框显示时自动获取焦点)

<template>
  <div class="app-container">
    <h3 ref="myh3">MyRef组件</h3>
    <input type="text" v-if="inputVisible" ref="myipt" @blur="showButton">
    <button v-else @click="showInput">展示输入框</button>
    
  </div>
</template>

<script>
// import Left from '@/components/Left.vue'
// import Right from '@/components/Right.vue'

export default {
  data() {
    return {
      inputVisible: false
    }
  },
  methods: {
    showInput() {
      this.inputVisible = true;
      console.log(this.$refs.myipt);   //调用showInput时,数据刚刚发生了改变,而这行和上一行代码之间的时间间隔太短,
                                      // 导致DOM结构没有进行完渲染,所以此时出现undefined
      this.$nextTick(() => {           //组件的$nextTick()方法,会把回调函数推迟到下一个DOM更新周期之后执行
        console.log(this.$refs.myipt);
        this.$refs.myipt.focus();
      })
    },
    showButton() {
      this.inputVisible = false;  
    }
  },
  // components: {
  //   Left, 
  // }
}
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

this.$nextTick(callback)方法

组件的**$nextTick(callback)方法会把callback推迟到下一个DOM更新周期之后执行**,即等组件的DOM更新完之后,再执行callback回调函数。从而保证回调函数能操作到最新的DOM元素。

上面的例子不能在生命周期函数中的updated()中使用input.focus(),因为data中的数据一发生变化,updated()都会执行一次,即input隐藏时也会执行,此时压根没有input元素,又何来input.focus()之说呢。

6. 动态组件

动态组件指的是动态切换组件的显示与隐藏

vue提供了一个内置的组件,专门用来实现动态组件的渲染

  • 使用组件的三大步骤:

    1. 引入组件
    2. 注册组件
    3. 通过标签使用组件

    第三步,可以使用内置的component组件,通过is属性来动态指定要渲染的组件

    即上面的3可以换成<component :is="'Left'"></component>

动态指定渲染组件示例:

App组件

<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <button @click="comName='Left'">展示Left组件</button>
    <button @click="comName='Right'">展示Right组件</button>
    <hr />

    <div class="box">
      <component :is="comName"></component>
    </div>
  </div>
</template>

<script>
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'
export default {
  data() {
    return {
      comName: 'Left'
    }
  },
  components: {
    Left,
    Right
  }
}
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

Left组件(注释代码是后面的部分才用上的)

<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <h4>count的值是 {{ count }}</h4>
    <button @click="count += 1">+1</button>
  </div>
</template>

<script>
export default {
  // name: 'MyLeft',
  data() {
    return {
      count: 0
    }
  },
  created() {
    console.log('左侧被创建了');
  },
  destroyed() {
    console.log('左侧被销毁了');
  },
  // activated() {
  //   console.log('左侧被激活了');
  // },
  // deactivated() {
  //   console.log('左侧休息了');
  // }
}
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>

问题:在上面的示例中,点击Left组件的+1按钮,已经改变了数值,但是,点击展示Right组件后,再重新展示Left组件,会发现数值又回归了初始状态。从控制台中的输出,可以知道,原因是动态指定渲染Right组件时,Left组件会被销毁,之后右重新创建,所以数据会是初始状态。

可以通过vue内置的<keep-alive>组件保持动态组件的状态。

用法:用keep-alive组件包住动态组件就可以。

<keep-alive>
   <component :is="comName"></component>
</keep-alive>

当组件被激活时,会自动触发组件的activated生命周期函数。

当组件休眠时,会自动触发组件的deactivated生命周期函数。

把上面Left组件的注释代码启用。可以发现,当Left组件激活时(展示Left),会打印出"左侧被激活了";而Left休眠时(展示Right),会打印出"左侧休息了"。

include属性:实现只有名字匹配的组件才会被缓存。就是说,上面的例子中,Left组件中有数据,想要保存Left的状态,但是Right组件没有必要缓存,甚至有可能Right组件是一次性的用法。这个时候通过include属性,可以指定谁会被缓存。多个组件间用英文的逗号**分隔。

逗号左右不要有空格

用法:

<keep-alive include="Left">
   <component :is="comName"></component>
</keep-alive>

可以发现,上面的Right组件在调试工具中显示的名字并不是Right,而是自定义的MyRight。这是通过组件的name节点修改的。

如果修改了组件的名称,那么在include属性中的名字应该是修改后的名字。

exclude属性:表示不缓存哪些组件。和include属性一起用没有意义,要不就是取消掉include属性的作用。要不就是多此一举。还会让代码可读性下降。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值