vue总结

304 阅读3分钟

1. v-if和v-for哪个优先级更高?如果两个同时出现,应该怎么优化得到更好的性

源码中找答案 位置:compiler/codegen/index.js

    <div v-for="item in items" v-if="condition">

做个测试如下

    <!DOCTYPE html>
    <html> 
    <head><title>Vue事件处理</title></head>
    <body> 
        <div id="demo">
        <h1>v-for和v-if谁的优先级高?应该如何正确使用避免性能问题?</h1> 
        <!-- <p v-for="child in children" v-if="isFolder">{{child.title}}</p> --> 

        <template v-if="isFolder"> 
            <p v-for="child in children">{{child.title}}</p>
        </template> 
    </div> 

    <script src="https://lib.baomitu.com/vue/2.5.2/vue.js"></script> 

    <script> 
        // 创建实例 
        const app = new Vue({
            el: '#demo', 
            data() { 
                return { 
                    children: [
                        {title:'foo'},
                        {title:'bar'}, 
                    ] 
                } 
            }, 

            computed: { 
                isFolder() { 
                    return this.children && this.children.length > 0
                } 
            }, 
        }); 

        console.log(app.$options.render); 

    </script> 
    </body> 
    </html>
  • 两者同级时,渲染函数如下:
    (function anonymous( 
    ) { 
    with(this){return _c('div',{attrs:{"id":"demo"}},[_c('h1',[_v("v-for和v-if谁的优先 级高?应该如何正确使用避免性能问题?")]),_v(" "), 
    _l((children),function(child){return (isFolder)?_c('p', [_v(_s(child.title))]):_e()})],2)} 
})

可以看到_l包含isFolder的条件判断.

  • 两者不同级时,渲染函数如下:
    (function anonymous( 
    ) { 
    with(this){return _c('div',{attrs:{"id":"demo"}},[_c('h1',[_v("v-for和v-if谁的优先 级高?应该如何正确使用避免性能问题?")]),_v(" "), 
    (isFolder)?_l((children),function(child){return _c('p', [_v(_s(child.title))])}):_e()],2)} 
    })

可以看出先判断了条件再看是否执行_l

结论

  1. 显然v-for优先于v-if被解析
  2. 如果同时出现,每次渲染都会先执行循环再判断条件,无论如何循环都不可避免,浪费了性能
  3. 要避免出现这种情况,则可在外层嵌套template,在这一层进行v-if判断,然后在内部进行v-for循环
  4. 如果条件出现在循环内部,可通过计算属性提前过滤掉那些不需要显示的项

2. Vue组件data为什么必须是个函数而Vue的根实例则没有此限制?

源码中找答案 位置:src\core\instance\state.js - initData()

函数每次执行都会返回全新data对象实例

测试代码如下:

    <!DOCTYPE html> <html> 
    <head>
    <title>Vue事件处理</title> </head>
    <body>
        <div id="demo"> 
            <h1>vue组件data为什么必须是个函数? </h1>
            <comp></comp> 
            <comp></comp> 
        </div> 
    <script src="https://lib.baomitu.com/vue/2.5.2/vue.js"></script> 
    <script> 
        Vue.component('comp', { 
            template:'<div @click="counter++">{{counter}}</div>',  data: {counter: 0} 
        }) 

        // 创建实例 
        const app = new Vue({ 
            el: '#demo', 
        }); 
    </script> 
    </body> 
</html>

image.png

结论

Vue组件可能存在多个实例,如果使用对象形式定义data,则会导致它们共用一个data对象,那么状态变更将会影响所以组件实例,这是不合理的;采用函数形式定义,在initData时会将其作为工厂函数返回全新 data 对象,有效规避多实例之间状态污染问题。而在Vue根实例创建过程中则不存在该限制,也是因为根实例只能有一个,不需要担心这种情况。

3. 你知道vue中key的作用和工作原理吗?说说你对它的理解。

源码中找答案 位置:src\core\vdom\patch.js - updateChildren()

测试代码如下:

    <!DOCTYPE html> <html> 
    <head>
    <title>03-key的作用及原理?</title> </head> 
    <body>
        <div id="demo"> 
            <p v-for="item in items" :key="item">{{item}}</p>
       </div> 
    <script src="https://lib.baomitu.com/vue/2.5.2/vue.js"></script>
    <script>
        // 创建实例 
        const app = new Vue({ 
            el: '#demo', 
            data: { items: ['a', 'b', 'c', 'd', 'e'] },
            mounted () { 
                setTimeout(() => {
                    this.items.splice(2, 0, 'f')
                }, 2000); 
            }, 
        }); 
    </script> 
</body> 
</html>

上面案例重现的过程如下

image.png

  • 不使用 key

image.png

  • 如果使用key
    // 首次循环 patch A
    A B C D E
    A B F C D E
    
    // 第二次循环 patch
    B C D E
    B F C D E
    
    // 第三次循环 patch
    C D E
    F C D E
    
    // 第四次循环 patch
    C D
    F C D
    
    // 第五次循环 patch
    C
    F C
    
    // oldCh全部处理结束,newCh中剩下的F, 创建 F 并插入到 C 前面

结论

  1. key 的作用主要是为了高效的更新虚拟DOM,其原理是 vue 在 patch 过程中通过 key 可以精确判断两个节点是否同一个,从而避免频繁更新不同元素,使得整个 patch 过程更加高效, 减少 DOM 操作量,提高性能。
  2. 另外,若不设置 key 还可能在列表更新时引发一些隐蔽的 bug
  3. vue 中在使用相同标签名元素的过渡切换时,也会使用到 key 属性,其目的也是为了让 vue 可以区分它们,否则 vue 只会替换其内部属性而不会触发过渡效果。

4. 你怎么理解 vue 中的 diff 算法?

image.png

源码分析1:必要性,lifecycle.js - mountComponent() 

组件中可能存在很多个 data 中的 key 使用

源码分析2:执行方式,patch.js - patchVnode()

patchVnode 是 diff 发生的地方。整体策略:深度优先,同层比较

源码分析3:高效性。patch - updateChildren()

测试代码:

    <!DOCTYPE html> <html> 
    <head> 
        <title>Vue源码剖析</title> 
        <script src="https://lib.baomitu.com/vue/2.5.2/vue.js
    </head> 
    <body> 
        <div id="demo">
            <h1>虚拟DOM</h1>
            <p>{{foo}}</p> 
        </div> 
    <script> 
        // 创建实例 
        const app = new Vue({
            el: '#demo',
            data: { foo: 'foo' }, 
            mounted() {
                setTimeout(() => {
                    this.foo = 'fooooo'         
                }, 1000); 
            } 
        }); 

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

总结

  1. diff 算法是虚拟 DOM 技术的必然产物:通过新旧虚拟 DOM 作对比 (即 diff),将变化的地方更新在真实 DOM 上;另外,也需要 diff 高效的执行对比过程,从而降低时间复杂度为 O(n)。
  2. vue 2.x 中为了降低 Watcher 粒度,每个组件只有一个 Watcher 与之对应,只有引入 diff 才能精确找到发生变化的地方。
  3. vue 中 diff 执行的时刻是组件实例执行其更新函数时,它会对比上一次渲染结果 oldVnode 和新的渲染结果 newVnode, 此过程称为 patch。
  4. diff 过程整体遵循深度优先、同层比较的策略;两个节点之间比较会根据它们是否拥有子节点或者文本节点做不同操作;比较两组子节点是算法的重点,首先假设头尾点可能相同做4次对比尝试,如果没有找到相同节点才按照通用方法遍历查找,查找结束再按照情况处理剩下的节点;借助 key 通常可以非常精确找到相同节点,因此整个 patch 过程非常高效。

5.谈一谈对 vue 组件化的理解?

回答总体思路:

组件化定义、优点、使用场景和注意事项等方面展开陈述,同时要强调 vue 中组件化的一些特点

  • 源码分析1: 两种组件定义方式
    // 组件定义 
    Vue.component('comp', { 
        template: '<div>this is a component</div>' 
    })

组件定义,src\core\global-api\assets.js

    <template> 
        <div> 
            this is a component 
        </div> 
    </template>

vue-loader 会编译 template 为 render 函数,最终导出的依然是组件配置对象。

  • 源码分析2:组件化优点 文件位置:lifecycle.js - mountComponent()

组件、Watcher、渲染函数和更新函数之间的关系

  • 源码分析3: 组件化实现

    1. 构造函数:src\core\global-api\extend.js

    2. 实例化及挂载,src\core\vdom\patch.js - createElm()

总结

  1. 组件是独立和可复用的代码组织单元。组件系统是 vue 核心特性之一,它使开发者使用小型、独立和通用可复用的组件构建大型应用。

  2. 组件化开发能大幅提高应用开发效率、测试性、复用性等。

  3. 组件使用按分类有:页面组件、业务组件、通用组件。

  4. vue 的组件是基于配置的,我们通常编写的组件是组件配置而非组件,框架后续会生成其构造函数,它们基于 VueComponent,扩展于 vue。

  5. vue 中常见组件化技术有:属性 prop,自定义事件,插槽等,它们主要用于组件通信、扩展等。

  6. 合理的划分组件,有助于提升应用性能。

7.组件应该是高内聚、低耦合的。

  1. 遵循单向数据流的原则。

6. 谈一谈对 vue 设计原则的理解?

在 vue 的 官网 上写着大大的定义和特点:

  • 渐进式 JavaScript 框架

  • 易用、灵活和高效 所以阐述此题的整体思路按照这个展开即可。

  • 渐进式 JavaScript

    • 与其他大型框架不同的是,vue 被设计为可以自底向上逐层应用。vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或者既有项目整合。另一方面,当与 现代化的工具链 以及各种 支持类库 结合使用时,vue 也能完全能够为复杂的单页面应用提供驱动。

image.png

  • 易用性
    • vue提供数据响应式、声明式模板语法和基于配置的组件系统等核心特性。这些使我们只需要关注应用的核心业务即可,只要会写 js、html、css 就能轻松编写 vue 应用。
  • 灵活性
    • 渐进式框架的最大优点就是灵活性,如果应用足够小,我们可能仅需要 vue 核心特性即可完成功能;随着应用规模不断扩大,我们才可能逐渐引入路由、状态管理、vue-cli等库和工具,不管是应用体积还是学习难度都是一个逐渐增加的平和曲线。
  • 高效性
    • 超快的虚拟 DOM 和 diff 算法使我们的应用拥有最佳的性能表现。
    • 追求高效的过程还在继续,vue3 中引入 Proxy 对数据响应式改进以及编译器中对静态内容编译的改进都会让 vue 更加高效。

7. vue 中组件之间的通信方式?

组件可以有以下几种关系:

image.png

A-B、B-C、B-D 都是父子关系

C-D 是兄弟关系

A-C、A-D 是隔代关系

  • 不同使用场景,如何选择有效的通信方式? vue 组件中通信的几种方式?
    1. props
    2. $emit/$on 事件总线
    3. vuex
    4. $parent/$children
    5. $attr/$listeners
    6. provide/inject
  • vue 中组件之间通信?

    常见使用场景可分为三类:

    • 父子组件通信
    • 兄弟组件通信
    • 跨层组件通信
    1. props

    父组件 A 通过 props 向子组件 B 传递值; B 组件传递 A : 组件通过 $emit, A 组件通过 v-on/@ 触发 1.1. 父组件=>子组件传值

    // 父组件 
    <template> 
        <div id="app"> 
            <Child v-bind:child="users"></Child> //前者自定义名称便于子组件调用,后者要传递 数据名 
        </div> 
    </template> 
    <script> 
    import Child from "./components/Child" //子组件 
    export default { 
        name: 'App', 
        data(){ 
            return{
                users:["Eric","Andy","Sai"]
            } 
        }, 
        components:{
            "Child":Child 
        } 
    } 
</script> 
 
// 子组件 
<template> 
    <div class="hello"> 
        <ul> 
            <li v-for="item in child">{{ item }}</li> //遍历传递过来的值渲染页面        
        </ul> 
    </div> 
</template> 
<script> 
    export default { 
        name: 'Hello World', 
        props:{ 
            child:{           //这个就是父组件中子标签自定义名字
              type:Array,     //对传递过来的值进行校验 
              required:true   //必添 
            } 
        } 
    } 
</script>
            

总结

父组件通过 props 向下传递数据给子组件

1.2 子组件=> 父组件传值

// 子组件 Header.vue 
<template> 
    <div> 
       <h1 @click="changeTitle">{{ title }}</h1> //绑定一个点击事件 
    </div> 
</template> 
<script> 
    export default { 
      name: 'header', 
      data() { 
        return { 
          title:"Vue.js Demo" 
        } 
      }, 
      methods:{ 
        changeTitle() { 
          this.$emit("titleChanged","子向父组件传值"); //自定义事件  传递值“子向父组件 传值” 
        } 
      } 
    } 
</script>

// 父组件 
<template> 
  <div id="app"> 
    <header v-on:titleChanged="updateTitle"></header>  //与子组件titleChanged自定义事件保持一致 
    // updateTitle($event)接受传递过来的文字 
    <h2>{{ title }}</h2> 
  </div> 
</template> 
<script> 
import Header from "./components/Header" 
    export default { 
      name: 'App', 
      data(){ 
        return{ 
          title:"传递的是一个值" 
        } 
      }, 
       methods:{ 
        updateTitle(e){   //声明这个函数 
          this.title = e; 
        } 
      }, 
      components:{ 
       "app-header":Header, 
      } 
    } 
</script>

总结

子组件通过events给父组件发送消息,实际上就是子组件把自己的数据发送到父组件。

  1. $emit / $on => $bus

vue实例 作为事件总线(事件中心)用来触发事件和监听事件,可以通过此种方式进行组件间通信包括: 父子组件、兄弟组件、跨级组件

例:

创建 bus 文件

import Vue from 'vue' 

export defult new Vue()
// gg组件 
<template id="a"> 
  <div> 
    <h3>gg组件</h3> 
    <button @click="sendMsg">将数据发送给dd组件</button>   </div> 
</template> 
<script> 
import bus from './bus' 
export default { 
    methods: { 
        sendMsg(){
            bus.$emit('sendTitle','传递的值')
        }    
    }
} 
</script>
// dd组件 
<template> 
    <div>
        接收gg传递过来的值:{{msg}} 
    </div> 
</template> 
<script> 
import bus from './bus' 
export default { 
    data(){ 
        return { 
            mag: '' 
        } 
    } 
    mounted(){ 
        bus.$on('sendTitle',(val)=>{
            this.mag = val
        }) 
    } 
} 
</script>
  1. vuex

image.png

3.1 vuex 介绍

  • vuex实现了一个单向数据流,在全局拥有一个 State 存放数据,当组件要更改 State 中的数据时,必须通过 Mutatuion 提交修改信息,Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或者批量的同步操作需要走 Action,但 Action 也是无法直接修改 State 的,还是需要通过 Mutation 来修改 State 的数据。最后,根据 State 的变化,渲染到视图上。

3.2 vuex 中核心概念

  • state: vuex 的唯一数据源,如果获取多个 state,可以使用 ...mapState。

    export const store = new Vuex.Store({    
    // 注意Store的S大写 
    <!-- 状态储存 --> 
       state: { 
            productList: [
               {
                   name: 'goods 1',
                   price: 100 
               } 
    

       ]     }  }) ```

  • getter: 可以将 getter 理解为计算属性,getter 的返回值根据他的依赖缓存起来,依赖发生变 化才会被重新计算。
  import Vue from 'vue'
  import Vuex from 'vuex';
  Vue.use(Vuexexport const store = new Vuex.Store({  
        state: { 
            productList: [ 
                { 
                     name: 'goods 1', 
                     price: 100 
                }, 
            ] 
        }, 
        // 辅助对象 mapGetter 
        getters: { 
            getSaledPrice: (state) => { 
                let saleProduct = state.productList.map((item) => {               
                  return { 
                        name: '**' + item.name + '**',
                        price: item.price / 2 
                    } 
                }) 
                return saleProduct; 
            } 
        } 
 })
   // 获取getter计算后的值 
   export default { 
          data () { 
             return { 
                  productList : this.$store.getters.getSaledPrice
             } 
           } 
     }
  • mutation: 更改 vuex 的 state 中唯一的方是提交 mutation 都有一个字符串和一个回调函数。 回调函数就是使劲进行状态修改的地方。并且会接收 state 作为第一个参数 payload 为第二个参 数,payload 为自定义函数,mutation 必须是同步函数。
     // 辅助对象 mapMutations 
     mutations: { 
            <!-- payload 为自定义函数名--> 
         reducePrice: (state, payload) => { 
                 return state.productList.forEach((product) => {            
                  product.price -= payload; 
                 })
            } 
     } 
     <!-- 页面使用 --> 
     methods: { 
            reducePrice(){ 
                this.$store.commit('reducePrice', 4)    
         } 
     }
  • action: action 类似 mutation 都是修改状态,不同之处

    action提交的mutation不是直接修改状态

    action可以包含异步操作,而mutation不行

    action中的回调函数第一个参数是context,是一个与store实例具有相同属性的方法的对象 

    action通过store.dispatch触发,mutation通过store.commit提交

        actions: {    
        // 提交的是mutation,可以包含异步操作 
             reducePriceAsync: (context, payload) => { 
                   setTimeout(()=> { 
                       context.commit('reducePrice', payload);  // reducePrice为上一步 mutation中的属性 
                  },2000) 
              } 
         }
         
        <!-- 页面使用 --> 
        // 辅助对象 mapActions 
        methods: { 
              reducePriceAsync(){ 
                   this.$store.dispatch('reducePriceAsync', 2)
             }, 
        }
  • module: 由于是使用单一状态树,应用的所有状态集中到比较大的对象,当应用变得非常复杂时,store对象就有可能变得相当臃肿。为了解决以上问题,vuex允许我们将store分割成模块,每个模块拥有自己的state, mutation, action ,getter, 甚至是嵌套子模块从上至下进行同样方式分割。
    const moduleA = { 
        state: {...}, 
        mutations: {...}, 
        actions: {...}, 
        getters: {...} 
    } 

    const moduleB = { 
        state: {...}, 
        mutations: {...}, 
        actions: {...}, 
        getters: {...} 
    } 

    const store = new Vuex.Store({    
         a: moduleA, 
         b: moduleB 

    }) 
    
    store.state.a 
    store.state.b

3.3 vuex中数据存储 localStorage

  • vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之 后,如果localStorage里有保存的数据,取出来再替换store里的state。

例:

let defaultCity = "上海" 
try {     
  // 用户关闭了本地存储功能,此时在外层加个try...catch 
  if (!defaultCity){
  // 复制一份 
        defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))       
  } 
}catch(e){ 
    console.log(e) 
} 

export default new Vuex.Store({ 
  state: { 
    city: defaultCity 
  }, 
  
  mutations: { 
    changeCity(state, city) { 
      state.city = city 
      try { 
      window.localStorage.setItem('defaultCity', JSON.stringify(state.city));   // 数据改变的时候把数据拷贝一份保存到localStorage里面 
      } catch (e) {} 
    } 
  } 
})

注意:vuex里,保存的状态,都是数组,而localStorage只支持字符串,所以需要用JSON转换

JSON.stringify(state.subscribeList)// array -> string 

JSON.parse(window.localStorage.getItem("subscribeList"))// string -> array

  1. $attr/$listeners 4.1 简介

    多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此Vue2.4 版本提供了另一种方法----attrs/attrs/listeners

  • $attrs: 包含了父作用域中不被 prop 所识别(且获取)的特性绑定(class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父级作用域的绑定(class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。
  • $listeners: 包含了父作用域中的(不含 native 修饰器的)v-on 事件监听。它可以通过 v-on="$listeners" 传入内部组件。

例:

// index.vue 
<template> 
  <div> 
    <h2>王者峡谷</h2> 
    <child-com1 :foo="foo" :boo="boo" :coo="coo" :doo="doo" title="前端工匠"> </child-com1> 
  </div> 
</template> 
<script> 
    const childCom1 = () => import("./childCom1.vue"); 
    export default { 
      components: { childCom1 }, 
      data() { 
        return { 
          foo: "Javascript", 
          boo: "Html", 
          coo: "CSS", 
          doo: "Vue" 
        }; 
      } 
    }; 
</script>
//childCom1.vue 
<template class="border">   
    <div> 
        <p>foo: {{ foo }}</p>
        <p>childCom1的$attrs: {{ $attrs }}</p> 
        <child-com2 v-bind="$attrs"></child-com2> 
    </div> 
</template> 

<script> 
    const childCom2 = () => import("./childCom2.vue"); 
    export default { 
      components: { 
        childCom2 
      }, 
      inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性       
      
      props: { 
        foo: String // foo作为props属性绑定 
      }, 
      
      created() { 
        console.log(this.$attrs);  
        // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }    
      } 
    }; 
</script>
// childCom2.vue 
<template> 
  <div class="border"> 
    <p>boo: {{ boo }}</p> 
    <p>childCom2: {{ $attrs }}</p> 
    <child-com3 v-bind="$attrs"></child-com3> 
  </div> 
</template> 

<script> 
const childCom3 = () => import("./childCom3.vue"); 
export default { 
  components: { 
    childCom3 
  }, 
  inheritAttrs: false, 
  props: { 
    boo: String 
  }, 

  created() { 
    console.log(this.$attrs);  
    // {"coo": "CSS", "doo": "Vue", "title": "前端工匠" }   } 
  }; 
</script>
// childCom3.vue 
<template> 
  <div class="border">
    <p>childCom3: {{ $attrs }}</p>
  </div> 
</template> 

<script> 
    export default { 
      props: { 
        coo: String,
        title: String     
      } 
    }; 
</script>

所示 $attrs 表示没有继承数据的对象,格式为{属性名:属性值}。vue2.4 提供了 $attrs$listeners 来传递数据与事件,跨级组件之间的通信变得更简单。

简单的来说:$attrs$listeners 是两个对象,$attrs 里存放的是 父组件中绑定的非 Props 属性, $lsteners 里存放的是父组件中绑定的非原生事件。

  1. provide/inject

5.1 简介

  • vue2.2 新增 API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层级有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过 provide 来提供变量,然后在子孙组件中通过 inject 来注入变量。
  • provide / inject API 主要解决了跨级组件之间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立一种主动提供与依赖注入的关系。

例:

//a.vue 
export default { 
    provide: { 
        name: '王者峡谷' //这种绑定是不可响应的  
    } 
}
// b.vue 
export default { 
    inject: ['name'], 
    mounted () { 
        console.log(this.name) //输出王者峡谷   
    } 
}

A.vue,我们设置了一个 provide: name,值为王者峡谷,将 name 这个变量提供给它的所以子组件。

B.vue,通过 inject 注入了从 A组件中提供的 name 变量,组件 B 中,直接通过 this.name 访问这个变量了。

这就是 provide/inject API 最核心的用法。

需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意而为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的 ---- vue 官方文档,上面 A.vue 的 name 如果改变了,B.vue 的this.name 是不会改变的。

5.2 provide 与 inject 怎么实现数据响应式

两种方法

 5.2.1 provide 祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有一个缺点就是这个实例上挂载很多没有必要的东西 比如:props methods

 5.2.2
   - 使用 2.6 最新 API Vue.observable 优化响应式 provide(推荐)
 例:
     组件D、E 和 F 获取 A 组件抽题传递过来的 color 值,并能实现数据响应式,即 A 组件的 color变化后,组件D、E、F 会跟着变(核心代码如下:)

image.png

// A 组件  
<div> 
      <h1>A 组件</h1> 
      <button @click="() => changeColor()">改变color</button>       <ChildrenB /> 
      <ChildrenC /> 
</div> 
  data() { 
    return { 
      color: "blue" 
    }; 
  }, 
  // provide() { 
  //   return { 
  //     theme: { 
  //       color: this.color //这种方式绑定的数据并不是可响应的 
  //     } // 即A组件的color变化后,组件D、E、F不会跟着变 
  //   }; 
  // }, 

  provide() { 
    return { 
      theme: this//方法一:提供祖先组件的实例 
    }; 
  },
  methods: { 
    changeColor(color) { 
      if (color) { 
        this.color = color; 
      } else { 
        this.color = this.color === "blue" ? "red" : "blue"; 
      } 
    } 
  } 

  // 方法二:使用2.6最新API Vue.observable 优化响应式 provide 
  // provide() { 
  //   this.theme = Vue.observable({ 
  //     color: "blue" 
  //   }); 
  //   return { 
  //     theme: this.theme 
  //   }; 
  // }, 

  // methods: { 
  //   changeColor(color) { 
  //     if (color) { 
  //       this.theme.color = color; 
  //     } else { 
  //       this.theme.color = this.theme.color === "blue" ? "red" : "blue";   //     } 
  //   } 
  // } 

// F 组件  
<template functional> 
  <div class="border2"> 
    <h3 :style="{ color: injections.theme.color }">F 组件</h3>   </div> 
</template> 

<script> 
export default { 
  inject: { 
    theme: { 
      //函数式组件取值不一样 
      default: () => ({}) 
    } 
  } 
}; 
</script>

注: provide 和 inject 主要为高阶插件/组件库提供用例,能在业务中熟练运用,可以达到事半功倍的效果!

  1. $parent/$childrenref

    • ref: 如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就是指向组件实例。

    • $parent/$children: 访问父/子实例

    注意:这两个都是直接得到组件实例,使用后可以直接调用组件的方法或者访问数据。我们先来看用 ref 来访问组件

例:

export default { 
  data () { 
    return { 
      title: 'Vue.js' 
    } 
  }, 

  methods: { 
    sayHello () {
      window.alert('Hello');    
    } 
  } 
} 

<template> 
    <component-a ref="comA"></component-a>
</template> 

<script> 
  export default { 
    mounted () { 
      const comA = this.$refs.comA; 
      console.log(comA.title);  // Vue.js       
      comA.sayHello();  // 弹窗 
    } 
  } 
</script>

: 这两种方法的弊端是,无法在跨级组件或者兄弟组件间通信。

我们想在 component-a 中,访问到引用它的页面中 (这就是 parent.vue)的两个 component-b 组件,那这种情况下,就得配置额外的插件或者工具了,比如 Vuex 和 Bus 的解决方案。

总结

  • vue中常用的通信方式有 6 钟,分别是:

    1. props (父传子)
    2. $emit/$on 事件总线(跨层级通信)
    3. vuex (状态管理 常用 皆可)优点: 一次存储数据,所以页面都可以访问
    4. $parent/$children (父 子 项目中不建议使用)缺点:不可跨层级
    5. $attrs/$listeners (皆可 可能比较少接触)
    6. provide/inject (高阶用法 推荐使用)优点:使用简单 缺点:不是响应式

8. vue 为什么要求组件模板只能有一个根元素?

从三方面考虑:

1. newVue({el: '#app'})
2. 单文件组件中,template 下的元素 div。其实就是 “树” 状数据结构中的 “根”。
3. diff 算法要求的,源码中,patch.jspatchVnode()。

一、实例化 Vue 时:

<body> 
    <div id='app'></div> 
</body> 
<script> 
    var vm = new Vue({ 
        el:'#app' 
    }) 
</script>

如果我在body下这样:

<body> 
    <div id='app1'></div>    
    <div id='app2'></div> 
</body>

Vue其实并不知道哪一个才是我们的入口。如果同时设置了多个入口,那么vue就不知道哪一个才是这 个‘类’。

二、在webpack搭建的vue开发环境下,使用单文件组件时:

<template>    
    <div> 
    </div> 
</template>
template这个标签,它有三个特性:
   1. 隐藏性:该标签不会显示在页面的任何地方,即便里面有多少内容,它永远都是隐藏的状态。
   2. 任意性:该标签可以写在任何地方,甚至是 head、body、script 标签内。
   3. 无效性:该标签的任何 HTML 内容都是无效的,不会起任何作用,只能 innerHTML 来获取到里面的内容。
  • 一个vue单文件组件就是一个vue实例,如果template下有多个div那么如何指定vue实例的根入口呢, 为了让组件可以正常生成一个vue实例,这个div会自然的处理成程序的入口,通过这个根节点,来递归 遍历整个vue树下的所有节点,并处理为vdom,最后再渲染成真正的HTML,插入在正确的位置。

三、diff中patchvnode方法,用来比较新旧节点 -- 这一点需要看源码

总结

  1. new Vue({el:'#app'})
  2. 单文件组件中,template 下的元素 div。其实就是 “树” 状数据结构中的 “根”。
  3. diff 算法要求,源码中,patch.js 里 patchVnode()

d17aa86f3d5e0ef08ae03255197d630.jpg