JavaScript 前端 学习笔记 编程 ·

一个前端的vue2.0学习笔记

一篇小小的vue2.0教程。首先,本笔记只针对于vue2.0。因为最近在学vue.js,因为准备要做项目,所以先准备系统的学习一下。此篇笔记会较为全面的记录vue中各个比较重要的知识点,我会写的比较注重代码质量和格式,希望能为其他一起学习vue的小伙伴们能带来一点帮助。

过渡动画

原生css实现过渡动画

dom结构

<div id="app">
    <button @click="toggle">显示和隐藏数据</button>
    <transition name="show">
        <span v-show="isshow">hello vuejs</span>
    </transition>
</div>

js部分

var vm = new Vue({
    el: "#app",
    data: {
        isshow: false
    },
    methods: {
        toggle: function () {
            this.isshow = !this.isshow;
        }
    }
})
在vue2.0中,需要有过渡动画的元素应在该元素外层包裹`<transition></transition>`

接下来是css部分
```css
.show-enter-active,.show-leave-active {
    transition: all 0.4s ease;
    padding-left: 10px;
}

.show-enter,.show-leave-active {
    padding-left: 100px;
}

在vue2.0中,过渡动画的css有着固定的写法,在该例子中开始和结束状态时类名为.show-enter-active,和.show-leave-active。开始和结束的过程的类名为.show-enter,show-leave-active

Animate.css实现过渡动画

首先需要引入animate.css

<link rel="stylesheet" href="animate.css">

dom结构

<div id="app">
    <button @click="toggle">显示和隐藏数据</button>
    <transition enter-active-class="fadeInRight" leave-active-class="fadeOutRight">
        <div v-show="isshow" class="animated show">hello vuejs</div>
    </transition>
</div>

js

var vm = new Vue({
    el: "#app",
    data: {
        isshow: false
    },
    methods: {
        toggle: function () {
            this.isshow = !this.isshow;
        }
    }
})

首先在vue2.0中,我们需要在<transition>标签中定义好进场动画和出场动画的class类,属性名分别为enter-active-classleave-active-class

在这里依旧需要在产生过渡动画的元素外包裹<transition></transition>,另外和前面不同的是,animate.css所生效的对象必须要是块级元素,所以我们在这里将之前的<span>换成了<div>

到这里,利用animate.css来完成过渡动画就已经完成了。

结合钩子函数实现过渡动画

此方法是结合vue生命周期钩子函数的思路来分别设置进场动画过场动画出场动画三个流程,来完成单向的过渡动画效果。

dom结构

<div id="app">
        <button @click="toggle">显示和隐藏数据</button>
        <transition @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter">
            <div v-show="isshow" class="animated show">hello vuejs</div>
        </transition>
    </div>

我们在需要实现效果的元素外包裹<transition>,然后在标签中注册了三个方法,分别是enterenterafter-enter

js

var vm = new Vue({
    el: "#app",
    data: {
        isshow: false
    },
    methods: {
        toggle: function () {
            this.isshow = !this.isshow;
        },
        beforeEnter: function (el) {
            el.style.transform = "translate(100px,0)";
        },
        enter: function (el, done) {
            el.offsetWidth;
            el.style.transform = "translate(10px, 0";
            done();

        },
        afterEnter: function (el) {
            this.isshow = !this.isshow;
        }
    }
})

随后我们需要在methods中定义这三个方法,在这里描述元素变换的过程。

.show {
    transition: all 0.4s ease;
}

然后在样式中,必须要定义好该元素的过渡属性,才能实现过渡效果。

到这里,利用animate.css来完成过渡动画就已经完成了。

组件

全局注册

Vue.component('my-component-name', {
  // ... 选项 ...
})

这些组件全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。比如:

Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })

new Vue({ el: '#app' })
<div id="app">
  <component-a></component-a>
  <component-b></component-b>
  <component-c></component-c>
</div>

在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用。

局部注册

在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }

然后在 components 选项中定义你想要使用的组件:

new Vue({
  el: '#app'
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

对于 components 对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。

注意局部注册的组件在其子组件中不可用。例如,如果你希望 ComponentAComponentB 中可用,则你需要这样写:

var ComponentA = { /* ... */ }

var ComponentB = {
  components: {
    'component-a': ComponentA
  },
  // ...
}

dom和组件的绑定

vue1.0和vue2.0的写法有点区别,在vue1.0中针对dom和组件他分别有v-elv-ref这两种指令来进行绑定,那么在2.0中认为这两种其实都是dom结构中的一部分,所以统一改为ref来进行绑定,那么后面只详细讲vue2.0的写法,对1.0有兴趣的同学可以自行百度。

<div id="div1" ref="mydiv">hello v-el</div>

绑定元素时我们需要在标签中加上ref指令来绑定对象,指定绑定名称之后我们可以随意的在javascript代码中来调用

我们尝试着在控制台打印出该对象

new Vue({
    el: '#app',
    methods:{
        getdom: function(){
            console.log(this.$refs.mydiv);//<div id="div1">hello v-el</div>
        }
    }
})

利用parcel打包vue项目

点击我:parcel官网

首先用npm工具安装parcel

npm i -g parcel-bundler

执行命令(生产环境):

设置环境变量: parcel build index.html NODE_ENV=production
设置输出目录: parcel build index.html -d build/output
设置要提供服务的公共 URL: parcel build index.html --public-url ./
禁用压缩: parcel build index.html --no-minify
禁用文件系统缓存: parcel build index.html --no-cache

如果提示babel没有安装,就根据报错的提示进行npm 安装babel

最后,在package.json中添加(vue官方文档中的安装部分也有说明)

{
  // ...
  "alias": {
    "vue" : "./node_modules/vue/dist/vue.common.js"
  }
}

更详细的教程:点击我:parcel与vue

export default 和 data(){ return {} } 的两个问题

(已经找到解答)

1)首先就是.vue结尾的文件为何需要export default,就像下面的代码一样?
2)为什么data需要return,我不用return,直接 data(){
    menu:MENU.data,
    poi:POILIST.data
}不行吗?
<script>
    import { POILIST, MENU } from '../config/vuex.js';
    export default {
        data() {
            return {
                menu: MENU.data,
                poi: POILIST.data
            }
        },
        methods: {
            set() {
                MENU.list.push('首页');
                POILIST.list.push({
                    lng: 124.1,
                    lat: 42.3
                });
            }
        }
    }
</script>
  1. export default是ES6的语法,意思是将这个东西导出,你要import 引入东西,导出了才能引用。

  2. data是一个函数是因为data是被很多组件共享的,如果 data 是一个的对象的话,每次实例化会造成所有的实例共享引用同一个数据对象,如下

var fnc= function() {}
fnc.prototype.data = {
  a: 1,
  b: 2,
}

var fnc1 = new fnc()
var fnc2 = new fnc()

fnc1.data.a === fnc2.data.a // true
fnc2.data.b = 1;
fnc2.data.b // ==1

data 是函数的话,每次创建一个新实例后,调用 data 函数,用return返回初始数据的一个全新副本数据对象,就避免了所有实例共享引用同一个数据对象。

ES6笔记
因为学vue的过程中发现里面有着许多es6的知识,如果没有系统全面的掌握es6,学起来是非常吃力的,所以决定中途先把es6好好补一下,然后继续学习vue。

let

看了MDN的文档,我得到的信息有这么几条:
1. let 声明的变量的作用域是块级的;
2. let 不能重复声明已存在的变量;
3. let 有暂时死区,不会被提升。
4. let 的「创建」过程被提升了,但是初始化没有提升。
5. var 的「创建」和「初始化」都被提升了。
6. function 的「创建」「初始化」和「赋值」都被提升了。

var liList = document.querySelectorAll('li') // 共5个li
for( let i=0; i<liList.length; i++){
  liList[i].onclick = function(){
    console.log(i)
  }
}

另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

const

const声明一个只读的常量。一旦声明,常量的值就不能改变。

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

字符串方法

1. includes()
2. startsWidth()
3. endsWidth()
4. repeat()
5. padStart()
6. padEnd()
7. 模版字符串``

数组方法

1. set()
2. add()
3. delete()
4. has()
5. clear()

数值方法

1. Number.isFinite()  //判断数值是否有限
2. Number.isNaN()  //判断数值是否为NaN
3. Number.parseInt()  //将数值转换为Int
4. Number.parseFloat()  //将数值转换为浮点数
5. Number.isInteger()  //判断数值是否为整数
6. Number.EPSILON  //ES6 在Number对象上面,新增一个极小的常量Number.EPSILON。根据规格,它表示 1 与大于 1 的最小浮点数之间的差。
7. Number.isSafeInteger()  //判断数值是否为安全整数  区间为-2^53到2^53之间(不含两个端点)
8. Math.trunc()  //扩展方法,去掉小数部分
9. Math.sign()  //来判断Math.sign方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
它会返回五种值。
参数为正数,返回+1;
参数为负数,返回-1;
参数为 0,返回0;
参数为-0,返回-0;
其他值,返回NaN。
10. Math.cbrt()  //Math.cbrt方法用于计算一个数的立方根。

函数方法

1. 函数参数默认值  //参数可直接设置默认值,若未传参时,参数等于预设的默认值
2. rest 参数  // ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
3. 严格模式  //  从 ES5 开始,函数内部可以设定为严格模式。ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
4. name 属性  //函数的name属性,返回该函数的函数名。、
function foo() {}
foo.name // "foo"
5. 箭头函数  //ES6 允许使用“箭头”(=>)定义函数。
var f = v => v;

// 等同于
var f = function (v) {
  return v;
};

箭头函数有几个使用注意点。

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
  5.1 嵌套的箭头函数  //箭头函数内部,还可以再使用箭头函数。下面是一个 ES5 语法的多重嵌套函数。
6. 双冒号运算符  //箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call、apply、bind)。但是,箭头函数并不适用于所有场合,所以现在有一个提案,提出了“函数绑定”(function bind)运算符,用来取代call、apply、bind调用。
7. 尾调用优化  //尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。
8. 尾递归  //函数调用自身,称为递归。如果尾调用自身,就称为尾递归。
9. 函数参数的尾逗号 //ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。

props

父组件与子组件如何传值?

首先我们列举两个文件,父组件:todo.vue,子组件:item.vue
那么在父组件中,我们必然看到父组件调用了子组件,以下为父组件todo.vue中的部分代码:(已删除无关代码)

<template>
  <section class="real-app">
    <Item :todo="todo"
          v-for="todo in todos"
          :key="todo.id"
          @del="deleteTodo"></Item>
  </section>
</template>
<script>
import Item from './item.vue'
export default {
  data() {
    return {
      todos: [],
      filter: 'all'
    }
  },
  components: {
    Item
  },
  methods: {
    addTodo(e) {
      this.todos.unshift({
        id: id++,
        content: e.target.value.trim(),
        completed: false
      })
      e.target.value = ''
    },
    deleteTodo() {
      return true
    }
  }
}
</script>

我们可以看到在父组件中定义了一个<Item></Item>标签中我们用v-for循环出了todo对象,并在标签上用:todo="todo"将todo对象进行传入。

由于业务需要我们必须要将值传入item.vue中进行判断。

这时,我们便可以用到props方法,那究竟怎么用呢?talk is cheap,来人给客官上代码!以下为子组件item.vue中的部分代码:

<template>
  <div :class="['todo-item', todo.completed ? 'completed' : '']">
    <input type="checkbox"
           class="toggle"
           v-model="todo.completed">
    <label>{{todo.content}}</label>
  </div>
</template>

<script>
export default {
  props: {
    todo: {
      type: Object,
      required: true
    }
  }
}
</script>

这里,我们在js中用props方法将父组件的todo传了进来,类型为Object,而且不能为空。现在我们就可以在上面尽情的用todo对象啦。

emmit

在子组件触发了click事件时我们需要修改父组件的值,那么我们该如何做?

方法有很多,比如我们在父组件中定义了一个方法,随后用props传进来,然后在子组件中调用。那么,目前来说,更流行的方法为emmit,我们根据实际代码来讲。以下为子组件item.vue中的部分代码:

<template>
  <div>
    <button class="destory"
            v-bind:click="deleteTodo"></button>
  </div>
</template>
<script>
export default {
  props: {
    todo: {
      type: Object,
      required: true
    }
  },
  methods: {
    deleteTodo() {
      this.$emit('del', this.todo.id)
    }
  }
}
</script>

我们在子组件中用@click绑定了一个方法,在方法中,我们用emit注册了del,并传出一个this.todo.id

那么我们在父组件中,便可以在标签中通过@del=""或者v-on:del=""来监听到这里传出的值。以下为父组件todo.vue中的部分代码

<template>
  <section class="real-app">
    <Item :todo="todo"
          v-for="todo in todos"
          :key="todo.id"
          @del="deleteTodo"></Item>
  </section>
</template>
<script>
import Item from './item.vue'
export default {
  data() {
    return {
      todos: [],
      filter: 'all'
    }
  },
  components: {
    Item,
  },
  methods: {
    deleteTodo(id) {
      // 通过参数接收将子组件传出的值
      return true
    }
  }
}
</script>

Vuex

初使用Vuex有感,首先npm i vuex -D完成Vuex的安装。
然后我们在src中的目录结构为

src
├─store
│  │  index.js
│  │  
│  ├─getters
│  │      index.js
│  │      
│  ├─mutations
│  │      index.js
│  │      
│  └─state
│          index.js

为了方便日后的维护和统一修改,我将gettermutationsstate等相关的配置全都放到单独的文件夹中。

store/index.js文件中,我们的项目如果使用的是服务端渲染,那么我们需要注意的是在我们最后将store对象传出时,为了防止内存溢出,我们注意的是不能直接传出store,而是用箭头函数的形式传出。相关代码:

export default () => {
  return new Vuex.Store({
    state: defaultState,
    mutations,
    getters
  })
}

我们有两种方法可以在app.vue中调用vuex。(第一种更优)

computed: {
  // 第一种
  // ...mapState(['count']),

  // 第二种
  // ...mapState({
  //   count: 'count'
  // }),

  // 第三种
  ...mapState({
    count: (state) => state.count
  }),

  // 第四种
  // count() {
  //   return this.$store.state.count
  // },
  fullName() {
    return this.$store.getters.fullName
  }
}

以及如何更优雅的在app.vue中去调用actionsmutations。(方法二更优)代码:

mounted() {
  // console.log(this.$store)
  let i = 1
  setInterval(() => {
    // mutations方法一
    // this.$store.commit('updateCount', i++)

    // mutations方法二
    this.updateCount(i++)
  }, 1000)

  // actions方法一
  // this.$store.dispatch('updateCountAsync', {

  // actions方法二
  this.updateCountAsync({
    num: 5,
    time: 2000
  })
},
methods: {
  // 此处为方法二进行注册
  ...mapActions(['updateCountAsync']),
  ...mapMutations(['updateCount'])
},

Vue-router 底部导航栏 一级路由显示 子路由不显示

最近碰到了vue-router切换时底部导航在手机端会被拖拽导致显示的问题,查询后发现了vue-router中meta中的属性可以直接绑定到模板中的v-if中

talk is cheap,上代码

router/index.js

const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: '/first', component: firstView, meta: { navShow: true, cname: '一级页面' }, name: 'first' },
    { path: '/sub', component: subView, meta: { navShow: false, cname: '子页面' }, name: 'sub' },
  ],
});

src/app.vue

<Bar v-show="$route.meta.navShow">

.....................
.....................
xxxx - 剩下内容待填坑中

参与评论