new Promise()
MVC、MVVM
跨域、ajax
v-model原理
v-model的另一种实现方式
<!-- 第一种写法 --> <input type="text" :value="num" @input="num = $event.target.value" > <!-- 第二种写法 --> <input type="text" :value="num" @input="numInput" > <script> const app = new Vue({ el: "#app", components: { cpn: { template: "#cpn" } }, methods: { numInput(event) { this.num = event.target.value; } } }) </script>
vue中为什么data()是一个函数,这里用到的js中的闭包的原理,使每个组件的data都有自己的私有作用域,使每个组件的变量数据互不干扰互不影响。闭包的另一个作用是延长变量的生命周期。
1、push()在数组尾部添加
2、pop()在数组尾部删除
3、shift()在数组头部删除
4、unshift()在数组头部添加
5、splice()可以在数组任意位置删除添加
6、sort()数组排序
7、reverse()数组反转
上述7个函数,修改数组,在Vue中都是响应式的。
但是通过数组的索引值修改数组,不是响应式的。
this.arr[0] = “abc”;
Vue.set()方法是Vue中提供的方法,也可以做到响应式。
Vue.set(要修改的数组,数组索引值,修改后的值)
const nums = [10, 20, 40, 900, 190]; // $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ // filter 筛选数组中小于100的值 let newNums = nums.filter(function(n){ // 回调函数回调次数和数组长度有关 每次回调都是把数组的某个值给n return n < 100; // return 为true时会返回当前n的值,为false则过滤掉 }) // map 小于100的值再乘以2 let new2Nums = newNums.map(function(n){ // 回调函数回调次数和数组长度有关 每次回调都是把数组的某个值给n return n * 2; // return 的值进行某些操作,如加减程序,再返回 }) // reduce 对数组中的所有内容进行汇总 let total = new2Nums.reduce(function(preValue, n){ // 回调函数回调次数和数组长度有关 每次回调都是把数组的某个值给n return preValue + n; // return 返回的值会再次赋给回调函数中的preValue参数 }, 0) // 这里的参数0,是初始化preValue的值 // 第一次:preValue = 0; n = 20; return 20; // 第二次:preValue = 20; n = 40; return 60; // 第三次:preValue = 60; n = 80; return 140; // 三个函数结合使用 $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ let total = nums.filter(function(n){ return n < 100; }).map(function(n){ return n * 2; }).reduce(function(preValue, n){ return preValue + n; }, 0) // 箭头函数 $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ let total = nums.filter(n => n < 100).map(n => n * 2).reduce((preValue, n) => preValue + n)
input标签互斥,通过name=“sex”属性进行设置
<input type="radio" name="sex" >男 <input type="radio" name="sex" >女
v-on:click.stop阻值事件冒泡
v-on:click.stop
v-on:click.prevent阻值默认事件,如form input自动提交事件
v-on:click.prevent
v-on:keyup.enter监听键盘回车键松开的事件
v-on:keyup.enter
v-on:click.native监听组件根元素的原生事件
v-on:click.native
v-on.once只触发一次
v-on.once
v-model.lazyinput框的双向绑定,只在回车或者失去焦点后才进行双向数据响应
v-model.lazy
v-model.numberinput虽然type=“number”但是依然会转换为string类型,此修饰符将把input中的值string类型转化为number类型
v-model.number
v-model.trim自动清除值两边的空格
v-model.trim
<!-- 父组件模板 --> <div id="app"> <cpn v-bind:cmovies="movies"></cpn> <!-- 3.使用组件 --> </div> <!-- 子组件模板 --> <template id="cpn"> <div> <p>子组件</p> <h2>{{cmovies}}</h2> </div> </template> <script> // 子组件 const cpn = Vue.extend({ // 1.创建组件构造器 也可直接 const cpn = {template: '#cpn'} template: '#cpn', data() { return {} }), // props: ['cmovies'] // 此方法为简洁的数组写法 // props: {cmovies: Array} // 此方法为限制传入参数类型的对象写法 // props: {cmovies: [Array, String]} // 参数有多个可能的类型,可以这样写 props: { // 此方法有传入参数类型、默认值、此参数是否必须传入 cmovies: { type:Array, // default: [], // 如果此参数为对象Object/数组Array时,默认值必须为一个函数return出对象或者数组,此方式写法错误,以下写法正确 default() { return [] }, required: true } } }) // 父组件root const app = new Vue({ el: "#app", data: { movies: ["海王", "海贼王", "海尔兄弟"] }, conponents: { // 2.注册组件 // 'cpn': cpn cpn // 对象自变量属性的增强写法 } }) </script>
<!-- 父组件模板 --> <div id="app"> <cpn @item-click="cpnClick"></cpn> </div> <!-- 子组件模板 --> <template id="cpn"> <div> <button v-for="item in categories" @click="btnClick(item)">{{item.name}}</button> </div> </template> <script> // 子组件 const cpn = { template: '#cpn', data() { return { categories: [ {id: 'a', name: '热门推荐'}, {id: 'b', name: '手机数码'}, {id: 'c', name: '家用家电'}, {id: 'd', name: '电脑办公'} ] } }, methods: { btnClick(item) { // 自定义事件 this.$emit('item-click', item) } } } // 父组件root const app = new Vue({ el: "#app", data: {}, methods: { cpnClick(item) { console.log(item); } }, conponents: { // 2.注册组件 cpn } }) </script>
<script> props: { number1: number }, data() { return { num1: this.number1; } }, watch:{ // 可以监听props、data中的变量 number1(newValue, oldValue) { console.log("新的值:" + newValue + "; 旧的值" + oldValue); } } </script>
<div id="app"> <cpn ref="a"></cpn> <cpn ref="b"></cpn> </div> <script> data: { }, methods: { bthClick() { console.log(this.$children[0].name, this.$children[0].showMessage()); // $children 此方法不常用 console.log(this.$refs.a.name, this.$refs.a.showMessage()); // $refs 此方法常用 } }, components: { cpn: { template: "#cpn", data() { return { name: "我是子组件的name" }, methods: { showMessage() { console.log("showMessage"); } } } } } </script>
类似的操作 this.$parent.name
this.$parent.name
不管是在子组件还是子子组件,通过this.$root都会直接访问Vue实例
this.$root
使组件具有扩展性
<div id="app"> <cpn><button>按钮</button></cpn> <cpn><span>span标签</span></cpn> <cpn><i>i标签</i></cpn> </div> <template id="cpn"> <div> <h1>这是一个组件</h1> <slot><button>按钮</button></slot> <!-- 此时的button标签就是slot插槽的默认值 --> </div> </template>
组件中存在多个插槽时,需要精准操作某个插槽,可以给组件中的插槽设置独立的name=”center”属性,在使用组件时,给内容标签设置slot=”center”属性即可
<div id="app"> <cpn><span slot="center">中间内容</span></cpn> </div> <template id="cpn"> <div> <slot name="left"><span>左边</span></slot> <slot name="center"><span>中间</span></slot> <slot name="right"><span>右边</span></slot> </div> </template>
父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
<div id="app"> <cpn> <template slot-scope="slot"> <span v-for="item in slot.data">{{item}}</span> </template> </cpn> </div> <template id="cpn"> <div> <slot :data="pLanguages"> <ul> <li v-for="item in pLanguages">{{item}}</li> </ul> </slot> </div> </template> <script> const app = new Vue({ el: "#app", data: {}, components: { cpn: { template: "#cpn", data() { return { pLanguages: ["Java", "C", "PHP"] } } } } }) </script>
ES5的模块化只能借助闭包进行模块化开发,借助函数存在自己的作用域
将想要用到的参数return出去
A.js
; var moduleA = (function () { var obj = {} var name = "A" var age = 20 obj.name = name; obj.age = age; return obj })()
B.js
; var moduleB = (function () { var obj = {} var name = "B" var age = 22 var aaaa = moduleA.name // 在这里使用moduleA中的name参数 obj.name = name; obj.age = age; return obj })()
CommonJS
AMD
CMD
ES6的Modules
其中AMD、CMD并不常用,在nodeJS中CommonJS比较常用,ES6中的Modules也比较常用
CommonJS依赖于nodeJS
a.js
var flag = true var name = "a" module.exports = { // 导出 flag: flag, sum: sum }
b.js
var {flag, name} = require('./a.js') // 导入 // 另一种写法 var b = require('./a.js') var flag = b.flag var name = b.name
export导出
import导入
index.html
<script src="a.js" type="module"></script> <!-- type="module" --> <script src="b.js" type="module"></script>
// 1、先定义变量后导出 var flag = true var num = function (n1, n2) { return n1 + n2 } export {flag, num} // 导出 // 2、定义且导出 export let sum = 1000; export function mul(num1, num2) { return num1 + num2; } export class Person { run() { console.log("这是Person类中的run函数"); } } // 3、export default const address = "河南省" export default address // 默认导出只能导出一个参数
// 1、 import {flag, num} from "./a.js" // 导入 console.log(flag); console.log(num(1, 2)); // 2、 import {sum, mul, Person} from "./a.js" const p = new Person(); p.run(); // 3、 import addr from "./a.js" // 通过导入默认导出的参数,可以自定义导入的名称 console.log(addr); // 河南省 // 4、全部导入 import * as aaa from "./a.js" console.log(aaa.flag);
详情见博客heri.ganto.cn
webpack和gulp对比
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。
webpack官网
利用node中的软件包管理工具npm安装webpack
先安装好node node -v查看是否安装成功,再安装webpack
node -v
npm install webpack@3.6.0 -g
安装好webpack,webpack --version查看webpack版本号,检查是否安装成功
webpack --version
创建项目,以下结构
webpack ├── dist | └── bundle.js ├── src | ├──css | | ├── a.css | | └── s.less | ├── js | | └── a.js | └── main.js └── index.html
dist目录存放最终打包的文件src目录为项目开发文件夹
function add (num1, num2) { console.log(num1+num2); } function mul (num1, num2) { console.log(num1*num2); } // CommonJs模块化导出 module.exports = { add,mul } // ES6模块化导出 export let name = "Hyd";
main.js
// 使用CommonJs的模块化规范导入 const {add, mul} = require('./js/a.js'); add(2, 2); mul(3, 4); // 使用ES6模块化规范导入 import {name} from "./js/a.js";
利用webpack将main.js打包到bundle.js,因为mian.js依赖a.js,所以只需要打包main.js也可打包a.js
bundle.js
mian.js
webpack ./src/main.js ./dist/bundle.js
打包完成后,只需要在index.html中这样引用bundle.js即可
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script src="dist/bundle.js"></script> </body> </html>
以上运用了CommonJs模块化开发和ES6模块化开发
如果每次打包都需要声明被打包文件和打包文件,这样确实不够优雅,那当然可以更优雅一点需要配置一个webpack.confing.js文件
webpack.confing.js
webpack ├── dist ├── src ├── index.html └── webpack.config.js
webpack.config.js
const path = require('path'); module.export = { entry: './src/main.js', output: { path: path.resolve(__dirname, 'dist'), // 这里需要动态获取路径,需要用node实现 filename: 'bundle.js' } }
const path = require('path') 为导入node中的一个包,此path包可以处理绝对路径问题
const path = require('path')
如果需要用到node,首先需要初始化,初始化需要设置下边的关键参数,其他的非关键参数直接默认回车即可
npm init // ------------ package name: meetwebpack entry point: index.js
这样会生成package.json配置文件
package.json
这样就不要使用webpack ./src/main.js ./dist/bundle.js打包项目了,可以直接使用webpack如果打包的命令需要很多时,这样的命令依然会很长,依然不够优雅,那么就可以在配置文件package.json文件中这样配置
webpack
"scripts": { "build": "webpack" }
可以这样运行命令
npm run build
这样就优雅很多了,并且npm run build这样打包,会优先使用局部webpack环境,而直接使用webpack命令,会优先使用全局webpack环境,在真实开发中,优先使用开发环境打包,即局部webpack环境。可以这样安装局部webpack环境
npm install webpack@3.6.0 --save-dev
--save-dev即开发时依赖这样package.json会有这样的配置
--save-dev
"devDependencies": { "webpack": "^3.6.0" }
npm安装打包css、less所需要的依赖
npm install style-loader@0.23.1 --save-dev npm install css-loader@2.0.2 --save-dev npm install less-loader@4.1.0 less --save-dev
webpack.config.js这样配置
module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }, module: { rules: [ { test: /\.css$/, // 安装以下版本不会报错 其他版本打包会报错,不知道为啥 /** * npm install style-loader@0.23.1 --save-dev * npm install css-loader@2.0.2 --save-dev * npm install less-loader@4.1.0 less --save-dev * */ use: [ 'style-loader', 'css-loader' ] }, { test: /\.less$/, use: [{ loader: "style-loader" // creates style nodes from JS strings }, { loader: "css-loader" // translates CSS into CommonJS }, { loader: "less-loader" // compiles Less to CSS }] } ] } }
css、less样式文件这样导入
// 导入依赖css文件 require('./css/a.css'); // 导入依赖less文件 require('./css/s.less');
这样就可以打包css和less文件了
那么图片文件可以正常打包吗,当然还需要安装其他的依赖包
npm install url-loader --save-dev
然后这样配置webpack.config.js
module.exports = { module: { rules: [ { test: /\.(png|jpg|gif|jpeg)$/, use: [ { loader: 'url-loader', options: { limit: 13000 // 默认8192 设置的值要大于 图片大小11k乘以1024 } } ] } ] } }
当加载的图片,小于limit时,会将图片编译成base64字符串形式当加载的图片,大于limit时,要使用file-loader进行加载
limit
base64
file-loader
第一种情况base64不需要东西存储第二种情况需要把图片存储在某个文件夹下,路径要找对
还是报错,修改了limit大小,还是一样的报错,但是安装了file-loader之后就好了
npm install --save-dev file-loader
安装好后,并不需要配置,然后就可以打包图片文件了
看老师操作的,是只需要配置url-loader,但是要修改limit值大于图片的大小,就可以不需要安装file-loader就可以打包了(但是,我不行)然后如果图片大与limit的值的话,需要安装file-loader,打包的话就可以把图片转化为另一个图片保存在dist目录下,但是网页加载时,路径错误,需要在webpack.config.js这样配置一下路径就可以 output: { publicPath: 'dist/' } 但是,我的情况是,只安装url-loader设置limit的值大于图片的值依然报错,其他不动的情况下,安装好file-loader就可以打包了,然后图片大于limit的值依然报错,并且全程不会生成另一张图片保存在dist中
看老师操作的,是只需要配置url-loader,但是要修改limit值大于图片的大小,就可以不需要安装file-loader就可以打包了(但是,我不行)然后如果图片大与limit的值的话,需要安装file-loader,打包的话就可以把图片转化为另一个图片保存在dist目录下,但是网页加载时,路径错误,需要在webpack.config.js这样配置一下路径就可以
output: { publicPath: 'dist/' }
但是,我的情况是,只安装url-loader设置limit的值大于图片的值依然报错,其他不动的情况下,安装好file-loader就可以打包了,然后图片大于limit的值依然报错,并且全程不会生成另一张图片保存在dist中
打包url这一处,有点玄学…
为了打包的图片在dist目录下好管理,可以这样配置webpack.config.js
module.exports = { module: { rules: [ { test: /\.(png|jpg|gif|jpeg)$/, use: [ { loader: 'url-loader', options: { limit: 8192, name: 'img/[name].[hash:8].[ext]' } } ] } ] } }
这里的name的img/[name].[hash:8].[ext]参数的意思是在dist中创建img文件夹,然后图片命名是[name]原来的名字,[hash:8]hash值,只要8位,[ext]原来的后缀如果[name]为name则表示,图片名就为name.png,如果为name/则表示img下再创建name文件夹
img/[name].[hash:8].[ext]
安装
npm install babel-loader@7 babel-core babel-preset-es2015 --save-dev
module: { rules: [ { test: /\.js$/, // exclude: 排除 // include: 包含 exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { // presets: ['@babel/preset-env'] // 默认 presets: ['es2015'] } } } ] }
再次打包就会发现,bundle.js中再也没有ES6语法
npm安装vue
npm install vue --save
配置webpack.config.js
module.exports = { resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' } } }
创建如下结构
webpack ├── dist ├── src | ├──css | ├──img | ├──js | └──vue | └──App.vue ├── index.html └── webpack.config.js
使用Vuemain.js
import Vue from 'vue' import App form './vue/App.vue' new Vue({ el: '#app', template: '<App/>', components: { App } })
当Vue实例中既有el: ‘#app’ 又有template: ‘’时,<div id="app"></div>会被template的内容替换掉
<div id="app"></div>
App.vue
<template> <div> <h2>{{message}}</h2> <button @click="bthClick">按钮</button> <h2>{{name}}</h2> </div> </template> <script> export default { name: "App", data() { return { message: 'Hello Webpack', name: 'coderwhy' } }, methods: { btnClick() { alert(1); } } } </script> <style scoped> .title{ color: green; } </style>
打包vue文件,也需要安装相应的loader
npm install vue-loader vue-template-compiler --save-dev
module.exports = { module: { rules: [ { test: /\.vue$/, use: ['vue-loader'] } ] } }
如果现在打包出现报错 corresponding plugin.,大概率是vue-loader的版本过高,在package.json中修改”vue-loader”: “^13.0.0”, 然后执行npm install 重新安装修改vue-loader为低版本即可
corresponding plugin.
在引用vue模块化文件时,如果想省略.vue后缀,需要这样配置webpack.config.js
module.exports = { resolve: { extensions: ['.js', '.css', '.vue'] } }
在webpack.config.js文件中
const webpack = require('webpack') module.exports = { plugins: [ new webpack.BannerPlugin('最终版权归我所有!!!') ] }
此插件的作用就是在dist文件夹中自动生成index.html文件
dist
npm install html-webpack-plugin --save-dev
const HtmlWebpackPlugin = require('html-webpac-plugin') module.exports = { plugins: [ new webpack.BannerPlugin('最终版权归我所有!!!'), new HtmlWebpackPlugin() ] }
这时成功在dist文件夹内生成index.html文件,但是有一些问题需要解决:
publicPath: 'dist/'
module.exports = { plugins: [ new webpack.BannerPlugin('最终版权归我所有!!!'), new HtmlWebpackPlugin({ template: 'index.html' }) ] }
这样的目的,是让生成的index.html文件可以根据webpack.config.js同目录下的index.html文件进行生成。
npm install uglifyjs-wepack-plugin@1.1.1 --save-dev
const uglifyJsPlugin = require('uglifyjs-webpack-plugin') module.exports = { plugins: [ new webpack.BannerPlugin('最终版权归我所有!!!'), new HtmlWebpackPlugin({ template: 'index.html' }), new uglifyJsPlugin() ] }
基于node.js搭建,内部使用express框架
npm install webpack-dev-server@2.9.3 --save-dev
module.exports = { devServer: { contentBase: './dist', inline: true } }
在package.json文件中配置脚本
因为直接执行命令会通过全局进行执行
只有配置了脚本才会通过刚才安装的局部命令进行执行
{ "scripts": { "dev": "webpack-dev-server" // "dev": "webpack-dev-server --open" // 加上--open,再执行npm run dev会自动打开网页 } }
在终端这样开启服务
npm run dev
开启的本地服务器只是开发的时候更加方便,开发完成后仍然需要手动打包
在webpack.config.js中的配置中,有的配置是在开发的时候才需要,如开启本地服务,而有些配置是在最终打包的时候才需要配置,如js压缩,因为压缩后的js不利于开发调试。
可以这样分离不同阶段的webpack.config.js配置新建一个配置文件base.config.js
webpack ├── dist ├── build | ├──base.config.js 公共的 | ├──dev.config.js 开发时 | └──prod.config.js 生产时 ├── src | ├──css | ├──img | ├──js | └──vue ├── index.html └── webpack.config.js
// dev.config.js module.exports = { devServer: { contentBase: './dist', inline: true } }
// prod.config.js const uglifyJsPlugin = require('uglifyjs-webpack-plugin') module.exports = { plugins: [ new uglifyJsPlugin() ] }
其余公共部分放到base.config.js文件中
base.config.js
在开发时需要合并base.config.js和dev.config.js在最终打包时需要合并base.config.js和prod.config.js
dev.config.js
prod.config.js
这样就用到另一个工具,用来合并配置文件
npm install webpack-merge
这样配置
// dev.config.js const webpackMerge = require('webpack-merge') const baseConfig = require('./base.config') // 省略.js了 module.exports = webpackMerge(baseConfig, { devServer: [ contentBase: './dist', inline: true ] })
// prod.config.js const uglifyJsPlugin = require('uglifyjs-webpack-plugin') const webpackMerge = require('webpack-merge') const baseConfig = require('./base.config') // 省略.js了 module.exports = webpackMerge(baseConfig, { plugins: [ new uglifyJsPlugin() ] })
此时webpack.config.js可以删除了
然后需要指定默认的配置文件在package.json文件中配置
{ "scripts": { "build": "webpack --config ./build/prod.config.js", "dev": "webpack-dev-server --open --config ./build/dev.config.js" } }
配置好后,再进行打包,发现打包的dist文件夹在build文件夹内,需要这样修改base.config.js配置
module.export = { output: { path: path.resolve(__dirname, '../dist'), filename: 'bundle.js' } }
P90
全局安装,这种方法安装的是Vue CLI3的版本
npm install @vue/cli -g
vue --version查看脚手架版本
vue --version
拉取2.x模板
npm install @vue/cli-init -g
Vue CLI2初始化项目
Vue CLI2是根据webpack3打造的
vue init webpack my-project
Vue CLI3初始化项目
Vue CLI3是根据webpack4打造的
vue create my-project
Vue CLI3的可以到这里修改配置文件
在哪个文件夹下都可以执行此命令
vue ui
如果想要添加一些配置信息,可以在项目根目录下与src、public、node_modules等同目录下,创建vue.config.js配置文件,名字必须要为vue.config.js
vue.config.js
module.exports = { }
Vue CLI3中的配置文件被隐藏到了node_modules -> @vue -> cli-service -> webpack.config.js和node_modules -> @vue -> cli-service -> lib -> Service.js等
node_modules -> @vue -> cli-service -> webpack.config.js
node_modules -> @vue -> cli-service -> lib -> Service.js
C:\Users\ganto\AppData\Roaming\npm-cache
在config\index.js里关闭代码规范插件useEslint: false
config\index.js
useEslint: false
单词render渲染 parse解析
render渲染 parse解析
runtime-compiler
template -> ast -> render -> vdom -> UI
runtime-only
render -> vdom -> UI
runtime-only 更轻,性能更高 日后只用runtime-only
render函数
const cpn = { template: `<div>{{m}}</div>`, data() { return { m: '哈哈哈,我是cpn组件的m参数' } } } new Vue({ el: '#app', render: function(createElement) { // 1、普通用法 createElement('标签名', {标签属性}, ['标签内容']) // 可以套娃 return createElement('h2', {class: 'app'}, ['我是h2标签,哈哈哈', createElement('button', ['按钮'])]) // 2、传入组件用法 createElement(组件) return createElement(cpn) } // render: h => h(app) // 同上第2种用法 ES6的箭头函数 })
const num = () => { console.log('num') } const num1 = (a) => { return a } const num2 = a => a
箭头函数中的this
setTimeout(function() { console.log(this); // window }) setTimeout(() => { console.log(this); // window }) const obj = { a() { setTimeout(function() { console.log(this); // window }) setTimeout(() => { console.log(this); // obj对象 }) } } obj.a(); const obj = { a() { setTimeout(function() { setTimeout(function() { console.log(this); // window }) setTimeout(() => { console.log(this); // window }) }) setTimeout(() => { setTimeout(function() { console.log(this); // window }) setTimeout(() => { console.log(this); // Object }) }) } } obj.a();
前端渲染和后端渲染
前端路由和后端路由
前端SPA单页面
location.hash = ‘aaa’
history.pushState({}, ‘’, ‘home’)
history.pushState({}, ‘’, ‘about’)
history.pushState({}, ‘’, ‘me’)
history.pushState({}, ‘’, ‘demo’)
history.pushState()类似与栈结构,添加了上方的四个内容,栈内存是遵循先进后出的原则
history.back()移除一个栈内存元素,me将为栈顶显示出来
history.replaceState()此方法会替换之前的内容,导致无法返回
history.replaceState({}, ‘’, ‘home’)
先history.pushState({}, ‘’, ‘home’)
history.go(-1)等同于history.back()
history.go(-2)会返回两层,向后
go()参数也可以为正history.go(2),代表向前,history.go(1)等同于history.forward()
npm install vue-router --save
Vue CLI脚手架中可以直接选择安装路由,安装后,无需重复安装
安装vue-router后,在项目src目录下会多一个router路由文件夹
在router文件夹中的index.js
// 导入路由 import VueRouter from 'vue-router' import Vue from 'vue' import Home from '../components/Home' import About from '../components/About' // 1.通过Vue.use(插件),安装插件 Vue.use(VueRouter) // 2.创建VueRouter对象 const router = new VueRouter({ // routers 配置路由和组件之间的应用关系 routers: [ // 此处可以单独引入一个数组 { path: '/', // 当第一次进入网页,显示的默认页面 // redirect 重定向 redirect: '/home' }, { path: '/home', component: Home }, { path: '/about', component: About } ], mode: 'history' // 使url连接中不存在# }) // 3.将router对象传入到Vue实例 export default router // 导出
在src中的main.js
import Vue from 'vue' import App from './App' import router from './router/index' // 导入 index可以省略 new Vue({ el: '#app', router, // 使用 render: h => h(App) // render相当于template会直接替换id为app的div标签 })
router-link、router-view标签的使用
在src下的App.vue中
<template> <div id="app"> <router-link to="/home">首页</router-link> <router-link to="/about">关于</router-link> <router-view></router-view> </div> </template> <script> export default { name: 'App' } </script> <style> </style>
router-link标签可以用其他标签代替,如下
<template> <div id="app"> <button @click="homeClick">首页</button> <button @click="aboutClick">关于</button> <router-view></router-view> </div> </template> <script> export default { name: 'App', methods: { homeClick() { this.$router.push('/home') // 这种方式可以返回 // this.$router.replace('/home') // 这种方式不可以返回 }, aboutClick() { this.$router.push('/about') } } } </script> <style> </style>
tag属性:使生成的标签为button,默认生成a标签
<router-link to="/home" tag="button">首页</router-link>
replace属性:点击进入的路由页面,无法返回
<router-link to="/home" tag="button" replace>首页</router-link>
active-clase属性
router-link标签生成的标签会有默认的class类名,这个类名只有在某个按钮被点击后才会添加上去
<button class="router-link-exact-active router-link-active">首页</button>
可以根据这个类名设置点击后的样式
<style> .router-link-active{ color: red; } </style>
如果觉得router-link-active这个类名不太好,可以用active-clase属性设置
<router-link to="/home" tag="button" replace active-class="active">首页</router-link> <router-link to="/home" tag="button" replace active-class="active">关于</router-link>
如果如上,有很多router-link标签后,每个都设置确实很麻烦,可以在路由中统一设置
router -> index.js
const routers = [ { path: '/', redirect: '/home' // 默认首页重定向 }, { path: '/home', component: Home }, { path: '/about', component: About } ] const router = new VueRouter({ routers, mode: 'history', linkActiveClass: 'active' // 在这里统一设置 }) export default router
完成以下需求
url: localhost:8080/user/zhangsan
url: localhost:8080/user/lisi
在不同的用户链接下,展示不同用户的用户信息
路由这样配置
src -> router -> index.js
import VueRouter from 'vue-router' import Vue from 'vue' import Home from '../components/Home' import About from '../components/About' import User from '../components/User' // 导入组件 const routers = [ { path: '/', redirect: '/home' }, { path: '/home', component: Home }, { path: '/about', component: About }, { path: '/user/:userId', // 可以这样配置 component: User } ] const router = new VueRouter({ routers, mode: 'history', linkActiveClass: 'active' }) export default router
<router-link to="/home">首页</router-link> <router-link to="/about">关于</router-link> <router-link :to="'/user/' + userId">用户</router-link> <script> data() { return { userId: lisi } } </script>
User.vue中获取url地址中的userId参数
在User.vue中
<h2> {{userId}} 这里的userId是计算属性里的userId </h2> <h2> {{$route.params.userId}} </h2> <script> export default { name: "User", computed: { userId() { return this.$route.params.userId // 哪个路由活跃,就可以通过this.$route获取哪个路由,是$route不是$router // 此处的userId和路由中的userId对应 } } } </script>
不适用懒加载会在打开页面后,把所有组件全部加载
使用懒加载后,在请求某一个组件时,只会请求某一个组件页面,各组件之间分开打包
这样配置路由
在router中的index.js
// import Home from '../components/Home' // import About from '../components/About' const Home = () => import('../components/Home') const About = () => import('../components/About') const routers = [ { path: '/', redirect: '/home' }, { path: '/home', component: Home // 此处是将Home分离,结合上方 // component: () => import('../components/Home') }, { path: '/about', component: () => import('../components/About') } ]
有这样的需求,在home组件下,还有news组件和message组件
url: /home/news
url: /home/message
即:home路由下又有两个路由
在router -> index.js
const Home = () => import('../components/Home') const HomeNews = () => import('../components/HomeNews') const HomeMessage = () => import('../components/HomeMessage') const About = () => import('../components/About') const routers = [ ... { path: '/home', component: Home, children: [ { path: '', redirect: news }, { path: 'news', component: HomeNews }, { path: 'message', component: HomeMessage } ] } ... ]
在Home.vue中
<router-link to="/home/news">新闻</router-link> <router-link to="/home/message">消息</router-link> <router-view></router-view>
URL: 协议://主机:端口/路径?查询
URL: scheme://host:port/path?query#fragment
创建新的组件Profile.vue
<template> <div> <h2> 我是Profile组件 </h2> </div> </template> <script> export.default { name: "Profile" } </script> <style scoped> </style>
路由中映射
在router -> index.js中
const Profile = () => import('../components/Profile') const routers = [ ... { path: '/profile', component: Profile } ... ]
App.vue中
<router-link :to="{path: '/profile', query: {name: 'why', age: 20, height: 1.9}}">档案</router-link>
在Profile.vue组件中取出App.vue中的to属性中的query
<template> <div> <h2>我是Profile组件</h2> <h2>{{$route.query}}</h2> <h2>{{$route.query.name}}</h2> <h2>{{$route.query.age}}</h2> <h2>{{$route.query.height}}</h2> </div> </template> <script> export.default { name: "Profile" } </script> <style scoped> </style>
在App.vue中
<button @click="userClick">用户</button> <button @click="profileClick">档案</button> <script> data() { return { userId: 'zhangsan' } }, methos: { userClick() { this.$route.push('/user/' + this.userId) } profileClick() { this.$route.push({ path: '/profile', query: { name: 'why', age: '20' } }) } } </script>
const routers = [ { path: '/home', component: Home, meta: { // 添加这个meta属性 title: '首页' } } ] router.beforeEach((to, from, next) => { // 从from跳转到to console.log(to) document.title = to.meta.title document.title = to.matched[0].meta.title // 如home组件下有news、message组件,当在news组件时会导致title是undefined,这样写就不会有这个问题,这样永远获取父路由的title next() // next()方法必须写 })
除了beforeEach,还有afterEach,afterEach里面没有next()函数,因为afterEach是跳转后回调,不用进行下一步
const routers = [ { path: '/home', component: Home, beforeEnter: (to, from, next) => { // ... next() } } ]
导航守卫
<keep-alive> <router-view/> </keep-alive>
<script> export default { data() { return {} }, created() {}, destroyed() {}, // activated、deactivated只在使用keep-alive标签后才起作用 activated() { // 活跃调用此函数 this.$router.push(this.path); // 活跃的时候把保存的状态恢复 }, deactivated() {}, // 失去活跃调用此函数 beforeRouteLeave(to, from, next) { // 组件内的守卫 // 在导航离开渲染该组件的对应路由时调用 console.log(this.$router.path) this.path = this.$router.path // 离开之前保存状态 next() } } </script>
keep-alive的重要属性
在App.vue
<keep-alive exclude="Profile"> <!-- <keep-alive exclude="Profile,User"> --> <router-view/> </keep-alive>
这样设置之后,Profile组件就不会被保存状态,频繁切换Profile组件,会频繁创建销毁Profile组件
App.vue引用MainTabBar.vue引用TabBar.vue、TabBarItem.vue
路由跳转到首页Home、分类Caregory、购物车Cart、我的Prifile
首页Home
分类Caregory
购物车Cart
我的Prifile
在build -> webpack.base.conf.js中
{ resolve: { extensions: ['.js', '.vue', '.json'], alias: { '@': resolve('src'), // 'assets': resolve('@/assets'), // 脚手架2中好像不能直接用上方自定义的@,脚手架3中再验证 'assets': resolve('src/assets'), 'components': resolve('src/components'), 'views': resolve('src/views') } } }
不管在哪个文件内引入,都可以这样引入
在MainTabBar.vue中
<template> <tab-bar> <tab-bar-item path="/home" activeColor="blue"> <img slot="item-icon" src="~assets/img/tabbar/00.png" alt=""> <img slot="item-icon-active" src="~assets/img/tabbar/0.png" alt=""> <div slot="item-text">首页</div> </tab-bar-item> <tab-bar-item path="/category" activeColor="blue"> <img slot="item-icon" src="~assets/img/tabbar/00.png" alt=""> <img slot="item-icon-active" src="~assets/img/tabbar/1.png" alt=""> <div slot="item-text">分类</div> </tab-bar-item> <tab-bar-item path="/cart" activeColor="blue"> <img slot="item-icon" src="~assets/img/tabbar/00.png" alt=""> <img slot="item-icon-active" src="~assets/img/tabbar/2.png" alt=""> <div slot="item-text">购物车</div> </tab-bar-item> <tab-bar-item path="/profile" activeColor="blue"> <img slot="item-icon" src="~assets/img/tabbar/00.png" alt=""> <img slot="item-icon-active" src="~assets/img/tabbar/3.png" alt=""> <div slot="item-text">我的</div> </tab-bar-item> </tab-bar> </template> <script> import TabBar from 'components/tabbar/TabBar' import TabBarItem from 'components/tabbar/TabBarItem' export default { name: 'MainTabBar', components: { TabBar, TabBarItem } } </script> <style> .tab-bar-item{ flex: 1; text-align: center; height: 49px; } .tab-bar-item img{ width: 24px; height: 24px; } </style>
在HTML代码中路径前要用~号,import引用可以直接使用别名
~
回调地狱
$.ajax('url', (data) => { console.log(data); })
Promise是ES6中的
new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }).then(() => { console.log("setTimeout0"); return new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }) }).then(() => { console.log("setTimeout1"); return new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }) }).then(() => { console.log("setTimeout2"); return new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }) }).then(() => { console.log("setTimeout3"); })
什么情况下用Promise
一般情况下是有异步操作时,使用Promise对这个异步操作进行封装
new Promise((resolve, reject) => { // 此处专门进行网络请求 setTimeout(() => { // 成功的时候调用resolve resolve("haha") // 失败的时候调用reject reject('error') }, 1000) }).then(data => { // 成功的时候调用then() // 此处专门对请求来的数据进行处理 console.log(data) }).catch(err => { // 失败的时候调用catch() console.log(err) })
sync / Synchronization同步
async / Asynchronization异步
operation操作
第一aaa 第二次aaa+111 第三次aaa111+222
第一aaa
第二次aaa+111
第三次aaa111+222
new Promise((resolve, reject) => { setTimeout(() => { resolve("aaa") reject('error') }, 1000) }).then(res => { console.log(res) return new Promise((resolve, reject) => { resolve(res + '111') }) }).then(res => { console.log(res) return new Promise((resolve, reject) => { resolve(res + '222') }) }).then(res => { console.log(res) })
另一种写法
// return Promise.resolve(res + '111') new Promise((resolve, reject) => { setTimeout(() => { resolve("aaa") reject('error') }, 1000) }).then(res => { console.log(res) return Promise.resolve(res + '111') }).then(res => { console.log(res) return Promise.resolve(res + '222') }).then(res => { console.log(res) }) // return Promise.reject('err') new Promise((resolve, reject) => { setTimeout(() => { resolve("aaa") reject('error') }, 1000) }).then(res => { console.log(res) return Promise.reject('err') // 也可以手动抛出异常 throw 'err' }).then(res => { console.log(res) return Promise.resolve(res + '222') }).then(res => { console.log(res) }).catch(err => { console.log(err) })
又另一种写法
// return res + '111' new Promise((resolve, reject) => { setTimeout(() => { resolve("aaa") reject('error') }, 1000) }).then(res => { console.log(res) return res + '111' }).then(res => { console.log(res) return res + '222' }).then(res => { console.log(res) })
同时进行多个网络请求,并且只在多个请求都成功后,才执行操作Promise.all
Promise.all([ new Promise((resolve, reject) => { setTimeout(() => { resolve({name: 'why', age: 18}) }, 1000) }), new Promise((resolve, reject) => { setTimeout(() => { resolve({name: 'kobe', age: 19}) }, 2000) }) ]).then(results => { console.log(results) })
Vuex是一个专门为Vue.js应用程序开发的状态管理模式。
伪代码
const shareObj = { name: 'kobe' } Vue.prototype.shareObj = shareObj Vue.component('cpn1', { this.shareObj.name }) Vue.component('cpn2', { this.shareObj.name }) const app = new Vue({ el: '#app' })
Vuex管理的状态
单页面管理
Actions -> State -> View -> Actions (三者循环)
操作 -> 数据 -> 视图 -> 操作 (三者循环)
Vuex安装
npm install vuex --save
创建这样的文件存放vuex,src/store/index.js
src/store/index.js
import Vue from 'vue' import Vuex from 'vuex' // 1.安装插件 Vue.use(Vuex) // 2.创建对象 const store = new Vuex.Store({ state: { counter: 1000 }, mutations: {}, actions: {}, getters: {}, modules: {} }) // 3.导出store对象 export default store
src/main.js 挂载的方式和vue-router一模一样
import router from './router' // 自动会找router下的index.js文件 import store from './store' // 自动会找store下的index.js文件 new Vue({ el: '#app', router, store, render: h => h(app) })
挂载之后会把store对象给Vue.prototype.$store = store
在其他组件中获取Vuex中的数据
<template> <div> <h2>{{$store.state.counter}}</h2> <!-- 通过$store.state获取 --> </div> </template> <script> export default { name: "HelloVuex" } </script> <style> </style>
修改Vuex中的数据,最好不要直接通过$store.state.counter进行修改,而是通过mutations进行修改
import Vue from 'vue' import Vuex from 'vuex' // 1.安装插件 Vue.use(Vuex) // 2.创建对象 const store = new Vuex.Store({ state: { counter: 1000 }, mutations: { // 方法 increment(state) { state.counter++ }, decrement(state) { state.counter-- } }, actions: {}, getters: {}, modules: {} }) // 3.导出store对象 export default store
<h2>{{$store.state.counter}}</h2> <button @click="addition">+</button> <button @click="subtraction">-</button> <script> export default { methods: { addition() { this.$store.commit('increment') // commit提交 }, subtraction() { this.$store.commit('decrement') } } } </script>
一个项目中,只创建一个store
类似于计算属性
state: { counter: 1000, students: [ {id: 0, name: 'name0', age: 19}, {id: 1, name: 'name1', age: 20}, {id: 2, name: 'name2', age: 21}, {id: 3, name: 'name3', age: 22} ] }, getters: { powerCounter(state) { return state.counter * state.counter }, more20stu(state) { return state.students.filter(s => s.age > 20) }, more20stuLength(state, getters) { //return state.students.filter(s => s.age > 20).length return getters.more20stu.length }, moreAgeStu(state) { // 这里不接受自定义传入的参数,只能为state、getters等传过来的值,名字可以自定义 return function(age) { return state.students.filter(s => s.age > age) } } }
<h2>{{$store.getters.powerCounter {{$store.getters.more20stu {{$store.getters.more20stuLength {{$store.getters.moreAgeStu(12) ``` ## Mutations 以后只要修改$store里边state,一定要通过mutation进行修改 ### mutation传递参数,载荷(Payload) store/index.js ```js mutations: { incrementCount(state, count) { state.counter += count }, addStudent(state, stu) { state.students.push(stu) } } ``` App.vue ```html +5 +10 添加学生 ``` ### mutation提交风格 store/index.js ```js mutations: { incrementCount(state, payload) { state.counter += payload.count // 取传过来的payload对象中的count } } ``` App.vue ```html +5 +10 ``` ### mutation响应规则 store/index.js ```js state: { info: { name: 'kobe', age: 40, height: 1.98 // 提前在store中定义好的初始化过的属性在属性值改变时有响应式 } }, mutations: { updateInfo(state) { state.info['address'] = '洛杉矶' // 通过程序添加的属性没有响应式 Vue.set(state.info, 'address', '洛杉矶') // 通过Vue.set方法传入,是响应式的 delete state.info.age // 删除info中的一些属性,此方式,也是没有响应式 Vue.delete(state.info, 'age') // 通过Vue.delete方法删除,是响应式的 } } ``` App.vue ```html {{$store.state.info}}</h2> <button @click="addInfoDate">向info添加属性</button> <script> export default { methods: { addInfoDate() { this.$store.commit('updateInfo') } } } </script>
store/mutation-types.js
export const INCREMENT = 'increment'
<template> <button @click="addition">+</button> </template> <script> import {INCREMENT} from './store/mutation-types' export default { methods: { addition() { this.$store.commit(INCREMENT) } } } </script>
stroe/index.js
import {INCREMENT} from './store/mutation-types' const store = new Vuex.Store({ state: { counter: '100' }, mutations: { [INCREMENT](state) { state.counter++ } } })
Vuex要求Mutation中的方法必须是同步方法,不能为异步方法,如果是异步方法,devtools将无法捕捉mutation快照
store/index.js
state: { info: { name: 'kobe', age: 40, height: 1.98 } }, mutations: { updateInfo(state) { // state.info.name = 'codewhy' // 响应式的 setTimeout(() => { // 模拟异步操作 state.info.name = 'ganto' // 视图是响应式的,但是devtools插件里边的显示的数据没有被修改,但实时已经修改了,只是devtools插件没有跟踪到 }, 1000) } }
<h2>{{$store.state.info}}</h2> <button @click="updateInfo">更改info</button> <script> export default { methods: { updateInfo() { this.$store.commit('updateInfo') } } } </script>
如果必须要有异步操作,可以用Action替代Mutation进行异步操作
state: { info: { name: 'kobe', age: 40, height: 1.98 } }, mutations: { updateInfo(state) { state.info.name = 'ganto' } }, actions: { // context: 上下文 aUpdateInfo(context, payload) { setTimeout(() => { context.commit('updateInfo') console.log(payload) }, 1000) } }
<h2>{{$store.state.info}}</h2> <button @click="updateInfo">更改info</button> <script> export default { methods: { updateInfo() { // this.$store.commit('updateInfo') this.$store.dispatch('aUpdateInfo') } } } </script>
commit是提交的mutation,在Actions中的commit提交的也是mutation
dispatch才是操作Actions
state: { info: { name: 'kobe', age: 40, height: 1.98 } }, mutations: { updateInfo(state) { state.info.name = 'ganto' } }, actions: { aUpdateInfo(context, payload) { setTimeout(() => { context.commit('updateInfo') console.log(payload) }, 1000) } }
<h2>{{$store.state.info}}</h2> <button @click="updateInfo">更改info</button> <script> export default { methods: { updateInfo() { this.$store.dispatch('aUpdateInfo', '我是actions传过去的参数') } } } </script>
state: { info: { name: 'kobe', age: 40, height: 1.98 } }, mutations: { updateInfo(state) { state.info.name = 'ganto' } }, actions: { aUpdateInfo(context, payload) { setTimeout(() => { context.commit('updateInfo') console.log(payload.message) payload.success() }, 1000) } }
<h2>{{$store.state.info}}</h2> <button @click="updateInfo">更改info</button> <script> export default { methods: { updateInfo() { this.$store.dispatch('aUpdateInfo', { message: '我是携带的信息', success: () => { console.log('里边已经完成了') } }) } } } </script>
另一种优雅的写法
state: { info: { name: 'kobe', age: 40, height: 1.98 } }, mutations: { updateInfo(state) { state.info.name = 'ganto' } }, actions: { aUpdateInfo(context, payload) { return new Promise((resolve, reject) => { setTimeout(() => { context.commit('updateInfo') console.log(payload) resolve() }, 1000) }) } }
<h2>{{$store.state.info}}</h2> <button @click="updateInfo">更改info</button> <script> export default { methods: { updateInfo() { this.$store.dispatch('aUpdateInfo', '我是携带的信息').then(res => { console.log(res) console.log("里边完成了提交") }) } } } </script>
const moduleA = { state: { name: 'ganto' }, mutations: {}, actions: {}, getters: () } const store = new Vuex.Store({ state: {}, mutations: {}, actions: {}, getters: {}, modules: { a: moduleA // 这里定义的a会被添加到主state中 } })
<template> <div id="app"> <h2>{{$store.state.a.name}}</h2> <!-- 这里不用$store.state.a.state.name,可以直接$store.state.a.name拿到数据 --> </div> </template>
const moduleA = { state: { name: 'ganto' }, mutations: { updateName(state, payload) { state.name = payload } }, actions: {}, getters: () } const store = new Vuex.Store({ state: {}, mutations: {}, actions: {}, getters: {}, modules: { a: moduleA // 这里定义的a会被添加到主state中 } })
<template> <div id="app"> <h2>{{$store.state.a.name}}</h2> <!-- 这里不用$store.state.a.state.name,可以直接$store.state.a.name拿到数据 --> <button @click="updateName">修改name</button> </div> </template> <script> export default { name: 'App', methods: { updateName() { this.$store.commit('updateName', 'kobe') // 这里提交和其他无异 } } } </script>
const moduleA = { state: { name: 'ganto' }, mutations: {}, actions: {}, getters: { fullname(state) { return state.name + 'x' // gantox }, fullname2(state, getters) { return getters.fullname + 'x' // gantoxx }, fullname3(state, getters, rootState) { // 这里的rootState是根的State return getters.fullname2 + rootState.counter // gantoxx1000 } } } const store = new Vuex.Store({ state: { counter: 1000 }, mutations: {}, actions: {}, getters: {}, modules: { a: moduleA // 这里定义的a会被添加到主state中 } })
<template> <div id="app"> <h2>{{$store.state.a.name}}</h2> <h2>{{$store.getters.fullname}}</h2> <!-- gantox --> <h2>{{$store.getters.fullname2}}</h2> <!-- gantoxx --> <h2>{{$store.getters.fullname3}}</h2> <!-- gantoxx1000 --> </div> </template> <script> export default { name: 'App', methods: {} } </script>
const moduleA = { state: { name: 'ganto' }, mutations: { updateName(state, payload){ state.name = payload } }, actions: { //aUpdateName({state, getters, rootstate}) { // 对象结构 //} aUpdateName(context) { setTimeout(() => { context.commit('updateName', 'zhangsan') }, 1000) } }, getters: {} } const store = new Vuex.Store({ state: { counter: 1000 }, mutations: {}, actions: {}, getters: {}, modules: { a: moduleA // 这里定义的a会被添加到主state中 } })
<template> <div id="app"> <h2>{{$store.state.a.name}}</h2> <button @click="asyncUpdateName">异步修改name</button> </div> </template> <script> export default { name: 'App', methods: { asyncUpdateName() { this.$store.dispatch('aUpdateName') } } } </script>
vuex中的代码,如果全部写入store/index.js文件中,不利于日后维护管理
state可以直接在store/index.js中抽离,其他Vuex核心,抽离成独立的文件
import Vue from 'vue' import Vuex from 'vuex' import mutations from './mutations' import actions from './actions' import getters from './getters' import moduleA from './modules/moduleA' import moduleB from './modules/moduleB' const state = { counter: 1000, info: { name: 'kobe', age: 40 } } const store = new Vuex.Store({ state, mutations, actions, getters, modules: { a: moduleA b: moduleB } })
store/mutations.js
import {INCREMENT} from "./mutations-types" export default { [INCREMENT](state) { state.counter++ }, decrement(state) { state.counter-- } }
store/actions.js、store/getters.js也是一样的操作
store/modules/moduleA.js
export default { state: {...}, mutations: {...}, actions: {...}, getters: {...}, modules: {...} }
store/modules/moduleB.js
例:定义一个对象,如要获取对象内的属性,要这样获取
const obj = { name: 'ganto', age: 10, } console.log(obj.age, " ", obj.name) // 10ganto
对象解构的写法,可以这样写
并且const {age, name} = obj的{age, name}前后顺序不影响结果
const {age, name} = obj
{age, name}
const obj = { name: 'ganto', age: 10, } const {age, name} = obj console.log(age, " ", name) // 10 ganto
const names = ['ganto', 'cevno'] const [name1, name2] = names console.log(name1, " ", name2)
网络模块封装
传统的Ajax是基于XMLHttpRequest(XHR)
JSONP
npm install axios --save
src/main.js
import axios from 'axios' axios({ url: 'http://123.207.32.32:8000/home/multidata', method: 'get' }).then(res => { console.log(res) }) axios({ url: 'http://123.207.32.32:8000/home/data?type=sell&page=1' }).then(res => { console.log(res) }) axios({ url: 'http://123.207.32.32:8000/home/data', // 专门针对get请求的参数拼接 params: { type: 'pop', page: 1 } }).then(res => { console.log(res) }) axios.get('http://123.207.32.32:8000/home/data', {params: {type: 'sell', page: 1}}).then(res => { console.log(res) }).catch(err => { console.log(err) }) // axios(config) // axios.request(config) // axios.get(url[, config]) // axios.delete(url[, config]) // axios.head(url[, config]) // axios.post(url[, date[, config]]) // axios.put(url[, date[, config]]) // axios.patch(url[, date[, config]])
method是设置请求方式,默认get,此接口暂不支持post,也没有跨域问题
多个请求成功后才进行下一步操作
axios.all([ axios({ url: 'http://123.207.32.32:8000/home/multidata' }), axios({ url: 'http://123.207.32.32:8000/home/data', params: { type: 'sell', page: 3 } }) ]).then(results => { console.log(results) }) axios.all([ axios({ url: 'http://123.207.32.32:8000/home/multidata' }), axios({ url: 'http://123.207.32.32:8000/home/data', params: { type: 'sell', page: 3 } }) ]).then(axios.spread((res1, res2) => { console.log(res1) console.log(res2) }))
// 公共配置 axios.defaults.baseURL = 'http://123.207.32.32:8000' axios.defaults.timeout = 5 axios.all([ axios({ url: '/home/multidata' }), axios({ // 设置了全局配置,这里的URL和timeout(超时时间)就可以删除了 // baseURL: 'http://123.207.32.32:8000', // timeout: 5, url: '/home/data', params: { type: 'sell', page: 3 } }) ]).then(results => { console.log(results) })
请求地址 url: '/user' 请求类型 method: 'get' 请求根路径 baseURL: 'http://www.mt.com/api' 请求前的数据处理 transformRequest: [function(data){ }] 请求后的数据处理 transformResponse: [function(data){ }] 自定义的请求头 header: {'x-Requested-With':'XMLHttpRequest'} URL查询对象 params: {id: 12} 查询对象序列化函数 paramsSerializer: function(params){ } request body data: {key: 'aa'} 超时设置ms timeout: 1000 跨域是否带Token withCredentials: false 自定义请求处理 adapter: function(resolve, reject, config){ } 身份验证信息 auth: {uname: '', pwd: '12'} 响应的数据格式json/blob/document/arraybuffer/text/stream responseType: 'json'
method: 'get'和params: {id: 12}配合使用
method: 'get'
params: {id: 12}
method: 'post'和data: {key: 'aa'}配合使用
method: 'post'
data: {key: 'aa'}
创建不同的实例,可以单独给实例设置全局配置
const instance1 = axios.create({ baseURL: 'http://111.222.333.444:8080', timeout: 5000 }) instance1({ url: '/home/multidata' }).then(res => { console.log(res) }) instance1({ url: '/home/data', params: { type: 'pop', page: 1 } }).then(res => { console.log(res) }) const instance2 = axios.create({ baserURL: 'http://222.111.222.111:8000', timeout: 10000, headers: {} }) instance2({ url: '/data/h' }).then(res => { console.log(res) })
第三方封装对项目的日后维护有很大的现实意义
src/network/request.js
import axios from 'axios' export function request(config) { const instance = axios.create({ baseURL: 'http://123.207.32.32:8000', timeout: 5000 }) instance(config).then(res => { }).catch(err => { }) }
如上封装,会有个一问题,就是then()和catch()要写在哪里,显然不能写在src/network/request.js中来进行处理请求的数据,和处理请求失败
可以这样处理
import axios from 'axios' export function request(config, success, failure) { const instance = axios.create({ baseURL: 'http://123.207.32.32:8000', timeout: 5000 }) instance(config).then(res => { success(res) }).catch(err => { failure(err) }) }
import {request} from './network/request' request({ url: '/home/multidata' }, res => { console.log(res) }, err => { console.log(err) })
也可以这样处理
import axios from 'axios' export function request(config) { const instance = axios.create({ baseURL: 'http://123.207.32.32:8000', timeout: 5000 }) instance(config.baseConfig).then(res => { config.success(res) }).catch(err => { config.failure(err) }) }
import {request} from './network/request' request({ baseConfig: { }, success: function(res) { }, failure: function(err) { } })
再另一种
import axios from 'axios' export function request(config) { return new Promise((resolve, reject) => { const instance = axios.create({ baseURL: 'http://123.207.32.32:8000', timeout: 5000 }) instance(config).then(res => { // 这里的then和catch是因为这个自定义的axios本身就会返回promise的then和catch函数 resolve(res) }).catch(err => { reject(err) }) }) }
import {request} from './network/request' request({ url: '/home/multidata' }).then(res => { console.log(res) }).catch(rer => { console.log(err) })
再再一种
import axios from 'axios' export function request(config) { const instance = axios.create({ baseURL: 'http://123.207.32.32:8000', timeout: 5000 }) return instance(config) // 调用instance本身就会返回promise }
请求成功 请求失败 响应成功 响应失败
import axios from 'axios' export function request(config) { const instance = axios.create({ baseURL: 'http://123.207.32.32:8000', timeout: 5000 }) // axios的拦截器 // 请求拦截 instance.interceptors.request.use(config => { // 请求成功 console.log(config) // 请求前的参数 /** 为什么要拦截? * 1.比如config中的一些信息不符合服务器的要求,对数据进行一些修改 * 2.比如每次发送网路请求时,都希望在网站界面中显示一个请求的图标 * 3.某些网络请求(比如登录(token)),必须携带一些特殊信息 */ return config // 拦截成功后要把数据返回,要不然拿不到config数据了 }, err => { // 请求失败 console.log(err) }) // 响应拦截 instance.interceptors.response.use(res => { console.log(res) // 请求返回的结果 return res.data // 拦截成功后要把数据返回,这里只需要返回data数据就行,其他的数据无用 }, err => { console.log(err) }) return instance(config) // 调用instance本身就会返回promise }
创建项目,使用vue CLI3
vue create demomall
git的一些常用指令
git clone https://github.com/ganto-cn/demomall.git git status git add . git commit -m '第一次提交' git push git remote add origin https://github.com/ganto-cn/demomall.git git pull origin master git pull --rebase origin master git push -u origin master git push -u origin master -f git branch [name] git push -u origin [name]
1、在用Vue CLI3创建完项目后,将项目与github仓库建立联系,并将项目推送到github
这里的仓库最好是全空仓库,如果不是全空仓库大有可能会报错,README.md、MIT声明文件也别加
git remote add origin https://github.com/ganto-cn/demomall.git git push -u origin master
2、另一种操作方式,先克隆仓库到本地
git clone https://github.com/ganto-cn/demomall.git
然后将Vue CLI3创建完成的项目复制到克隆到本地仓库中,不复制.git和node_modules这俩不要复制,然后执行以下操作
.git
node_modules
git status git add . git commit -m '第一次提交项目' git push
这种方式需要初始化项目中的node环境
npm install
常用第一种方式提交项目
assets 资源
common 公共的
src / assets / css / normalize.css
normalize.css
src / assets / css / base.css
@import "./normalize.css"; :root { --color-text: #666; --color-high-text: #ff5777; --color-tint: #ff8198; --color-background: #fff; --font-size: 14px; --line-height: 1.5; } *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif; user-select: none; /* 禁止用户鼠标在页面上选中文字/图片等 */ -webkit-tap-highlight-color: transparent; /* webkit是苹果浏览器引擎,tap点击,highlight背景高亮,color颜色,颜色用数值调节 */ background: var(--color-background); color: var(--color-text); width: 100vw; } a { color: var(--color-text); text-decoration: none; } .clear-fix::after { clear: both; content: ''; display: block; width: 0; height: 0; visibility: hidden; } .clear-fix { zoom: 1; } .arrow-right { border-top: 1px solid #999; border-left: 1px solid #999; width: 9px; height: 9px; background-color: transparent; transform: rotate(135deg); display: inline-block; margin-left: .1rem; } .left { float: left; } .right { float: right; }
在App.vue中引用base.css
<style> /* @import url(./assets/css/base.css) */ @import './assets/css/base.css' </style>
这里的vue.config.js配置文件一直不起作用 Vue CLI3.2.1 Vue-router3.5.2 Vue2.6.14 Vuex3.6.2 用了@vue/cli 5.0.4重新创建的项目才可以 Vue-router3.5.3 Vue2.6.14 Vuex3.6.2 以下命令查询版本 npm list vue vue --version vue -V npm list vuex npm list vue-router
这里的vue.config.js配置文件一直不起作用
Vue CLI3.2.1 Vue-router3.5.2 Vue2.6.14 Vuex3.6.2
用了@vue/cli 5.0.4重新创建的项目才可以 Vue-router3.5.3 Vue2.6.14 Vuex3.6.2
以下命令查询版本
npm list vue vue --version vue -V npm list vuex npm list vue-router
const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, configureWebpack: { resolve: { alias: { 'assets': '@/assets', 'common': '@/common', 'components': '@/components', 'network': '@/network', 'views': '@/views' } } } })
.editorconfig
root = true [*] charset = utf-8 indent_style = space indent_size = 2 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true
src / router / index.js
import Vue from 'vue' import VueRouter from 'vue-router' // 懒加载,只有在用到这个路由时才会加载出来 const Home = () => import('views/home/Home') const Category = () => import('views/category/Category') const Cart = () => import('views/cart/Cart') const Profile = () => import('views/profile/Profile') Vue.use(VueRouter) // 解决重复点击路由,报错的问题 const originalPush = VueRouter.prototype.push VueRouter.prototype.push = function push(location) { return originalPush.call(this, location).catch(err => err) } const routes = [ { path: '/', redirect: '/home' }, { path: '/home', component: Home }, { path: '/category', component: Category }, { path: '/cart', component: Cart }, { path: '/profile', component: Profile } ] export default new VueRouter({ routes, mode: 'history', base: process.env.BASE_URL })
src / main.js
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' Vue.config.productionTip = false new Vue({ router, store, render: h => h(App) }).$mount('#app')
本文链接:https://heri.ganto.cn/2021/11/19/Vue%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/
本文更新于:2022-04-28 12:39:23
小站声明:如果你需要“转载”、“引用”小站的文章,可以不需要作者同意,请务必标明出处和文章链接。