【二十二】vue理解

在这里插入图片描述

前言

本篇博客回顾了vue的模式、单页面的优缺点、渐进式框架的理解。

面试回答

1.MVVM模式:MVVM模式也就是Model-View-ViewModel,分别负责数据、视图以及通信桥梁的工作,这种模式实现了业务逻辑与渲染之间的解耦。在vue中最直接的表现就是胡子语法以及双向绑定。

2.React和vue的区别:vue组件分为全局注册和局部注册,而react中都是通过import引用;vue增加了许多语法糖比如computed和watch,而react需要自己实现;vue中多了指令系统,能够让开发更加便捷,而react只能自己去实现。

3.React绑定:不管有几种绑定方式,绑定的this都指向这个类,只不过在使用上有区别,总共有三种方式:第一种是在构造函数中用bind绑定this。第二种是在调用普通函数的时候用bind绑定this。第三种是通过使用箭头函数绑定this。

4.setState理解:setState本质上是同步任务,而这个异步指的是调用setState不会立即更新this.state。这是因为setState会通过一个队列机制,会将需要更新的state潜合并到状态队列中,但不会立即更新state,会找个时机批量更新。而这个时机指的是触发合成事件,也就是batchedUpdates函数,这个函数会在react调用事件函数之前被调用。一般我们用第二个参数设置回调拿到。

5.高阶组件:高阶组件是一种增强函数,接收一个组件,再返回一个组件,主要是为了代码复用。一般用来添加默认参数、提取状态转换成受控组件、新增DOM元素包裹组件、通过super进行反向继承,比如埋点这类性能监控、通过if进行条件渲染,比如权限控制。

知识点

1.MVVM

Vue采用的是MVVM的模式,即Model-View-ViewModel。

  • Model:模型层,负责处理业务逻辑以及和服务器端进行交互
  • View:视图层:负责将数据模型转化为UI展示出来,可以简单的理解为HTML页面
  • ViewModel:视图模型层,用来连接Model和View,是Model和View之间的通信桥梁,含DOM Listeners和Data Bindings,其中 Data Bindings 用于将数据绑定到 View 上显示,DOM Listeners 用于监听操作。

View层和Model层并没有直接联系,而是通过ViewModel层的双向数据绑定将View层和Model层连接起来进行交互,双向数据绑定即

  • 1.将模型转换成视图,即将后端传递的数据转换成看到的页面。 实现方式是:数据绑定。
  • 2.将视图转换成模型,即将看到的页面(用户操作)转换成后端的数据。实现的方式是:DOM 事件监听
MVVM相对于MVC的优势?
  1. MVVM 实现了数据与页面的双向绑定,MVC 只实现了 Model 和 View 的单向绑定。
  2. MVVM 实现了页面业务逻辑和渲染之间的解耦,也实现了数据与视图的解耦,并且可以组件化开发。
VUE是如何体现MVVM思想的?
  1. 胡子语法,实现了数据与视图的绑定,PS:胡子语法,双大括号标签会把里面的值全部当作字符串来处理,如果值是HTML片段,则可以使用三个大括号来绑定。
  2. v-on 事件绑定,通过事件操作数据时,v-model 会发生相应的变化。

MVVM实现原理具体实现会在后续的vue.js两大核心的博客中去进一步理解,参考博客:https://juejin.cn/post/6844903960789123086;https://juejin.cn/post/6844904067525935118

在这里插入图片描述

主要分为四个模块:

  • Compile(模板编译)
  • Observer(数据劫持)
  • Watcher(数据监听)
  • Dep(发布订阅)

实现步骤:

  1. 实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。

  2. 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者(Dep)

  3. 实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应的Dep (发布),从而更新视图

  4. MVVM入口函数,整合以上三者

简易图理解:

在这里插入图片描述

vue等SPA单页面应用及其优缺点

优点:

  1. 前后端分工明确,前端负责页面,后端负责数据。
  2. 用户体验好,速度快,路由切换时不需要再像服务端发起请求。
  3. 相对于服务器压力小

缺点:

  1. 不利于seo搜索,因为页面数据都是前端异步加载的方式,不利于搜索引擎的抓取。
  2. 如果打包的js过大,会导致请求过长,从而造成首屏加载响应慢。

2.vue与react的区别

参考博客:https://juejin.cn/post/7071889478305972255?searchId=20230825104430795B0679ABB7E360DF50

1.核心思想

Vue的核心思想是尽可能的降低前端开发的门槛,是一个灵活易用的渐进式双向绑定的MVVM框架。

React的核心思想是声明式渲染和组件化、单向数据流,React既不属于MVC也不属于MVVM架构。

  • RQ1:声明式是什么意思?

声明式与之相对应的是命令式,命令式指的是通过DOM操作一步步把网页变成想要的样子,而声明式则是只需要通过状态去形容最后的网页长什么样子即可。

  • RQ2:组件化是什么意思?

组件化指的是尽可能的将页面拆分成一个个较小的、可以复用的组件,这样让我们的代码更加方便组织和管理,并且拓展性页更强。

  • RQ3:如何理解React的单向数据流?

React的单向数据流指的是数据主要从父节点通过props传递到子节点,如果顶层某个props改变了,React会重新渲染所有的子节点,但是单向数据流并非单向绑定,React想要从一个组件去更新另一个组件的状态,需要进行状态提升,即将状态提升到他们最近的祖先组件中,触发父组件的状态变更,从而影响另一个组件的显示。单向数据流的好处是能够保证状态改变的可追溯性,假如,父组件维护了一个状态,子组件如果能够随意更改父组件的状态,那么各组件的状态改变就会变得难以追溯。

2.组件写法上不同

Vue的组件写法是通过template的单文件组件格式,具体形式则是分为HTML、CSS、JS三个模块

React的组件写法是JSX+inline style,也就是吧HTML和CSS全部写进JavaScript中,具体形式则是将HTML、CSS、JS糅杂在一起,最后用render渲染

3.Diff算法

vue和react的diff算法都是进行同层次的比较,主要有以下两点不同:

  1. vue对比节点,如果节点元素类型相同,但是className不同,认为是不同类型的元素,会进行删除重建,但是react则会认为是同类型的节点,只会修改节点属性。
  2. vue的列表比对采用的是首尾指针法,而react采用的是从左到右依次比对的方式,当一个集合只是把最后一个节点移动到了第一个,react会把前面的节点依次移动,而vue只会把最后一个节点移动到最后一个,从这点上来说vue的对比方式更加高效。
  3. vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树,而react每当应用的状态被改变时,全部组件都会重新渲染,所以react中会需要shouldComponentUpdate这个生命周期函数方法来进行控制
4.响应式原理不同

React的响应式原理

React主要是通过setState()方法来更新状态,状态更新之后,组件也会重新渲染。

总结:

  • React基于状态机,手动优化,数据不可变,需要setState驱动新的State替换老的State。
  • 当数据改变时,以组件为根目录,默认全部重新渲染

Vue的响应式原理

vue会遍历data数据对象,使用Object.definedProperty()将每个属性都转换为getter和setter,每个Vue组件实例都有一个对应的watcher实例,在组件初次渲染的时候会记录组件用到了那些数据,当数据发生改变的时候,会触发setter方法,并通知所有依赖这个数据的watcher实例调用update方法去触发组件的compile渲染方法,进行渲染数据。

总结:

  • Vue依赖收集,自动优化,数据可变。
  • Vue递归监听data的所有属性,直接修改。
  • 当数据改变时,自动找到引用组件重新渲染。
5.封装程度不同

封装程度,vue封装程度更高,内置多个指令和数据双向绑定,react封装度比较低,适合扩展。

3.vue中高阶组件的应用

参考博客:https://juejin.cn/post/7212822448147382330?searchId=2023082511504600C8D8756984736B38E7

高阶组件(HOC):是将另一个函数作为参数接受或将函数作为返回值的函数;

Hoc组件

export default function getHOC(comMap) {
    const ignoreArr = ['change', 'input']
    return {
        computed: {
            // 过滤掉input和change事件监听
            listeners() {
                const res = {}
                for(let key in this.$listeners) {
                    if(!ignoreArr.includes(key)) {
                        res[key] = this.$listeners[key]
                    }
                }
                return res
            },
            // 处理插槽
            slotsMap() {
                const tempSlotsMap = Object.keys(this.$slots)
                .reduce((arr, cc) => {
                    const slot = this.$slots[cc][0]
                    const type = slot.data.attrs.comType || 'default'
                    if(!arr[type]) {
                        arr[type] = []
                    }
                    slot.context = this._self
                    arr[type].push(slot)
                    return arr
                }, {})
                return tempSlotsMap
            }
        },
        methods: {
            // 处理scopedSlot插槽
            handleScopeSlots(slots, name) {
                let slotsMap = {}
                for(let key in slots) {
                    const res = key.split('_')
                    if(res[0]===name) {
                        slotsMap[res[1]]=slots[key]
                    }
                }
                return slotsMap
            },
            // 处理渲染dom
            handleNodes(h) {
                const { config, model } = this.$attrs || {}
                let hArr = []
                for(let key in comMap) {
                    const slots = this.slotsMap[key]
                    const scopedSlots = this.handleScopeSlots(this.$scopedSlots, key)
                    hArr = [...hArr, h(comMap[key], {
                        props: {
                            value: model ? model[config?.[key]?.prop] : '',
                            ...(this.$props?.config?.[key] || {}),
                            ...config[key]
                        },
                        attrs: {
                            key: `${key}-${config[key].prop}`,
                            ...config[key],
                        },
                        on: {
                            change: (v) => {
                                model[config?.[key]?.prop] = v
                                this.$emit('change', {prop: config[key].prop, v})
                            },
                            input: (v) => {
                                model[config?.[key]?.prop] = v
                                this.$emit('input', {prop: config[key].prop, v})
                            },
                            ...this.listeners
                        },
                        style: {
                            marginBottom: '10px',
                            maxWidth: '300px',
                            ...(config?.[key]?.style || {})
                        },
                        scopedSlots: scopedSlots
                    }, slots || null)]
                }
                return hArr
            }
        },
        render(h) {
            const nodes = this.handleNodes(h)
            return h('div', null, [
                h('div', null, this.slotsMap.default),
                ...nodes
            ])
        }
    }
}

高阶组件的使用

<template>
  <div>
    <Hoc
        :model="data"
        :config="config"
        @input="input"
        @change="change"
        @blur="blur"
        @select="handleSelect"
    >
        <template>
            <h2 slot="header" style="padding-bottom: 20px;">我是高阶组件</h2>
        </template>
        <template>
            <i slot="prefix" class="el-icon-date" style="margin-top: 13px;" comType="input"></i>
        </template>
        <template slot="autocomplete_default" slot-scope="{ item }">
            <div class="name">{{ item.value }}</div>
            <span class="addr">{{ item.address }}</span>
        </template>
    </Hoc>
    <div>{{data}}</div>
  </div>
</template>

<script>
import getHOC from '../components/hoc'
import MySelect from '../components/select.vue'
import { Input, Switch, rate, Autocomplete } from 'element-ui'

const Hoc = getHOC({input: Input, mySelect: MySelect, switch: Switch, rate, autocomplete: Autocomplete})
export default {
    components: {
        Hoc,
    },
    data() {
        return {
            data: {
                inputValue: '',
                autocompleteValue: '',
                selectValue: '',
                switchValue: false,
                rateValue: 0
            },
            config: {
                input: {
                    prop: 'inputValue',
                    placeholder:'我是输入框',
                    style: {},
                },
                autocomplete: {
                    prop: 'autocompleteValue',
                    placeholder: '请输入内容',
                    fetchSuggestions: this.querySearch
                },
                mySelect: {
                    prop: 'selectValue',
                    placeholder: '请选择',
                    list: [{
                        value: '选项1',
                        label: '黄金糕'
                        }, {
                        value: '选项2',
                        label: '双皮奶'
                    }]
                },
                switch: {
                    prop: 'switchValue',
                    activeColor: "#13ce66",
                    inactiveColor: "#ff4949"
                },
                rate: {
                    prop: 'rateValue'
                },
                restaurants: []
            }
        }
    },
    mounted() {
      this.restaurants = this.loadAll();
    },
    methods: {
        change(v) {
            console.log('v: ', v);
            
        },
        input(v) {
            console.log('v222: ', v)

        },
        blur(e) {
            console.log('event: ', e)
        },
        handleSelect(item) {
            console.log('item: ', item);
        },
        querySearch(queryString, cb) {
            const restaurants = this.restaurants;
            const results = queryString ? restaurants.filter(this.createFilter(queryString)) : restaurants;
            // 调用 callback 返回建议列表的数据
            cb(results);
        },
        createFilter(queryString) {
            return (restaurant) => {
            return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0);
            };
        },
        loadAll() {
            return [
                    { "value": "三全鲜食(北新泾店)", "address": "长宁区新渔路144号" },
                    { "value": "Hot honey 首尔炸鸡(仙霞路)", "address": "上海市长宁区淞虹路661号" },
                    { "value": "新旺角茶餐厅", "address": "上海市普陀区真北路988号创邑金沙谷6号楼113" },
                ]
        }
    }
}
</script>

最后

走过路过,不要错过,点赞、收藏、评论三连~

  • 21
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值