vue 问题一览

550 阅读7分钟

Vue 是什么?Vue的优点?Vue与React的对比?

一套用于构建用户界面的 **渐进式框架**

模板引擎 & 组件

/**
 * 模板引擎
 *      - 数据
 *      - 模板
 *
 * 组件
 *      对具有特定对独立的或者可复用的模板的封装
 *
 *      Vue2中组件类别:
 *          - 根组件
 *              - 在一个Vue的应用中,有且仅有一个根组件,类似于HTML标签
 *              - new Vue => 应用
 *          - 可复用组件
 *              - 一个应用根组件中就是由N个可复用的组件组合而成的
 */
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

    <div id="app"></div>

    <script src="./js/vue.js"></script>
    <script>
        // 组件在创建的时候,可以接收一个配置参数,Vue内部就会根据这个配置参数(对象)来处理组件内部的一些逻辑(数据拦截,绑定,模板渲染)
        
        let app = new Vue({
            // data: 用来存储该组件需要使用到的数据
            data: {
                title: '欢迎大家'
            },
            // template: 模板
            template: `
                <div id="app">
                    <h1>test</h1>
                    <p>{{title}}</p>
                </div>
            `,
            // 模板渲染完成以后的DOM的挂载点 
            el: '#app'
        });

    </script>
</body>
</html>

组件实例对象 app

/**
 * app 就是一个组件实例对象,该对象提供组件的许多属性和方法
 *      _ 开头:组件内部私有属性和方法(不要去调用)
 *      $ 开头:组件对外提供的公有属性和方法
 *      其它:我们用户(定义组件创建组件的程序员们自定义的)
 */
<div id="app"></div>

let app = new Vue({
    el: '#app',
    data: {
        n: 1
    },
    template: `
        <div id="app">
            <h1>123</h1>
            <p>{{n}}</p>
        </div>
    `
});

console.log('app', app);

如果没有template or el 会发生什么呢?

1. 如果选项中没有 template
  但是有 el 选项,那么Vue 会把 el 元素的 outerHTML(包含自己) 作为 template
  let app = new Vue({
      el: '#app',
      data: {
          n: 1
      }
  });

 2. 如果没有 el,那么这个时候就是没有挂载点
   可以通过 $mount('#app') 来进行挂载

render 函数

html字符串模板 => vNode

    vNode: 虚拟节点
    Node: 真实节点,也是一个js对象

数据交换问题

后端有数据
    前端也会要用后端提供的数据

        1、后端的数据类型(数据结构定义)和前端(js)数据类型并不一致
        2、数据在传输过程中会转成二进制(没有什么数据类型)
   语言之间(平台之间、网络)需要通过不同的手段方式去转换交换的数据到当前这门语言对应一些数据结构,我们可以为这种交换数据定义一个通用格式(json)

dom

文档对象模型
  html 是一门语言
  javascript 也是一门语言
  
  js为了能够操作html这种结构,需要对html进行解析,解析成js方便操作的数据,把html解析成js中的对象,用js中的对象去描述html中每一个节点
  每个对象就和页面中的对应标签有一定的关联关系,当我们操作对象的时候,对象的变化会同步更新页面结构(浏览器)

比较差异 & 更新视图

真实的DOM Node结构很复杂
vue 内部自己会先创建一个对象,这个对象结构类似于原生DOM对象,但是这个对象结构比较简单,该对象的变化不会立即渲染视图,在这个过程我们可以去比较新的虚拟对象和原来的虚拟对象之间的差异,更新差异的部分

相关代码

let app = new Vue({
    el: '#app',
    data: {
        n: 1
    },
    // template: `
    //     <div id="app">
    //         <h1>111</h1>
    //         <p>{{n}}</p>
    //     </div>
    // `
    render(createElement) {
        let vNode = createElement(
            'div',
            {
                attrs: {
                    id: 'app'
                }
            },
            [
                createElement(
                    'h1',
                    '111'
                ),
                createElement(
                    'p',
                    this.n
                )
            ]
        );

        console.log('vNode', vNode);

        // patch(vNode1, vNode2);

        return vNode;
    }
});

数据到绑定 & v-model

默认( {{}},v-bind)等实现到数据绑定,都是单向:数据 => 视图,数据变化 => 视图渲染 而视图变化是不会更新数据的

<input type="text" v-model="title" />

数据 title 更新,视图中 inputvalue 就会更新。同时,当 input 中的 value 更新的时候,数据 title 也会更新。

⚠️:不是所有元素组件都支持 v-model 的,默认情况下,input、select 等可交互的表单元素支持。

模版 & 渲染:直接使用 render函数 性能比 html 要高

/*
    el:
        挂载点
    template:
        模板
    render: 
        渲染函数

    组件:对应用可复用的结构、样式、行为的封装


直接使用 render函数 性能比 html 要高
但是,实际上我们工作中还是写html要多一点
    - html比render写起来要方便
    - webpack => vue-loader => 把html编译成render函数,实际生产环境中直接使用的render函数

   render() => vNode => DOM => 挂载到指定的位置
*/
            
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
    <div id="app1"></div>

    <hr />

    <div id="app2"></div>

    <script src="./js/vue.js"></script>
    <script>

        // render
        // let app1 = new Vue({
        //     el: '#app1',
        //     render(h) {
        //         // 渲染函数
        //         // 返回一个 VNode 的对象
        //         // VNode: 虚拟节点
        //         // Vue 会根据虚拟节点进行渲染,得到真实的DOM节点

        //         let vNode = h(
        //             'div',
        //             {
        //                 attrs: {
        //                     id: 'app'
        //                 }
        //             },
        //             [
        //                 h(
        //                     'h1',
        //                     'test'
        //                 )
        //             ]
        //         );

        //         // console.log( vNode );
                   // vNode -> 真实node diff
        //         return vNode;
        //     }
        // });

        // template
        /*
            template String => ? => render() => vNode => html
        */
        let app2 = new Vue({
            el: '#app2',
            // tmeplate => vNode
            template: `
                <div id="app2">
                    <h1>test</h1>
                </div>
            `
        });
        app2.$mount( '#app2' );
        // app2.$mount( document.querySelector('#app2') );
        /**
         *  内部query函数,如果传入不是字符串,直接返回
         */


        // function render() {
        //     with(this){ //
        //         return _c(
        //             'div',
        //             {
        //                 attrs:{
        //                     "id":"app2"
        //                 }
        //             },
        //             [
        //                 _c(
        //                     'h1',
        //                     [_v("test")]
        //                 )
        //             ]
        //         )
        //     }
        // }

    </script>

    <script type="text/template" id="template1">
        <div id="app">
            <h1>test</h1>
        </div>
    </script>
</body>
</html>

源码解析流程

new Vue()


// 模板渲染流程
if (vm.$options.el) {
    vm.$mount(vm.$options.el);
}

vm.$mount => Vue.prototype.$mount = function(){}

el 不能是body和html这两个元素,内部会有警告




当确定了 el 以后,紧接着看的不是 template,先看是否存在 render 函数

优先级 : render => template => 会根据template的类型去处理模板的内容来源 => el 存在,el.outerHTML



获取到 template 内容以后:

template: 
有el: 字符串/#/nodeType(document.querySelector)
有el, 无template: 获取到outerHtml


ref = compileToFunctions(template);
返回一个对象 ref
ref.render
options.render = ref.render

通过 compileToFunctions 把字符串模板编译一个对象,该对象有一个render方法,这个方法就会作为组件的render方法


<!-- 模版解析及ast -->
compileToFunctions
模板解析
var compiled = compile(template, options);

parse 把 html => ast 对象
ast 抽象语法树 => 可生成各种代码 => (js / html / java / php)
var ast = parse(template.trim(), options);

generate 函数 把 ast 生成我们需要的代码
var code = generate(ast, options);

--------------------------------------------------------------------
字符串
function() {
    with(this){ //
        return _c('div',{attrs:{"id":"app2"}},[_c('h1',[_v("test")])])
    }
}

new Function();

function fn1() {
    console.log(123)
}
=> 
let fn1 = new Function('console.log(123)')
--------------------------------------------------------------------


关于createElemnet函数
- 一个直接调用,render函数里面的createElement
- 一个通过 template 生成

 vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };



流程

( html -> AST -> )渲染函数 -> vNode -> DOM

可复用组件

使用 Vue.component 方法来定义一个可复用的组件 - 可复用和new Vue 得到的对象基本是一致,只是有一些细节上差异 - 选项上也大致相同的,一些细节上有差异

Vue.component方法会返回的是一个Vue的子类

命名规则

数据

组件间数据通信

双向绑定

ref

slot

组件生命周期

动画库提供的生命周期点

一些框架或者库在完成它本身应该做的事情的同时,会对外提供一些接口来在框架或内容某个时间段去调用
  比如,一个动画库,它提供某些方式(回调函数),在运动的不同阶段执行这些回调

animation('div', {
  attrs: {
      widht: 100,
      height: 200
  },
  start() {

  },
  end() {

  }
});
// 这里的startend其实就是我们要说的生命周期钩子函数
startend就是这个动画库提供的生命周期点

组件生命周期

组件在创建 - 销毁这段生命过程中的周期 父组件更新,子组件无变化则不更新,如子组件有数据发生变化,会一起更新

mount/create 请求api的区别?

一般来说没太大区别,放在create 里 一般请求也会比渲染慢,而请求api的过程是异步的,
不会等待拉取完数据后再渲染。但有时候也会有差别,比如请求api确实很快,
那么这种时候mount等于就会再次渲染dom

用watch 还是用 updated / $nextTick ?

/**
 *  问题: 用watch 还是用 updated / $nextTick ?
 *
 *  场景: 数据变化做什么事情,用watch
 *  场景: 数据发生改变以后,同时渲染完成以后再去调用。获取渲染后的结果,dom结构等。  
 *  使用: updated / $nextTick
 */
new Vue({
    el: '#app',

    data: {
        a: 1,
        b: 1
    },

    // 生命周期钩子 Start ---------------------------------
    
    beforeCreate() {
        console.log('Vue', 'beforeCreate');
    },
    created() {
        console.log('Vue', 'created');
    },
	// 只会执行一次
    beforeMount() {
        console.log('Vue', 'beforeMount');
    },
    // 只会执行一次
    mounted() {
        console.log('Vue', 'mounted');
    },

    beforeUpdate() {
        console.log('Vue', 'beforeUpdate');
    },
    updated() {
        console.log('Vue', 'updated');
    },
	// 从页面中移除一个组件
    beforeDestroy() {
        console.log('Vue', 'beforeDestroy');
    },
    destroyed() {
        console.log('Vue', 'destroyed');
    },
    
    // 生命周期钩子 End ---------------------------------
    methods: {
        changeA() {
            this.a++;

            console.log('值:', this.a, this.$refs.aElement.innerHTML);

            // 数据发生改变以后,同时渲染完成以后再去调用
            this.$nextTick(() => {
                console.log('干活', this.$refs.aElement.innerHTML);
            });
        },
        changeB() {
            this.b++;
        }
    }
});

错误捕获

动态组件

keep-alive 按需加载 & 缓存

<component :is= 动态组件></component>

自定义指令

自定义指令 - 拖拽

过滤器

Vue基础 - 事件处理与响应式数据监听

1、模板与渲染

  • 每一个独立的组件模板有且仅有一个顶层父级元素。
  • 因为组件挂载的时候是使用解析生成的结构替换掉挂载点的元素,所以挂载点不推荐使用 htmlbody 这两个元素,所以我们通常会指定 body 中的一个空白元素作为整个应用的顶层组件容器。
1-1、el 选项

根组件可以指定 el 选项来绑定挂载点。

  • 自动调用 $mount
  • 指定 el 同时又没有指定 template ,则把 elouterHTML 作为 template
1-2、render 选项

先了解一下 虚拟 DOM

1-3、虚拟 DOM
1-3-1、原生 DOM 对象
  • 结构信息复杂
  • 操作耗时
  • 消耗资源
  • ……
1-3-2、Virtual DOM

通过原生的纯对象来间接描述一个真实 DOM 对象,只提供必要的信息,同时在需要更新的时候,会加入一些算法(diff)来比较上一次的 VDOM 与 更新后的 VDOM 之间的差异,找出确实需要更新点,最后再统一反馈去更新对应的真实 DOM。

1-4、render 函数

Vue 提供的一个用于直接返回 VDOM 的渲染函数,该函数的第一个参数是 Vue 内置的一个 VDOM 构建函数:createElement

createElement

1-4-1、执行流程

html -> AST -> 生成渲染函数 -> vNode -> DOM

2、数据定义

2-1、data 的对象模式
let data = {
  a: 1
};

let app1 = new Vue({
  data
});

let app2 = new Vue({
  data
});

app1.a = 100;
// app2 data 中的数据也会被修改
app2.a //100
2-2、data 的函数模式
let data = function() {
  return {
    a: 1
  }
}

let app1 = new Vue({
  data
});

let app2 = new Vue({
  data
});

app1.a = 100;
// app2 data 中的数据不会被修改
app2.a //1
2-3、命名空间
  • data、methods、computed、props 中定义的数据都会被挂载到组件实例上,以便访问,但同时也带来一个问题,它们之间在定义数据的时候就会出现冲突问题,所以在以上选项中定义数据的时候要格外注意。
  • 同时在 Vue 实例本身会有许多内置属性和方法,所以在定义用户数据的时候也要注意不要与 Vue 实例原有的内置属性和方法冲突。
    • _ 开始的为内部调用属性和方法,不建议用户代码直接调用。
    • $ 开始的为对外提供的 API ,用户代码可以调用,如:app.$data
2-4、响应式数据

Vue2 中响应式数据实现是建立在 Object.defineProperty 方法上,由于该方法的本身的一些特性,在拦截数据处理上会有一些问题需要注意:

  • 对象新增属性无法拦截。
  • 数组变动无法拦截:
    • 利用索引直接设置一个数组项时。
      • 解决:Vue.set()、(new Vue()).$set(),两者等价。
    • 修改数组的长度时。
      • 解决:方法变异(Vue 内部已处理)。
2-5、computed
2-5-1、简写

计算属性函数是一个 getter setter 函数,如果计算属性只有 getter 需求,则可以简写为一个函数。

2-5-2、缓存

计算属性的值依赖计算函数中依赖的其它响应式数据,如果依赖的其它响应式数据没有发生变化,计算属性的值可以缓存,得到结果是最近一次变化产生的值。

2-6、watch
2-6-1、多层监听

对于多层数据的监听,可以使用字符串+点语法。

watch: {
  'a.b.c': function() {
    //...
  }
}
2-6-2、深度监听

默认情况下,watch 只对当前指定的值进行一层监听,如果需要对对象进行深度监听,可以使用下面的形式:

watch: {
  a: {
    handler() {
      console.log('a deep');
    },
    deep: true
  }
}

3、指令

3-1、指令修饰符

一个指令可以包含的内容包括:

  • 指令名称
  • 指令值
  • 指令参数
  • 指令修饰符
<组件 指令:参数.修饰符1.修饰符2="值" />
.lazy

取代 input 监听 change 事件

.number

输入字符串转为有效的数字

.trim

输入首尾空格过滤

3-1-1、事件系列修饰符:
  • .stop

  • .prevent

  • .capture

  • .self

  • .once

  • .passive

3-2、:key 属性

默认情况下,在渲染 DOM 过程中使用 原地复用 ,这样一般情况下会比较高效,但是对于循环列表,特别是依赖某种状态的列表,会有一些问题,我们可以通过 :key 属性,来给每个循环节点添加一个标识。