Vue——王红元
Vue——王红元
本课程又小码哥教育王红元提供
目录
Vuejs知识量化
一. 邂逅Vuejs
二. Vue基础语法
三. 组件化开发
四. Vue Cli详解
五. Vue-Router
六. Vuex详解(状态管理)
七. 网络封装
八. 项目实战
九. 项目部署
十. 原理相关
杂记
ES定义变量
ES6一般不用var,用let(变量)和const(常量)
MVVM
View <> ViewModel(Vue) <> Model
Vue的生命周期
debug/release
Vue源码分析
src > core(核心) > index.js(入口)
../instance > index.js > init.js
Vue实例下定义一些生命周期函数(钩子函数)来查看周期
代码规范
缩进 > 前端开发缩进2个空格更流行
CLI > .editconfig 2个空格
代码编辑器模板
数据结构
数组、栈、堆、链表、树结构
搜资料
其他问题
手机页面显示问题
URL
协议://服务器[:端口]/路径?查询(query)
scheme://host:port/path?query#fragment
基础语法
Vue——Html部分
<div id="app">
<!-- 4种数据绑定----------------------------------------->
<h1>{{ message }}</h1> // 数据绑定 {{}}、v-html、v-text
<input type="text" v-model="message"/> // 双向绑定 v-model
<button v-on:click="handleIncrement"></button> // 函数绑定 v-on:方法、@方法
<h1 v-bind:class="{done: item.done}"></h1> // 属性绑定 v-bind:属性、:属性
<!-- 2种流程语法------------------------------------------->
<li v-if="show">mess</li> // 显示隐藏 v-if、v-else-if、v-else、v-show
<li v-for="item in items">{{item.mess}}</li> // 循环指令 v-for; item in shuzu、item in obj取v
</div>
补充
数据绑定的补充,插值语法
<h1>{{ message }}</h1> // Mustache语法,可运算
<h1 v-html=''></h1> // 编译内容
<h1 v-text=''></h1> // 不编译内容
<h1 v-cloak></h1> // 解析再显 v-cloak 斗篷,解决js卡顿时用户能看到{{}}的问题
<h1 v-once>{{ message }}</h1> // 禁用改变 v-once 只改变一次(不让数据驱动视图)
<h1 v-pre>{{ message }}</h1> // 禁用编译 v-pre 文本显示
双向绑定的补充
多种表单的的绑定
绑定文本框 // 返回内容
绑定单选框 // (多个绑定同一个变量)返回选中值
绑定复选框单选 // 返回布尔
绑定复选框多选 // (多个绑定同一个变量)返回数组
绑定Select单选 // 返回Value
修饰符
v-model.lazy // 懒绑定,让input事件更新数据 转换为 回车/失焦时更新数据
v-model.number // 把内容自动转换为数字
v-model.trim // 删除两侧空格
属性绑定的补充,对象语法和数组语法
对象语 法,后接对象 :class="{active: true, line: false}" :style="{font-size: '50px'}"
数组语法,后接数组 :class=[actice, 'line'] :style="[{}, {}]"
也可以接返回对象/数组的函数 :class="getClasses()"
方法绑定的补充,'()'的省略与修饰符
括号省略
方法无参时 // 可省略'()'
方法有参时 // 不省略'()'时传递undefine,省略'()'时传递浏览器生成的event时间对象
event值 // 也可用'($event)'手动传递,这个值挺有用的:如:event.target.value
修饰符
.stop // 阻止事件冒泡(嵌套事件监听),Ex:@click.stop=''
.prevent // 阻止默认行为(后接函数以外),Ex:@submit.prevent,@click.stop.prevent=''
.(键值/别名) // 监听指定键盘键帽(键别名/键值)Ex:@keyup.enter='onEnter',@keydown.13='onEnter'
.native // (在组件内才会用到)
.once // 只触发一次回调,Ex:@click.once='doThis'
if补充
v-if/v-else-if/v-else(整体删减))、v-show(display隐藏)
Virtual Dom对Dom的复用问题
for补充
v-for; (item, index) in shuzu、(value, key, index) in obj
Virtual Dom对Dom的复用问题。这里建议加key:item
能更好复用。如数组内插入元素后,原来的dom不会修改
Vue——Script部分
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app', // 1. 挂载点
data: { // 2. 对象内容 —— 数据(或方法),组件当中则必须是方法
message: 'Hello Vue.js'
handleIncrement: function(){}
},
computed: { // 3. 对象内容 —— 计算属性
fullName: function () {} // 【与methods区别】这里用变量属性名调用方法,不加'()'
// 【与methods区别】重复调用不会多次计算,节约性能
},
methods: { // 4. 对象内容 —— 方法
handleIncrement2: function(){} // 【与data区别】data函数的this是Window,这里的this是Vue对象
handleIncrement2(){} // ES6简写
}
生命周期函数: {} // 5. Vue生命周期函数,钩子函数,可查看Vue的生命周期/在指定流程中引用。详细看流程图
filters: { // 6. 过滤器方法
show(price){return 1} // 使用时:xxx | show
}
components: { // 7. 局部组件定义(模块化)
标签名: 模块名
}
props: { // 8. 组件属性定义
属性名: {
type: 类型,
default: 默认值,
required: true
}
}
})
</script>
补充
计算属性
计算属性并非函数,实质是个对象,有setter和getter属性
computed: {
fullName: {
set: function (newValue) { // 计算属性一般没有set方法,只读属性。当给fullName赋值时启用get方法
const names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[1]
},
get: function () {
return this.firstName + ' ' +this.lastName
}
}
}
其他补充
创建Vue时,Option能放什么
el、data、computed、methods、生命周期函数
2. 插值语法
mustache语法、v-html、v-text;v-once、v-pre、v-cloak:斗篷
3. v-bind
v-bind:src、:herf、对象语法、数组语法、函数语法
4. 计算属性
组件化
组件化使用
组件使用三个步骤:创建组件构造器、注册组件、使用组件
组件的注册分为:全局组件、局部组件
创建组件构造器【Vue.extend】
const cpnC1 = Vue.extend({
template:`
<div>
<h2>组件标题<h2>
</div>
`
})
注册组件【Vue,component】,并定义组件标签的名称
全局组件注册(可以在多个Vue实例下使用)
Vue.component('cpn1', cpnC1) // 全局组件
局部组件注册(只能在一个Vue实例下使用)
常用,且一般只有一个Vue实例
//const app = new Vue({
// el:'',
components: {
cpn1: cpnC1
}
//})
使用组件
<cpn1></cpn1>
父组件和子组件
嵌套
const cpnC2 = Vue.extend({
template:`
<h1>组件标题<h1>
<cpn1></cpn1> // 内部可用子组件
`,
components: { // 在父组件cpnC2里注册子组件cpnC1
cpn1: cpnC1
}
})
Vue实例也是组件 & el和template共存
Vue实例也可叫 root 组件
事实上 Vue.extend( {}) 和 new Vue({}),即组件构造器和Vue实例之间很像
区别在于前者用"template"进行注册组件,而后者用"el"进行挂载,且后者也可以写"template"的
当后el和template同时存在,会自动把template内容自动替换到el中
好处是不再需要修改index.html的代码
组件化使用的语法糖
全局注册组件
Vue.component('cpn1', {
template:`
<div>
<h2>组件标题<h2>
</div>
`
})
局部注册组件
components: {
cpn1: {
template:`
<div>
<h2>组件标题<h2>
</div>
`
}
}
简单来说就是不用你写Vue.extend()构造器了,把组件构造器名直接替换成内容
组件模板抽离&组件数据分离
方法1
<script type="text/x-template" id="cpn"> // 注意这里的type,id给的js
<div>这是一个模板</div>
</script>
// ......
template: '#cpn' // 这里就很类似于el:'#app'的写法了
方法2 √
<template id="cpn">
<div>这是一个模板</div>
</template>
// ......
template: '#cpn' // 这里就很类似于el:'#app'的写法了
数据抽离
{{title}}
// ......
template: '#cpn',
data() { // 这里的data变成了方法,且需要返回值。匿名函数封包
reutrn { // 为什么要这么设计:避免这些组件共享data!!!
title: 'abc' // 每次回会返回一个新的对象
}
methods: {}
}
// ——————————————————————
// 如果想相互影响:
const obj = {count:0}
data() {return: obj}
父子组件的通信 (Parent-Child)
父组件传递数据给子组件:props(properties),自定义属性
注意:
v-bind中不支持驼峰命名!props绑定的属性名若是驼峰命名
如cInfo,那么在绑定时应该写为:c-info!
脚手架可以写驼峰
<div id="app">
<cpn :cmessage="message"></cpn> // 信息传递
</div>
<template id="cpn">
<div>
{{message}}
</div>
</template>
// ......
const cpn = {
template: '#cpn'
props: ['cmessage'], // 定义组件属性-方式1
props: { // 定义组件属性-方式2,可指定类型
cmessage: String,
}
props: { // 定义组件属性-方式3,可指定默认值
cmessage: {
type: String,
default: 'aaa' // 注意!当类型是对象或数组时,默认值必须是函数
required: true
}
}
props: { // 定义组件属性-方式4,自定义验证函数
validator: function (value) {
return ['a','b','c'].indexOf(value) !== -1
}
}
// 属性:Number/Boolean/Array/Object/Data/Function/Symbol
data() {
return {
}
},
methods: {}
}
const app = new({
el: '#app',
data: {
message: 'hi',
}
componts: {
cpn
}
})
子组件传递数据给子组件:$emit Event,自定义事件
注意:
v-on中不支持驼峰命名!$emit发送的时间名若是驼峰命名
如cClick,那么在绑定时应该写为:c-click!
脚手架可以写驼峰
<div id="app">
<cpn v-on:itemclick="cpnClick"></cpn> // 父组件监听自定义子组件的事件
</div>
<template id="cpn">
<div>
<button @click="btnClick(1)">{{message}}</button>
</div>
</template>
// ......
const cpn = {
template: '#cpn',
data() {
return {
message: 'Hi'
}
},
methods: {
btnClick(item) { // 目的:吧item传给父组件
this.$emit('itemclick', item) // 向父组件发送事件,第二参取代event参
}
}
}
const app = new({
el: '#app',
data: {
message: 'hi',
},
componts: {
cpn
},
methods: {
cpnClick(item) {
console.log('cpnClick', item) // 父组件接收到子组件传来的信息
}
}
})
互通信案例
一般方法:
不直接v-model绑定props里的值! 单向绑定data并$emit自定义事件
data () {
return {
数据属性: this.props属性
}
}
Watch方法 & v-model:
组件实例属性
<template>
<input type="text" v-model="dnum">
</template>
// ......
<script>
export default {
template: {/**/},
props:{
num:Number // 自定义组件属性num,从父组件拿数据
},
data() {
return {
dnum: this.num // 把组件属性的值给组件数据dnum
}
},
watch: { // 监听组件数据的改变
dnum(newValue) {
this.$emit('num_change', newValue);
} // 自定义组件事件,传递给父组件
}
}
</script>
父组件访问子组件:$children,$refs(reference引用)
$children,返回一个数组。还可以拿到所有的组件
$refs,返回一个对象。更常用,定位更准确
<div id="app">
<cpn ref="aaa"></cpn> // ref标签
<button @click="btnClick">父组件按钮</button>
</div>
<template id="cpn">
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el:"app",
data: {},
method: {
btnClick() {
this.$children[0].showMessage() // 父组件调用第一个子组件的方法
console.log(this.$children[0].name) // 父组件调用第一个子组件的数据
console.log(this.$refs.aaa.name) // 父组件调用ref标签为aaa的组件
}
},
components: {
cpn: {
template: '#cpn',
data (){
return {
name: "子组件内的数据"
}
},
methods: {
showMessage(){
console.log('子组件内的方法')
}
},
}
}
})
</script>
子组件访问父组件:$parent,$root
**【不建议使用】**子组件访问父组件的话复用性很差!
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<button @click="btnClick">子组件按钮</button>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el:"app",
data: {},
components: {
cpn: {
template: '#cpn',
methods: {
btnClick() {
console.log(this.$parent);// 访问父组件
console.log(this.$root); // 访问根组件
}
},
}
}
})
</script>
插槽slot
slot目的:让组件具有更强的扩展性
简单来说就是给组件一个预留的空间
插槽
注意1:若插槽内有多个内容,则内容全部替换掉原插槽
注意2:若组件有多个插槽,则替换内容替换全部插槽
<div id="app">
<cpn>
<button>插入到插槽里的按钮</button>
</cpn>
</div>
<template id="cpn">
<h2>子组件相同的部分</h2>
<slot> // 插槽
<button>无内容时插槽里的按钮</button>
</solt>
</template>
<script src="../js/vue .js"></script>
<script>
const app = new Vue({
el:"app",
data: {},
components: {
cpn: {
template: '#cpn',
}
}
})
</script>
具名插槽
<div id="app">
<cpn>
<span slot="center">新中间</span>
</cpn>
</div>
<template id="cpn">
<slot name='left'>左侧</solt>
<slot name='center'>中间</solt>
<slot name='right'>右侧</solt>
</template>
<script src="../js/vue .js"></script>
<script>
const app = new Vue({
el:"app",
data: {},
components: {
cpn: {
template: '#cpn',
}
}
})
</script>
编译作用域与作用域插槽(伪 · 父用子)
编译作用域:使用的变量会在你所在的组件里查找
作用域插槽:父组件替换插槽标签,但内容由子组件提供吗
<div id="app">
<cpn>
<span v-for="i in items">{{i}}</span> // 拿不到子组件的
<span v-for="i in items"> // 方案
<template slot-scope="slot"> // 新版本可把换为div等标签
<span v-for="i in slot.data1">{{i}}</span>
</template>
</span>
</cpn>
</div>
<template id="cpn">
<slot :data1="items"> // 作用域插槽传递
<li v-for="i in items">{{i}}</li>
</solt>
</template>
<script src="../js/vue .js"></script>
<script>
const app = new Vue({
el:"app",
data: {},
components: {
cpn: {
template: '#cpn',
data() {
return {
item: ['1','2','3']
}
}
}
}
})
</script>
Demo
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el:"app",
data: {},
components: {
cpn: {
template: '#cpn',
methods: {},
props: {}
}
}
})
</script>
模块化开发(webpack,一堆配置)
Webpack(依赖于node环境)
描述
- 是一个现代的JavaScript应用的【静态模块打包】工具
- 静态模块打包工具:frunt/gulp/webpack,前两者对模块化支持不强
- 可以用CommonJs写,然后打包成ES5给浏览器执行
打包
- scss/less->css, jsx/.vue->js
- ES6->ES5, TypeSctipt->JavaScript
- 图片压缩
流程
- 开发 --> webpack模块化打包成项目 --> 放到服务器 --> 客户端
安装(node -v 大于8.9)
- npm install -g webpack@3.6.0
- 【注意】vue-cli2 依赖 webpack3.6.0
其他打包工具
- grunt, gulp
- 核心是Task,被称为前端自动化任务管理工具
- 更加强调前端流程自动化
- Webpack
- 更强大,支持模块化管理
- 更强调模块化开发管理
项目目录
src // source code file
main.js
mathUtils.js
index.html // 最终放置......
dist // distribution 发布
package.json
webpack.config.js
index.html // 开发时放置......
终端打包
终端打包
webpack配置实现智能打包
webpack.config.js文件
const path = require('path')
module.exports = {
entry: './src/main.js', // 入口
output: { // 出口
path: path.resolve(__dirname, 'dist'), // 要绝对路径。为便于移植,建议动态获取路径
filename: 'bundle.js'
},
}
Webpack配置loader
loader(CSS)
- 作用
- 给webpack扩展,使其能转换文件
- 安装
- 有很多不同版本的loader,建议直接去官网查
- 使用
npm install --save-dev css-loader // 解析css文件后,用import加载并返回css代码
npm install --save-dev style-loader // 将模块的导出作为样式添加到DOM里
// ————————入口文件main.js
require('./css/normal.css') // 依赖css文件
// ————————webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'] // 这里有两个loader,且从右向左读
}
]
}
}
- less样式格式的同理
- 上官网查、需要:
- use: ['style-loader', 'css-loader', 'less-loader'] // 从右往左用
loader(图片)
图片文件的处理
需要url-loader、file-loader
- 当加载图片小于limit时:会把图片格式编译为base64字符串
- base64编码的容量稍大了,但减少了请求次数
- 当加载图片大于limit时:需要file-loader加载,file-loader不需要配置
- 这种方式会把图片打包进dist里,并生成了一个32位Hash名
// ————————normal.css
body {
background: url("../img/test.jpg")
}
// ————————webpack.config.js
rules: [
{/**/},
{/**/},
{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 8192 // 文件大小分割线
}
}
}
]
图片路径问题
开发阶段时,默认情况引用的是外部的.jpg而非/dist下的,要配置publicPath
发布阶段时,由于index.html在dist中,则不需要该配置
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: 'dist/' // 开发阶段解决路径问题,在url前加上这个
},
module: {
rules: [
]
}
}
图片名称问题
有时图片名有要求时,自动生成的32位图片Hash能防止重复,但可能很乱
rules: [
{/**/},
{/**/},
{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 8192,
name: 'img/[name].[hash:8].[ext]' // 自定义转换的图片名
}
}
}
]
loader(ES5-ES6)
babel-loader
// ———————— 终端
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
或
npm install babel-loader babel-ceore babel-preset-env webpack
// ———————— webpack.config.js
rules:[
{...},
{...},
{...},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/, // 排除文件夹
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
loader(.Vue)
vue-loader、vue-template-compiler
// ———————— 终端
npm install vue-loader vue-template-compiler --save-dev
// ———————— webpack.config.js
rules:[
{...},
{...},
{...},
{...},
{
test: /\.vue$/,
use: {
use: ['vue-loader']
}
}
]
报错问题
报错:使用vue-loader的v14版本以后,需要一个插件
临时解决方案:换低版本
webpack配置Vue
三种Vue安装方式
- 下载到本地
- CDN引入
- npm安装(Vue就是模块)
- npm install vue --save // 运行时依赖
报错 runtime-only
原因:Vue的两个不同版本
runtime-only // 该版本不可以有任何的template(包括Vue实例)
runtime-compiler // 可以有template
解决方案:配置,指定runtime-compiler
module.exports = {
entry: './src/main.js',
output: {...},
module: {rules: [...]},
resolve: { // 一般用来解决路径问题
extensions: ['.js', '.css', '.vue'],// (可选)这个配置可以引用时省略后缀名
alias: { // 别名
'vue$': 'vue/dist/vue.esm.js' // 包括compiler
}
}
}
Vue最终方案
目录结构
- dist
- node_modules
- src
- css
- img
- js
- vue
- app.js
- App.vue
- main.js
- index.html
- package.json
- package-lock.json
- webpack.config.js
代码
// ———————— ./index.html(不再需要修改)
<div id="app"></div>
// ———————— ./src/vue/App.vue(Vue组件文件)
<template>
<div>
<h2>{{message}}</h2>
<button @click="btnClick">按钮</button>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
message: 'HelloWorld',
}
},
methods: {
btnClick() {}
}
}
</script>
<style scoped>
.title {
color: green;
}
</style>
// ———————— ./src/vue/app.js(负责组件里面的所有东西)
export default {
}
// ———————— ./src/main.js
import Vue from 'vue'
import App from './vue/App.vue'
new Vue({
el: '#app',
template: '<App/>',
componts: {
App
}
})
Plugin——webpack插件
框架和插件
插件plugin > 对现有的架构进行扩展
loader和plugin区别
loader,主要用于转换某些类型的模块,是转换器
plugin,对webpack本身的扩展,是一个扩展器
其他插件:webpack.BannerPlugin
plugins: [
new webpack.BannerPlugin('最终版权归xxx所有') // 横幅插件,webpath自带
],
其他插件:html-webpack-plugin
更具html模板自动生成dist下的html代码,而且会帮引用bundle.js
// npm install html-webpack-plugin --save-dev
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins: [ // 注意:这里的webpack版本要3.2.0不然报错
new HtmlWebpackPlugin({
template: 'index.html'
})
],
其他插件:uglifyjs-webpack-plugin
丑化压缩插件,把空格和换行删掉
// npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
plugins: [
new UglifyjsWebpackPlugin()
],
其他插件:webpack-dev-server
提供一个可选的本地开发服务器,且可以实现浏览器自动刷新显示修改后的结果!而不用重新打包!
// npm install webpack-dev-server@2.9.3 --save-dev
plugins: [...],
devServer: {
contentBase: './dist',
inline: true
}
// ./node_modules/.bin/webpack-dev-server 或:
"scripts": {
"dev": "webpack-dev-server --open" // --open可选,能自动打开浏览器
},
webpack配置文件的分离
配置分离
build文件夹,存放配置
base.config.js,复制原来的webpack.config.js,但只放公共的东西
prod.config.js,放产品的东西,如uglifyjs-webpack-plugin的配置
dev.config.js,刚开发时的东西,如webpack-dev-serverd的配置
配置文件合并
// npm install webpack-merge --save-dev
// ———————— 如 prod.config.js,同理修改dev.config.js
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
const webpackMerge = require('webpack-merge')
const baseConfig = require('./base.config')
module.exports = webpackMerge(baseConfig, {
plugins: [
new uglifyjsWebpackPlugin()
]
})
// ———————— package.json // 改
"scripts": {
"build": "webpack --config ./build/prod.config.js"
"dev": "webpack-dev-server --open --config ./build/dev.config.js"
}
// ———————— base.config.js // 改
path: path.resolve(__dirname, '../dist')