Realização simples de ligação bidirecional, ciclo de vida e propriedades calculadas no Vue! ! !

Imagem de efeito

Como conseguir uma vinculação bidirecional já é um tema comum, e recentemente escrevi uma demonstração de vinculação bidirecional , a apresentação final da seguinte forma (ponto de demonstração Wuguai):

Insira a descrição da imagem aqui

Clique na visualização da demonstração, você pode visualizar online

Prefácio

Organize Favorites recentemente me encontrei manualmente para conseguir uma ligação de dados bidirecional simples mvvm neste blog, é muito fácil de entender exemplos para explicar o núcleo responsivo do Vue - ligação bidirecional. Depois de ler, também escrevi uma ligação bidirecional e adicionei algumas funções simples:

  • ciclo da vida
  • Métodos e eventos
  • Atributos calculados

Agora descreverei brevemente a realização dessas funções.

O que é MVVM

Passando para a ligação bidirecional, seria preciso mencionar o padrão MVVM, que é a abreviação Sim Model-View-ViewModel.

Na página de front-end, o Modelo é representado por um objeto JavaScript puro e a Visualização é responsável pela exibição, e os dois são separados ao máximo.

Associar Model e View é ViewModel. O ViewModel é responsável por sincronizar os dados do Model para a View para exibição, e também é responsável por sincronizar a modificação da View de volta para o Model.

Insira a descrição da imagem aqui

No aplicativo, podemos Vue <div id="app"></div>template dentro da View considerada , o datavalor de retorno da função vista como Model , e ViewModel é uma função de ligação bidirecional, ele pode detectar mudanças nos dados, usado para conectar a View e o Model .

O que o ViewModel no Vue faz? Deixe-me dar um exemplo mais simples:

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

Será Hello worldatribuído às tags de entrada e h1, aqui está o modelo de inicialização, Compile do. E ao modificar o valor de msg, então os valores de h1 e de entrada são alterados, e inserir outros valores na entrada para alterar os valores de h1 e msg, a realização de tal função é a realização de dois -way binding. Uma ligação bidirecional simples do Vue pode ser dividida nas seguintes funções:

  • Compilar a página de inicialização do modelo de análise
  • Dep depende de coleta e notificação
  • Watcher fornece dependências e atualiza a visão
  • O observador ouve as alterações de dados e notifica as atualizações da vista

Insira a descrição da imagem aqui

A imagem acima é o fluxograma de ligação bidirecional que desenhei. Deve ser muito mais intuitivo do que o texto. O código está logo abaixo.

opções

Você pode examinar o código de chamada final primeiro. Quando tentamos simular uma estrutura existente, podemos usar os resultados existentes para inferir como nosso código foi projetado.

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)
        }
    }
})

Aula Vue

O código 1: 1 acima restaura o método de escrita de chamadas do Vue. É óbvio que é natural escrever uma classe Vue e passar um objeto de opções como um 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]
        })
    }
}

Chamadas de classe Vue observere Compilepara executar uma operação de inicialização, os parâmetros necessários coletados instância Vue pendurados na operação subsequente fácil. Aqui, adicionei dados, cálculos e métodos à instância Vue. Por que fiz isso será mencionado mais tarde.

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)
    }
}

Se você quiser ver os resultados imediatamente, então, escreva Compilepara ter certeza. Vou postar o código final completo aqui, então haverá mais código. Se você dividi-lo em detalhes, ele terá as seguintes funções:

  • #appConteúdo analítico , os elementos { {}}ou v-modelno valor real atribuído e anexado a um nó virtual. Sobre Por que createDocumentFragmentcriar um método de nó virtual, a primeira vantagem é a conveniência e o segundo benefício é reduzir a sobrecarga de desempenho. No navegador, sempre que você adicionar e excluir elementos fará com que a página reflua, é necessário recalcular a posição de outros elementos. Se houver centenas de elementos adicionados sequencialmente ao dom causará o redesenho centenas de vezes, mas adicionar todos os elementos ao nó virtual no redesenho levaria a um refluxo.
  • Gerando Watcherinstância, ele armazena em cache a chave dependente, e um método de atualização de dados adicionados de dom, é contar com o valor no momento em que foi alterado para atualizar 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)
                        }
                    }
                })
            }
        })
    })
}

observerNa verdade, um ouvinte, aqui usado para monitorar alterações e dados computados e atualizar o dom de notificação, então aqui Object.definePropertyestá para conseguir uma ligação bidirecional Vue é a pedra angular. É realmente difícil coletar dependências automaticamente em atributos calculados calculados.A conversão esquisita e esquisita no código-fonte do Vue é realmente difícil de suportar, então eu tive que escrever uma maneira simples e rude de consegui-la. O atributo calculado é essencialmente o valor de retorno de um método. Todos os meus princípios de implementação aqui são: uma chave dependente corresponde a vários métodos calculados, detecta a atualização da chave dependente e aciona o método calculado ao mesmo tempo.

DepE o Watchercódigo é relativamente simples, nada para falar, não é postado, mais tarde no artigo será rotulado como código-fonte.

Como perceber o ciclo de vida?

Se você me perguntasse como o ciclo de vida em Vue era realizado antes? Eu realmente não sei, raramente leio as perguntas da entrevista. Quando entrei em contato com o Vue pela primeira vez, imaginei que seu ciclo de vida poderia ser realizado por algum js api que eu não conhecia. Na verdade, antes ou depois de algum código js ser executado, uma função de ciclo de vida é chamada para formar um ciclo de vida. Em Compile, após a conclusão da análise do modelo para preencher dom, chamar a mountedfunção será alcançado o ciclo de vida montado.

Como recuperar facilmente valores de dados e atributos calculados?

No Mothods, os ciclos de vida e são calculados em computação e aquisição de dados, as propriedades necessárias diretamente no uso arbitrário Vue thispodem ser recuperados todos os valores. Eu escrevi a frase acima pelos resultados existentes para inferir como nosso design de código , a maneira mais simples e grosseira de se agarrar a esses dados, o exemplo Vue é resolvido, Vue de fato, faz isso.

Resumindo

Na verdade, meu código em comparação com os exemplos online pode ser relativamente longo, porque a adição de alguns recursos extras, sou apenas eu explorando outras funções implementadas para o Vue.

No final de 2020 e início de 2021, senti apenas uma palavra - "ocupado". Não tenho tempo para blogar ou fazer outras coisas quando estou ocupado, então não tenho tempo para escrever este blog de do mais raso para o mais profundo. É fácil de entender. As pequenas funções vão sendo divididas e escritas aos poucos, e o comprimento é superior a 10 vezes.Também se considera para ser escrito o mais breve possível.É o único artigo produzido em janeiro de 2021.

Código fonte

Clique para ver index.html

referência

Acho que você gosta

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