vue

✍️ Tangxt ⏳ 2020-07-22 🏷️ 技巧

10-Vue 的常用技巧

文档不会说的内容

v-model 不是双向绑定

1)React 和 Vue 的区别是什么?

经典答法:

React 是单向数据流,而 Vue 则是双向绑定的

不过,这个答案是错的,但是你就是可以这样回答! -> 因为使用 React 的人,对 Vue 的了解,肯定是停留在以前的 Vue ,以前的 Vue 确实是双向绑定,但是现在的 Vue 是咩有双向绑定的,全部都是单向的

总之,去一个 React 的公司就告诉他,Vue 是双向绑定的,React 是单向绑定的,反之,去一个 Vue 的公司,就告诉他「Vue 1 是以双向绑定为主的,而 Vue 2 逐渐地就使用单向数据流了,而v-model是语法糖 」 -> 对不同的公司给出的答案是不一样的!

2)信息点

1、`v-model`不是双向绑定,它是在骗你的,其本质是个语法糖

input & #app 的关系就像是子组件与父组件一样

v-model 的本质

#app这个区域看到的变量、表达式啥的,都是#app这个父组件的数据哈! -> input这个子组件内部会触发input事件,并把用户输入的value带上来给父组件,父组件会监听到这个事件,所以$event.target.value的值就是用户输入的valuemsg2 = value就是在改父组件的msg2的值,这个msg2一变化,就会响应到:value="msg2"

2、能让你出现`bug`的测试

Vue 记不住光标的位置:

bug

DOM 变化情况:

view

可以看到,所谓的「双向绑定」其实是单向的……即 用户在页面上改 DOM -> 事件 -> msg2 = 用户输入的值 -> DOM 更新……注意这是跨父子组件的更新!而不是子组件的更新,直接就反映在子组件的 DOM 上了……如果真这样,那才是真正意义上的「双向绑定」!

话说,$event.target指的是不是那个触发事件的元素,如上边的span元素! -> e.currentTarget始终是监听事件者(xxx.addEventListener()),而e.target是事件的真正发出者(触发事件的那个元素

3)Vue 的「双向绑定」 `v-model`

从 Vue 0.x 开始,Vue 就用 v-model来实现「双向绑定」。

如:

data: {
  user: { name: 'xxx'}
}
<input v-model="user.name">

能实现以下两个绑定:

  1. user.name 的变化自动同步到 input.value
  2. input.value 的变化自动同步到 user.name

但是当 FLUX 单向数据流兴起之后:

Flux

Flux 是一种架构思想,专门解决软件的结构问题。它跟 MVC 架构是同一类东西,但是更加简单和清晰。Flux 存在多种实现(至少 15 种),常用 Facebook 的官方实现 -> Flux 会将一个应用分成四个部分。

Vue 的作者重新审视了「双向绑定」,发现「双向绑定」的一些问题之后,就更倾向于「单向绑定」了。v-model 被拆成两部分

data: {
    user: { name: 'frank'}
},
<input :value="user.name" @input="user.name = $event">

这样一来,数据的变更权就回到数据拥有者手上了。

用一个 contenteditable 的 bug 来理解单向数据流。

同时为了巩固这一模式,Vue 规定子组件不能修改父组件传给它的 props,一旦发现就会打印出一个警告。

我们以保存用户名来举例说明什么是单向数据流。

可以看出起优点有:

这些都是在双向绑定中很难做到的。

★豪车与嫩模

1)双向绑定的不好之处

用户改了数据,就会立刻生效 -> 数据是不受控制的!

双向绑定不好之处

而单向数据流则可以:

就像是 React 那样可以阻止用户对表单直接输入那样……

形象点来说:

双向绑定

单向绑定

注意,以前的双向绑定是真得双向绑定啊!没有什么事件在改data之前杵着,你往input里输入数据,就会立即改内存里的data,中间不带喘气的,而现在的v-model你看起来以为是双向绑定,但其实并不是啊!也是单向数据流呀! -> Vue 现在是通过两个单向绑定(:value="xxx"父到子传数据」和@input="xxx = $event.target.value"子到父传数据」)来模拟双向绑定呀!即 Vue 的双向绑定是假的双向绑定,所以这就是为啥我们可以拦截数据的原因!

A input维护自己的一个dataB input维护自己的一个datadata就是组件的状态! -> 我们为input文本框输入的内容也是input这个组件的状态! -> 双向绑定的感觉就像是把input#app这俩个组件融合到一起了……如<input type="text" v-model="msg1">,你输入内容,就是在改变input的状态,而这个状态情况能实时交给msg1,就像是这样<div>🟡🟡msg1🟡🟡</div>,你改msg1的值,就是在往input#text里输入内容,也就是在为msg1赋值……

input 看法

看看 0.x 的 Vue 的双向绑定:Handling Forms - vue.js

2)三种写法

不好的姿势:

demo1

selected是父元素拥有的,如果你直接在儿子里边改父元素的这个selected data 的话,那就是在搞双向绑定了! -> props过来的data可不是子组件的!只是父组件给子组件用的data

对了,子组件直接改selected的值,并不会也改了父组件的value值,即便 value 是引用类型的值也没事儿(疑问???)…… -> 这相当于是:子组件是一个函数,selected是形参,父组件传了个value过来作为子组件的实参,子组件的函数体,selected = 1 -> 问题来了,父组件传的value是个{name:'father'}呢? -> 经过我测试,或者说,对 JS 函数的基本认识,引用类型的参数值,可以在函数体内修改同一个引用地址……

demo1-1

如果是引用类型的value

引用类型的 value

通知爸爸去 update data:

demo2

通过$event可以拿到emit过来的1,注意这只能传一个值哈!

这种姿势,控制台没有警告,可以看到 vue 更倾向于单向数据流,而不是子元素直接改父元素传入的这个selected值!也就是父元素的那个value值(实参)!

vue1 用户要吃糖:

demo3

写法上看,对子组件的操作,数字化,也就是传了一个参数1 or 2给父组件value = 1 or 2

脱糖:

脱糖

我们点击button,冒泡 -> 发布-订阅 -> update:selected事件的callback触发执行!

3)`.sync` 修饰符

<div :selected.sync="x"> 等价于 <div :selected="x" @update:selected="x = $event">

例子:

Vue.component('child', {
    props: ['selected'],
    template: `
      <div>
        selected: 🟡🟡selected🟡🟡
        <hr>
        <button @click="$emit('update:selected',1)">1</button>
        <button @click="$emit('update:selected',2)">2</button>
      </div>
    `
})
var vm = new Vue({
    el: '#app',
    data: {
        value: 2
    },
    template: `
      <div>
        爸爸
        <div style="border: 1px solid red;">
        <child :selected="value" @update:selected="selected=$event"></child>  
        </div>
      </div>
    `
})

.sync 的作用和 v-model 一模一样,就是用双向绑定的语法糖,实现两个单向绑定。好处同上。

4)小结


★制造一个tabs轮子

写一个 tabs 功能是很好写的,但是造一个 tabs 轮子则是很难造的……

1)什么叫写一个`tabs`功能?

Tabs 功能

这叫写出一个 Tabs 功能,可不是一个 Tabs 轮子,因为如果人家要用你这个 Tabs 的话,需要拷贝代码,然后一点一点地修改代码,如 selectedTab === 'a'selectedTab === 'b'……

所以说,这不叫写一个 Tabs 轮子

似乎平时抄人家的代码就是在抄功能呀!

那么什么叫写一个 Tabs 轮子呢? -> 你造一个出来就知道了

2)如何造一个 Tabs 轮子

  1. 看开源的 UI 轮子库,如 element-ui……
  2. 设计 Tabs 用法的 API,只要人家引入了我们这个 Tabs 轮子,就能使用这样的template结构,就能实现这样的 Tabs 效果……

Tabs API

1、定义接口(`props`)

接口定义

2、组件雏形

组件雏形

图中的<slot>要闭合一下 -> <slot/>,不然会报错,虽然不闭合也可以正常显示内容……

3、如何只显示`tab1`?

不要用v-show来控制组件的显示和隐藏 -> 用class吧!

Q:孙子如何得到`selectedTab`的值?

爷爷(tabs)告诉爸爸(tabs-navs),爸爸告诉儿子 (tabs-navs-item)

可是爷爷如何告诉爸爸呢?

爸爸是slot,我们要拿到插入进来的tabs-navs

使用 mounted 钩子(created钩子是拿不到的) -> this.$children

由于,爸爸有两个,即tabs-navs & tabs-panes

所以我们需要区分它们俩 -> 定义这俩组件的时候,请添加一个name选项(这是name选项的意义之一,还有一个意义是使用 vue-devtools 来方便debugger

遍历this.$children -> vm.$options.name可获取tabs-navs or tabs-panes

如果是tabs-navs组件实例,那么就vm.selectedTab = this.selectedTab

我们vm.selectedTab,就是在给tabs-navs实例定义了一个叫selectedTabprop -> 所以tabs-navs的选项需要有个props:['selectedTab']

同理,爸爸 -> 儿子,也是爷爷 -> 爸爸这样一个过程

整个过程来看,孙子如何得到selectedTab的值的呢?

口口相传:爷爷 -> 爸爸 -> 儿子

测试,结果咩有正常运行

因为mounted的顺序,是从里到外的,即儿子先 append 在爸爸,然后再是爸爸 append 爷爷……最后是爷爷 append 到 app,即 mounted 到页面上!

而这意味着,我们本来以为mounted是从高处往下走的,即selectedTab这个数据从高处流向低处,但其实mounted是从低处往上走的!即低处的mounted是无法通过this.selectedTab拿到值的,因为此时并无水从高处流下来呀!

mounted 的顺序

虽然儿子等咩有 mounted 到页面上,但它们 mounted 上一级也是可以的,而它们 mounted 完之后,就处于组件的运行状态了!

created的顺序是从上往下流的!

所以这个思路得变了……

思路:

  1. 爷爷拿到了爸爸,让爸爸调用它自身的方法去设置selectedTab的值,所以爸爸的selectedTab就不能写在props里边了,而是写在data里边了!
  2. 同理爸爸也是如此处理,这样儿子就能拿到从高处流下来的「」了
  3. 测试:直接手动更改在tabs上的selectedTab1 or 2,会不会有相应的变化!

4、绑定事件,点击孙子会更新爷爷的`selectedTab`值吗?

不行呀!

儿子点击tab-navs-item(有@click) -> $emit的自定义事件,如xxx,是不会冒泡的…… -> 所以你得为儿子、爸爸、爷爷都得$on一个xxx事件 -> 到爷爷后,你得updated一下,因为selectedTab的值变化了,你得更新一下爷爷的selected值!不然,不会自动更新组件的状态!

孙子更新爷爷

孙子更新爷爷

5、其它人如何使用这个 Tabs 轮子?

代码:https://jsbin.com/zewuvir/edit?js,output

3)小结

★Vue 的渐进式

1)概述

Vue 的另一个大特点就是「渐进式」,意思就是你可以渐渐地用 Vue。而 React 几乎做不到这一点。

  1. 你可以继续操作 DOM
  2. 你可以很方便地做 SEO
  3. 你可以局部做单页面
  4. 你可以整体做单页面

2)信息点

补充:

SEO click

taobao

computedwatchmethods的区别

1)概述

1、触发时机

2、使用形式

其它看文档

2)信息点

3)小结

★测试

1)写一个 Tabs 组件

试着写一个 Tabs 组件,API 在以下两种风格中二选一:

<!-- 风格一: -->

<template>
  <el-tabs v-model="activeName" @tab-click="handleClick">
    <el-tab-pane label="用户管理" name="first">用户管理</el-tab-pane>
    <el-tab-pane label="配置管理" name="second">配置管理</el-tab-pane>
    <el-tab-pane label="角色管理" name="third">角色管理</el-tab-pane>
    <el-tab-pane label="定时任务补偿" name="fourth">定时任务补偿</el-tab-pane>
  </el-tabs>
</template>
<script>
  export default {
    data() {
      return {
        activeName: 'second'
      };
    },
    methods: {
      handleClick(tab, event) {
        console.log(tab, event);
      }
    }
  };
</script>
// 风格二:

<tabs class="tabs" :selectedTab.sync="x">
  <tabs-navs>
    <tabs-navs-item name="ChoiceQuestion">
        选择题
    </tabs-navs-item>
    <tabs-navs-item name="ClozeQuestion">
        填空题
    </tabs-navs-item>
  </tabs-navs>
  <tabs-panes class="tabs-panes">
    <tabs-panes-item name="ChoiceQuestion">
        1
    </tabs-panes-item>
    <tabs-panes-item name="ClozeQuestion">
        2
    </tabs-panes-item>
  </tabs-panes>
</tabs>

★了解更多

➹:Flux 架构入门教程 - 阮一峰的网络日志

★总结

★Q&A

1)vue 中的 `$event`?

不传参数:

<button v-on:click="click">click me</button>

click(event) {
    console.log(typeof event);    // object MouseEvent
}

给括号不传参数:

<button v-on:click="click()">click me</button>
click(event) {
    console.log(typeof event);    // undefined
}

给括号也传参数:

<button v-on:click="click($event, 233)">click me</button>
click(event, val) {
    console.log(typeof event);    // object
}

所以:

  1. 使用不带圆括号的形式,event 对象将被自动当作实参传入;
  2. 使用带圆括号的形式,我们需要使用 $event 变量显式传入 event 对象
  3. event是个关键字!

可假如我们这样呢:

event

event失效的姿势是,把event作为形参,然后不给$event实参!

总之,$event就是MouseEvent,如果你的handler没有用event作为形参,你也咩有传入$event实参,那么获取MouseEvent的姿势就是event

对了,当子组件$emit('enlarge-text', 0.1),父组件v-on:enlarge-text="postFontSize += $event"时,通过 $event 可以访问到被抛出的这个0.1值 -> 这其实类似于我们原生点击一个button时,默认就会有一个e作为第一个参数传给callback一样,同理0.1这个值也是第一个参数!

➹:vue.js - 奇怪的 event 对象 - 简书

➹:使用事件抛出一个值

➹:深入理解 e.target 与 e.currentTarget - 掘金

2)`v-show` 和 `v-if` 的区别?

➹:v-if 和 v-show 的区别 - 掘金