Vue中v-if/v-show的实现你知道吗?

184 阅读2分钟

v-if/v-show基本使用

本文将会讲解v-if/v-show之间的使用以及简单实现。

v-if

  1. 是条件渲染,条件为真,元素才会被渲染,为假,则不会渲染
  2. 首次不会渲染,开销小
  3. 元素切换高开销
  4. 具有v-if、v-else-if v-else

v-show

  1. 通过style的display来达到显示隐藏
  2. 首次会渲染,开销大
  3. 元素切换低开销

基本使用

    var App = {
        data() {
            return {
                isShowImg1: false,
                isShowImg2: false
            }
        },
        template: `
            <div>
                <div>
                    <img width="200" src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1111%2F0Q91Q50307%2F1PQ9150307-8.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1646723639&t=d56432506160e9523b3129375d285d7a" v-if="isShowImg1" />
                    <img width="200" src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2F911%2F0R415123342%2F150R4123342-6-1200.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1646723639&t=ad8ee9e66ef1b282b78dcf00298425d0" v-show="isShowImg2" />
                </div>
                <button @click="handleShowImg1">显示图片1</button>
                <button @click="handleShowImg2">显示图片2</button>
            </div>
        `,
        methods: {
            handleShowImg1() {
                this.isShowImg1 = !this.isShowImg1
            },
            handleShowImg2() {
                this.isShowImg2 = !this.isShowImg2
            }
        }
    }

    var vm = Vue.createApp(App).mount('#app')
    console.log(vm)

image.png

简单实现v-if/v-show

app.js

    import Vue from "./modules/Vue"

    var vm = new Vue({
        el: '#app',
        data() {
            return {
                isShowImg1: false,
                isShowImg2: false
            }
        },
        template: `
            <div>
                <div>
                    <img width="200" src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1111%2F0Q91Q50307%2F1PQ9150307-8.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1646723639&t=d56432506160e9523b3129375d285d7a" v-if="isShowImg1" />
                    <img width="200" src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2F911%2F0R415123342%2F150R4123342-6-1200.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1646723639&t=ad8ee9e66ef1b282b78dcf00298425d0" v-show="isShowImg2" />
                </div>
                <button @click="handleShowImg1">显示图片1</button>
                <button @click="handleShowImg2">显示图片2</button>
            </div>
        `,
        methods: {
            handleShowImg1() {
                this.isShowImg1 = !this.isShowImg1
            },
            handleShowImg2() {
                this.isShowImg2 = !this.isShowImg2
            }
        }
    })

    console.log(vm)

Vue.js

    var Vue = (function() {
        function Vue(options) {
            // 将$el、$data暴露在实例中
            this.$el = document.querySelector(options.el)
            this.$data = options.data()

            this._init(this, options.template, options.methods)
        }

        Vue.prototype._init = function(vm, template, methods) {

            var container = document.createElement('div')
            container.innerHTML = template

            var showPool = new Map(),
                eventPool = new Map();

            // 数据劫持 监听数据
            initData(vm, showPool)
            // 存储属性、事件
            initPool(container, methods, showPool, eventPool)
            // 绑定事件
            bindEvent(vm, eventPool)
            // 渲染
            render(vm, showPool, container)
        }

        function initData(vm, showPool) {
            var _data = vm.$data

            for(var key in _data) {
                (function(key) {
                    // 对数据进行监听 vm.key -> vm.$data.key
                    Object.defineProperty(vm, key, {
                        get: function() {
                            return _data[key]
                        },
                        set: function(newValue) {
                            _data[key] = newValue
                            // 数据改变时触发update 更新视图
                            update(vm, key, showPool)
                        }
                    })
                })(key)
            }
        }

        function initPool(container, methods, showPool, eventPool) {
            var allNodes = container.getElementsByTagName('*'),
                dom = null;

            for(var i = 0; i < allNodes.length; i++) {
                dom = allNodes[i]

                // 获取dom元素上的属性 v-if v-show click
                var vIfData = dom.getAttribute('v-if'),
                    vShowData = dom.getAttribute('v-show'),
                    vEventData = dom.getAttribute('@click');

                if(vIfData) { // v-if存在时
                    showPool.set(
                        dom,
                        {
                            type: 'v-if',
                            prop: vIfData
                        }
                    )
                    dom.removeAttribute('v-if')
                } else if(vShowData) { // v-show存在时
                    showPool.set(
                        dom,
                        {
                            type: 'v-show',
                            prop: vShowData
                        }
                    )
                    dom.removeAttribute('v-show')
                }

                if(vEventData) { // click事件存在时
                    eventPool.set(
                        dom,
                        methods[vEventData]
                    )
                    // 每次在最后需要把元素上绑定的属性删除
                    dom.removeAttribute('@click')
                }
            }
        }

        function bindEvent(vm, eventPool) {
            for(var [ dom, handle ] of eventPool) {
                // 把methos里面的方法暴露在vm/this里面
                vm[handle.name] = handle
                // 给dom绑定对应的方法 改变this指向
                dom.addEventListener('click', handle.bind(vm), false)
            }
        }

        function render(vm, showPool, container) {
            var _data = vm.$data,
                _el = vm.$el;

            // Map数据可以迭代 使用for of
            for(var [ dom, info ] of showPool) {
                switch(info.type) {
                    case 'v-if': 
                        // 创建注释节点 prop为false时 把dom替换成注释节点 replaceChild
                        info.commont = document.createComment(['v-if'])
                        !_data[info.prop] && dom.parentNode.replaceChild(info.commont, dom)
                        break;
                    case 'v-show':
                        // prop为false时 设置dom的display为none
                        !_data[info.prop] && (dom.style.display = 'none')
                        break;
                    default:
                        break;
                }
            }

            // 将container插入到el中
            _el.appendChild(container)
        }

        function update(vm, key, showPool) {
            var _data = vm.$data

            for(var [ dom, info ] of showPool) {
                if(info.prop === key) {
                    switch(info.type) {
                        case 'v-if':
                            !_data[info.prop] ? dom.parentNode.replaceChild(info.commont, dom)
                                              : info.commont.parentNode.replaceChild(dom, info.commont)
                            break;
                        case 'v-show':
                            // 当prop存在时 不建议直接removeAttribute style属性 因为可能其他影响其他地方
                            !_data[info.prop] ? dom.style.display = 'none'
                                              : dom.style.display = 'block'
                            break;
                        default:
                            break;
                    }
                }
            }
        }

        return Vue
    })()

    export default Vue