前端-09-vue

2020-09-12  本文已影响0人  西海岸虎皮猫大人

1.概述

渐进式,可以嵌入到现有程序,也可单独处理
响应式,数据变化引起视图更新
可复用组件,拥有自己的js css
vue更适合快速构建,适合中小型项目
不支持ie8及以下,支持所有支持es5的浏览器
调试工具 vue devtools
默认构建方案webpack
命令行工具vue cli,市场占有最多是2.x版,本教程基于2.x
vue兼具angular和react的有点
文档
https://cn.vuejs.org/v2/guide/
vue cli文档
https://cli.vuejs.org/zh/guide/

2.环境

版本,vue-cli改成@vue-cli

# 安装(速度略慢)
npm install -g @vue/cli
# 查看版本
vue -V
# 拉取旧版2.x
npm install -g @vue/cli-init
# 创建项目
vue init webpack 01-basic
# 选择npm
# 安装依赖
npm install
# 启动
npm start
# 访问
http://localhost:8080
# 目录介绍
# static 静态文件
# .babelrc es6解析
# .postcssrc.js css配置

3.语法

main.js中创建vue实例

// 创建实例
new Vue({
  // id
  el: '#app',
  // 组件
  components: { App },
  // 模板
  template: '<App/>'
})

HelloWorld.vue

<template>
  <div class="hello">
    <!--模版语法{{}} 只能存在单行语句-->
    <!--{{ '哈哈' }}-->
    <!--{{ 20 + 1 }}-->
    <!--{{ "OK" ? "yes" : "no" }}-->
    <!--{{ "hello".split("").reverse().join("") }}-->
    <!--{{ msg }}-->
    <!--只渲染一次-->
    <!--<span v-once>{{ msg }}</span>-->
    <!--<div v-html="hello"></div>-->
    <!--绑定属性url-->
    <!--<a v-bind:href="url">{{ url_name }}</a>-->
    <!--<div v-bind:class="divClass">容器</div>-->
    <!--简写-->
    <!--<div :class="divClass">容器</div>-->
    <!--属性可以写表达式-->
    <!--<div :class="div2Class+'-1'"></div>-->
    <!--条件渲染-->
    <div v-if="flag">孙悟空</div>
    <div v-else>六耳猕猴</div>
    <!--<div v-show="flag">真*三国无双</div>-->
<!--    <ul>
      <li v-for="name in names">{{ name }}</li>
    </ul>-->
    <!-- 遍历复杂对象 -->
<!--    <ul>
      <li v-for="name in names">{{ name.name }}{{ name.age }}</li>
    </ul>-->
    <!--下标 :key-->
<!--    <ul>
      <li v-for="(name, index) in names" :key="index">{{index}}&#45;&#45;{{ name.name }}&#45;&#45;{{ name.age }}</li>
    </ul>-->
    <!--遍历对象-->
<!--    <ul>
      <li v-for="(value, key, index) in obj">{{value}}-{{key}}-{{index}}</li>
    </ul>-->
    <!--事件 传递参数-->
    <button @click="clickHandler('hahaha', $event)">按钮</button>
    <ul>
      <li v-for="item in helloArr">{{ item }}</li>
    </ul>
    <!-- 点击按钮添加数组数据 -->
    <button @click="addHandler">添加数据</button>
  </div>
</template>

<script>
export default {
  // v-bind 绑定属性
  // v-if 条件渲染
  // v-show 条件渲染,效果与单一v-if相同
  // v-if 确保子组件销毁重建,高开销
  // v-show总是渲染,通过css控制隐藏
  // v-for 列表渲染
  // 遍历推荐加key
  // 事件
  name: 'HelloWorld',
  methods:{
    clickHandler(data) {
      // 接收参数
      console.log(data);
      console.log(event);
      // 点击按钮改变data数据
      // 引起视图更新
      // this.flag = true;
      this.flag = !this.flag;
    },
    addHandler(event) {
      // 注意数组方法是改变原数组还是创建新数组
      // 变异方法,改变原数组,引起视图变化 push pop...
      // this.helloArr.push("hello4");
      // 创建新数组,不引起视图变化 filter slice concat
      this.helloArr.concat(["hello4", "hello5"]);
    }
  },
  // 相当于react的state
  data () {
    return {
      msg: 'Hello Vue',
      hello: "<h3>Hello H3</h3>",
      url_name: "百度",
      url: "https://www.baidu.com",
      divClass: "isActive",
      div2Class: "list",
      flag: false,
/*      names: [
        "张三",
        "李四",
        "王五"
      ],*/
      names: [
        {
          name: "zhangsan",
          age: 18
        },
        {
          name: "lisi",
          age: 19
        }
      ],
      obj: {
        name: "vincent",
        age: 18
      },
      helloArr: ["hello1", 'hello2', "hello3"]
    }
  }
}
</script>

<style scoped>
</style>
计算属性 class与style绑定 v-model
<template>
    <div id="VueDemo">
      <!--重复表达式-->
      <!--{{ getMsg }}-->
      <!--{{ mMsg() }}-->
      <!--<div>-->
        <!--{{ getMsg }}-->
        <!--{{ mMsg() }}-->
      <!--</div>-->
      <!--根据值判断key是否存在-->
      <!--静态class不会被替代-->
      <!--<div class="old" :class="{'active':classDemo, 'txt': classDemo}">-->
        <!--css-->
      <!--</div>-->

      <!--<div :class="{'c1':c1, 'c2':c2}">test</div>-->
      <!--<div :class="cssObj">test</div>-->

      <!--通过事件改变css-->
      <!--<button @click="changCss">改变css</button>-->

      <!--属性数组-->
<!--      <div :class="[activeClass, errorClass]" >
        test1
      </div>-->
      <!--属性数组 三目表达式-->
      <!--<div :class="[isActive ? activeClass : '', errorClass]">test2</div>-->
      <!--复杂属性表达式-->
      <!--<div class="old" :class="[isActive ? 'active' : 'noActive', {'text': classDemo}]">test3</div>-->
      <!--属性还可以拼接字符串 对象key不能拼接字符串-->
      <!--<div class="old" :class="[isActive ? 'active' : helloC+'noActive', {'text': classDemo}]">test3</div>-->
      <!--内联样式-->
<!--      <div :style="{color:'red', fontSize:'40px'}">
        style
      </div>-->

      <!--v-model双向数据绑定,忽略value-->
<!--      <div class="myInput">
        <input v-model="inputMsg" type="text">
        <p>{{ inputMsg }}</p>

        <input type="checkbox" id="checkbox" v-model="checked">
        <label for="checkbox">{{ checked }}</label>
      </div>-->

      <!--修饰符-->
      <!--
        lazy 回车或失去焦点才会绑定
        number 只接收数字类型
        trim 去掉前后空格
      -->
      <!--<input v-model.lazy="inputMsg" type="text">-->
      <!--<input v-model.trim="inputMsg" type="text">-->
      <p>{{ inputMsg }}</p>
      <button @click="changeName">改变myname</button>
    </div>
</template>

<script>
    export default {
      name: "VueDemo",
      data() {
        return {
          msg: "hello",
          classDemo: true,
          // c1: true,
          // c2: true,
          cssObj: {
            'c1': true,
            'c2': true
          },
          activeClass: 'active',
          errorClass: 'text-danger',
          isActive: false,
          helloC: 'he_',
          inputMsg: '',
          checked: true,
          myname: 'vincent'
        }
      },
      // 实时监听数据变化
      // 计算属性vs watch 不要滥用watch
      watch: {
        inputMsg(data) {
          if(data == '100') {
            console.log(100);
          }
        },
        myname(data) {
          console.log(data)
        }
      },
      // 计算属性
      // 计算属性根据依赖进行缓存
      computed: {
        getMsg() {
          // 如果数据不改变不会重新计算
          return this.msg.split('').reverse().join('')
        }
      },
      methods: {
        // 函数每次调用都会执行
        mMsg() {
          return this.msg.split('').reverse().join('');
        },
        changCss() {
          this.cssObj = {
            c1: false,
            c2: true
          }
        },
        changeName() {
          this.myname += "1";
        }
      }
    }
</script>

<style scoped>
  .active {
    color: red;
  }

  .txt {
    font-size: 30px;
  }

</style>

3.组件

A.vue

<template>
  <div>
    组件A: {{ msg }}
    <!--切换组件后组件消失-->
    <button @click="change">change</button>
  </div>
</template>

<script>
    export default {
      name: "A",
      data() {
          return {
            msg: "默认"
          }
      },
      methods: {
        change() {
          this.msg = "改变";
        }
      }
    }
</script>

<style scoped>

</style>

B.vue

<template>
    <div>
      组件B
    </div>
</template>

<script>
    export default {
        name: "B"
    }
</script>

<style scoped>

</style>

Learn.vue

<template>
  <!--只能存在一个根容器-->
  <div class="container">
    新的组件: {{ title }}
    <button @click="sendMsg">向父组件传递数据</button>
    <input v-model="searchText" type="text">
  </div>
</template>

<script>
    export default {
      name: "Learn",
      // 所有初始化状态放入data
      data() {
        return {
          searchText: ""
        }
      },
      // 接收父组件传递的数据
      props: ["title"],
      methods: {
        sendMsg() {
          // this.$emit("getMsg", "我是儿子的数据");
          this.$emit("getMsg", this.searchText);
        }
      }
    }
</script>

<!--样式 scoped限制css只作用于当前组件-->
<style lang="css" scoped>
.container {
  background: red;
}
</style>

App.vue

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <!--<HelloWorld/>-->
    <!--<VueDemo />-->
    <!--组件传递数据-->
    <!--<Learn title="learn vue"/>-->
<!--    <input v-model="parentText" type="text">
    <Learn @getMsg="getSonMsg" :title="parentText"/>
    {{ demo }}-->
<!--    <A />
    <B />-->

    <!-- 组件不重复渲染以保存状态 -->
    <keep-alive>
      <component v-bind:is="currentComponent"></component>
    </keep-alive>
    <button @click="changeComponent">切换组件</button>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld'
import VueDemo from './components/VueDemo'
// 引入
import Learn from './components/Learn'
import A from './components/A'
import B from './components/B'

export default {
  name: 'App',
  data() {
    return {
      demo: "",
      parentText: "",
      title: {
        name: "vincent"
      },
      currentComponent: A,
      flag: false
    }
  },
  components: {
    // HelloWorld,
    // VueDemo,
    // 注入
    Learn,
    A,
    B
  },
  methods: {
    getSonMsg(data) {
      this.demo = data;
    },
    changeComponent() {
      if(this.flag) {
        this.flag = false;
        this.currentComponent = A;
      } else {
        this.flag = true;
        this.currentComponent = B;
      }
    }
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

4.动画 | 自定义指令

<template>
  <div class="anim">
    <button @click="show = !show">toggle</button>
<!--    <transition name="fade">
      <p v-if="show">hello</p>
    </transition>-->

    <!--自定义名字-->
<!--    <transition name="vincent">
      &lt;!&ndash;<p v-if="show">hello</p>&ndash;&gt;
      <p class="box" v-if="show"></p>
    </transition>-->

    <transition
      name="custom-classes-transition"
      enter-active-class="animated rollIn"
      leave-active-class="animated zoomOutDown"
    >
      <p class="box" v-if="show">hello</p>
    </transition>

    <input v-focus type="text">
    <p v-myStyle>哈哈哈</p>
  </div>
</template>

<script>
    // 动画
    export default {
      name: "Anim",
      data() {
        return {
          show: true
        }
      },
      // 局部指令
      // 很多位置实现相同效果时用指令实现
      directives: {
        focus: {
          inserted: function (el) {
              el.focus();
          }
        },
        myStyle: {
          inserted: function (el) {
            el.style.fontSize = "40px";
          }
        }
      }
    }
</script>

<style scoped>
/*   .fade-enter-active, .fade-leave-active {
    transition: opacity .5s;
  }
  .fade-enter, .fade-leave-to {
    opacity: 0;
  }*/

/*  .vincent-enter, .vincent-leave-to {
    opacity: 0;
  }
  .vincent-leave-to, .vincent-enter {
    opacity: 1;
  }
  .vincent-enter-active, .vincent-leave-active {
    transition: opacity .5s;
  }*/

  /* 过渡效果 */
/*  .vincent-enter, .vincent-leave-to {
    opacity: 0;
    width: 0;
  }
  .vincent-enter-to, .vincent-leave {
    opacity: 1;
    width: 100px;
  }
  .vincent-enter-active, .vincent-leave-active {
    transition: all 1s;
  }

  .box {
    width: 100px;
    height: 100px;
    background: red;
  }*/

/*  .vincent-enter, .vincent-leave-to {
  }
  .vincent-enter-to, .vincent-leave {
  }
  .vincent-enter-active {
    animation: bounce-in 1s;
  }
  .vincent-leave-active {
    animation: bounce-out 1s;
  }

  @keyframes bounce-in {
    0% {
      transform: scale(0);
    }
    50% {
      transform: scale(1.5);
    }
    100% {
      transform: scale(1);
    }
  }

  @keyframes bounce-out {
    0% {
      transform: scale(1);
    }
    50% {
      transform: scale(1.5);
    }
    100% {
      transform: scale(0);
    }
  }*/

.box {
    width: 100px;
    height: 100px;
    background: red;
  }
</style>

5.过滤器

Child.vue

<template>
  <div>
    {{ money | rmb }}<br>
    {{ txt | author }}
  </div>
</template>

<script>
    export default {
        name: "FilterDemo",
      data() {
          return {
            money: 20,
            txt: "To be or not to be"
          }
      },
      // 过滤器对变量格式化处理
      filters: {
        rmb: function (value) {
          if(!value) return;
          value = value.toString();
          return "$" + value;
        },
        author: function (value) {
          if(!value) return;
          return value + "--by vincent";
        }
      }
    }
</script>

<style scoped>

</style>

6.父子组件 | 插槽

<template>
    <div class="child">
      {{ getFoo }}
      子组件: {{ title }}--{{ age }}--{{ nick }}--{{ parent }}
      <ul>
        <li v-for="g in girlFriend">{{ g }}</li>
      </ul>
      <button @click="sendMonney">赚了</button>
    </div>
</template>

<script>
    // 子组件可以读取父组件中的元素,但不建议,破坏耦合性
    // $root全局变量也不建议
    export default {
      name: "Child",
      data() {
        return {

        }
      },
      methods: {
        sendMonney() {
          this.$emit("money", "翻了5倍");
        }
      },
      computed: {
        getFoo() {
          return this.$root.foo;
        }
      },
      // props: ["title", "age"]
      // 指定类型
      props: {
        title: String,
        age: Number,
        // 默认值
        nick: {
          type: String,
          default: "国民儿子"
        },
        parent: {
          type: String,
          required: true
        },
        // 默认值如果是数组或对象必须返回function
        girlFriend: {
          type: Array,
          default: function () {
            return  ["凤姐", "芙蓉姐姐"]
          }
        }
      }
    }
</script>

<style scoped>

</style>

Parent.vue

<template>
    <div class="parent">
      <slot ct="我是parent的数据">
      </slot>
      父组件: {{ money }}
      <Child @money="getMoney" parent="王健林" nick="娱乐圈键盘侠" title="一个亿的小目标" :age="age"/>
    </div>
</template>

<script>
    import Child from './Child'
    import SlotDemo from './SlotDemo'

    export default {
      name: "Parent",
      data() {
        return {
          money: "",
          age: 30
        }
      },
      components: {
        Child, SlotDemo
      },
      methods: {
        getMoney(data) {
          this.money = data;
        }
      }
    }
</script>

<style scoped>

</style>

SlotDemo.vue

<template>
<!--    <div class="slotDemo">
      <slot></slot>
    </div>-->

  <div class="slotDemo">
<!--    <slot name="v1"></slot>
    <slot name="v2">
      我是插槽默认信息
    </slot>-->
    <!--儿子给父亲传数据-->
    <slot ct="我是儿子的数据">
    </slot>
  </div>
</template>

<script>
    // 插槽,内容分发,传递视图
    // 具名插槽 可以传递多个插槽
    // 插槽可以设置默认信息
    // 插槽可以设置动态内容,注意作用域
    // 插槽的视图是父组件传递的
    // 视图要显示的内容由子组件决定
    // 效果由父组件决定,数据由子组件决定
    export default {
        name: "SlotDemo",
    }
</script>

<style scoped>

</style>

7.音乐 | 操作原生DOM

<template>
    <div class="music">
      音乐实例
      <p class="p1" ref="p1">原生P元素</p>
      <input type="text" ref="myinput">
      <button @click="getValue">按钮</button>
    </div>
</template>

<script>
    // 操作原生dom
    // 改变内容和属性可以通过绑定数据
    // input标签 audio标签需要通过原生dom操作
    // 没有必要不要操作原生dom,性能不如虚拟dom
    export default {
      name: "Music",
      data() {
          return {

          }
      },
      mounted() {
        this.$refs.p1.innerHTML = "改变吧"
        this.$refs.myinput.value = "哈哈"
        console.log(this.$refs.p1)
      },
      methods: {
        getValue() {
          console.log(this.$refs.myinput.value)
        }
      }
    }
</script>

<style scoped>

</style>

8.网络请求

vue-resource不维护,推荐axios
axios基于promise
中文文档
https://www.kancloud.cn/yunye/axios/234845

# 安装
npm install --save axios
# main.js引入
import Axios from "axios"
Vue.prototype.$axios = Axios

main.js

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import Axios from "axios"

Vue.prototype.$axios = Axios

Vue.config.productionTip = false

// 全局配置
Axios.defaults.baseURL = 'http://localhost';
Axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

// 拦截器
// 添加请求拦截器
Axios.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  if(config.method === "post"){
    config.data = qs.stringify(config.data)
  }
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 添加响应拦截器
Axios.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  if(!response.data){
    return {
      msg:"数据返回不合理"
    }
  }
  return response;
}, function (error) {
  // 对响应错误做点什么
  return Promise.reject(error);
});

// 自定义全局指令
Vue.directive('focus', {
  // 常用
  bind:function() {
    console.log("只调用一次")
  },
  // 常用
  // 当前指令生命周期
  // 插入到元素当中时执行
  inserted:function (el, binding) {
    console.log(binding);
    console.log(el);
    el.focus();
  },
  update:function () {
    console.log("更新")
  }
})

/* eslint-disable no-new */
new Vue({
  el: '#app',
  // 全局变量
  data:{foo: "hellofoo"},
  components: { App },
  template: '<App/>'
})

跨域问题

使用代理解决
config/index.js

    // 跨域处理
    proxyTable: {
      '/dfun_api':{
        target: 'http://localhost',
        pathRewrite: {
          '^/dfun_api': ''
        },
        changeOrigin: true
      }
    },

main.js
注意注掉之前的全局配置

// 跨域处理
Vue.prototype.HOST = "/dfun_api"

请求

 var url = this.HOST + "/04_php/basic/b1_ajax.php";
    // post请求, 跨域
    this.$axios.post(url, qs.stringify({
...

修改配置文件需要重启服务器

9 路由

https://router.vuejs.org/zh/

# 创建项目
vue init webpack 03-router
cd 03-router
npm install

# 安装路由
npm install vue-router --save

# main.js 
# 引入路由,创建路由,注入实例
------
import VueRouter from 'vue-router'
vue.use(VueRouter)

// 创建路由
const router = new VueRouter({
  routes: [
    {
      path: '/hello',
      name: 'HelloWorld',
      component: HelloWorld
    }
  ]

new Vue({
  el: '#app',
  // 注入实例对象
  router,
  components: { App },
  template: '<App/>'
})
------

# 显示路由 App.vue
------
  <div id="app">
    <!--显示路由-->
    <router-view />
  </div>
------
路由嵌套
编程式导航
 gotoHello() {
      // this.$router.push("/hello")

      // 传递对象
      /* this.$router.push({
         'path': '/hello'
       })*/

      // replace方式,不会向history添加记录
      this.$router.replace({path: 'hello'})

      // 回退到之前一个页面
      this.$router.go(-1)
    }

10 element-ui

https://element.eleme.cn/#/zh-CN/component
https://iviewui.com/docs/introduce
npm i element-ui -S
npm install babel-plugin-component -D

11 swipper组件

https://github.com/surmon-china/vue-awesome-swiper
https://www.swiper.com.cn/
npm install swiper vue-awesome-swiper --save
npm install --save axios

11 组件库

https://github.com/vuejs/awesome-vue
使用见组件文档

npm install --save vue-awesome-swiper

npm install --save pull-to-refresh

上一篇下一篇

猜你喜欢

热点阅读