首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Vue自定义指令使用更新的Dom (或$el)

Vue自定义指令使用更新的Dom (或$el)
EN

Stack Overflow用户
提问于 2018-05-30 00:35:22
回答 2查看 3.6K关注 0票数 1

我想为Dom树中的所有<strong>cx</strong>设计一个自定义指令,将“cx”替换为“TextNodes”。

以下是我迄今所做的尝试:

代码语言:javascript
复制
Vue.config.productionTip = false

function removeKeywords(el, keyword){
  if(!keyword) return
  let n = null
  let founds = []
  walk=document.createTreeWalker(el,NodeFilter.SHOW_TEXT,null,false)
  while(n=walk.nextNode()) {
    if(n.textContent.trim().length < 1) continue
    founds.push(n)
  }
  let result = []
  founds.forEach((item) => {
    if( new RegExp('cx', 'ig').test(item.textContent) ) {
      let kNode = document.createElement('span')
      kNode.innerHTML = item.textContent.replace(new RegExp('(.*?)(cx)(.*?)', 'ig'), '$1<strong>$2</strong>$3')
      item.parentNode.insertBefore(kNode, item)
      item.parentNode.removeChild(item)
    }
  })
}

let myDirective = {}
myDirective.install = function install(Vue) {
  let timeoutIDs = {}
  Vue.directive('keyword-highlight', {
    bind: function bind(el, binding, vnode) {
      clearTimeout(timeoutIDs[binding.value.id])
      if(!binding.value) return
      timeoutIDs[binding.value.id] = setTimeout(() => {
        removeKeywords(el, binding.value.keyword)
      }, 500)
    },
    componentUpdated: function componentUpdated(el, binding, vnode) {
      clearTimeout(timeoutIDs[binding.value.id])
      timeoutIDs[binding.value.id] = setTimeout(() => {
        removeKeywords(el, binding.value.keyword)
      }, 500)
    }
  });
};
Vue.use(myDirective)
app = new Vue({
  el: "#app",
  data: {
    keyword: 'abc',
    keyword1: 'xyz'
  },
  methods: {
  }
})
代码语言:javascript
复制
.header {
  background-color:red;
}

strong {
  background-color:yellow
}
代码语言:javascript
复制
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<div id="app">
  <input v-model="keyword">
  <input v-model="keyword1">
  <h1>Test Case 1: try to change 2nd input to <span class="header">anything</span></h1>
  <div v-keyword-highlight="{keyword:keyword, id:1}">
    <p>Test1<span>Test2</span>Test3<span>{{keyword}}{{keyword1}}</span></p>
  </div>
  <h1>Test Case 2 which is working</h1>
  <div :key="keyword+keyword1" v-keyword-highlight="{keyword:keyword, id:2}">
    <p>Test1<span>Test2</span>Test3<span>{{keyword}}{{keyword1}}</span></p>
  </div>
</div>

First Case:它应该是由已经被<span><strong></strong></span>替换的相关VNode引起的,因此不会正确地使用数据属性进行更新。

第二个案例:它按预期工作。解决方案是添加:key以强制挂载组件,因此当触发更新时,它将使用模板和最新的数据属性呈现,然后挂载。

但是我更喜欢强制安装指令钩子,而不是在组件上绑定:key,或者根据模板和最新的数据属性获得更新的Dom($el)。因此,任何想要使用此指令的人都不需要使用:key

非常感谢您的光临。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-05-30 03:54:42

我不确定这是最好的实践,因为有关于修改vnode的警告,但是在您的示例中,这可以动态地添加密钥

代码语言:javascript
复制
vnode.key = vnode.elm.innerText

奇怪的是,我注意到第一个指令响应componentUpdated,但第二个内部元素没有响应,尽管第二个内部元素更新了它们的值,但是第一个元素没有--这与您所期望的相反。

请注意,发生更改是因为第二个实例在输入更改时再次调用bind,而不是因为componentUpdated中的代码。

代码语言:javascript
复制
console.clear()
Vue.config.productionTip = false

function removeKeywords(el, keyword){
  console.log(el, keyword)
  if(!keyword) return
  let n = null
  let founds = []
  walk=document.createTreeWalker(el,NodeFilter.SHOW_TEXT,null,false)
  while(n=walk.nextNode()) {
    if(n.textContent.trim().length < 1) continue
    founds.push(n)
  }
  let result = []
  founds.forEach((item) => {
    if( new RegExp('cx', 'ig').test(item.textContent) ) {
      let kNode = document.createElement('span')
      kNode.innerHTML = item.textContent.replace(new RegExp('(.*?)(cx)(.*?)', 'ig'), '$1<strong>$2</strong>$3')
      item.parentNode.insertBefore(kNode, item)
      item.parentNode.removeChild(item)
    }
  })
}

let myDirective = {}
myDirective.install = function install(Vue) {
  let timeoutIDs = {}
  Vue.directive('keyword-highlight', {
    bind: function bind(el, binding, vnode) {
      console.log('bind', binding.value.id)
      clearTimeout(timeoutIDs[binding.value.id])
      if(!binding.value) return
      vnode.key = vnode.elm.innerText
      timeoutIDs[binding.value.id] = setTimeout(() => {
        removeKeywords(el, binding.value.keyword)
      }, 500)
    },
    componentUpdated: function componentUpdated(el, binding, vnode) {
      //clearTimeout(timeoutIDs[binding.value.id])
      //timeoutIDs[binding.value.id] = setTimeout(() => {
        //removeKeywords(el, binding.value.keyword)
      //}, 500)
    }
  });
};
Vue.use(myDirective)
app = new Vue({
  el: "#app",
  data: {
    keyword: 'abc',
    keyword1: 'xyz'
  },
  methods: {
  }
})
代码语言:javascript
复制
.header {
  background-color:red;
}

strong {
  background-color:yellow
}
代码语言:javascript
复制
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<div id="app">
  <input v-model="keyword">
  <input v-model="keyword1">
  <h1>Test Case 1: try to change 2nd input to <span class="header">anything</span></h1>
  <div v-keyword-highlight="{keyword:keyword, id:1}">
    <p>Test1<span>Test2</span>Test3<span>{{keyword}}{{keyword1}}</span></p>
  </div>
  <h1>Test Case 2 which is working</h1>
  <div :key="keyword+keyword1" v-keyword-highlight.keyword1="{keyword:keyword, id:2}">
    <p>Test1<span>Test2</span>Test3<span>{{keyword}}{{keyword1}}</span></p>
  </div>
</div>

票数 1
EN

Stack Overflow用户

发布于 2018-05-30 06:31:31

我发现Vue使用补丁来比较新旧节点,然后生成Dom元素。

检查Vue Github生命周期源代码,因此第一个元素可以是一个将被挂载的Dom对象。

因此,我按照步骤使用指令钩的第三个参数(bind、componentUpdated、update等)生成新的Dom元素,然后将其复制到指令钩子的第一个参数。

最后,下面的演示似乎是可行的:没有强制重装,只有重新编译VNodes。

PS:我使用deepClone方法克隆vnode,因为在函数__patch__(oldNode, newNode, hydrating)中,它将修改newNode

PS:正如Vue指令访问其实例所说,在指令的钩子内部,使用vnode.context访问实例。

编辑:循环test下的所有子程序,然后附加到el,简单地将test.innerHTML复制到el.innerHTML将导致一些问题,比如按钮不工作。

然后在我的实际项目(如<div v-keyword-highlight>very complicated template</div> )中测试这个指令,到目前为止,它运行得很好。

代码语言:javascript
复制
function deepClone (vnodes, createElement) {
  let clonedProperties = ['text', 'isComment', 'componentOptions', 'elm', 'context', 'ns', 'isStatic', 'key']
  function cloneVNode (vnode) {
    let clonedChildren = vnode.children && vnode.children.map(cloneVNode)
    let cloned = createElement(vnode.tag, vnode.data, clonedChildren)
    clonedProperties.forEach(function (item) {
      cloned[item] = vnode[item]
    })
    return cloned
  }
  return vnodes.map(cloneVNode)
}

function addStylesForKeywords(el, keyword){
  if(!keyword) return
  let n = null
  let founds = []
  walk=document.createTreeWalker(el,NodeFilter.SHOW_TEXT,null,false)
  while(n=walk.nextNode()) {
    if(n.textContent.trim().length < 1) continue
    founds.push(n)
  }
  let result = []
  founds.forEach((item) => {
    if( new RegExp('cx', 'ig').test(item.textContent) ) {
      let kNode = document.createElement('span')
      kNode.innerHTML = item.textContent.replace(new RegExp('(.*?)(cx)(.*?)', 'ig'), '$1<strong>$2</strong>$3')
      item.parentNode.insertBefore(kNode, item)
      item.parentNode.removeChild(item)
    }
  })
}

let myDirective = {}
myDirective.install = function install(Vue) {
  let timeoutIDs = {}
  let temp = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>'
  })
  let fakeVue = new temp()
  Vue.directive('keyword-highlight', {
    bind: function bind(el, binding, vnode) {
      clearTimeout(timeoutIDs[binding.value.id])
      if(!binding.value) return
      timeoutIDs[binding.value.id] = setTimeout(() => {
        addStylesForKeywords(el, binding.value.keyword)
      }, 500)
    },
    componentUpdated: function componentUpdated(el, binding, vnode) {
      let fakeELement = document.createElement('div')
      //vnode is readonly, but method=__patch__(orgNode, newNode) will load new dom into the second parameter=newNode.$el, so uses the cloned one instead
      let clonedNewNode = deepClone([vnode], vnode.context.$createElement)[0]
      let test = clonedNewNode.context.__patch__(fakeELement, clonedNewNode)

      while (el.firstChild) {
          el.removeChild(el.firstChild);
      }
      test.childNodes.forEach((item) => {
        el.appendChild(item)
      })
      clearTimeout(timeoutIDs[binding.value.id])
      timeoutIDs[binding.value.id] = setTimeout(() => {
        addStylesForKeywords(el, binding.value.keyword)
      }, 500)
    }
  });
};
Vue.use(myDirective)
Vue.config.productionTip = false
app = new Vue({
  el: "#app",
  data: {
    keyword: 'abc',
    keyword1: 'xyz'
  },
  methods: {
    changeData: function () {
      this.keyword += 'c'
      this.keyword1 = 'x' + this.keyword1
      console.log('test')
    }
  }
})
代码语言:javascript
复制
.header {
  background-color:red;
}

strong {
  background-color:yellow
}
代码语言:javascript
复制
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<script src="https://unpkg.com/lodash"></script>
<div id="app">
  <input v-model="keyword">
  <input v-model="keyword1">
  <h4>Test Case 3 <span class="header"></span></h4>
  <div v-keyword-highlight="{keyword:keyword, id:1}">
    <p>Test1<span>Test2</span>Test3<span>{{keyword}}{{keyword1}}</span></p>
    <button @click="changeData()">Click me</button>
  </div>
</div>

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50594818

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档