VUE 总结 (持续刷新。。。)

VUE 总结 (持续刷新。。。)


VUE是渐进式框架

  • 可将VUE作为一部分应用嵌入
  • 可以使用全家桶构建整个项目

MVVM

  • View:视图层,DOM层,展示信息

  • Model: 从服务器请求来的数据

  • VueModel: 视图模型层,沟通view和model。一方面实现data binding,将model改变反映到view,一方面实现dom listener,当dom发生一些事件,可以被监听,并在需要时改变对应数据。

Vue实例options对象属性

  • el(string):决定实例会管理哪个DOM

  • data(Object | Function):子组件中的data必须是函数,并返回对象。数据对象。

  • methods ({[key: string]: Function}):vue中的方法

  • computed 计算属性

  • filters:过滤器 {{ message|capitalize }} v-bind:"message|capitalize" 拿到message的只进行方法过滤

  filters: {
    captitalize: function(value) {
        if (!value) return ''
          value = value.toString()
          return value.charAt(0).toUpperCase() + value.slice(1)
    }
  }
所有组件都继承自vue源型
vue.prototype.test = function(){...("test")}
组件中 this.test()

模板语法

  • Mustache:{{name}} 标签中使用,也可以是表达式

  • v-once:数据只渲染一次

  • v-html:按照string的html解析出来并渲染 v-html="http://www.baidu.com"

  • v-cloak:避免浏览器出现未来得及编译的mustache标签,直接显示编译后的内容

  • v-bind:语法糖(:) 将标签属性和vue data进行动态绑定 绑定class 绑定style

  :class="{'active': isActive, 'line': isLine}"  
  :style="{color: currentColor, fontSize:fontSize+'px'}"  对象语法
  :class="{'active','line'}" 
  :style="[baseStyles]"  数组语法
  • 计算属性:需要对数据进行进一步转化后才显示。计算属性有getter和setter方法,但set方法不常用,故而不显示。计算属性相比于methods会进行缓存,多次使用只会调用一次。
  <div id="app">
    <h2>书籍价值</h2>
  </div>
<div :style="activeStyle"><slot> name=".."</div>
  <script>
      let vue = new Vue({
          el: '#app',
          data: {
              books: [
                  {name: '编程思想', price: 99, count: 3},
                  ...
              ]
          }
          computed: {
              totalPrice() {
                  return this.books.reduce((total, b) => {
                      return total + b.price * b.count
                  })
              }
              isActive(){
                  # 判断当前路由是否处于活跃状态
                  return this.$route.path.indexof(this.path) !== -1
              }
            activeStyle(){
                return ths.isActive?{color:this.activeColor}:{}
            }
          }
      })
  </script>
  • v-on:语法糖(@) 监听用户点击,拖拽等交互式事件。
在定义事件时写方法忽略了小括号,而方法又需要一个参数,vue会默认将浏览器产生的event事件对象作为参数传入方法
<button @click="handleAdd">+1</button> 不传参数可以省略() 方法会将event原生事件传进去
 <button @click="handleAdd(10, $event)">+1</button> 拿到事件可以进行一些事件处理(.stop,.prevent,.native,.once)
@click.stop 阻止冒泡 <div @click="FU"> <div @click="ZI"> 点击子,扶不会被调用
    .prevent 阻止事件自己去方法处理数据
    keyup.enter="keyup" 监听敲击回车抬起后触发
    .native 自定义组件需要加native才会触发
  • vue响应式会监听数组改变的方法:push(), pop(), shift()删除第一个元素,unshift()头部添加元素,splice(),sort(),reverse()
  arr.splice(2, 0, "wang") index=2的位置插入元素
  arr.splice(2, 3, "wang") index=2的位置开始向后删除3个元素
  arr.splice(2, 1, "wang") 删除index=2的元素,并用wang代替
  arr.splice(2, 3,"wang") 从index=2开始删除三个元素,并插入一个元素wang
  arr.slice(6) 提取index=6开始到最后的字符串
  arr.slice(6, 10) 提取index从6到10的字符串
  • v-model:实现表单元素和数据元素的双向绑定。实质上是语法糖

<input type="text" v-model="message">  <==> vbind绑定表单元素 von给数据元素绑定input事件
 <input type="text" v-bind:value="message" v-on:input="message=$event.target.value">
  v-model : radio(单选框) checkbox(复选框) select(复选框)
  v-model.lazy(数据失去焦点或回车才会更新) .number(输入内容自动转换为数字类型) .trim(去掉首尾空格) 
  • input
<input placeholder="账号" key="username"> 
<input placeholder="邮箱" key="mail">
如果input不加key,当先在账号框输入123时,vue内部会维护一个虚拟dom,当判断下一个input会直接使用vsdom,导致切换到邮箱框仍显示123,所以需要用key来通知vue区新创建一份儿

ES6 语法

  • let/var:var只有在函数中才有作用域,在其他地方无作用域。而let具有块级作用域,es5 if,for无块级作用域,es6中if,for有块级作用域
  • const:常量,const obj={ name:"why" },obj.name=shuai,const只能保证变量指向内存地址的数据不变,但是对于对象,数组,function等复合变量无法保证指针指向的地址数据结构不变
# 剩余运算符
let [a,...b]=[1,2,3] // a=1,b=[2,3]
let {a,b,...rest}={a:10,b:20,c:20,d:40} // a=10,b=20,rest={c:30,d:40}
function sum(...num){
    var sumNum=0;
    for (let i=0;i<num.length;i++){    <=>  let sum = nums.reduce((x,y)) => {return x+y})
              sumNum += parserInt(num[i])                      
    }
    console.log(sumNum)
}
#  拓展运算符
let person = {name: "amy", age:15}
let someone = {...person} //拷贝了一份儿
let someone = {...person, name:"mike"} // {name:mike, age:15} 后面会覆盖前面的
let someone = {name:"mike", ...person} // {name: "amy", age:15} 后面会覆盖前面的
object.assign(target,object2,object3) // 对象浅拷贝
assign([2,3],[5]) // [5,3] 
    [2,3] -> {0:2,1:3}
    {0:2,1:3} -> {0:5, 1:3}
    -> [5,3]
# MAP object
obj的键只能存字符串或symbols,而map可以存任意值,有序,size获取个数
var myMap = new Map() myMap.set(skyFunc,"123") myMap.get(keyFunc)
迭代
for (var [key, value] of myMap) {
    console.log()key+"="+value
}
var key of myMap.keys()
   value        .values()
  myMap.forEach(function(value,key){
    key+value
  },myMap)
const person = {
    seyHi(){
        .log("hi")    person.hi()
    }
}
object.is(value1,value2) === 判断两对象是否严格相同
is(1,1) true is([1],[1]) false is({g:1},{g:1}) false
# set 存储唯一值
let mySet = new set()
mySet.add(1)  .add(1,5)
# array
myArray = [...mySet]
去重 var mySet = new set([1,2,3,4,4])  [...mySet] // [1,2,3,4]
并集 new Set([...a, ...b])
交集       ([...a.filter(x=>b.has(x))])
差集       ([...a.filter(x=>!b.has(x))])
# 字符串
 .includes .startswith .endswith .indexof .lastIndexOf .repeat(2) // "123,123"
模板字符串 let name="mike" `my name is ${name}`  ${f()} 调用函数
# 增强字面量
对象增强写法,增强字面量:let obj = { name, obj } <==> let obj = { name: name, age: age }   test: function() {} <==> test () {}
const age=12, name="amy" const person={age, name}
# 函数
function f(...value) {
    ... value.length     f(1,2) f(1,2,3,4)
}
var f = (a,b) => a+b
var f = (id,name) => ({id:id, name:name}) 对象需要加()
# 箭头函数体中this对象,是定义函数时对象而不是使用函数的对象
function fn(){
    setTimeout(()=>{
        (this.a)  // this绑定的是fn中的this对象
    }, 0)
}
(param1 ... paramN) => {statements}
                    => expression <=> {return expression}
只有一个参数时圆括号可选 singleParam => {statements}
无参数可写成圆括号 () => {statements}
let f = ([a,b]=[1,2],{x,c}={x:a+b}) => a+b+c // f() 6
function(element) {return element.length}
= (element) => {return element.length}
= element => {return element.length}
= element => element.length
var func = () => ({foo:1}) # 对象自必须加()
render:h=>(App) <=> render(h){ return h(App) }
# 导入导出
import {myName,myClass} from "./test.js"
let myName = "Tom"
export {myName as exportName}
import {exportName} from "./test.js"
Promise
  • 异步编程的一种解决方案,封装一个网络请求,不能立即拿到结果,往往会传入一个函数,在数据请求成功后,将数据通过传入的函数回调出去,异步操作
  链式调用
  new Promise((resolve, reject) => {
      setTimeout(function() {
          resolve('Hello World') // 成功时调用resolve, 后续的then会被回调
      }, 1000)
  }).then(data => {
      console.log(data) // Hello World
      return Promise.resolve(data+'111') // 可以简写为 return data+'111'
  }).then({
      console.log(data) // Hello World111
      return Promise.resolve(data+'222')
  }).then(error => {
      console.log(data) // Hello World111222
      return Promise.reject(data+'error')  // 失败时调用reject, 后续的catch会被回调
  }).then({
      console.log(data) // 不会执行
      return Promise.resolve(data+'333')
  }).catch({
      console.log(data) // Hello World111222error
    return Promise.resolve(data+'444
  }).then({
      console.log(data) // Hello World111222error444
  
  Promise的三种状态:
  pending:等待 正在进行网络请求时,或者定制器没有到时间
  fulfill:满足状态 当我们主动回调了resolve时,处于该状态,并且会回调.then()
  reject:拒绝状态 当我们主动调用了reject时,并且会回调.catch()

条件判断与循环遍历

  • v-if,v-else-if, v-else: <input v-if="type=='username'" key="username-input"> 问题:如果input标签中的元素已经被写入内容,在条件判断切换类型以后内容还在,因为Vue在进行DOM渲染的时候,会尽可能复用已经存在的元素。解决:为input添加key元素,保证key的唯一性,就不在复用。
  • v-show <h2 v-show="isHoow"></h2> 和if区别:if不显示不会渲染DOM,show不显示仅仅是设置样式display:none,当if条件时false时,包含v-if元素根本就不会在DOM中,而v-show只是给元素增加了display:none的行内样式
  • v-for 最好加上key。说明:VUE在插入节点时会使用DIFF算法,也就是会将要更新的节点先覆盖插入位置,再往后逐次更新,最后插入最后一个元素,因此需要用key来给每个节点做唯一标识,算法就可以正确识别节点。为了高效的更新虚拟DOM。
<li v-for="(value, key, index) in info">
    {{value}}-{{key}}-{{index}}
</li>
info : {
    name: 'wang'
}

组件

全局组件注册语法糖
vue.component('cpn',{
    template:`
        <div></div>
    `
})
注册局部组件
const app = new Vue({
    ..
    components:{
        'cpn2':{
            templates ..
        }
    }
})
# 模板分离
1. script 标签
<script type="text/x-template" id="cpn">
2. template标签
<template id="cpn"></>
vue.component('cpn', {
    template: '#cpn',
    data() {
        return {
            message: 'hello' 
        }
    }
})
组件里有data属性,必须为一函数,并且函数要返回一对象,对象内部保存数据
why? 当有多个组件引用时会操作同一个对象,如果返回新对象不会相互影响

父子组件

模板
<div id="app">
    <my-cpn></my-cpn> html页面不支持驼峰形式命令 改为横线
</div>
<template id="myCpn">
    <div>{{message}}</div>
</template>
<script>
    let app = new Vue({
        el: "#app",
        components: {
            template: 'myCpn'
            data() {
                return {
                    message: 'Hello World'  子组件的数据必须是一个返回对象的方法,如果不返回不同对象,同一对象多次调用会相互影响
                }
            }
        }
    })
</script>
通信
父传子
  • 父节点通过props属性值向子组件传递数据
子组件中: props: ['message-zi']
父组件中: data: { message-fu: 'hello' }
<child-cpn :message-zi="message-fu"></child-cpn> 通过vbind监听父组件message-fu,将数据给message-zi
<template id="childCpn"><div>{{message-zi}}</div></template> 在子组件模板中使用
  • props可以是数组,对象,优先使用对象形式 props: { A: Number, B: [string, Number], C: { type: string, required: true}, D: {type: Number, default: 100}, E : {type: Object, default: function() {return { message: ’hello‘}}}} 对象或数组的默认值必须从一个工厂函数中获取
子传父
  • 子组件通过$emit()触发事件,父组件通过v-on监听事件
子组件方法:increment(){
    this.counter++;
    this.$emit('increment', this.counter)  子组件发出事件
}
子组件模板:<button @click="increment">
父组件标签:<child-cpn @increment="changeTotal"> 子组件标签监听该事件并发生时间时调用父组件方法changeTotal
父组件方法:changeTotal(counter) {
    this.total = counter  父组件收到子组件传递的数据并赋值给父组件的data
}
子组件接受父组件参数后就不要对这个属性修改,修改要由父组件完成
子组件
<template id="cpn"> <input type="text" :value="dnumber" @input="num1Input"> 1
cpn:{
    template: "#cpn",
    props:{number1:number1, number2:number}, 4
    data() {
        return {
            dnumber: this.number1, 3
            dnumber2:this.number2
        }
    },
    method: {
        num1Input(event) { 2
            this.dnumber1=event.target.value 5
            this.$emit('num1change', this.dnumber1)
        }
    },
    watch: {
        dumber1(newValue, oldValue){
            this.dnumber2=newValue*100
            this.emit('num1change',newValue)
         }
    }
}
 父组件
    <div id="app"> <cpp :number1="num1" @num1change="num1change">
       ..
        methods:{
            num1change(value){ 3
                this.num1=value
            }
        }
<!-- 字父组件双向绑定 -->
    点击1 -> 触发2 -> 3 设置num1 子->父   -> 4-> 5 设置dnumber1 父传子

访问方式

  • $children,$ref:父组件可以通过两种方式访问子组件的数据 this.$children 拿到所有子组件的数组对象,推荐使用ref
<child-cpn1 ref="child1"></child-cpn1>
<child-cpn2 ref="child2"></child-cpn2>
<button @click="showRef"></button>
showRef() {
    consloe.log(this.$refs.child1.message) 通过ref指定子组件标签的唯一id,再用refs就可以使用该id进行指定子组件标签的访问
}
  • $parent 子组件中使用this.$parent可以拿到父组件的属性,但是不建议这样用,子组件应该保持独立,不要和父组件有耦合事件产生

slot插槽

  • 当有一类组件有公共的部分,但在某些方面又各有区别,比如每个页面的导航栏。这时可以使用插槽,抽取公共部分组件,不同部分预留插槽,插槽部分可以让使用者根据自己的需求进行定制化插入操作。
子组件模板:
<div>
    <slot>插槽默认内容</slot>
    <slot name="left">具名插槽 左侧内容</slot>
</div>
使用子组件:<my-cpn></my-cpn>
<my-cpn>
    <h2>替换插槽</h2>
    <p>再次替换插槽</p>
    <span slot="left">替换左侧</span> 可以使用具名插槽来指定替换的插槽 默认会全部替换
</my-cpn>
  • 作用域插槽:某些场景对同一组数据,需要有不同的显示格式,可以用作用域插槽
子组件模板:<slot :data="pLanguages"></slot>  pLanguages : ['Java', 'Python', 'C++']
使用: 列表形式展现 
<template slot-scope="slotProps">
    <ul><li v-for="info in slotProps.data">{{info}}</li></ul>
</template> 
水平展示: <span v-for="info in slotProps.data">{{info}}</span>

模块化

  • CommonJS的导入导出:
  module.exports = {
      flag: true,
      test (a, b){
          return a+b
      }
  }
  let { test, flag } = require('moduleA')
  let _mA = require('moduleA')
  let test = _mA.test
  • ES6 导入导出 export { name, age, height }
    export function test(){} 导出方法
    export class Person { 导出类
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
    }
    <script src="info.js" type="module">
    import {name, age, height} from './info.js' 当导出不是default的时候导入必须加{}
    import * as info from './info.js'  可以导入*并制定别名 info.name
    当某个模块的方法不希望命名,可以让导入者自己决定方法名称
    export default function() {}
    import myFunc form './info.js' 自己定义的名字 自己在起名字不用加{}

WEBPACK打包

  • webpack模块化打包,webpack为了正常运行,依赖node环境,而node环境为了正常执行代码,必须有各种依赖包,npm(node package manager)

  • static module bundler for modern JavaScript applications 现代JS应用的静态模块打包工具

  • npm install webpack@3.6.0 --save-dev(开发时依赖) webpack src/main.js dist/bundle.js 将main.js及其引入的文件打包到bundle文件中

<script src="./dist/bundle.js"></script> 引入打包后的文件 或定义出入口: 
module.exports = {
    entry: './src/main.js'
    output: {
        path: path.resolve(_dirname, 'dist'),
        filename: 'bundle.js',
        publicPath: 'dist'
    }
}
  • loader转换器 css文件处理: css-loader,style-loader,less文件处理:less-loader, 图片文件处理: url-loader, 小于8kb的图片进行base64编码,大于8kb图片,需要file-loader,ES6->ES5: bable-loader
  el用于指定vue要管理的DOM,如果vue实例中制定了template, 会替换掉挂载的对应el模板
  这样就不用操作index.html,只需更改模板
  vue文件三部分分离:template script style
  new Vue({
      el: '#app',
      template: '<div id="app">{{message}}</div>'
      data: {
        message: 'wang'
  }
  })
  • vue-loader和vue-template-complier可以解析vue格式的文件
  webpack.config.js
  {
      test: /\.vue$/,
      use: ['vue-loader']
  }
  • plugin插件 loader是转换某些类型的模块,plugin是对webpack的扩展,扩展器
  bannerPlugin 打包后的bundle添加版权
  HtmlWebpackPlugin 将index.html打包到dist文件夹下
  uglyfyjs-webpack-plugin 对打包后的js文件进行压缩

VUE CLI

  • command line interface 命令行界面 脚手架
  • NPM(Node Package Manager) NodeJS包管理和分发工具
  • node 为js提供了运行环境
  npm install -g @vue/cli 安装vue cli3
  npm install -g @vue/cli-init  安装 vue cli2
  vue init webpack my-project 初始化 vue cli2项目
  vue create my-project 初始化 vue cli3项目
  • Runtime-Complier(运行时编译)和Runtime-only(只有运行时)
  complier
  new Vue({
      el: '#app'
      components: {App}
      template: '<App/>'
  })
  only
  <div id="app"></div>
  new Vue({
      el: '#app'
      render: h=>h(App) <=> render(h) { return h(App) } 会把div标签替换掉
  }) 
  render函数:
  new Vue({
      render: (createElement) => {
        return createElement('标签', '相关数据对象(选传)', ['内容数组'])
           return createElement('div', '{class: 'box'}', ['wang'])
           // <div class="box">wang</div>
           return createElement('div', '{class: 'box'}', ['wang', createElement('h2', ['标题'])])
           // <div class="box">wang<h2>标题</h2></div>
  })
  template -> ast -> render -> vdom -> 真实dom
  only 代码中不允许有template,complier可以有template,用于编译,默认使用only
  vue运行过程: template被编译成ast(抽象语法数),转换为render函数,返回virtual dom(虚拟dom)
  only就是跳过了第一步,直接使用render函数将语法数转换为虚拟dom,比complier少一步,因此更加轻量级,尽量用only进行开发
      
  webpack.base.conf.js起别名
  resolve: {
    extensions: ['.js', '.vue', '.json'] // 不用在导入时写后缀
      alias: {
          '@': resolve('src'),
          'pages': resolve('src/pages'),
          'components': resolve('src/components') // <img src="~components/..."> html要加~
     }
  }
  • vue-cli3 大大简化配置,移除文件根目录下的build和config配置文件夹,移除static,新增public文件夹,并将index.html移到public中 使用vue ui可视化配置
  cli3起别名
  vue.config.js
  module.exports = {
      configureWebpack: {
          resolve: {
            alias: {
                  'components': '@/components',
                  'pages': '@/pages'
              }    
          }
      }
  }

VUE ROUTER

  • 路由:路由决定数据包从来源到目的地的路径。转送:将输入端的数据转移到合适的输出端。路由中的路由表(映射表)决定了数据包的指向

  • 后端路由:每个网页有对应的URL,服务器根据URL进行正则匹配,交给相应的controller处理,将生成的HTML数据交给前端,前端模块交给后端人员,前端人员要开发,需要PHP或JAVA,逻辑与HTML混在一起。

  • 前端路由:后端只提供API,前端通过Ajax获取数据,通过JS将数据渲染到页面中。

  • 单页面富应用(SPA):在前后端分离的基础上加了一层前端路由。

history接口
  location.href = "http://192.168.1.101:8000/examples/urlChange" 锚点#
  history.pushState({}, '', '/foo') // "...8000/foo" 页面可以回退
  histtory.replaceState({}, '', '/foo') // "...8000/foo" 页面无法回退
  history.go(-1) <=> history.back() // href 后退一级 
  history.go(1) <=> history.forward() // href 前进一级
  • npm install vue-router --save 安装
  index.js
  import Vue from 'vue'
  import VueRouter from 'vue-router'
  const Message = () => import('../components/message') // 箭头函数无参数用() 函数体只有一句话可以省略 {} 和 return 路由懒加载
  const routes = [
      {path: '/',
      redirect: '/home'}, // 设置默认路径
      {path: '/home',
      component: () => import('../components/Home')} // 传统会在构建时将所有页面都打包在一个js文件,导致请求页面耗时,路由的懒加载 将路有对应的组件打包成对应的js代码块,只有在这个路由在被访问到的时候,才去加载对应的组件
      {path: '/home',
      components: Home,
      children: [
        {path: '',
           component: Message} // 嵌套路由设置默认路径
          {path: 'message',
           component: Message}, // /home/message
          {path: 'news',
           component: News} // /home/news
      ]},
      {path: '/about',
      components: About},
      {path: '/user/:id', // /user/123 动态路由 {{$route.params.id}} 123
      components: User}
  ] // 定义路由
  const router = new VueRouter({ // 创建路由实例
      routes,
      mode: 'history' // 改变路径的显示由hash变为HTML的history模式
  })
  export default router // 导出路由实例
main.js
  import router from './router'
  new Vue({
      el: '#app'
      router, // 挂载到vue实例
      render: h => h(App)
  })
  App.vue
  <router-link to="/home">首页 vue-router组件被渲染为a标签 tag: 指定被渲染成什么标签tag='li', replace: 无history记录,无法回退
  
  <router-link to="/about">关于
  <router-view/> 路由占位标签 会根据当前路径 动态渲染出不同的组件
  • 路由代码跳转
    this.$router.push('/home') 代码方式跳转到主页
- 参数传递
    params类型:
    /router/:id 在path后跟对应的值 /router/123, /router/abc
    query:
    对象中使用query的key作为传递方式 /router?id=123
    router方式传参
    <router-link :to="{path: '/profile'+123, query: {name: 'wang', age: '18'}}">
    JS代码方式传参
    this.router.push({ path: '/profile' + 123, query: {name: 'wang', age: 18}})
    获取参数
    $route.params // {"id": 123}
    $route.query // {name: 'wang', age: 18}
    this.$route 当前活跃的路由 { params:{id:"zhangsan"}, path:"/user/zhangsandf;''"}
    $router: VueRouter实例,想要导航道不同URL就用$router.push
    $route: 当前活跃的路由信息,可以获取name, path, query, params。。
  • 导航守卫:监听路由的进入和离开 beforeEach 路由即将改变前触发 afterEach 路由改变后触发
    改变网页的标题
    {path: 'home',
    component: 
    meta: {
        title: '首页'
    }}
    // 路由实例 to: 即将要进入的目标路由对象 from: 即将要离开的路有对象 next: 必须调用next方法,才能进入下一个钩子
    router.beforeEach((to, from, next) => {
        window.document.title = to.meta.title
        next()
    })
  • keep-alive 可以使被包含的组件保留状态,避免重新渲染

Vuex

  • Vuex是专门为Vue应用程序开发的状态管理模式。提供了在多个组件间共享状态的插件,保证所有的属性做到响应式。

  • 用户的登录状态,用户名称,头像,地理位置,商品的收藏,购物车中的物品可以放在其中。

  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--
        }
      }
  })
  export default store
  main.js
  import store from './store'
  new Vue({
      el: '#app',
      store,
      render: h => h(App)
  })
  使用Vuex
  this.$store.state.count
  this.$store.commit('increment') // 必须通过commit的方式来修改状态,为了让Vuex可以追踪状态的变化
  • state:单一状态树 Single Source of Truth 单一数据源

  • getters: 获取state中变异后的状态

    getters: {
        greaterAgesStus: state => {
            return state.students.filter(s => s.age >= 20)
        },
        greaterAgesCount: (state, getters) => {
            return getters.greaterAgesStus.length
        },
        stuById: state => { // getters默认不返回参数,如果要传递参数,只能让getters返回另一个函数 function stuById(state) { return function (id) {return ... }}
            return id => {
                return state.students.find(s=>s.id===id)
            }
        }
    }
  • mutation:更新store状态的唯一方式就是提交mutation,同步方法
mutations: {
    带参数
    decrement(state, n) {
        state.count -= n   // this.$store.commit('decrement', 2)
    }
    多个参数
    changeCount(state, paylocad) {
        state.count = payload.count  // this.$store.commit('changeCount', {count: 0}) 
    }                                // 或者 this.$store.commit({ type: 'changeCount', count: 100})
    updateInfo(state, payload) {
        state.info['height'] = payload.height // 这样赋值页面不会刷新,不是响应式
        Vue.set(state.info, 'height', payload.height) // 响应式
        state.info = {...state.info, 'height': payload.height} // 拓展运算符更新属性 响应式
    }
}

  • mutation常量类型 考虑到mutation的方法会越来越多,可以使用常量来定义mutation里事件的类型
  mutation-types.js
  export const UPDATA_INFO = 'UPDATE_INFO'
  vuex
  import * as types from './mutation-types'
  mutations: {
    [types.UPDATE_INFO](){
        
    }
  }
component
import {UPDATE_INFO} from "./store/mutation-types"
this.$store.commit(UPDATE_INFO, {height: 1.88})
  • Action 代替mutation进行异步操作 component dispatch => action commit => mutations => state render => component
actions: {
    increment(context, payload) { // context 上下文变量
        setTimeout(() => {
            context.commit('increment', payload)
        })
    }
}
this.store.dispatch('increment, {cCount, 5}') // 调用action方法时,用dispatch
action返回Promise
action: {
    increment(context) {
        return new Promise((resolve) => {
            ...
        })
    }
}
this.$store.dispatch('increment').then(res => {...}) // 因为返回的是Promise对象,可以使用它的then
  • Module 可以将store分割为多个模块,每个模块有自己的state,mutation。。。
const moduleA = { state: {..}, mutations: {..}, ...}
const moduleB = { state: {..}, mutations: {..}, ...} 
const store = new Vuex.Store({
    modules: {
        a: moduleA,
        b: moduleB
    }
})
store.state.a // moduleA状态
store.state.b // moduleB状态

项目结构

|- index.html
|- main.js
|- api
    |- ... # 抽取出API请求
    ...
|- store
    |- index.html # 组装模块并导出 store 的地方
    |- actions.js # 根级别的 action
    |- mutations
    |- modules
        |- cart.js # 子模块
        |- products.js
    

vue3

3.0六大亮点
-performance: 性能比vue2.x 快1.2 - 2倍
-Tree shaking support: 按需编译,体积比2.x更小
-Composition API:组合API(类似react hooks)
-Better TypeScript support:更好Ts支持
-Custom Renderer API:暴露了自定义渲染的API
-Fragment,Teleport(Protal),Suspense:更先进的组件
# Vue3.0如何变快的
- diff 算法优化 https://vue-next-template-explorer.netlify.app/
    Vue2中的虚拟dom是进行全量的对比
    Vue3新增了静态标记(PatchFlag)
    在与上次虚拟节点进行对比的时候,只对比带有patch flag的节点,并且可以通过flag信息得知当前节点要对比的具体内容
<div>
    <p>知播渔</p>
    <p>知播渔</p>
    <p>知播渔</p>
    <p>{{msg}}}</p>
</div>

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("p", null, "知播渔"),
    _createVNode("p", null, "知播渔"),
    _createVNode("p", null, "知播渔"),
    _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
  ]))
}
- hotStatic静态提升
    vue2中无论元素是否参与更新,每次都会重新创建,然后再渲染
    Vue3中对于不参与更新的元素,会做静态提升,只会创建一次,在渲染时直接复用即可
<div>
    <p>知播渔</p>
    <p>知播渔</p>
    <p>知播渔</p>
    <p>{{msg}}}</p>
</div>
静态提升之前:
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("p", null, "知播渔"),
    _createVNode("p", null, "知播渔"),
    _createVNode("p", null, "知播渔"),
    _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
  ]))
}
静态提升之后:
const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "知播渔", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "知播渔", -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/_createVNode("p", null, "知播渔", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _hoisted_1,
    _hoisted_2,
    _hoisted_3,
    _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
  ]))
}
- cacheHandlers事件侦听器缓存
    默认情况下onClick会被视为动态绑定,所以每次都会去追踪他的变化,但是是同一个函数,就直接缓存起来复用即可
事件监听缓存
<div>
  <button @click="onClick">按钮</button>
</div>

开启事件监听缓存之前:
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("button", { onClick: _ctx.onClick }, "按钮", 8 /* PROPS */, ["onClick"])
  ]))
}
开启事件监听缓存之后:
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("button", {
      onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args)))
    }, "按钮")
  ]))
}
注意点: 转换之后的代码, 大家可能还看不懂, 但是不要紧
        我们只需要观察有没有静态标记即可
        因为我们知道在Vue3的diff算法中, 只有有静态标记的才会进行比较, 才会进行追踪
- ssr渲染
    + 当有大量静态的内容时候,这些内容会被当做纯字符串推进一个buffer里面,
      即使存在动态的绑定,会通过模板插值嵌入进去。这样会比通过虚拟dmo来渲染的快上很多很多。
    + 当静态内容大到一定量级时候,会用_createStaticVNode方法在客户端去生成一个static node,
      这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。        
PatchFlags
附录: PatchFlags
export const enum PatchFlags {
  TEXT = 1,// 动态文本节点
  CLASS = 1 << 1, // 2  // 动态 class
  STYLE = 1 << 2, // 4 // 动态 style
  PROPS = 1 << 3, // 8 // 动态属性,但不包含类名和样式
  FULL_PROPS = 1 << 4, // 16 // 具有动态 key 属性,当 key 改变时,需要进行完整的 diff 比较。
  HYDRATE_EVENTS = 1 << 5, // 32 // 带有监听事件的节点
  STABLE_FRAGMENT = 1 << 6, // 64 // 一个不会改变子节点顺序的 fragment
  KEYED_FRAGMENT = 1 << 7, // 128 // 带有 key 属性的 fragment 或部分子字节有 key
  UNKEYED_FRAGMENT = 1 << 8, // 256 // 子节点没有 key 的 fragment
  NEED_PATCH = 1 << 9, // 512 // 一个节点只会进行非 props 比较
  DYNAMIC_SLOTS = 1 << 10, // 1024 // 动态 slot
  HOISTED = -1, // 静态节点
  // 指示在 diff 过程应该要退出优化模式
  BAIL = -2
}
vite
Vite是作者开发的一款意图取代webpack的工具,实现原理是利用ES6的import会发送请求去加载文件特性,拦截这些请求,做一些预编译,省去webpack冗长的打包时间
安装vite:npm install -g create-vite-app
利用vite创建vue3项目:create-vite-app projectName
安装依赖运行项目 cd projectName; npm install; npm run dev
组合API
import { ref } from 'vue' 
setup() {
    let count = ref(0) // 定义了count变量,初始值是0,发生改变后,Vue会自动更新UI
    // 组合API中定义方法直接定义
    function myFn() {
        count.value += 1
    }
    // 在组合API中定义的变量/方法,要想在外界使用,必须通过return暴露出去
    return { count, myFn }
}
<ul>
    <li v-for="(stu, index) in state.stus" :key="stu.id" @click="remStu(index)">    
</ul>
// 将方法抽离出去,数据和业务逻辑绑定
function useRemoveStudent() {
    // ref函数只能监听简单类型变化,不能监听复杂类型变化(对象/数组)
    let state = reactive({
        stus: [
            {id:1, name:'zs',age:10},
            {id:2, name:'ls',age:20},
        ]
    })
    function remStu(index) {
        state.stus = state.stus.filter((stu, inx) => idx !== index)
    }
    return {state, remStu}
}
setup {
    let { state, remStu } = useRemoveStudent()
    return { state, remStu }
}
// setup函数执行时机
beforeCreate: 表示组件刚刚被创建出来,组件的data和methods还没有初始化好
setup
Create: 组件刚刚被创建出来,并且data和methods已经初始化好
注意点:
    - 由于在执行setup函数时,还没有create生命周期方法,所以无法使用data和methods
    - 所以vue为了避免我们出错,直接将setup函数中的this修改为了undefined
    - setup函数只能是同步不能是异步的
// reactive
- reactive是vue3中提供实现响应式数据的方法
- 在vue2中响应式数据是通过defineProperty实现的,而在vue3中响应式数据时通过ES6的proxy实现的
注意点:
- reactive参数必须是对象(json/arr)
- 如果给reactive传递了其他对象,修改对象,界面不会刷新,如果想刷新,可以通过重新赋值的方式
setup() {
    let state = reactive({
        time: new Date()
    })
    function myFn() {
        // 直接修改以前的不会刷新
        state.time.setDate(state.time.getTime())
        // 重新赋值
        const newTime = new Date(state.time.getTime())
        newTime.setDate(state.time.getDate() + 1)
        state.time = newTime
    }
    return {state, myFn}
}
// 什么是ref
- ref和reactive一样,也是用来实现响应式数据的方法
- 由于reactive必须传递一个对象,如果只想让某个变量实现响应式会非常麻烦,vue3就提供了ref方法,实现对简单值的监听
ref本质
- ref底层本质还是reactive,系统会自动根据我们给ref传入的值转换成ref(xx) -> reactive({value: xx})
注意点
- 在vue中使用ref的值不用通过value获取,在js中使用ref值必须通过value获取
<p>{{age}}</p>
setup() {
    let age = ref(18)
    function myFn(){
        age.value = 666
    }
    return {age, myFn}
}
// ref 和 reactive的区别
如果在template里使用的是ref,vue会自动帮我们添加.value
如果在template使用的是reactive数据,vue不会自动帮我们添加.value
vue如何决定是否需要自动添加.value的
    vue在解析数据之前,会自动判断这个数据是否是ref类型的,如果是就自动添加.value,如果不是就不自动添加.value
vue如何判断当前的数据是否是ref类型
    通过当前数据的__v_ref来判断
    如果有这个私有属性,并且取值为true,就代表一个ref类型的数据
如何判断数据是ref还是reactive  -> isRef/isReactive方法
isRef(age) isReactive(age)
递归监听
默认情况下,无论是通过ref还是reactive都是递归监听
递归监听存在数据量较大,非常消耗性能的问题
setup() {
    let state = reactive({
         a:'a',
        gf:{
            b:'b',
            f:{
                c:'c',
                s:{
                    d:'d'
                }
            }
        }
    })
    function myFn() {
        // state.a = '1';
        // state.gf.b = '2';
        // state.gf.f.c = '3';
        // state.gf.f.s.d = '4';

        // state.value.a = '1';
        // state.value.gf.b = '2';
        // state.value.gf.f.c = '3';
        // state.value.gf.f.s.d = '4';
    }
}
// 非递归监听
shallowRef / shallowReactive
如何触发非递归监听属性更新界面
    shallowRef通过triggerRef触发
一般情况下,使用ref和reactive即可,只有在需要监听的数据量比较大的时候,才使用shallowRef/shallowReactive
ref -> reactive
ref(10) -> reactive({value: 10})
shallowRef -> shallowReactive
shallowRef(10) -> shallowReactive({value: 10})
setup(){
    // let state = shallowReactive({
    let state = shallowRef({
        a:'a',
        gf:{
            b:'b',
            f:{
                c:'c',
                s:{
                    d:'d'
                }
            }
        }
    });
    // state.value = {
    //     a:'1',
    //     gf:{
    //         b:'2',
    //         f:{
    //             c:'3',
    //             s:{
    //                 d:'4'
    //             }
    //         }
    //     }
    // }
    // state.value.a = '1';
    // state.value.gf.b = '2';
    // state.value.gf.f.c = '3';
    // state.value.gf.f.s.d = '4';
    state.value.gf.f.s.d = '4';
    注意:vue3只提供了triggerRef方法,没有提供triggerReactive方法,所以如果是reactive类型数据,是无法主动触发界面更新的
    triggerRef(state)
    注意:如果是shallowRef创建的数据,vue监听的是.value的变化,并不是第一层的变化,因为底层实质上value才是第一层
    console.log(state.value.gf.f.s)
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • VUE介绍 Vue的特点构建用户界面,只关注View层简单易学,简洁、轻量、快速渐进式框架 框架VS库库,是一封装...
    多多酱_DuoDuo_阅读 2,684评论 1 17
  • Vue 实例 属性和方法 每个 Vue 实例都会代理其 data 对象里所有的属性:var data = { a:...
    云之外阅读 2,198评论 0 6
  • 主要还是自己看的,所有内容来自官方文档。 介绍 Vue.js 是什么 Vue (读音 /vjuː/,类似于 vie...
    Leonzai阅读 3,324评论 0 25
  • 一、了解Vue.js 1.1.1 Vue.js是什么? 简单小巧、渐进式、功能强大的技术栈 1.1.2 为什么学习...
    蔡华鹏阅读 3,311评论 0 3
  • 以下内容是我在学习和研究Vue时,对Vue的特性、重点和注意事项的提取、精练和总结,可以做为Vue特性的字典; 1...
    科研者阅读 14,044评论 3 24