Vue 技术探索: Vue 进阶

382 阅读4分钟

文章主题: 掌握 Vue 进阶概念与技术应用

模板化: 插槽 & watch & computed & jsx

组件化: 抽象复用 | 精简&聚合 | 渲染顺序 | mixin | extends | 插件 | provide & inject

一、模板化

用模板抽象出一切可能的存在。

包括 v-for / v-if / 动态化节点 / 组件 / 动态组件 / 结构体(插槽) / JSX / 计算属性 / 监听属性

插槽

默认插槽 | 具名插槽 | 作用域插槽

默认插槽

  • 如果有多个节点会怎么处理? 比如多个 p 标签? => 统一节点的形式, 集成化封装的操作。
  • 如果安排多个插槽会怎么样? 会渲染三遍, 聚合式渲染。都会放到 default 插槽。
  • 即多个默认插槽会怎么样? => 重复"替换"渲染
  • 多个插槽分开布局怎么办? => 具名插槽

插槽组件

<template>
    <div class="hello">
        <slot></slot>
    </div>
</template>

使用组件

<template>
    <HelloWorld>
        <!--默认插槽-->
        <p>{{ msg }}</p>
    </HelloWorld>
</template>

<script>
import HelloWorld from "./components/HelloWorld"
export default {
    components: { HelloWorld },
    data() {
        return {
            msg: 'Hello world'
        }
    }
}
</script>

具名插槽

name 的形式标识插槽的身份, 从而在组件内部做到可区分。

  • 为什么可以通过 name 区分插槽?

    • vue 将所有没有起名字的插槽全部合在了一起,起名叫 default。而具名插槽则是自己的 name。 name 索引了上下文空间。封装包裹的作用。
  • 为什么默认插槽用 template 包裹不显示?

    • template编译时标签, 最终会被去掉。
  • 多个具名插槽会怎么样?

    • 后来者会覆盖前者, 不会重复渲染, vue 会报 warning。
  • 父组件如何获取所有的子组件插槽内容?

    • refs
  • 请问如果具名插槽想要做参数的整合合并(参数的作用域隔离)怎么办? 作用域插槽

具名插槽组件

<template>
    <div class="hello">
        <slot name="header"></slot>
        <slot></slot>
        <slot name="footer"></slot>
    </div>
</template>

使用具名插槽组件

<template>
    <HelloWorld>
        <template slot="header"></template>
        <p>{{ msg }}</p>
        <template v-slot:footer></template>
    </HelloWorld>
</template>

<script>
import HelloWorld from "./components/HelloWorld"
export default {
    components: { HelloWorld },
    data() {
        return {
            msg: 'Hello world'
        }
    }
}
</script>

作用域插槽

参数合并(参数作用域隔离) => 作用域插槽

外部做结构的描述勾勒, 内部做传参。

插槽组件内部数据暴露给父组件。

  • 考察点: 插槽传参 - 数据传递 - 尽量保持名称的对齐

插槽内层绑定数据

父组件调用组件通过指定插槽的 slot-scope 获取子组件插槽暴露出的数据

父组件期望在插槽中使用子组件域的数据。

<template>
    <div class="hello">
        <slot name="header"></slot>
        <slot></slot>
        <slot name="footer"></slot>
        <!--插槽内层-->
        <slot name="content" :slotProps="slotProps"></slot>
       
    </div>
</template>

<script>
    export default {
        data(){
            return {
                slotProps:"slotProps"
            }
        }
    }
</script>
<template>
    <HelloWorld>
        <template slot="header"></template>
        <p>{{ msg }}</p>
        <template v-slot:footer></template>
        <!--作用域插槽-->
        <template slot="content" slot-scope="{slotProps}">
            {{ slotProps }}
        </template>
         <!--vue3-->
        <template v-slot:content="slotProps">{{ slotProps }}</slot>
        <!--简写#-->
        <template #content="slotProps">{{ slotProps }}</slot> 
    </HelloWorld>
</template>

<script>
import HelloWorld from "./components/HelloWorld"
export default {
    components: { HelloWorld },
    data() {
        return {
            msg: 'Hello world'
        }
    }
}
</script>

模板的二次加工方案

watch & computed

watch 和 computed 的区别

  • 使用写法的写法: watch 一个函数 或者对象, 监听某一个 target , 传递一个目标的处理流程, 可以没有返回值; computed 根据一个或多个变量计算出来一个结果, computed 一定会有返回。一个关注流程 , 一个关注结果
  • 原理不同: watch 对劫持的数据进行观察后, 触发响应的回调。 computed 收集依赖 => 依赖劫持 => 触发重新计算
  • 参数不同: watch<once,immediate,deep:true, handler>; computed

收集的依赖是什么? 依赖: 一个值的变化会影响到另一个值的变化。

其它

方案一: 函数 - {{ calcAdd(header) }} ;管道符 filters , 在 vue3 中已经取消了, filter 中 this 是 undefined, 不是当前实例(纯函数)。

方案二: v-text 和 v-html

方案三: 直给 - {{ ...三元表达式 }}

JSX

可以用来做更加灵活且JS化的方案。

<script>
    import content from "content.vue"
    export default {
        name:"HelloWorld",
        components:{content},
        data(){
            return {
                options:[{}]
            }
        },
        methods:{
            handleClick(){}
        },
        // 最终返回的是一个node节点
        // h => createElement
        // h(tag, data, children)
        render(h){
            // 嵌套
            const moneyNode = (<p>{this.money > 99 ? 99 : this.money}</p>)
            return (
                <ul>
                    {
                        this.options.map(item=>{
                            return (
                                <content item={item} onClick={this.handleClick}>
                                    {options}
                                    {moneyNode}
                                </li>
                                )
                        })
                    }
                </ul>
            )
        }
    }
</script>
  • 如何在 jsx 中实现 v-for?
  • 如何在 jsx 中实现 v-if?
  • 如何在 jsx 中实现嵌套循环?
  • 如何在 jsx 中绑定事件?
  • 如何在 jsx 中使用组件? 如何使用插槽?

二、组件化

特点

  • 抽象复用
  • 精简 & 聚合
  • 渲染顺序

mixin - 逻辑混入

mixin 不建议继续去使用。

  1. 应用: 抽离公共逻辑(逻辑相同、模板不同) 对比 extends 核心逻辑是继承(拓展)

  2. 合并策略: 变量补充, 多 mixin 情况下, 后者覆盖前者, 但是本体(组件内容)优先。

    1. vue 组件存在同名变量时, 不会覆盖本体。

    2. 声明周期: 生命周期在父组件和子组件之间。

      1. 父组件created => mixin (多个按顺序) created => 子组件 created
  3. extends: 不会覆盖本体; 生命周期: father 和 children 之间; 只有单个; extends 在 mixin 之前执行

  4. mixin 是共用 extends 是扩展

  5. extends 和 mixin 中相同状态, 取 mixin 最后一个。

demo-mixin.js

export default {
    data(){
        return {
            msg: "I'm mixin",
            obj:{
                title: 'mixin title',
                header: 'mixin header',
            }
        }
    },
    created(){
        console.log("mixin")
    }
}

vue 组件中使用 mixin

<script>
import demoMixin from 'demo-mixin.js'
export default{
        mixin:['demoMixin']
}
  
</script>

extends

vue 组件中使用 extends: extends 的使用方式

demo-extends.js

// demo-extends.js
export default {
    data(){
        return {
            msg: "I'm extends",
            obj:{
                title: 'extends title',
                header: 'extends header',
            }
        }
    },
    created(){
        console.log("extends")
    }
}
<script>
import demoExtends from 'demo-extends.js'
export default{
  extends:demoExtends
}
  
</script>