Realización simple de enlace bidireccional, ciclo de vida y propiedades calculadas en Vue! ! !

Imagen de efecto

Cómo lograr un enlace bidireccional ya es un tema común, y recientemente escribí una demostración de enlace bidireccional , la presentación final de la siguiente manera (demo ugly point Wuguai):

Inserte la descripción de la imagen aquí

Haga clic en la vista previa de la demostración, puede obtener una vista previa en línea

Prefacio

Organize Favorites recientemente me encontré manualmente para lograr un enlace de datos bidireccional simple mvvm en este blog, es muy fácil entender ejemplos para explicar el núcleo receptivo de Vue: enlace bidireccional. Después de leerlo, también escribí un enlace bidireccional y agregué algunas funciones simples:

  • ciclo vital
  • Métodos y eventos
  • Atributos calculados

Ahora describiré brevemente la realización de estas funciones.

Que es MVVM

Pasando a la vinculación bidireccional tendría que mencionar el patrón MVVM, sí, la abreviatura Model-View-ViewModel.

En la página de inicio, el modelo está representado por un objeto de JavaScript puro, y la vista es responsable de la visualización, y los dos están separados en la mayor medida posible.

La asociación de modelo y vista es ViewModel. ViewModel es responsable de sincronizar los datos del modelo con la vista para su visualización, y también es responsable de sincronizar la modificación de la vista con el modelo.

Inserte la descripción de la imagen aquí

En la aplicación, podemos la <div id="app"></div>plantilla Vue dentro de la Vista considerada , el datavalor de retorno de la función vista como Modelo , y ViewModel es una función de enlace bidireccional, puede detectar cambios en los datos, que se utilizan para conectar la Vista y el Modelo .

¿Qué hace ViewModel en Vue? Déjame darte el ejemplo más simple:

<div id="app">
    <h1>{
   
   { msg }}</h1>
    <input type="text" v-model="msg" />
</div>
<script>
	new Vue({
        el: '#app',
        data () {
            return { msg: 'Hello world' }
        }
    })
</script>

Se Hello worldasignará a las etiquetas input y h1, aquí está la plantilla de inicialización, Compile do. Y al modificar el valor de msg, luego se cambian los valores de h1 y de entrada, y al ingresar otros valores en la entrada para cambiar los valores de h1 y msg, la realización de dicha función es la realización de dos -vías vinculantes. Un enlace bidireccional simple de Vue se puede dividir en las siguientes funciones:

  • Compilar la página de inicialización de la plantilla de análisis
  • Dep se basa en la recopilación y la notificación
  • Watcher proporciona dependencias y actualiza la vista
  • Observer escucha los cambios de datos y notifica las actualizaciones de la vista

Inserte la descripción de la imagen aquí

La imagen de arriba es el diagrama de flujo de encuadernación bidireccional que dibujé. Debería ser mucho más intuitivo que el texto. El código está directamente debajo.

opciones

Primero puede ver el código de llamada final. Cuando intentamos simular un marco existente, podemos usar los resultados existentes para inferir cómo está diseñado nuestro código.

new Vue({
    
    
    el: '#app',
    data() {
    
    
        return {
    
    
            foo: 'hello',
            bar: 'world'
        }
    },
    computed: {
    
    
        fooBar() {
    
    
            return this.foo + ' ' + this.bar
        },
        barFoo() {
    
    
            return this.bar + ' ' + this.foo
        }
    },
    mounted() {
    
    
        console.log(document.querySelector('h1'));
        console.log(this.foo);
    },
    methods: {
    
    
        clickHandler() {
    
    
            this.foo = 'hello'
            this.bar = 'world'
            console.log('实现一个简单事件!')
            console.log(this)
        }
    }
})

Clase vue

El código anterior 1: 1 restaura el método de escritura de llamadas de Vue Es obvio que es natural escribir una clase de Vue y pasar un objeto de opciones como parámetro.

class Vue {
    
    
    constructor(options) {
    
    
        this.$options = options
        this.$el = document.querySelector(options.el)
        // 缓存data中的key,用来数据劫持
        // (ps: Vue 将 options.data 中的属性挂在 Vue实例 上, Object.defineProperty 劫持的其实是 Vue实例 上的属性, options.data 里的数据初始化之后应该用处不大)
        this.$depKeys = Object.keys({
    
    ...options.data(), ...options.computed})
        // 计算属性的依赖数据
        this._computedDep = {
    
    }
        this._addProperty(options)
        this._getComputedDep(options.computed)
        this._init()
    }

    _init() {
    
    
        observer(this)
        new Compile(this)
    }

    // 获取计算属性依赖
    _getComputedDep(computed) {
    
    
        Object.keys(computed).forEach(key => {
    
    
            const computedFn = computed[key]
            const computedDeps = this._getDep(computedFn.toString())

            computedDeps.forEach(dep => {
    
    
                if (!this._computedDep[dep]) {
    
    
                    this._computedDep[dep] = {
    
    
                        [key]: computed[key]
                    }
                } else {
    
    
                    Object.assign(this._computedDep[dep], {
    
    
                        [key]: computed[key]
                    })
                }
            })
        })
    }

    _getDep(fnStr) {
    
    
        const NOT_REQUIRED = ['(', ')', '{', '}', '+', '*', '/', '\'']
        return fnStr.replace(/[\r\n ]/g, '')
            .split('')
            .filter(item => !NOT_REQUIRED.includes(item))
            .join('')
            .split('return')[1]
            .split('this.')
            .filter(Boolean)
    }

    // 将 data 和 methods 中的值注入Vue实例中(实现在方法或生命周期等能直接用 this[key] 来取值)
    _addProperty(options) {
    
    
        const {
    
    computed, data, methods} = options
        const _computed = {
    
    }
        Object.keys(computed).forEach(key => {
    
    
            _computed[key] = computed[key].call(data())
        })

        const allData = {
    
    ...data(), ...methods, ..._computed}
        Object.keys(allData).forEach(key => {
    
    
            this[key] = allData[key]
        })
    }
}

Llamadas a la clase Vue observery Compilepara realizar una operación de inicialización, los parámetros necesarios recogidos de la instancia de Vue colgando en la operación posterior son fáciles. Aquí agregué datos, cálculos y métodos a la instancia de Vue. Por qué hice esto se mencionará más adelante.

Compilar

// 编译模板
class Compile {
    
    
    constructor(vm) {
    
    
        this.vm = vm
        this._init()
    }

    _init() {
    
    
        // 搬来 Vue 中匹配 {
    
    { 插值 }} 的正则
        const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/;
        // 获取主容器
        const container = this.vm.$el
        // 创建虚拟节点
        const fragment = document.createDocumentFragment()
        // 只取元素节点
        let firstChild = container.firstElementChild
        while (firstChild) {
    
    
            // 这里 append 一个,container 就会少一个 子元素,若没有子元素则会返回 null
            fragment.appendChild(firstChild)
            firstChild = container.firstElementChild
        }
        fragment.childNodes.forEach(node => {
    
    
            // 将属性节点(ArrayLike)转换为数组
            [...node.attributes].forEach(attr => {
    
    
                // 匹配 v-model 指令
                if (attr.name === 'v-model') {
    
    
                    const key = attr.value
                    node.value = this.vm[key]

                    new Watcher(this.vm, key, val => {
    
    
                        node.value = val
                    })

                    node.addEventListener('input', e => {
    
    
                        // input 事件触发 set 方法,并通知 Watcher 实例操作变更dom
                        this.vm[key] = e.target.value
                    })
                }
                // 匹配 @click 绑定点击事件
                if (attr.name === '@click') {
    
    
                    // 使用 bind 将此函数内部 this 改为 Vue实例
                    node.addEventListener('click', this.vm[attr.value].bind(this.vm))
                }
            })

            // 匹配双花括号插值(textContent取赋值 比 innerText 好一点)
            if (node.textContent && defaultTagRE.test(node.textContent)) {
    
    
                console.log(node.textContent);
                const key = RegExp.$1.trim()
                // 替换 {
    
    {}} 后的文本,用于初始化页面
                const replaceTextContent = node.textContent.replace(defaultTagRE, this.vm[key])
                // 移除 {
    
    {}} 后的文本,用于响应性更新
                const removeMustache = node.textContent.replace(defaultTagRE, '')
                node.textContent = replaceTextContent

                new Watcher(this.vm, key, val => {
    
    
                    node.textContent = removeMustache + val
                })
            }
        })

        // 将 虚拟节点 添加到主容器中(这里可以将虚拟节点理解为 Vue 中的 template 标签,只起到一个包裹作用不会存在真实标签)
        this.vm.$el.appendChild(fragment)
        // 此处定义 mounted 生命周期
        typeof this.vm.$options.mounted === 'function' && this.vm.$options.mounted.call(this.vm)
    }
}

Si desea ver los resultados de inmediato, escriba Compilepara estar seguro. Publicaré el código final completo aquí, por lo que habrá más código. Si lo divide en detalle, tendrá las siguientes funciones:

  • #appContenido analítico , los elementos { {}}o v-modelen el valor real asignado, y anexado a un nodo virtual. Acerca de Por qué createDocumentFragmentcrear un método de nodo virtual, la primera ventaja es la conveniencia y el segundo beneficio es reducir la sobrecarga de rendimiento. En el navegador, cada vez que agrega y elimina elementos, la página se reajustará, es necesario volver a calcular la posición de otros elementos. Si hay cientos de elementos agregados secuencialmente al dom, se volverá a dibujar cientos de veces, pero agregar todos los elementos al nodo virtual en el redibujo provocaría un reflujo.
  • Al generar la Watcherinstancia, almacena en caché la clave dependiente, y un método para actualizar los datos agregados de dom es confiar en el valor en el momento en que se cambió para actualizar dom.

Observador

function observer(vm) {
    
    
    const dep = new Dep()
    const {
    
    _computedDep} = vm
    vm.$depKeys.forEach(key => {
    
    
        let value = vm[key]
        Object.defineProperty(vm, key, {
    
    
            enumerable: true,
            configurable: true,
            get() {
    
    
                if (Dep.target) {
    
    
                    // 添加订阅者-Watcher实例
                    dep.add(Dep.target)
                }
                return value
            },
            set(newVal) {
    
    
                value = newVal
                // (ps:可以在此处根据 key 值通知对应的 Watcher 进行更新)
                dep.notify()

                Object.keys(_computedDep).forEach(computedDep => {
    
    
                    // 在 set 中匹配对应的依赖项更新对应的计算属性
                    if (key === computedDep) {
    
    
                        for (let getter in _computedDep[key]) {
    
    
                            vm[getter] = _computedDep[key][getter].call(vm)
                        }
                    }
                })
            }
        })
    })
}

observerDe hecho, un oyente, aquí utilizado para monitorear los cambios y los datos computados y actualizar el dom de notificación, por lo que aquí Object.definePropertyes para lograr un enlace bidireccional Vue es una piedra angular. Es realmente difícil recopilar automáticamente las dependencias de los atributos calculados calculados. La conversión extraña y extraña en el código fuente de Vue es realmente difícil de soportar, así que tuve que escribir una manera simple y grosera de lograrlo. El atributo calculado es esencialmente el valor de retorno de un método. Todos mis principios de implementación aquí son: una clave dependiente corresponde a múltiples métodos calculados, detecta la actualización de la clave dependiente y activa el método calculado al mismo tiempo.

DepY el Watchercódigo es relativamente simple, no hay nada de qué hablar, no se publica, más adelante en el artículo se etiquetará como código fuente.

¿Cómo realizar el ciclo de vida?

Si me preguntaras cómo se realiza el ciclo de vida en Vue antes. Realmente no lo sé, rara vez leo las preguntas de las entrevistas. Cuando entré por primera vez en contacto con Vue, supuse que su ciclo de vida podría ser realizado por alguna api js que no conocía. De hecho, antes o después de que se ejecute algún código js, ​​se llama a una función de ciclo de vida para formar un ciclo de vida. En Compile, después de completar el análisis de la plantilla para llenar dom, mountedse logrará llamar a la función del ciclo de vida montado.

¿Cómo recuperar fácilmente valores de datos y atributos calculados?

En Mothods, los ciclos de vida y se calculan en la computación y la adquisición de datos, las propiedades requeridas directamente en el uso arbitrario de Vue thisse pueden recuperar todos los valores. Escribí la oración anterior por los resultados existentes para inferir cómo se resuelve nuestro diseño de código , la forma más simple y cruda de aferrarse a estos datos, el ejemplo de Vue, Vue de hecho, lo hace.

para resumir

De hecho, mi código en comparación con los ejemplos en línea puede ser relativamente largo, debido a la adición de algunas características adicionales, solo estoy explorando otras funciones implementadas para Vue.

A finales de 2020 y principios de 2021, me sentí solo una palabra: "ocupado". No tengo tiempo para bloguear o hacer otras cosas cuando estoy ocupado, así que no tengo tiempo para escribir este blog desde cuanto menos profundo a más profundo, es fácil de entender. Las funciones pequeñas se dividen y escriben poco a poco, y la extensión es de más de 10 veces. También se considera que está escrito lo antes posible. Es el único artículo producido en enero de 2021.

Código fuente

Haga clic para ver index.html

referencia

Supongo que te gusta

Origin blog.csdn.net/dizuncainiao/article/details/113482795
Recomendado
Clasificación