Vue项目核心要点总结

Vue核心要点理解

Vue性能操作

  • computed计算属性的缓存和methods,watch
  • v-if和v-show
  • :key 解决唯一性,循环遍历的时候最好用数据的id,而不是下标
  • 使用v-once来提高动态组件之间的切换,是组件第一次渲染之后放在内存中,后面操作直接调用,提高性能
  • 函数节流
  • 使用keep-alive结合钩子函数activated进行页面性能优化

Vue的数据传递

  • 父传子 通过v-bind:传递,子组件通过props接收
  • 子传父 子组件通过$emit发送事件,父组件通过v-on监听事件
  • 非父子 通过bus/发布订阅/总线机制来传递,或者使用vuex传递
  • 父传子可以通过slot插槽传递数据

MVVM和MVC

  1. MVVM:M表示数据model,是获取到的数据。V表示视图view,是展示的界面。VM表示视图模型,用来连接model和view,是vue底层自动实现,不需要认为操作。MVVM是数据驱动的。
  2. MVC: M表示数据model,是从后台用ajax获取到的数据。V表示视图层view,是前端渲染的页面。C表示控制器controler,用来将获取到的数据使用dom操作来更新页面,VMMC中数据和操作dom是混合的,controler控制器部分比较庞大,比如原始的jquery就是MVC模式。

虚拟DOM和Object的defineProperties

前端的组件化

父子组件之间的传值

  1. 父传子
    • 使用v-bind:属性名=变量值传递
    • 使用props: [属性名]接收
  2. 子传父
    • 子组件通过事件使用this.$emit(事件名,数据)发送事件和数据
    • 父组件通过v-on:事件名="父组件事件"监听并接收传递过来的数据

Vue的实例

vue的每一个组件都是一个vue的实例。

vue的项目都是用多个vue实例拼装而成的。

Vue的生命周期

生命周期函数就是vue实例在某个事件点会自动执行的函数

beforeCreate: 在此之前只有默认的一些事件和生命周期钩子函数

created: 在此过程中,data和methods都已经被初始化好了,最早可以在created中操作方法和数据

beforeMount: 表示模板已经在内存中编译完成了,但是尚未渲染到页面中,页面中的元素还没有替换过来,还是之前的模板字符串,页面数据还未编译到模板template中

mounted: 内存中的模板已经挂在到了页面中,实例已经被完全创建好了

beforeUpdate: 页面中的数据被更新了,此时data中的数据是最新的,页面尚未和最新的数据保持同步。

updated: 更新之后,页面和data中的数据保持同步,都是最新的。

beforeDestroy: 当调用了vm.$destroy()函数的时候触发

destroyed: 当vue实例被销毁的时候触发

计算属性

计算属性是基于它们的依赖进行缓存的,如果依赖的值没有发生改变,计算属性也不会执行

相对于watch监听和方法来说,同样的功能,计算属性相对来说比较简单而且有缓存

计算属性默认调用的是get,还可以设置set.

watch监听,和compute计算属性类似,也具有缓存。只有依赖发生改变时候才会触发,但是写法比较麻烦

get,set的使用

样式绑定

class

  1. 对象绑定

    :class={类名:变量}

  2. 数组绑定

    :class=[变量]

style

  1. 对象绑定

    :style={变量}

  2. 数组绑定

    :style=[变量]

iff算法

条件渲染

v-if: 每一次都会删除和添加dom,性能不好

v-show: 经常操作的可以使用v-show,性能好

问题:

# 解决vue的复用问题,在不添加key的时候,当切换显示和隐藏时候,vue会复用文本框,里面内容也不会改变,解决办法就是给元素加一个key就可以使之唯一,vue就会重新渲染。
用户名:
密码:

vue数组的变异方法

Vue 包含一组观察数组的变异方法,所以它们也将会触发视图更新。这些方法如下

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

改变数组内容的方法:变异方法和引用(list = [])、set方法(Vue.set(vm.userInfo,1,5))

改变对象内容的方法:引用(obj = {})、set方法(vm.$set(vm.userInfo,‘address’,‘beijing’))

template标签的使用

用来占位,包裹一些元素,但是不显示

使用is解决组件模板标签的bug问题

	

子组件中定义data必须是函数

# 作用:是为了让每个可复用的组件拥有自己的独立数据

使用ref来获取dom

# 在html元素上的ref可以获取dom
hello world
# 使用发布订阅的方式,在组件上的ref可以获取该组件的引用
{{total}}

父子组件之间的数据传递

vue中的单项数据流,只允许父组件向子组件传递数据,不允许子组件改变父组件传递过来的数据。如果需要修改父组件传递过来的数据,则可以将传递过来的数据保存为自己的变量,然后做更改。

组件参数校验

props: {
    content: String,
    age: Number,
    id: [Number, String],
    address: {
		type: String,
         required: true,
         default: 'default value',
         validator: function(value){
            return value.length > 5
         }
    }
}

非props属性

  • 非props特性:当父组件向子组件传递数据时候,子组件并没有用props接收;在子组件中无法使用
  • 非props特性传递给子组件的属性及属性值会显示到dom中html的属性中

给组件绑定原生事件

在组件上绑定事件的时候,这个事件是属于监听事件,而不是原生事件。因此当点击时候不会触发。如果需要触发,有以下两种操作:

  • 通过子组件使用事件$.emit(‘事件名’),发送一个事件,然后父组件监听@事件名='自己的事件’来触发
  • 通过事件修饰符,只需要在子组件上定义的事件后面加上一个.native.@click.native="chandleClick"

非父子组件间的传值

通过(Bus/总线/发布订阅模式/观察者模式)

  1. 非父子组件之间数据传递的方式
    • 逐层传递
    • 使用Vuex
    • 使用发布订阅模式总线机制传递
    

Vue中的插槽

在vue中,父组件向子组件传递数据的时候,通过绑定属性向子组件传递,子组件通过props来接收

通过插槽的方式可以批量的在子组件内部传递数据,然后通过slot来接收,并且可以接收具名slot内容。

    
header
footer

Vue中的作用域插槽

    

动态组件

    

在Vue中 使用animate.css库

hello world

在Vue中同时使用过渡和动画



	
hello world

Vue动画中属性声明javascript钩子


  

Vue中使用Velocity.js

中文文档:http://www.mrfront.com/docs/velocity.js/index.html

Vue中多个元素/组件的过渡和动画

vue中的dom的复用,导致动画不会出现,需要添加不同的key值



	
hello world
bye world

Vue中的列表过渡




	
{{item.title}}

Vue中动画的封装

hello world
Vue.component('fade', { props: ['show'], template: ` `, methods: { handleBeforeEnter: function(el){ el.style.color="red" }, handleEnter: function(el, done){ setTimeout(() => { el.style.color = 'green' done() }, 2000) } } })

Vue项目开发环境

  • 安装node.js
    • 检测npm -v和 node -v
  • 创建码云的仓库
    • 用账号登录码云
    • 然后选择项目>Private点击+号创建私有的项目
    • 依次填写项目名称>项目介绍>是否公开(私有)>选择语言(JavaScript)> 添加开源许可证(MIT License)> 使用Readme文件初始化这个项目>创建即可
  • 安装git并使用git将本地仓库和远程仓库进行关联
    • 点击码云头像>设置>SSH公钥>项目管理>公钥 管理>生成/添加SSH公钥
    • 在git-bash中三次回车执行ssh-keygen -t rsa -C "[email protected]",如果之前生成过公钥,这会提示已经生成,不需要再次生成了
    • 然后在 git-bash中执行 cat ~/.ssh/id_rsa.pub获取公钥
    • 然后将公钥添加到SSH公钥的里面,标题会自动生成。
    • 这样在本地会有一个私钥,然后私钥和公钥匹配之后就可以不用密码来上传和更新。
  • 配置SSH私钥并建立连接
  • 将线上的仓库通过SSH克隆到本地。在本地执行git clone SSH地址
  • 安装vue脚手架工具npm install --global -vue-cli
  • 在克隆下来的文件夹中使用vue-cli创建vue项目初始化项目:vue init webpack projectName(项目名)  **ps:**注:项目名不能大写,不能使用中文
  • 进入项目文件夹,执行cnpm install下载依赖
  • 运行 npm run dev
  • 在项目文件夹中执行git add .git commit -m ''提交到本地仓库
  • 然后执行git push将本地库提交到码云仓库

详细的环境配置:https://blog.csdn.net/yw00yw/article/details/81201670

单页应用VS多页应用

  • 多页应用网站
    • 当每次请求跳转的时候后台都会返回一个新的html文件,则为多页应用网站
      • 优点:首屏时间快,SEO效果好
      • 缺点:页面切换慢
  • 单页应用网站
    • 页面每次跳转并不加载html页面,而是通过js动态清除当前页面内容然后渲染新的html页面内容,只请求加载了一次。
      • 优点:页面切换快
      • 缺点:首屏时间稍慢,SEO差

项目初始化

  • 增加meta标签

样式问题——在main.js中引入

  • 引入reset.css使各个浏览器显示的效果一样
    • import './assets/styles/reset.css'
  • 引入border.css解决移动端1px问题
    • import './assets/styles/border.css'

js问题

  • fastclick 解决移动端click300ms延迟问题
    • import fastClick from 'fastclick'
    • fastClick.attach(document.body)

第三方字体库

  • iconfont

css预处理器

stylus——> npm install stylus stylus-loader --save


rem的使用

# 在rest.css中设置了html的根字体大小为 50px
# 因为设计稿一般是750px是iphone6的二倍图
# 那么可以简单在设计稿上量取的高度/宽度 = html * 2 = 50 * 2 = 1rem / 100
# 即 1px = 0.01rem

配置文件路径的别名,简化路径

build > webpack.base.conf.js中配置

resolve: {
        extensions: ['.js', '.vue', '.json'],
        alias: {
            '@': resolve('src'),
            'styles': resolve('src/assets/styles'),
        }
    }

引入

# 在css中引入必须加~
# 在main.js中引入不需要加前面的~
@import '~styles/stylus/varibles.styl'

分支的创建

# 在码云git仓库中创建一个分支,并起用分支名,然后将线上仓库使用`git pull`拉取到本地,然后使用`git checkout ...`切换到新分支上,然后进行开发,该功能模块开发完成之后依次提交到本地仓库`git commit`、线上仓库`git push`的当前分支上,然后切换到主分支,使用`git merge ...`合并到主分支,然后`git push`提交到远程仓库

轮播插件

使用Vue-Awesome-Swiper轮播插件

npm install vue-awesome-swiper --save

import Vue from 'vue'
import VueAwesomeSwiper from 'vue-awesome-swiper'

// require styles
import 'swiper/dist/css/swiper.css'

Vue.use(VueAwesomeSwiper, /* { default global options } */)

解决图片的宽高比例自适应问题,即图片的自适应,用来占位,防止页面的抖动

.wrapper
	overflow: hidden
    width: 100%
    height: 0
    padding-bottom: 31.25% /*图片的高宽比例*/
	.swiper-img
		width 100%

样式的穿透,使子组件的样式不受scoped的限制

.wrapper >>> .swiper-pagination-bullet-active
        background  #fff !important

使用swiper解决多页小图标轮播

computed: {
    pages () {
        const pages = []
        this.iconList.forEach((item, index) => {
            const page = Math.floor(index / 9)
            if (!pages[page]) {
                pages[page] = []
            }
            pages[page].push(item)
        })
        return pages
    }
}

使用axios进行ajax数据的请求

  • fetch
  • vue-resource
  • axios
  • npm install axios --save

在项目目录static目录下模拟json数据,因为只有static才可以在地址栏中请求到。

  • http://localhost:8082/static/mock/index.json

在.gitignore中写入不需要提交到仓库的文件

  • static/mock
  • 则可以使用axios.get('/static/mock/index.json').then(this.getHomeInfoSucc)请求到数据

请求代理的替换,使用webpack提供的proxyTable解决跨域问题

打开项目根目录文件夹config下的index.js,然后在proxyTable属性中更改

proxyTable: {
    '/api': {
        target: 'http://localhost:8080', //目标接口域名
            changeOrigin: true, //是否跨域
            pathRewrite: {
                '^/api': '/static/mock' //重写接口
            }
    }
}

请求:

axios.get('/api/index.json').then(this.getHomeInfoSucc)

父子组件之间的数据传递

将父组件中请求到的数据传递给所有子组件

移动端文字垂直居中问题

关于移动端文字无法垂直居中(或line-height不起作用)的问题的解决方案

使用better-scroll实现列表滚动

cnpm install better-scroll --save

<div class="wrapper">
  <ul class="content">
    <li>...li>
  ul>
div>
import BScroll from 'better-scroll'
const wrapper = document.querySelector('.wrapper')
const scroll = new BScroll(wrapper)

获取源事件的元素

 handleLetterClick (e) {
     console.log(e.target.innerText)
 }

使用scrollToElement实现滚动

# 同级组件1
handleLetterClick (e) {
    this.$emit('change', e.target.innerText)
}
# 同级组件2
watch: {
    letter () {
        if (this.letter) {
            const element = this.$refs[this.letter][0]
            this.scroll.scrollToElement(element)
        }
    }
}

触摸滑动

<div class="list">
    <ul>
        <li class="item" v-for="(city, key) of cities" :key="key" 
            @touchstart="handleTouchStart"
            @touchmove="handleTouchMove"
            @touchend="handleTouchEnd"
            @click="handleLetterClick"
            >{{key}}</li>
    </ul>
</div>

    data () {
        return {
        	touchStatus: false
        }
     },
    computed: {
        letters () {
            const letters = []
            for (let i in this.cities) {
                letters.push(i)
            }
            return letters
        }
    },
    methods: {
        handleLetterClick (e) {
            this.$emit('change', e.target.innerText)
        },
        handleTouchStart () {
            this.touchStatus = false
        },
        handleTouchMove (e) {
           // 1. 获取A元素距离顶部的距离
           // 2. 获取手指滑动距离顶部的当前距离
           // 3. 相减得到手指滑动到字母的距离
           // 4. 使用在字母上得到的距离/字母的高度=字母下标
            const startY = this.$refs['A'][0].offsetTop
            const touchY = e.touches[0].clientY - 79
            const index = Math.floor((touchY - startY) / 20 )
            if (index >= 0 && index < this.letters.length) {
                this.$emit('change', this.letters[index])
            }
        },
        handleTouchEnd () {
            this.touchStatus = true
        }
    }

列表性能优化

  • 函数节流
if (this.touchStatus) {
    if (this.timer) {
    	clearTimeout(this.timer)
    }
    this.timer =  setTimeout(() => {
           const touchY = e.touches[0].clientY - 79
           const index = Math.floor((touchY - this.startY) / 20 )
           if (index >= 0 && index < this.letters.length) {
               this.$emit('change', this.letters[index])
           }
    }, 16)          
}

搜索功能 实现

watch: {
keyWord () {
    if (this.timer) {
    	clearTimeout(this.timer)
    }
    if (!this.keyWord) {
    	this.list = []
    	return
    }
    this.timer = setTimeout(()  => {
        const result = []
        for (let i in this.cities) {
            this.cities[i].forEach(value => {
                if (value.spell.indexOf(this.keyWord) > -1 || value.name.indexOf(this.keyWord) > -1) {
                	result.push(value)
                }
            })
        }
        this.list = result
        }, 100)
    }
},
mounted () {
this.scroll = new BScroll('.search-content')
}

使用vuex实现数据管理

export default new Vuex.Store({
    state: {
        city: '北京'
    },
    getters: {},
    actions: {
        changeCity(ctx, city) {
            ctx.commit('changeCity', city)
        }
    },
    mutations: {
        changeCity(state, city) {
            state.city = city
        }
    }
})

使用localStorage实现切换不同城市的数据存储,实现城市保存

let defaultCity = '北京'

try {
    if (localStorage.city) {
        defaultCity = localStorage.city
    }
} catch (e) {}

export default new Vuex.Store({
    state: {
        city: defaultCity
    },
    getters: {},
    actions: {
        changeCity(ctx, city) {
            ctx.commit('changeCity', city)
        }
    },
    mutations: {
        changeCity(state, city) {
            state.city = city
                // 保存城市
            try {
                localStorage.city = city
            } catch (e) {}
        }
    }
})

使用keep-alive性能优化

每一次切换路由的时候,对应的ajax就会被执行请求,原因是因为执行了mounted钩子函数,正常情况下只需要获取一次,可以减少性能

keep-alive 包含的组件第一次执行的时候被放到内存中,下一次不需要请求,直接从内存中调用

activated

# 使用keep-alive会将请求的数据缓存到内存中,然后会调用activated钩子函数的执行,当页面改名的时候回执行该钩子函数。
activated () {
    if (this.lastCity !== this.city) {
        this.lastCity = this.city
        this.getHomeInfo()
    }
}

使用router-link或者动态路由实现页面详情



{
    path: '/detail/:id',
    name: 'Detail',
    component: Detail
}

在build目录下的webpack.base.config.js中配置别名

resolve: {
        extensions: ['.js', '.vue', '.json'],
        alias: {
            '@': resolve('src'),
            'styles': resolve('src/assets/styles'),
            'common': resolve('src/common')
        }
    }

使用swiper配置图片的轮播

swiperOption: {
    pagination: {
        el: '.swiper-pagination',
            type: 'fraction'
    },
    observer:true,
    observeParents:true
}

采用监听事件scroll实现header显示隐藏及动画

handleScroll () {
    const top = document.documentElement.scrollTop
    if (top > 60) {
        let opacity = top / 140
        opacity = opacity > 1 ? 1 : opacity
        this.opacityStyle = {
            opacity
        }
        this.showAbs = false
    } else {
        this.showAbs = true
    }

}

使用activated 和deactivated 钩子函数对全局函数的添加和解绑

activated () {
	window.addEventListener('scroll', this.handleScroll)
},
deactivated () {
    // 对全局事件的解绑
    window.removeEventListener('scroll', this.handleScroll)
}

使用递归组件实现多级列表数据展示

{{item.title}}

使用activated 解决对keep-alive缓存问题的解决,让重新请求数据

或者使用以下属性解决

  # 组件名,除了detail组件之外的被缓存
    

每一个组件的name的作用

  • 在做递归组件的时候回用到
  • 在页面上取消缓存时候会用到
  • vue-devtools工具里面的组件名字

解决路由切换过程中滚动问题

# 在路由中添加
scrollBehavior(to, from, savedPosition) {
    return { x: 0, y: 0 }
}

动画的封装和使用

FadeAnimation.vue






Banner.vue


            			

Vue项目的接口联调

# config/index.js
proxyTable: {
    '/api': {
        target: 'http://localhost:80' //api地址可以换成内网的IP或者外网的域名
    }
}

Vue项目的真机测试

# 获取当前电脑的内网IP
`ipconfig/all`

# 修改 package.json 文件(因为webpack的默认服务端口无法访问,只需要改成以下即可)
"dev": "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js"


# 解决低版本安卓机浏览器不支持promise,出现白屏问题
在项目目录下安装 
'npm install babel-polyfill --save'

# 并且在main.js中添加
import 'babel-polyfill'

然后重启之后使用同局域网的IP地址加端口号访问

解决手机上的全屏滑动出现的问题

@touchstart.prevent="handleTouchStart"

Vue项目的打包上线

# 运行
`npm run build`
打包成功后为dist的文件夹

# 一般直接将打包后的文件index.html和static文件夹放到后端项目的根目录下
# 如果想放到指定的目录,则需要在 config/index.js中修改
build: {
    assetsPublicPath: '/project'
}

你可能感兴趣的:(vue)