Vue基础-day03

今天不敲代码,从今天开始将采用vue脚手架的方式创建vue项目

一、什么是脚手架

我们都知道,在我们写项目的时候是必须要进行打包的,否则辛辛苦苦写的项目就永远都是死的,无法给别人进行运用,我们最一开始学的打包工具是webpack,程序猿应该都知道的,webpack的配置有多费cpu(我说的是我们的cpu,不是计算机😭),而且这种方法需要我们手动打包,对于我这种小迷糊来说,大家都懂。所以由此,有了脚手架,它可以帮助我们自动一键生成vue+webpack的项目模版,包括依赖库,免去你手动安装各种插件,方便吧,太诱人了,所以就开始安装吧!

二、安装脚手架

中文官网:htpps://cli.vuejs.org/zh/

安装命令如下:

npm install -g @vue/cli
# OR
yarn global add @vue/cli

但是这里我踩了一个坑,因为我的node版本过高,所以我卸载过一次,重新安装后没有配置镜像,所以记得一定要配置好镜像再执行安装命令,因为终端的报错对于我来说很难搞懂(哭哭),还有一点就是注意开启终端的位置,位置不对也会导致报错。(使用yarn会更快呦)

检查是否安装成功:

vue -V

三、创建项目

安装好了之后,我们就可以开始创建项目了

vue create 项目的名称(如:vue create demo-1)

这里项目名称是自己起的,可以想起什么起什么,但是避免使用中文,毕竟我们即将是前端工程师,要体现我们的英文水准。(哈哈哈开玩笑的,中文会报错的哦)

四、启动项目

cd 项目的名称 
npm run serve

这里需要注意的是要先切换到你的项目路径,否则你执行启动命令行,计算机是找不到你的文件位置的。

五、vue项目介绍

1.项目目录构成

src文件夹,这里就是我们需要写代码的位置

2.工程化的 Vue 项目运行流程

index.html:单页面应用的唯一的一个 html 页面 

\1. 中预留了一个 el 区域 

\2. 还引入一个打包后的脚本文件。 

main.js:webpack 打包的入口文件 

把 App.vue 渲染到了 index.html 所预留的 el 区域 

App.vue:项目中最顶层的组件

我们所有的组件都要在App.vue中引用,否则是不生效的

它包含了所有将来要在页面上渲染的 UI 结构

六、组件化开发

1. 什么是组件化开发

组件化开发指的是:根据封装的思想,把页面上可重用的 UI 结构的封装为组件,提高页面 UI 结构的复用性 ,从而方便项目的开发和维护。

2. vue 中的组件化开发

vue 是一个支持组件化开发的前端框架。

vue 中规定:组件的后缀名是 .vue。之前接触到的 App.vue 文件就是一个 vue 的组件。

3. vue 组件的三个组成部分

每个 vue 组件都由 3 部分构成,分别是:

 template -> 组件的模板结构 

 script -> 组件的 JavaScript 行为 

 style -> 组件的样式 

其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分。

接下来我将用代码展示

Template:

<template>
  <!--
    template:1.里面存放html模板结构
             2.只起到包裹作用,并不会渲染一个真正的DOM结构
             3.里面只能存放一个根元素
  -->
  <div>
    <h1>这是<span>APP</span>组件</h1>
  </div>
</template>

Script:

<script>
//script里面用来写JS代码
//data,methods,components,watch都要写在export default导出的对象中
export default {
  //在组件中data必须是一个函数,他会return一个对象,数据声明在这个对象中
  data() {
    return {
    }
  },
}
</script>

Style:

<style lang="less">
//style用于给组件结构设置样式,使用lang设置指定的css预处理器语言
h1 {
  color: red;

  span {
    color: blue;
  }
}

</style>

组件化开发,当然主要的就是组件,如何让组件间互相引用的,这里我还是用代码演示,我这里主要运用四个组件进行演示,分别为根组件App.vue,还有三个子组件,分别为Left.vue\Right.vue\Count.vue

第一步、创建两个vue文件,分别起名字为Left.vue和Right.vue ,你可以起任何名字,但同样需要注意的是名字应为英文,且首字母大写

第二步、就是在根组件中进行引用,这里分为三步

//使用组件步骤1:引入需要使用的组件
import Left from './components/Left.vue'
import Right from './components/Right.vue'
//  使用组件步骤2:注册组件
  components: {
    //组件注册名称:引入的组件
    //键值对
    Left,
    Right,
}
 <!--    使用组件步骤3:直接以标签的形式使用组件-->
    <Left></Left>
    <Right></Right>

这样我们两个组件就引用完成了,这时候我们写在子组件中的代码就会被渲染到根组件中。

注意📢:在components中注册的组件只是局部注册的,也就是说假如我们想在子组件中再引用第三个子组件,需要分别在L和R两个子 组件中分别注册,当我们需要将同一个子组件运用在多个界面中的时候,这种写法无疑是非常麻烦且不可取的,这时我们就要将 想要引用的组件注册为全局组件。(当然要先创建Count.vue组件,后面不再强调)

//全局注册组件组要写在main.js中,new Vue之前
//全局注册组件1:引入组件
import Count from './Count.vue'

//全局注册组件2:注册
//语法: Vue.component('组件的注册名称',引入的组件)
Vue.component('Count', Count)

由此,组件引用就完成了。

在这里我们提一个小需求:点击按钮,让count自增

<template>

  <div>
    <h5>这是Count组件</h5>
    <p>count的值是{{ count }}</p>
    <button @click="count++">+1</button>
  </div>

</template>
<script>
export default {
  name: "Count",
  data() {
    return {
      count:0
    }
  }
}
</script>
<style scoped>

</style>

这样我们这个需求就完成了,但是如果我不想让它自增1,我想让它加2,加3,加任何数,要怎么实现呢?

首先想到的肯定是传递参数,但是要如何传递呢?

<template>
  <div class="left">
    <h3>这是Left组件</h3>
    <!--    使用组件时,可以通过自定义属性给组件传递参数-->
    <Count :n="1"></Count>
  </div>
</template>
<template>
  <div>
    <h3>这是Right组件</h3>
    <Count :n="2" ></Count>
  </div>
<template>

  <div>
    <h5>这是Count组件</h5>
    <p>count的值是{{ count }}</p>
    <button @click="count+=n">+{{ n }}</button>
  </div>

</template>

<script>
export default {
  name: "Count",
  //数组形式
  //通过prop接收参数
  // props: ['n'],

  //对象形式
  props: {
    n: {
      //  type:约束接收到的参数的数据类型,如果传递的不是我们需要的类型会报错。
      type: Number,
      //  给传递过来的参数设置默认值,如果使用组件时,没有传递该prop,就直接使用默认值
      default: 1,
      //  required:如果值为true,表示该参数是必传的,不传会报错
      //default和required两者互相冲突,但是如果default和required同时存在,required优先级更高
      required: true
    }
  },
  data() {
    return {
      count:0
    }
  }
}
</script>

<style scoped>
</style>

这样就可以实现count加任何数字,只需要改变n的大小就可以了。

那么可以看到,在上面的代码中,我是从0开始加起的,如果我想让他从5,从10,从任何数字开始加,那又该怎么做呢?

<template>
  <div class="left">
    <h3>这是Left组件</h3>
    <!--    使用组件时,可以通过自定义属性给组件传递参数-->
    <Count :n="1" :init="5"></Count>
  </div>

</template>
<template>
  <div>
    <h3>这是Right组件</h3>
    <Count :n="2" :init="10"></Count>
  </div>
<template>

  <div>
    <h5>这是Count组件</h5>
    <p>count的值是{{ count }}</p>
    <button @click="count+=n">+{{ n }}</button>
  </div>

</template>

<script>
export default {
  name: "Count",
  //数组形式
  //通过prop接收参数
  // props: ['n'],

  //对象形式
  props: {
    n: {
      //  type:约束接收到的参数的数据类型,如果传递的不是我们需要的类型会报错。
      type: Number,
      //  给传递过来的参数设置默认值,如果使用组件时,没有传递该prop,就直接使用默认值
      default: 1,
      //  required:如果值为true,表示该参数是必传的,不传会报错
      //default和required两者互相冲突,但是如果default和required同时存在,required优先级更高
      required: true
    },
    //prop只能读不能改,例如此处我们想要count从init开始加,那就必须将this.init赋给count,而不能直接采用init进行改变
    init:{
      type:Number,
    }
  },
  data() {
    return {
      count: this.init
    }
  }
}
</script>

<style scoped>
h5{
  color: red;
}

</style>

这里注意看init的注释,prop是只读的,所以不能将template中的count直接换成init进行加减,需要将下面的count的初始值改变为init,这样还是count进行改变,这样才合法。

还有一个小问题就是,样式冲突问题。

可以发现,如果我们组件中设置了文字样式之后,默认style样式将在全局生效,当然如果我们设置scoped属性,该样式可以在当前组件中生效,但是如果我想在父组件中改变子组件的样式,又要怎么做呢?

这里我们引入一个概念,即样式穿透:

<!--> Left组件</-->
<style lang="less" scoped>
/*默认情况下,style样式是全局生效的*/
/*scoped:让样式只在当前组件生效*/

/*scoped原理:加上scoped相当于给元素标签增加一个自定义属性,在渲染样式的时候,回优先根据自定义属性渲染*/
h3 {
  color: pink;
}

/*在父组件中如果想要修改子组件的样式,需要进行样式穿透*/

/*
  css中:使用>>>来做穿透
        语法:父组件中的选择器>>>子组件中的选择器
*/
/*
  .left >>> h5{
    color: orange;
  }
*/

/*
less中:使用/deep/做穿透
       语法:/deep/ 子组件中的选择器
*/
/deep/ h5 {
  color: green;
}

</style>

七、生命周期

<template>
  <div>
    <h1 id="myH1">组件的生命周期</h1>
    <ul>
      <li v-for="item in list" :key="item.id">{{ item.bookname }}</li>
    </ul>
    <div id="main"></div>
  </div>
</template>

<script>

import axios from 'axios'
import * as echarts from 'echarts';

export default {
  data() {
    return {
      username: 'zhangsan',
      list: '',
      timer: null
    }
  },
  methods: {
    show() {
      console.log('调用了show方法')
    }
  },
  //创建周期
  beforeCreate() {
    //此时还拿不到组件的数据和方法
    console.log('-----beforeCreate----')
  },
  async created() {
    //此时可以拿到数据和方法

    this.timer = setInterval(() => {
      console.log(11111);
    }, 1000);
    console.log('----created-----')

    const {data: res} = await axios.get('http://www.liulongbin.top:3006/api/getbooks')
    console.log(res.data)
    this.list = res.data
  },
  beforeMount() {
    //此时还拿不到页面渲染之后的DOM
    console.log('----beforeMount-----')
    console.log(document.querySelector('#myH1'))
  },
  mounted() {
    //此时可以拿到页面渲染之后的DOM
    console.log('----mounted----')
    console.log(document.querySelector('#myH1'))
    var myChart = echarts.init(document.getElementById('main'));
    myChart.setOption({
      title: {
        text: 'ECharts 入门示例'
      },
      tooltip: {},
      xAxis: {
        data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
      },
      yAxis: {},
      series: [
        {
          name: '销量',
          type: 'bar',
          data: [5, 20, 36, 10, 10, 20]
        }
      ]
    });
  },
  //更新周期
  beforeUpdate() {
    console.log('-----beforeUpdate----');
    // 在 beforeUpdate 里,数据已经是更新后的数据
    console.log(this.username);
    // 在 beforeUpdate 里,dom 还没有更新
    console.log(document.querySelector('#myH1').innerHTML);
  },
  updated() {
    console.log('-----updated----');
    // 在 beforeUpdate 里,数据已经是更新后的数据
    console.log(this.username);
    // 在 beforeUpdate 里,dom 已经更新了
    console.log(document.querySelector('#myH1').innerHTML);
  },
  //销毁周期
  beforeDestroy() {
    console.log('-----beforeDestroy----');
  },
  destroyed() {
    console.log('-----destroyed----');
    clearInterval(this.timer)
  }
}
</script>

<style>
#main {
  width: 600px;
  height: 300px;
  border: 2px solid #333;
}
</style>

Q.E.D.