早期
js
作为一种脚本语言
,做简单的表单验证
或动画实现
等少量代码
后期
ajax异步请求
的出现,形成了前后端的分离
客户端
需要完成的事情越来越多,代码量与日俱增
如何应对
js
文件中,进行维护
缺陷
全局变量同名
问题js
文件的依赖顺序
几乎是强制性
的js
文件过多,理清文件顺序
很困难// aaa.js文件中,小明定义了一个变量,名称是flag,并且为true
flag = true
// bbb.js文件中,小丽也喜欢用flag这个变量名称,值为false
flag = false
// main.js文件中,小明想通过flag进行一些判断,完成后续的事情
if (flag) {
console.log('小明是个天才');
}
匿名函数
来解决方面的重名
问题
aaa.js
文件中,我们使用匿名函数
(function(){
var flag = true
})()
问题
main.js
文件中,用到flag
,应该如何处理呢?
另外一个文件中不容易使用
,因为flag
是一个局部
变量需要暴露到外面的变量
,使用一个模块
作为出口
匿名函数
内部,定义一个对象
给对象添加各种需要暴露到外面的属性和方法(不需要暴露的直接定义即可)
将这个对象返回
,并且在外面使用了一个MoudleA
接收man.js
中,只需要使用属于自己模块的属性和方法即可
var ModuleA = (function() {
// 1. 定义一个对象
var obj = {}
// 2. 在对象内部添加变量和方法
obj.flag = true
obj.myFunc = function(info){
console.log(info);
}
// 3. 将对象返回
return obj
})()
if (ModuleA.flag){
console.log('小明是个天才');
}
ModuleA.myFunc('小明长得真帅')
console.log(ModuleA);
常见的模块化规范
CommonJS
、AMD
、CMD
,也有ES6
的modules
导出
和导入
CommonJS 导出
module.exports
module.exports = {
flag: true,
test(a,b){
return a + b
},
demo(a,b){
return a * b
}
}
CommonJS 导入
require
// CommonJS模块
let {test,demo,flag} = require('moduleA');
// 等同于
let _mA = require('moduleA');
let test = _mA.test;
let demo = _mA.demo;
let flag = _mA.flag;
export
指令用于导出变量
// info.js
export let name = 'why'
export let age = 18
export let height = 1.88
// 另一种写法
// info.js
let name = 'why'
let age = 18
let height = 1.88
export {name,age,height}
输出变量
,也可以输出函数
或者输出类
export function test(content){
console.log(content);
}
export class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
run(){
console.log(this.name + '在奔跑');
}
}
function test(content){
console.log(content);
}
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
run(){
console.log(this.name + '在奔跑');
}
}
export {test,Person}
一个模块中包含某个的功能
,希望让导入者可以自己来命名
export default
## info.js
export default function(){
console.log('default function');
}
main.js
中可以根据需要命名它对应的名字
## mian.js
import myFunc from './info.js'
myFunc()
注意
export default
在同一个模块
中,不允许同时存在多个
export
指令导出了模块对外提供的接口
import
命令来加载对应的模块
HTML
代码中引入两个js
文件,并且类型需要设置为module
<script src="info.js" type="module">script>
<script src="main.js" type="module">script>
import
指令用于导入模块中的内容
## mian.js
import {name,age,height} from "./info.js"
console.log(name,age,height);
某个模块中所有的信息都导入
*
可以导入模块中所有的export
变量*
起一个别名
,方便后续的使用## mian.js
import * as info from './info.js'
console.log(info.name, info.age, info.height, info.friends);
Vue.js
开发大型应用
时,需要考虑代码目录结构
、项目结构
和部署
、热加载
、代码单元测试
等事情脚手架工具
来完成CLI
是什么意思?
CLI
是Command-Line Interface
, 翻译为命令行界面
, 但是俗称脚手架
Vue CLI
是一个官方发布 vue.js
项目脚手架vue-cli
可以快速搭建Vue
开发环境以及对应的webpack
配置安装NodeJS
检测安装的版本
Node
和NPM
8.9以上
或者更高版本node -v
npm -v
NPM
NPM
的全称是Node Package Manager
NodeJS
包管理和分发工具,已经成为了非官方的发布Node
模块(包)的标准Vue.js
官方脚手架工具就使用了webpack
模板
优化操作
一套完整的功能
,能够使得我们开发过程中变得高效
Webpack
的全局安装
npm install webpack -g
Vue
脚手架
npm install -g @vue/cli
vue --version
Vue CLI3
的版本,如果需要想按照Vue CLI2
的方式初始化项目时是不可以
的Vue CLI2
初始化项目
vue init webpack my-project
Vue CLI3
初始化项目
vue create my-project
template
,就需要选择Runtime-Compiler
.vue
文件夹开发,那么可以选择Runtime-only
Runtime-Compiler
和 Runtime-only
new Vue({
el: '#app',
components: { App },
template: ' '
})
new Vue({
el: '#app',
render: h => h(App)
})
方式一
new Vue({
el: '#app',
render: (createElement) => {
// 1. 使用方式一:
return createElement('标签','相关数据对象(可以不传)'.['内容数组'])
// 1.1 render函数基本使用
return createElement('div',{class: 'box'}, ['xxx'])
// 1.2 嵌套render函数
return createElement('div',{class: 'box'}, ['xxx', createElement('h2', ['标题'])])
}
})
方式二
const cpn = Vue.component('cpn',{
template: '我是cpn组件',
data() {
return {
}
}
})
new Vue({
el: '#app',
render: (createElement) => {
// 2. 使用方式二:传入一个组件对象
return createElement(cpn)
}
})
resolve: {
extensions: ['.js','.vue','.json'],
alias: {
'@': resolve('src'),
'pages': resolve('src/pages'),
'common':resolve('src/common'),
'components': resolve('src/components'),
'network': resolve('src/network')
}
}
vue-cli 3
与 2 版本有很大区别
vue-cli 3
是基于 webpack 4
打造,vue-cli 2
还是 webapck 3
vue-cli 3
的设计原则是“0配置
”,移除的配置文件根目录下的,build
和config
等目录vue-cli 3
提供了 vue ui
命令,提供了可视化配置
,更加人性化static
文件夹,新增了public
文件夹,并且index.html
移动到public
中UI
方面的配置
vue ui
const path = require('path')
function resolve (dir) {
return path.join(__dirname,dir)
}
module.exports = {
// 1. 基础的配置方式
configureWebpack: {
resolve: {
alias: {
'components': '@/components',
'pages': '@/pages'
}
}
},
// 2. 利用webpack4的webpack-chain来配置
chainWebpack: (config) => {
config.resolve.alias
.set('@$',resolve('src'))
.set('components',resolve('src/components')
}
}
概念
路由
(routing
)就是通过互联的网络把信息从源地址传输到目的地址的活动
. — 维基百科作用
路由
和转送
路由
决定数据包从来源到目的地的路径
转送
将输入端的数据转移到合适的输出端
路由表
路由表本质上就是一个映射表, 决定了数据包的指向
后端路由过程
服务器直接生产渲染好对应的HTML页面, 返回给客户端进行展示
URL
URL
会发送到服务器, 服务器会通过正则对该URL
进行匹配, 并且最后交给一个Controller
进行处理Controller
进行各种处理, 最终生成HTML
或者数据
, 返回给前端
IO操作
请求不同的路径内容
时, 交给服务器来进行处理
, 服务器渲染
好整个页面, 并且将页面返回
给客户端不需要单独加载任何的js和css
, 可以直接交给浏览器展示, 这样也有利于SEO的优化
缺点
整个页面的模块由后端人员来编写和维护的
前端开发人员如果要开发页面, 需要通过
PHP和
Java等语言来编写页面代码
HTML
代码和数据
以及对应的逻辑
会混在一起, 编写和维护
都是非常糟糕的事情前后端分离
Ajax
的出现, 有了前后端分离的开发模式
API
来返回数据
, 前端通过Ajax
获取数据, 并且可以通过JavaScript
将数据渲染到页面中优点
前后端责任的清晰
后端
专注于数据
前端
专注于交互
和可视化
移动端(iOS/Android)
出现后, 后端不需要进行任何处理
, 依然使用之前的一套API
即可单页面富应用阶段(Single Page Application)
SPA
最主要的特点就是在前后端分离的基础上加了一层前端路由
前端来维护一套路由规则
前端路由核心
URL
,但是页面不进行整体的刷新
URL的hash
URL
的hash
也就是锚点(#)
, 本质上是改变window.location
的href
属性直接赋值location.hash
来改变href
, 但是页面不发生刷新
history
接口是HTML5
新增的, 它有五种模式改变URL
而不刷新页面
pushState
history.pushState()
向浏览器历史添加了一个状态(增加一个记录)
一个状态对象、一个标题(现在被忽略了)以及一个可选的URL地址
不会触发页面刷新
,只是导致history对象发生变化
,地址栏会有反应
url参数
,设置了一个新的锚点值(即hash)
,并不会触发hashchange事件
跨域网址
,则会报错
replaceState
history.replaceState()
修改浏览历史中当前纪录
go
history.go()
加载历史列表中的某个具体的页面
history.back()
等价于 history.go(-1)
history.forward()
则等价于 history.go(1)
等同于浏览器界面的前进后退
Angular
的ngRouter
React
的ReactRouter
Vue
的vue-router
vue-router
vue-router
是Vue.js
官方的路由插件
,它和vue.js
是深度集成
的,适合用于构建单页面应用
vue-router
是基于路由
和组件
的
路由用于设定访问路径, 将路径和组件映射起来
vue-router
的单页面应用中, 页面的路径的改变就是组件的切换
vue-router
npm install vue-router --save
Vue.use()
来安装路由功能)
导入
路由对象,并且调用 Vue.use(VueRouter)
创建路由实例
,并且传入路由映射配置
Vue
实例中挂载
创建的路由实例
import Vue from 'vue'
import VueRouter form 'vue-router'
Vue.use(VueRouter)
vue-router
的步骤
创建路由组件
配置路由映射: 组件和路径映射关系
和
)步骤一:创建路由组件
步骤二:配置组件和路径的映射关系
步骤三:使用路由
: 该标签会根据当前的路径, 动态渲染出不同的组件
顶部的标题/导航, 或者底部的一些版权信息等
会和
处于同一个等级
路由切换
时, 切换的是
挂载的组件, 其他内容不会发生改变效果:
默认跳到首页
, 并且
渲染首页组件呢?只需要配置多配置一个映射就可以
const routes = [
{
path: '/',
redirect: '/home'
}
]
配置解析
routes
中又配置了一个映射path
配置的是根路径: /
redirect
是重定向
根路径重定向
到/home
的路径下, 这样就可以让路径默认跳到首页
URL的hash
HTML5的history
URL的hash
HTML5
的history
模式, 进行如下配置即可:const router = new VueRouter({
routes,
mode: 'history'
})
中, 使用属性: to
, 用于指定跳转的路径
还有一些其他属性:
tag
replace
replace 不会留下 history 记录
, 所以指定replace
的情况下, 后退键不能返回到上一个页面中
active-class
对应的路由匹配成功
时, 会自动给当前元素设置
一个router-link-active
的class
active-class
可以修改默认的名称
active-class="active"
,则 router-link-active 变为 active
高亮显示
的导航菜单或者底部tabbar
时, 会使用到该类通常不会修改类的属性
, 会直接使用默认的router-link-active
即可修改 router-link-active 的默认名称
class
具体的名称也可以通过router
实例的属性进行修改exact-active-class
active-class
, 只是在精准匹配
下才会出现的class
嵌套路由
时, 再看下这个属性页面的跳转
可能需要执行对应的JavaScript
代码this.$router.push('/home')
默认添加了 $router 属性
调用 $router 属性中的 push() 方法
push => pushState
调用 $router 属性中的 replace() 方法
,不能返回上一页
replace => replaceState
path
路径不确定
时
进入用户界面
时,希望是如下的路径:
/user/aaaa
或/user/bbbb
/user
之外,后面还跟上了用户的 ID
path
和Component
的匹配关系,称之为动态路由
(也是路由传递数据
的一种方式)router.js
{
path: '/user/:id',
component: User
}
<router-link :to="'/user/'+userId">用户router-link>
...
data(){
return {
userId:'123'
}
}
动态展示获取路由 ID 信息
$router 就是 router 文件夹 index.js 中定义的 router 对象
$route 就是当前活跃状态的路由对象
this.$route.params.userId
parmas 方式接收参数
<h2>{{userId}}h2>
...
data(){
return {
userId(){
// 接收 path: '/user/:id/' 中的 id
return this.$route.params.id
}
}
}
官方解释
打包构建应用
时,Javascript
包会变得非常大
,影响页面加载
把不同路由对应的组件分割成不同的代码块
,然后当路由被访问的时候才加载对应组件
,这样更加高效
如何理解
路由中通常会定义很多不同的页面
js
文件中js
文件中, 必然会造成这个页面非常的大
一次性从服务器请求这些页面
, 可能需要花费一定的时间
, 甚至还会出现了短暂空白
的情况如何解决
路由懒加载
路由懒加载作用
将路由对应的组件打包成一个个的js代码块
被访问
到的时候, 才加载对应的组件
结合Vue的异步组件和Webpack的代码分析
const Home = resolve => { require.ensure(['../components/Home.vue'], () => { resolve(require('../components/Home.vue')) })};
ADM写法
const About = resolve => require(['../components/About.vue'], resolve);
ES6
写法const Home = () => import('../components/Home.vue')
嵌套路由
是一个很常见的功能
home
页面中, 通过/home/news
和/home/message
访问一些内容一个路径映射一个组件
, 访问这两个路径也会分别渲染两个组件
路径和组件的关系
如下:嵌套路由步骤
创建对应的子组件
在路由映射中配置对应的子路由
标签定义两个组件
使用 children 设置子路由进行路由嵌套
使用 链接子组件
Profile.vue
配置路由映射
添加跳转
的
传递参数
主要有两种类型: params
和query
params
的类型
配置路由格式
: /router/:id
传递的方式
: 在path
后面跟上对应的值
传递后形成的路径
: /router/123
, /router/abc
this.$router.push({
name:'xxx'
params:{
id:id
}
})
query
的类型
配置路由格式
: /router
, 也就是普通配置
传递的方式
: 对象中使用query
的key
作为传递方式传递后形成的路径
: /router?id=123
, /router?id=abc
this.$router.push({
path:'/xxx'
query:{
id:id
}
})
如何使用
的方式JavaScript
代码方式
的方式
JavaScript代码
形式
添加方法
this.$router.push
path: '/路由/' + 参数
query: { key : value }
接收参数
通过$route
对象获取的
vue-router
的应用中,路由对象
会被注入每个组件中,赋值为 this.$route
当路由切换时,路由对象会被更新
$route
获取传递的信息如下
this.$route.params.id
this.$route.query.name/age
$route
和$router
是有区别的$route
和$router
是有区别的
$router
为VueRouter
实例,想要导航到不同URL
,则使用$router.push
方法$route
为当前router
跳转对象,里面可以获取name、path、query、params
等vue-router
提供的导航守卫主要用来监听路由的进入和离开
的vue-router
提供了beforeEach
和afterEach
的钩子函数
改变前
和改变后
触发需求: 在一个单页面应用中, 如何改变网页的标题呢?
来显示的, 但是SPA应用
只有一个固定的HTML
, 切换不同的页面时, 标题并不会改变
JavaScript
来修改
的内容
window.document.title = '新的标题'
Vue
项目中, 在哪里修改?
什么时候修改?
普通的修改方式
.vue
文件中mounted
声明周期函数
, 执行对应的代码进行修改即可不容易维护
(因为需要在多个页面执行类似的代码)导航守卫方式
全局守卫
beforeEach
进入组件前调用afterEach
进入组件后调用beforeEach
来完成标题的修改
定义一些标题
, 可以利用meta
来定义(meta元数据--描述数据的数据
)导航守卫
,修改标题
// 2.创建VueRouter对象
const routes = [
{
path: '',
// redirect重定向
redirect: '/home'
},
{
path: '/home',
component: Home,
meta: {
title: '首页'
},
children: [
// {
// path: '',
// redirect: 'news'
// },
{
path: 'news',
component: HomeNews
},
{
path: 'message',
component: HomeMessage
}
]
},
{
path: '/about',
component: About,
meta: {
title: '关于'
}
},
{
path: '/user/:id',
component: User,
meta: {
title: '用户'
},
},
{
path: '/profile',
component: Profile,
meta: {
title: '档案'
},
}
]
...
// 前置守卫(guard)
router.beforeEach((to, from, next) => {
// 从from跳转到to
document.title = to.matched[0].meta.title
next()
})
## beforeEach 源码
beforeEach (guard: NavigationGuard): Function;
export type NavigationGuard<V extends Vue = Vue> = (
to: Route,
from: Route,
next: (to?: RawLocation | false | ((vm: V) => any) | void) => void
) => any
导航钩子的三个参数解析
to
: 即将要进入的目标的路由对象
from
: 当前导航即将要离开的路由对象
next
: 调用该方法后, 才能进入下一个钩子
注意
后置钩子
, 也就是afterEach
不需要主动调用next()函数
// 后置钩子(hook)
router.afterEach((to, from) => {
})
## afterEach 源码
afterEach (hook: (to: Route, from: Route) => any): Function;
路由独享的守卫
beforeEnter
进入指定的组件
之前调用const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
组件内的守卫
组件内直接定义
路由导航守卫beforeRouteEnter
beforeRouterUpdate
beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不能获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id, 在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next){
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
keep-alive
是 Vue
内置的一个组件
使被包含的组件保留状态
,或避免重新渲染
两个重要的属性
include
- 字符串或正则表达,只有匹配的组件会被缓存
exclude
- 字符串或正则表达式,任何匹配的组件都不会被缓存
router-view
也是一个组件
直接被包在 keep-alive 里面
,所有路径匹配到的视图组件都会被缓存
<keep-alive>
<router-view>
router-view>
keep-alive>
create
声明周期函数来验证记录离开时的路由信息,当返回时依然是这个路由
<template>
<div>
<h2>我是首页h2>
<p>我是首页内容, 哈哈哈p>
<router-link to="/home/news">新闻router-link>
<router-link to="/home/message">消息router-link>
<router-view>router-view>
<h2>{{message}}h2>
div>
template>
<script>
export default {
name: "Home",
data() {
return {
message: '你好啊',
path: '/home/news' // 默认显示新闻的路由
}
},
created() {
console.log('home created');
},
destroyed() {
console.log('home destroyed');
},
// 这两个函数, 只有该组件被保持了状态使用了keep-alive时, 才是有效的
activated() {
this.$router.push(this.path);
console.log('activated');
},
deactivated() {
console.log('deactivated');
},
beforeRouteLeave (to, from, next) {
console.log(this.$route.path);
this.path = this.$route.path; // 离开前将路由记录在默认path中
next()
}
}
script>
<style scoped>
style>
如何封装自定义 TabBar 组件
TabBar
处于底部
,并且设置相关的样式
TabBar
中显示的内容由外界
决定
插槽
flex
布局平分TabBar
TabBarItem
,可以传入图片和文字
TabBarItem
,并且定义两个插槽:图片、文字
div
,用于设置样式
填充插槽
,实现底部TabBar
的效果高亮图片
active-icon
的数据isActive
,通过v-show
来决定是否显示对应的icon
TabBarItem 绑定路由数据
npm install vue-router —save
router/index.js
的内容,以及创建对应的组件
main.js
中注册router
APP
中加入
组件点击 item 跳转到对应路由
,并且动态决定 isActive
item
的点击,通过this.$router.replace()替换路由路径
this.$route.path.indexOf(this.link) !== -1
来判断是否是active
动态计算 active 样式
封装新的计算属性
:this.isActive ? {'color': 'red'} : {}
TabBar.vue
<template>
<div id="tab-bar">
<slot>slot>
div>
template>
<script>
export default {
name: "TabBar"
}
script>
<style scoped>
#tab-bar {
display: flex;
background-color: #f6f6f6;
position: fixed;
left: 0;
right: 0;
bottom: 0;
box-shadow: 0 -1px 1px rgba(100,100,100,.2);
}
style>
TabBarItem.vue
<template>
<div class="tab-bar-item" @click="itemClick">
<div v-if="!isActive"><slot name="item-icon">slot>div>
<div v-else><slot name="item-icon-active">slot>div>
<div :style="activeStyle"><slot name="item-text">slot>div>
div>
template>
<script>
export default {
name: "TabBarItem",
props: {
path: String,
activeColor: {
type: String,
default: 'red'
}
},
data() {
return {
// isActive: true
}
},
computed: {
isActive() {
// /home -> item1(/home) = true
// /home -> item1(/category) = false
// /home -> item1(/cart) = true
// /home -> item1(/profile) = true
return this.$route.path.indexOf(this.path) !== -1
},
activeStyle() {
return this.isActive ? {color: this.activeColor} : {}
}
},
methods: {
itemClick() {
this.$router.replace(this.path)
}
}
}
script>
<style scoped>
.tab-bar-item {
flex: 1;
text-align: center;
height: 49px;
font-size: 14px;
}
.tab-bar-item img {
width: 24px;
height: 24px;
margin-top: 3px;
vertical-align: middle;
margin-bottom: 2px;
}
style>
MainTabBar.vue
<template>
<tab-bar>
<tab-bar-item path="/home" activeColor="pink">
<img slot="item-icon" src="~assets/img/tabbar/home.svg" alt="">
<img slot="item-icon-active" src="~assets/img/tabbar/home_active.svg" alt="">
<div slot="item-text">首页div>
tab-bar-item>
<tab-bar-item path="/category" activeColor="pink">
<img slot="item-icon" src="../../assets/img/tabbar/category.svg" alt="">
<img slot="item-icon-active" src="../../assets/img/tabbar/category_active.svg" alt="">
<div slot="item-text">分类div>
tab-bar-item>
<tab-bar-item path="/cart" activeColor="pink">
<img slot="item-icon" src="../../assets/img/tabbar/shopcart.svg" alt="">
<img slot="item-icon-active" src="../../assets/img/tabbar/shopcart_active.svg" alt="">
<div slot="item-text">购物车div>
tab-bar-item>
<tab-bar-item path="/profile" activeColor="deepPink">
<img slot="item-icon" src="../../assets/img/tabbar/profile.svg" alt="">
<img slot="item-icon-active" src="../../assets/img/tabbar/profile_active.svg" 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 scoped>
style>
官方解释
Vuex
是一个专为 Vue.js
应用程序开发的状态管理模式
集中式存储管理
应用的所有组件的状态
相应的规则
保证状态以一种可预测
的方式发生变化Vuex
也集成到 Vue
的官方调试工具 devtools extension
time-travel
调试、状态快照
导入导出等高级调试功能状态管理
把需要多个组件共享的变量全部存储在一个对象里面
Vue
实例中,让其他组件可以使用
多个组件
就可以共享
这个对象中的所有变量属性
Vuex
就是为了提供这样一个在多个组件间共享状态
的插件多个状态,在多个界面间的共享问题
登录状态
、用户名称
、头像
、地理位置信息
等等商品的收藏
、购物车中的物品
等等状态信息
,都可以放在统一
的地方,对它进行保存和管理
,而且数据还是响应式
的State
:就是组件的状态
View
:视图层
,可以针对State
的变化,显示不同的信息
Actions
:这里的Actions
主要是用户的各种操作
:点击、输入
等等,会导致状态的改变
案例
counter
需要某种方式被记录
下来,也就是State
counter
目前的值需要被显示在界面中,也就是View
部分操作
时(用户的点击
,也可以是用户的input
),需要去更新状态
,也就是Actions
<template>
<div class="test">
<div>当前计数:{{counter}}div>
<button @click="counter+=1">+1button>
<button @click="counter-=1">-1button>
div>
template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
counter: 0
}
}
}
script>
多个视图都依赖同一个状态
一个状态改了,多个界面需要进行更新
不同界面的 Actions 都想修改同一个状态
Home.vue
需要修改,Profile.vue
也需要修改这个状态如何理解
(状态1/状态2/状态3)
来说只属于某一个视图
状态1/状态2/状态3 自己管理自己用
(状态a/状态b/状态c)
属于多个视图共同想要维护
的
状态a/状态b/状态c 统一管理
Vuex
就是提供统一管理
的工具
Vuex
基本思想
将共享的状态抽取出来
,交给Vuex
,统一进行管理
每个视图,按照规定好的规定,进行访问和修改等操作
Devtools 工具
对组件变化状态进行跟踪
通过 Devtools 知道哪个具体的组件改变了 State
store
index.js
文件index.js
中存放Vuex
代码import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
},
decrement(state) {
state.count--
}
}
})
引入 Vuex
main.js
文件,导入store对象
,并且放在new Vue
中Vue组件
中,就可以通过this.$store
的方式,获取到这个store对象
main.js
import Vue from 'vue'
import App from './App'
import store from './store'
new Vue({
el: '#app',
store,
render: h => h(App)
})
<template>
<div id="app">
<p>{{count}}p>
<button @click="increment">+1button>
<button @click="decrement">-1button>
div>
template>
<script>
export default {
name: 'App',
components: {
},
computed: {
count: function() {
return this.$store.state.count // 拿到 Vuex 中的 count
}
},
methods: {
increment: function() {
this.$store.commit('increment')
},
decrement: function() {
this.$store.commit('decrement')
}
}
}
script>
Vuex
最简单的方式了store对象
,用于保存
在多个组件中共享的状态
store对象
放置在new Vue对象
中,这样可以保证在所有的组件中都可以使用到store对象
中保存的状态即可
this.$store.state.属性
的方式来访问
状态this.$store.commit('mutation中方法')
来修改
状态提交mutation
的方式,而非直接改变store.state.count
Vuex
可以更明确的追踪状态的变化,所以不要直接改变store.state.count
的值Vuex
提出使用单一状态树
, 什么是单一状态树呢?
Single Source of Truth
,也可以翻译成单一数据源
类比
记录
,比如上学时的个人档案
,工作后的社保记录
,公积金记录
,结婚后的婚姻信息
,以及其他相关的户口、医疗、文凭、房产记录
等等。低效
,而且不方便管理
,以及日后的维护也是一个庞大的工作(需要大量的各个部门的人力来维护
,当然国家目前已经在完善我们的这个系统了)。Store对象
中的,那么之后的管理
和维护
等等都会变得特别困难。Vuex
也使用了单一状态树
来管理应用层级的全部状态
单一状态树
能够让我们最直接
的方式找到某个状态的片段,而且在之后的维护
和调试
过程中,也可以非常方便的管理
和维护
store
中获取一些state变异后的状态
,比如下面的Store
中:
const store = new Vuex.Store({
state: {
students: [
{id: 110, name: 'zs', age: 18},
{id: 111, name: 'ls', age: 21},
{id: 112, name: 'we', age: 25},
{id: 113, name: 'zx', age: 30}
]
}
})
Store
中定义getters
computed: {
getGreaterAgesCount() {
return this.$store.students.filter(age => age >= 20).length
}
},
getters: {
greaterAgesCount: state => {
return state.students.filter(s => s.age >= 20).length
}
}
getters
, 那么代码可以这样来写getters: {
greaterAgesStus: state => {
return state.students.filter(s => s.age >= 20)
},
greaterAgesCount: (state,getters) => {
return getters.greaterAgesStus.length
}
}
getters
默认是不能传递参数的, 如果希望传递参数, 那么只能让getters
本身返回另一个函数.
ID
获取用户的信息getters: {
stuByID: state => {
return id => {
return state.students.find(s => s.id === id)
}
}
}
Vuex
的store状态
的更新唯一方式:提交Mutation
Mutation
主要包括两部分:
字符串的事件类型(type)
一个回调函数(handler)
,该回调函数的第一个参数就是state
mutation
的定义方式:mutations: {
increment(state) {
state.count++
}
}
mutation更新
increment: function () {
this.$store.commit('increment')
}
mutation
更新数据的时候, 有可能我们希望携带一些额外的参数
mutation的载荷(Payload)
Mutation
中的代码:decrement(state,n) {
state.count -= n
}
decrement: function () {
this.$store.commit('decrement',2)
}
以对象的形式传递
, 也就是payload
是一个对象
changeCount(state,payload) {
state.count = payload.count
}
changeCount: function () {
this.$store.commit('changeCount',{count: 0})
}
commit
进行提交是一种普通的方式Vue
还提供了另外一种风格, 它是一个包含type
属性的对象this.$store.commit({
type: 'changeCount',
count: 100
})
Mutation
中的处理方式是将整个commit
的对象作为payload
使用, 所以代码没有改变, 依然如下:changeCount(state,payload) {
state.count = payload.count
}
Vuex
的store
中的state
是响应式
的, 当state
中的数据发生改变时, Vue
组件会自动更新
Vuex
对应的规则:
store
中初始化好所需的属性.state
中的对象添加新属性时, 使用下面的方式:
Vue.set(obj, 'newProp', 123)
重新赋值
点击更新信息
时, 界面并没有发生对应改变.App.vue
<template>
<div id="app">
<p>我的个人信息:{{info}}p>
<button @click="updateInfo">更新信息button>
div>
template>
<script>
export default {
name: 'App',
components: {
},
computed: {
info () {
return this.$store.state.info
}
},
methods: {
updateInfo () {
this.$store.commit('updateInfo', {height: 1.88})
}
}
}
script>
Vuex
const store = new Vuex.Store({
state: {
info: {
name: 'xxx', age: 18
}
},
mutations: {
updateInfo(state,payload) {
state.info['height'] = payload.height
}
}
})
state
中的属性是响应式
的mutations: {
updateInfo(state,payload) {
// state.info['height'] = payload.height
// 方式一: Vue.set()
Vue.set(state.info, 'height', payload.height)
// 方式二: 给 info 赋值一个新的对象
state.info = {...state.info, 'height': payload.height}
}
}
mutation
中, 我们定义了很多事件类型(也就是其中的方法名称).Vuex
管理的状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutation
中的方法越来越多.Flux
实现中, 一种很常见的方案就是使用常量替代Mutation事件
的类型.app
所有的事件类型一目了然.mutation-types.js
, 并且在其中定义我们的常量.ES2015
中的风格, 使用一个常量来作为函数的名称.mutation-types.js
export const UPDATE_INFO = 'UPDATE_INFO'
index.js
import Vuex from 'vuex'
import Vue from 'vue'
import * as types from './mutation-types'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
info: {
name: 'xxx', age: 18
}
},
mutations: {
[types.UPDATE_INFO](state, payload) {
state.info = {...state.info, 'height': payload.height}
}
}
})
export default store
App.vue
<script>
import {UPDATE_INFO} from "./store/mutation-types";
export default {
name: 'App',
components: {
},
computed: {
info() {
return this.$store.state.info
}
},
methods: {
updateInfo() {
this.$store.commit(UPDATE_INFO, {height: 1.88})
}
}
}
script>
Vuex
要求我们Mutation
中的方法必须是同步方法
devtools
时, 可以devtools
可以帮助我们捕捉mutation
的快照.异步操作
, 那么devtools
将不能很好的追踪这个操作什么时候会被完成.devtools
中会有如下信息:Vuex
中的代码, 我们使用了异步函数
:mutations: {
[types.UPDATE_INFO](state,payload) {
setTimeout(() => {
state.info = {...state.info, 'height': payload.height}
},10000)
}
}
Mutation
中进行异步操作
Vuex
中进行一些异步操作
, 比如网络请求
, 必然是异步的. 这个时候怎么处理呢?Action
类似于Mutation
, 但是是用来代替Mutation
进行异步操作
的Action
的基本使用代码如下:context
是什么?
context
是和store对象
具有相同方法和属性的对象.context
去进行commit
相关的操作, 也可以获取context.state
等.Vuex
中有异步操作
, 那么我们就可以在actions
中完成了const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
increment(context) {
context.commit('increment')
}
}
})
Vue
组件中, 如果我们调用action
中的方法, 那么就需要使用dispatch
methods: {
increment() {
this.$store.dispatch('increment')
}
}
支持传递payload
methods: {
increment() {
this.$store.dispatch('increment',{cCount: 5})
}
}
mutations: {
increment(state,payload) {
state.count += payload.cCount
}
},
actions: {
increment(context,payload) {
setTimeout(() => {
context.commit('increment',payload)
},5000)
}
}
ES6
语法的时候, Promise
经常用于异步操作
Action
中, 我们可以将异步操作
放在一个Promise
中, 并且在成功或者失败后, 调用对应的resolve
或reject
actions: {
increment(context) {
return new Promise((resolve) => {
setTimeout(() => {
context.commit('increment')
resolve()
},1000)
})
}
}
methods: {
increment() {
this.$store.dispatch('increment').then(res => {
console.log('完成了更新操作')
})
}
}
Module
是模块的意思, 为什么在Vuex
中我们要使用模块呢?
Vue
使用单一状态树
,那么也意味着很多状态都会交给Vuex
来管理.store对象
就有可能变得相当臃肿.Vuex
允许我们将store
分割成模块(Module)
, 而每个模块拥有自己的state
、mutations
、actions
、getters
等const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
moduleA
中添加state
、mutations
、getters
mutation
和getters
接收的第一个参数是局部状态
对象const moduleA = {
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
getters: {
doubleCount(state) {
return state.count * 2
}
}
}
const moduleB = {
}
const store = new Vuex.Store({
state: {
gCount: 111
},
modules: {
a: moduleA,
b: moduleB
}
})
export default store;
<script>
export default {
name: 'App',
components: {
},
computed: {
count() {
return this.$store.getters.doubleCount
}
},
methods: {
increment() {
this.$store.commit('increment')
}
}
}
script>
doubleCount
和increment
都是定义在对象内部
的this.$store
来直接调用的actions
的写法呢? 接收一个context参数对象
局部状态
通过 context.state
暴露出来,根节点状态
则为 context.rootState
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state,commit,rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
getters
中也需要使用全局的状态, 可以接受更多的参数const moduleA = {
// ...
getters: {
sumWithRootCount (state,getters,rootState) {
return state.count + rootState.count
}
}
}
Vue
中发送网络请求有非常多的方式, 那么, 在开发中, 如何选择呢?
Ajax
Ajax
是基于XMLHttpRequest(XHR)
配置
和调用方式
等非常混乱.jQuery-Ajax
jQuery-Ajax
jQuery-Ajax
,相对于传统的Ajax
非常好用.Vue
的整个开发中都是不需要使用jQuery
了.jQuery
, 你觉得合理吗?jQuery
的代码1w+行.Vue
的代码才1w+行.网络请求
就引用这个重量级
的框架.Vue-resource
Vue1.x
的时候, 推出了Vue-resource
.
Vue-resource
的体积相对于jQuery
小很多.Vue-resource
是官方推出的.Vue2.0
退出后, Vue
作者就在GitHub
的Issues
中说明了去掉vue-resource
, 并且以后也不会再更新
.vue-reource
不再支持新的版本时, 也不会再继续更新
和维护
.隐患
.axios
vue-resource
的同时, 作者还推荐了一个框架: axios
axios
有非常多的优点, 并且用起来也非常方便.网络请求
方式就是JSONP
JSONP
最主要的原因往往是为了解决跨域访问
的问题.JSONP
的原理是什么呢?
JSONP
的核心在于通过