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):
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.
No aplicativo, podemos Vue <div id="app"></div>
template dentro da View considerada , o data
valor 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 world
atribuí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
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 observer
e Compile
para 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 Compile
para 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:
#app
Conteúdo analítico , os elementos{ {}}
ouv-model
no valor real atribuído e anexado a um nó virtual. Sobre Por quecreateDocumentFragment
criar 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
Watcher
instâ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)
}
}
})
}
})
})
}
observer
Na verdade, um ouvinte, aqui usado para monitorar alterações e dados computados e atualizar o dom de notificação, então aqui Object.defineProperty
está 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.
Dep
E o Watcher
có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 mounted
funçã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 this
podem 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
referência
- Implementar manualmente a vinculação de dados bidirecional simples mvvm
- A versão comentada em forma de tapete do exemplo online (os comentários que adicionei enquanto examinava o código acima)