点亮你的 Vue 技术栈(三):「入门续文」 & 「生命周期」

863 阅读5分钟

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。

filter 过滤器

🚫Vue 3 中剔除了过滤器

过滤器常用于文本的格式化,可以用于插值表达式v-bind 属性绑定

<!-- 在插值表达式中调用 capitalize 过滤器对 message 进行过滤 -->
<p>{{ message | capitalize }}</p>

<!-- 在 v-bind 中通过"管道符"调用 formatId 过滤器,对 rawId 过滤 -->
<div :id="rawId | formatId"></div>

过滤器定义分两种:私有过滤器全局过滤器

私有过滤器

在 filters 节点下定义的过滤器称为"私有过滤器",它只能在当前 vue 实例所控制的 el 区域内使用。

<template>
  <div>
    <h2>{{ message | capitalize('hi', 'hello') }}</h2>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'hello Vue!',
    }
  },
  filters: {
    // 过滤器也可以传参
    capitalize(str, arg1, arg2) {
      // arg1和arg2分别是传入过滤器的两个参数
      console.log(arg1)
      console.log(arg2)
      // str是要过滤的内容
      return str.charAt(0).toUpperCase() + str.slice(1)
    },
  },
}
</script>

全局过滤器

如果希望在多个 vue 实例之间可以共享过滤器,那可以如下定义全局过滤器。

// main.js
Vue.filter('capitalize', (str) => {
  return str.charAt(0).toUpperCase() + str.slice(1)
})

🪁注:过滤器可以串联进行调用,且可传入参数

<!-- message先交由filterA处理,处理结果再交由filterB进行过滤 -->
<span>{{ message | filterA(arg1) | filterB }}</span>

watch 数据监视

watch 侦听器可以监视数据的变化,从而针对数据的变化做特定的操作。

🐖注意:监听的属性必须在 data 中存在

基本语法

<template>
  <div>
    <input type="text" v-model="username" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      username: '阿拉斯加湾',
    }
  },
  watch: {
    // username 在 data 中存在!
    username(newVal, oldVal) {
      // newVal: 当前值
      // oldVal: 旧值
      console.log('旧值: ' + oldVal)
      console.log('当前值: ' + newVal)
    },
  },
}
</script>

演示一下:

immediatedeep 选项

默认情况下,组件在初次加载完毕后不会调用 watch 侦听器(如上图),如果想让 watch 侦听器加载后立即被调用,则需要使用 immediate 选项。

export default {
  data() {
    return {
      username: '阿拉斯加湾',
    }
  },
  watch: {
    username: {
      // handler 是固定写法
      handler: function (newVal, oldVal) {
        console.log('新值: ' + newVal)
        console.log('旧值: ' + oldVal)
      },
      // 页面初次渲染好后,立即触发 watch 侦听器
      immediate: true,
    },
  },
}

效果大致如下:

出现 undefined 说明页面加载后立即被调用,immediate 成功应用✔


🔪如果 watch 侦听的是一个对象对象中的属性改变却无法被监听到。Vue 提供了对应的 deep 选项:

<template>
  <div id="app">
    <input type="text" v-model="user.name" />
    <input type="text" v-model="user.age" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: {
        name: 'w',
        age: 19,
      },
    }
  },
  watch: {
    user: {
      handler: function (val) {
        console.log(val.name)
        console.log(val.age)
      },
      immediate: true,
      deep: true,
    },
  },
}
</script>

演示:

但你会发现修改对象中的任一属性都会触发 watch 侦听,那如果只想侦听对象中的单个属性呢?

那就得使用字符串指定具体要侦听的属性:

<template>
  <div id="app">
    <input type="text" v-model="user.name" />
    <input type="text" v-model="user.age" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: {
        name: 'w',
        age: 19,
      },
    }
  },
  watch: {
    'user.name': {
      handler: function (val) {
        console.log('只侦听user.name属性: ' + val)
      }
    },
  },
}
</script>

演示:

经典案例

监听 username 值的变化,并使用 axios 发起 Ajax 请求,检测当前输入的用户名是否可用✔

watch: {
  async username(newVal) {
    const { data: result } = await axios.get('http://localhost:8090/api/findUser/' + newVal)
    console.log(result)
  },
}

computed 计算属性

🔍深入理解计算属性:Vue.js 计算属性的秘密

computed 作用是通过一系列的运算/操作后,最终得到一个属性值,运算/操作中所依赖的值一旦发生改变就会重新求值。

computed 还有一大特点:缓存计算结果(多处使用只会调用一次计算属性)

注意:

  • computed 中的属性不能与 data 中的属性同名,否则会报错。
  • 虽然 computed 在声明时被定义为方法,但本质却是一个属性

基本用法

案例一:计算全名

<template>
  <div id="app">
    <input type="text" v-model="firstName" /><br />
    <input type="text" v-model="lastName" />
    {{ fullName }}
  </div>
</template>

<script>
export default {
  data() {
    return {
      firstName: 'K',
      lastName: 'B',
    }
  },
  computed: {
    fullName() {
      return this.firstName + `·` + this.lastName
    },
  },
}
</script>

案例二:根据 RGB 动态变更底色

<template>
  <div id="app">
    R: <input type="text" v-model="r" /><br />
    G: <input type="text" v-model="g" /><br />
    B: <input type="text" v-model="b" />
    
    <div :style="{backgroundColor: rgb}">{{ rgb }}</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      r: '0',
      g: '0',
      b: '0',
    }
  },
  computed: {
    rgb() {
      return `rgb(${this.r}, ${this.g}, ${this.b})`
    },
  },
}
</script>

补充:watchcomputed 的区别

  • watch 支持异步,computed 不支持异步
  • watch 不支持缓存,computed 可以缓存计算结果

记住一个场景:只要一个属性是由其他属性计算而来的,这个属性依赖于其他属性,那么我们使用 computedwatch 一般只用于监听一个值/对象。

生命周期

简单来说,一个组件从创建到销毁所经历的各种状态,就是一个组件的生命周期。这个过程常常伴随着一些函数的子调用,我们把这些函数称为生命周期钩子函数

注意:

  • Vue 在执行过程中会自动调用 生命周期钩子函数,我们只需要提供这些提供函数即可。
  • 钩子函数名称都是 Vue 中规定好的了。

生命周期图示

官网:下图展示了实例的生命周期。你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。

beforeCreate()

在实例初始化之后,数据观测和 event / watcher 事件配置之前被调用

🛠注意:el 没有绑定作用域,此时无法获取 data 中的数据、methods 中的方法!

created()

🌀这是一个十分常用的生命周期,可以调用 methods 中的方法、改变 data 中的数据!但页面仍没有渲染出来。

常用场景:页面渲染之前获取数据的方法就是在 created() 中调用(登录系统时就需要显示信息)

created() {
  this.init()
},
methods: {
  // 初始化方法
  init() {
	// axios 请求
  }     
}

beforeMounted()

🌡el 绑定了作用域但未挂载,但是页面还没有真实数据。

mounted()

💝此时,vue 实例已经挂载到页面中(即数据已经渲染到页面上了),且可以获取 el 中的 DOM 元素,进行 DOM 操作。

beforeUpdate()

data 数据更新时会调用该钩子函数,发生在虚拟 DOM 重新渲染和打补丁之前。

🌈此处获取的数据是更新后的数据,但页面数据仍为旧数据,未更新。

updated()

🔍该钩子函数紧随 beforeUpdate() 函数,组件 DOM 已经更新,此时页面上已经是更新后的数据了。

beforeDestroy()

🍹组件销毁之前调用,此时 datamethods 等还没有被销毁,仍然可以调用。

使用场景:实例销毁之前,执行清理任务,比如清除定时器等。

destroyed()

🧫Vue 实例销毁后调用,解绑所有事件监听器、子实例等。

一览众山小

对生命周期有了一定的了解后,我们就再来回顾下生命周期图示(带注释讲解):

接下来定义并使用一下所有的生命周期函数:

export default {
  data() {
    return {
    }
  },
  methods: {
  },
  beforeCreate() {
    console.log('beforeCreated()')
  },
  created() {
    console.log('created()')
  },
  beforeMount() {
    console.log('beforeMount()')
  },
  mounted() {
    console.log('mounted()')
  },
  beforeUpdate() {
    console.log('beforeUpdate()')
  },
  updated() {
    console.log('updated()')
  },
  beforeDestroy() {
    console.log('beforeDestroy()')
  },
  destroyed() {
    console.log('destroyed()')
  },
}

更多:Vue 实例生命周期

ref 引用

🚫需求:我们想在不依赖于 jQuery 的情况下,获取 DOM 元素组件的引用。

🔍方案:使用 ref 引用辅助开发,每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象。

打印 this 试试(vue 实例)

引用 DOM 元素

基本使用

⭐案例 1:

<template>
  <div>
    <div ref="myDiv">DIV</div>
    <button @click="modifyStyle">修改DIV样式</button>
  </div>
</template>

<script>
export default {
  methods: {
    modifyStyle() {
      // 可用于DOM元素, 同理也可用于Vue组件!
      this.$refs.myDiv.style.backgroundColor = 'pink'
    }
  },
}
</script>

异步 DOM 更新: this.$nextTick(callback)

由案例 2 入手引出 this.$nextTick(callback)

👑案例 2:

<template>
  <div>
    <input type="text" v-if="inputVisible" ref="ipt" />
    <button v-else @click="showInput">展示input输入框</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      inputVisible: false,
    }
  },
  methods: {
    showInput() {
      // 显示 input 输入框
      this.inputVisible = true
      // 获取文本框的 DOM 引用,并调用 .focus() 时期自动获得焦点
      this.$refs.ipt.focus()
    }
  }
}
</script>

运行结果:

🔎 Q:提示报错信息 Cannot read properties of undefined (reading 'focus'),怎么会这样?

🏹 A:有认真看上文的生命周期就会明白,data 变化后,页面还没来得及重新渲染 Virtual DOM re-render and patch,就继续向下执行 this.$refs.ipt.focus() 了,显然此时的 ref="ipt" 指向的 input 元素还为渲染到页面 (undefined),所以无法调用 focus() 函数。

👑 解决办法:让 this.$refs.ipt.focus() 执行后延,延迟到页面重新渲染后再执行。

🚀 Vue 为组件提供了这么一个方法: this.$nextTick(cb),将 callback 回调推迟到下一个 DOM 更新周期之后执行。

🎨 简单来说:等组件的 DOM 更新完成之后,再执行 callback 回调函数,从而保证 callback 回调函数可以操作到最新的 DOM 元素

<template>
  <div>
    <input type="text" v-if="inputVisible" ref="ipt" />
    <button v-else @click="showInput">展示input输入框</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      inputVisible: false,
    }
  },
  methods: {
    showInput() {
      this.inputVisible = true
      // 推迟到下个 DOM 更新周期再执行
      this.$nextTick(() => {
        this.$refs.ipt.focus()
      })
    }
  }
}
</script>

执行结果:

没问题✔

引用组件实例

<template>
  <div>
    <my-counter ref="counterRef"></my-counter>
    <button @click="print">引用组件实例</button>
  </div>
</template>

<script>
export default {
  methods: {
    print() {
      console.log(this.$refs.counterRef)
    }
  },
}
</script>

自定义指令

私有指令

在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令

<template>
  <div>
    <span v-color='color'>自定义(私有)指令 v-color</span><br>
    <span v-color="'green'">自定义(私有)指令 v-color</span><br>
  </div>
</template>

<script>
export default {
  data() {
    return {
      color: 'red'
    }
  },
  // 自定义私有指令
  directives: {
    color: {
      // 只调用一次, 指令第一次绑定到元素时调用, 在这里可以进行一次性的初始化设置
      bind(el, binding) {
        console.log(binding);
        el.style.color = binding.value
      },
      // DOM重新渲染时调用
      update(el, binding) {
        el.style.color = binding.value
      }
    },
  }
}
</script>

演示结果:

🔍分析 binding 对象中的属性

🥊简写方式

如果 bindupdate 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式

directives: {
  // 对象格式!
  color: {
    bind(el, binding) {
      el.style.color = binding.value
    },
    update(el, binding) {
      el.style.color = binding.value
    }
  },
  // 函数格式!
  colors(el, binding) {
    el.style.color = binding.value
  }
}

全局指令

Directive Options

🍸全局自定义指令基本语法:

// 全局自定义指令(最全)
Vue.directive('directiveName', {
  bind(el, binding, vnode) {
    // el: 指令所绑定 DOM 元素, 可以直接用来操作 DOM
    // binding: 上文私有指令已详解
    // vnode: Vue 编译生成的虚拟节点
    // oldVnode: 上一个虚拟节点, 仅在 update 和 componentUpdated 钩子中使用
  },
  // inserted 钩子函数调用时, 当前元素已经插入页面中了, 也就是可以获取到父级节点
  inserted(el, binding, vnode) { },
  // DOM 重新渲染时
  update(el, binding, vnode, oldVnode) { },
  // DOM 重新渲染后
  componentUpdated(el, binding, vnode, oldVnode) { },
  // 只调用一次, 指令与元素解绑时调用
  unbind(el) {
    // 指令所在的元素在页面中消失时触发
  }
})

🚀如果你想在 bindupdate 时触发相同行为,而不关心其它的钩子:

// 如果你想在 bind 和 update 时触发相同行为,而不关心其它的钩子
Vue.directive('directiveName', function (el, binding) {
  // TODO
})

使用:全局指令同私有指令一致

ES6

let 关键字

ES6 新增了 let 关键字,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效

let 实际上为 JavaScript 新增了块级作用域

{
  let a = 10;
  var b = 1;
}

a // ReferenceError: a is not defined.
b // 1

for循环的计数器,就很合适使用let命令。

for (let i = 0; i < 10; i++) {
  console.log(i);
}

console.log(i);
//ReferenceError: i is not defined

const 常量

const 声明一个只读的常量。一旦声明,常量必须进行初始化且初始化的值就不能改变。且声明 const 不赋值就会报错。

const w = 1
w = 2   // Uncaught TypeError: "w" is read-only

const 实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动

所以对象 obj 不可被重新赋值,但其属性可以✔

const obj = {
  name: 'Q',
  age: 21
}
// 可以给其属性赋值
obj.name = 'w'
obj.age = 20

console.log(obj);   // {name: 'w', age: 20}

// 但不能给obj重新赋值
// Uncaught TypeError: "obj" is read-only
obj = {
  name: 'w',
  sex: 'M'
}

💌当我们修饰的标识符不会被再次赋值时,就可以使用 const 来保证数据的安全性。

// 场景: 发送 axios 请求向后端接口请求数据
async conditionalSelect() {
    // 获取到的 data 数据就是不会被再次赋值的常量!使用 const 保证其安全性。
    const { data: list } = await this.$axios.post('/selectList')
    this.list = list
}

数组常用操作

forEach

遍历数组

语法:

arr.forEach(function(currentValue, index, arr))
  • function():必需,数组中每个元素需要调用的函数
    • currentValue:必需,当前元素
    • index:可选,当前元素索引
    • arr:可选,当前元素所属数组对象

案例:

var colors = [1, 2, 3, 5]
var sum = 0
colors.forEach(function (currentValue, index, arr) {
    console.log('当前值: ' + currentValue + ', 索引: ' + index);
    sum += currentValue
})
console.log('和: ' + sum);

join

通过指定分隔符分隔数组

var arr = [1, 3, 2, 4]
var str = arr.join(',')
console.log(str);

reverse

反转数组

var nums = [1, 2, 3]
nums.reverse()
console.log(nums);

concat

连接两个/多个数组,返回连接后的数组

var colors = ["red", "green", "blue"]
var others = ["white", "black"]
var newColors = colors.concat(others)

console.log(newColors.toString());

push、unshift

push:在数组尾部添加元素,并返回新数组的长度

unshift:在数组头部添加元素,并返回新数组的长度

var colors = ["red", "green", "blue"]
colors.push(1)
colors.unshift(2)

console.log(colors);

pop、shift

pop:删除并返回数组的最后一个元素

shift:删除并返回数组的第一个元素

var colors = ["red", "green", "blue"]

console.log('pop(): ' + colors.pop())
console.log('shift(): ' + colors.shift())
console.log(colors);

slice

选取数组的一部分,并返回一个新数组

var fruits = ["Banana", "Orange", "Lemon", "Apple", "Mango"];
var citrus = fruits.slice(1, 3);
console.log(citrus);

some、every、filter

some: 检测数组元素中是否有元素符合指定条件。

every: 检测数值元素的每个元素是否都符合条件。

filter: 检测数值元素,并返回符合条件所有元素的数组

(1)some(function(item, index, array){ })

var num = [1, 3, 4, 6, 2, 7, 9]

// 只要有一个大于6,则打印true
flag = num.some((item, index, array) => {
    return (item > 6)
})

console.log(flag);    // true

(2)every(function(item, index, array){ })

var booleans = [
    { id: 1, name: "w", price: 11, state: true },
    { id: 2, name: "q", price: 12, state: true },
    { id: 3, name: "k", price: 13, state: false }
]

// state皆为true、result才为true
var result = booleans.every(item => booleans.state)
console.log(result);  // false

(3)filter()

var num = [1, 3, 4, 6, 2, 7, 9]

var newNums = num.filter((val, index, array) => {
    return (val > 5)
})

console.log(newNums);   // [6, 7, 9]

reduce

通过回调函数将数组元素计算为一个值(从左到右)

案例:

var list = [1, 2, 3, 4, 5]

var result = list.reduce(function (pre, cur, curIndex, arr) {
    return pre + cur
})

console.log(result);

splice

强大的数组操作方法

基本语法:

splice(index, count, item1, item2, ...)
  • index:起始位置,规定从何处添加/删除元素。
  • count:删除元素的个数
  • item1, item2, ..:要添加的新元素

常用操作:

  • 删除: splice(xx, 1)
  • 插入: splice(xx, 0, arg3, arg4, arg5..)
  • 替换: splice(xx, ?, arg3, arg4..)
var colors = ["red", "green", "blue"]

// start、deleteCount、number
// 从index=0的地方删除一个元素,然后添加一个"white",相当于red替换为white
var removed = colors.splice(0, 1, "white")
console.log(removed);  // red
console.log(colors);   // white, green, blue

colors.splice(1, 1, "red", "purple")
console.log(colors);   // white red purple blue

🌤更多JavaScript Array

❤️/ END / 如果本文对你有帮助,点个「赞」支持下吧。