整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Vue 中 强制组件重新渲染的正确方法

Vue 中 强制组件重新渲染的正确方法

者:Michael Thiessen

译者:前端小智

来源:hackernoon

有时候,依赖 Vue 响应方式来更新数据是不够的,相反,我们需要手动重新渲染组件来更新数据。或者,我们可能只想抛开当前的DOM,重新开始。那么,如何让Vue以正确的方式重新呈现组件呢?

强制 Vue 重新渲染组件的最佳方法是在组件上设置:key。当我们需要重新渲染组件时,只需更 key 的值,Vue 就会重新渲染组件。

这是一个非常简单的解决方案。

当然,你可能会对其他方式会更感兴趣:

  • 简单粗暴的方式:重新加载整个页面
  • 不妥的方式:使用 v-if
  • 较好的方法:使用Vue的内置forceUpdate方法
  • 最好的方法:在组件上进行 key 更改

简单粗暴的方式:重新加载整个页面

这相当于每次你想关闭应用程序时都要重新启动你的电脑。

这种方式或许有用,但这是一个非常糟糕的解决方案,不要这样做,我们来看看更好的方法。

不妥的方式:使用 `v-if`

v-if指令,该指令仅在组件为true时才渲染。如果为false,则该组件在DOM中不存在。

来看看,v-if 是怎么工作的,在template中,添加v-if指令:

<template>??
  <my-component?v-if="renderComponent"?/>
</template>

在script 中,使用nextTick的方法

<script>??export?default?{????data()?{??????return?{????????renderComponent:?true,??????};????},????methods:?{??????forceRerender()?{????????//?从?DOM?中删除?my-component?组件????????this.renderComponent?=?false;????????this.$nextTick(()?=>?{??????????//?在?DOM?中添加?my-component?组件??????????this.renderComponent?=?true;????????});??????}????}??};</script>

上面的过程大致如下:

  1. 刚开始 renderComponent设置为true,因此渲染 my-component 组件
  2. 当我们调用forceRerender时,我们立即将renderComponent设置为false
  3. 我们停止渲染my-component,因为v-if指令现在计算结果为false
  4. 在nextTick方法中将renderComponent设置回true
  5. 当v-if指令的计算结果为true时,再次渲染my-component

在这个过程中,有两个部分比较重要

首先,我们必须等到nextTick,否则我们不会看到任何变化。

Vue中,一个 tick 是一个DOM更新周期。Vue将收集在同一 tick 中进行的所有更新,在 tick 结束时,它将根据这些更新来渲染 DOM 中的内容。如果我们不等到next tick,我们对renderComponent的更新就会自动取消,什么也不会改变。

其次,当我们第二次渲染时,Vue将创建一个全新的组件。Vue 将销毁第一个,并创建一个新的,这意味着我们的新my-component将像正常情况一样经历其所有生命周期-created,mounted等。

另外,nextTick 可以与 promise 一起使用:

forceRerender()?{??
  //?从?DOM?中删除?my-component?组件??
  this.renderComponent?=?false;??
  this.$nextTick().then(()?=>?{????
    this.renderComponent?=?true;??
  });
}

不过,这并不是一个很好的解决方案,所以,让我们做 Vue 想让我们做的

较好的方法:forceUpdate 方法

这是解决这个问题的两种最佳方法之一,这两种方法都得到了Vue的官方支持。

通常情况下,Vue 会通过更新视图来响应依赖项中的更改。然而,当我们调用forceUpdate时,也可以强制执行更新,即使所有依赖项实际上都没有改变。

下面是大多数人使用这种方法时所犯的最大错误。

如果 Vue 在事情发生变化时自动更新,为什么我们需要强制更新呢?

原因是有时候 Vue 的响应系统会让人感到困惑,我们认为Vue会对某个属性或变量的变化做出响应,但实际上并不是这样。在某些情况下,Vue的响应系统根本检测不到任何变化。

所以就像上一个方法,如果你需要这个来重新渲染你的组件,可能有一个更好的方法。

有两种不同的方法可以在组件实例本身和全局调用forceUpdate:

//?全局
import?Vue?from?'vue';Vue.forceUpdate();
//?使用组件实例
export?default?{??
  methods:?{????
    methodThatForcesUpdate()?{??????
      //?...??????
      this.$forceUpdate();??????
      //?...????
    }??
 }}

重要提示:这不会更新任何计算属性,调用forceUpdate仅仅强制重新渲染视图。

最好的方法:在组件上进行 `key` 更改

在许多情况下,我们需要重新渲染组件。

要正确地做到这一点,我们将提供一个key属性,以便 Vue 知道特定的组件与特定的数据片段相关联。如果key保持不变,则不会更改组件,但是如果key发生更改,Vue 就会知道应该删除旧组件并创建新组件。

正是我们需要的!

但是首先,我们需要绕一小段路来理解为什么在Vue中使用key。

为什么我们需要在 Vue 中使用 key

一旦你理解了这一点,那么这是了解如何以正确方式强制重新渲染的很小的一步。

假设我们要渲染具有以下一项或多项内容的组件列表:

  • 有本地的状态
  • 某种初始化过程,通常在created或mounted钩子中
  • 通过jQuery或普通api进行无响应的DOM操作

如果你对该列表进行排序或以任何其他方式对其进行更新,则需要重新渲染列表的某些部分。但是,不会希望重新渲染列表中的所有内容,而只是重新渲染已更改的内容。

为了帮助 Vue 跟踪已更改和未更改的内容,我们提供了一个key属性。在这里使用数组的索引,因为索引没有绑定到列表中的特定对象。

const?people?=?[??
  {?name:?'Evan',?age:?34?},??
  {?name:?'Sarah',?age:?98?},??
  {?name:?'James',?age:?45?}
];

如果我们使用索引将其渲染出来,则会得到以下结果:

<ul>??
  <li?v-for="(person,?index)?in?people"?:key="index">????
    {{?person.name?}}?-?{{?index?}}??
      </li>
</ul>
//?OutputsEvan?-?0Sarah?-?1James?-?2

如果删除Sarah,得到:

Evan?-?0James?-?1

与James关联的索引被更改,即使James仍然是James。James会被重新渲染,这并不是我们希望的。

所以这里,我们可以使用唯一的 id 来作为 key

const?people?=?[??
  {?id:?'this-is-an-id',?name:?'Evan',?age:?34?},??
  {?id:?'unique-id',?name:?'Sarah',?age:?98?},??
  {?id:?'another-unique-id',?name:?'James',?age:?45?},
];
<ul>??
  <li?v-for="person?in?people"?:key="person.id">????
    {{?person.name?}}?-?{{?person.id?}}??
  </li>
</ul>

在我们从列表中删除Sarah之前,Vue删除了Sarah和James的组件,然后为James创建了一个新组件。现在,Vue知道它可以为Evan和James保留这两个组件,它所要做的就是删除Sarah的。

如果我们向列表中添加一个person,Vue 还知道可以保留所有现有的组件,并且只需要创建一个新组件并将其插入正确的位置。这是非常有用的,当我们有更复杂的组件,它们有自己的状态,有初始化逻辑,或者做任何类型的DOM操作时,这对我们很有帮助。

所以接下来看看,如果使用最好的方法来重新渲染组件。

更改 key 以强制重新渲染组件

最后,这是强制Vue重新渲染组件的最佳方法(我认为)。

我们可以采用这种将key分配给子组件的策略,但是每次想重新渲染组件时,只需更新该key即可。

这是一个非常基本的方法

<template>??
  <component-to-re-render?:key="componentKey"?/>
</template>
export?default?{?
  data()?{????
    return?{??????
      componentKey:?0,????
    };??
  },??
  methods:?{????
    forceRerender()?{??????
      this.componentKey?+=?1;??????
    }??
  }
}

每次forceRerender被调用时,我们的componentKey都会改变。当这种情况发生时,Vue将知道它必须销毁组件并创建一个新组件。我们得到的是一个子组件,它将重新初始化自身并“重置”其状态。

如果确实需要重新渲染某些内容,请选择key更改方法而不是其他方法。


原文:https://hackernoon.com/the-correct-way-to-force-vue-to-re-render-a-component-bde2caae34ad

生JS事件绑定例题

1、开关灯示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #d1{
            height: 400px;
            width: 400px;
            border-radius: 50%;
        }
        .bg_green{background-color: green}
        .bg_red{background-color: red}
    </style>
</head>
<body>
    <div id="d1" class="c1 bg_red bg_green"></div>
    <button id="d2">变色</button>
<script>
    let btnEle=document.getElementById('d2')
    let divEle=document.getElementById('d1')
    btnEle.onclick=function () {          //点击时调用事件句柄
        divEle.classList.toggle('bg_red')   //有则删除,无则添加
    }
</script>
</body>
</html>

2、input框获取焦点示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<input type="text" value="hello world" id="d1">

<script>
    let iEle=document.getElementById('d1')  // 点击了后执行的操作(获取焦点)
    iEle.onfocus=function (){
        iEle.value=''  // 重新赋值为空
    }
    iEle.onblur=function (){  // 鼠标点击了其他地方执行的操作(移除焦点)
        iEle.value='goodbye'
    }
</script>
</body>
</html>

3、input框实时刷新时间实例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<input type="text" id="d1" style="display: block;height: 50px;width:200px">
<button id="d2">开始</button>
<button id="d3">结束</button>

<script>
    //4、创建一个变量t,用来存储定时器
    let t=null
    //1、 把要操作的标签生成对象
    let inputEle=document.getElementById('d1')
    let starEle=document.getElementById('d2')
    let endEle=document.getElementById('d3')
    //2、定义一个函数用于展示实时时间
    function showTime(){
        let time=new Date();  //  生成一个时间对象
        inputEle.value=time.toLocaleString()  //获取当前时间,并赋值给input标签
    }
    //3、给页面上的按钮绑定功能,一个开始,一个结束
    starEle.onclick=function (){
        if (!t){
            t=setInterval(showTime,1000)    //每点击一下就会生成一个定时器
        }
    }
    endEle.onclick=function (){
        clearInterval(t)    //只能清除一个定时器
        t=null
    }
</script>
</body>
</html>

4、省市联动

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<select name="" id="d1" >
    <option value="---请选择---" selected disabled>---请选择---</option>
</select>
<select name="" id="d2"></select>

<script>
    let proEle=document.getElementById('d1')
    let cityEle=document.getElementById('d2')
    //1、先模拟省市数据
    data={
        '江西':['南昌','九江','赣州','上饶'],
        '浙江':['杭州','义乌','绍兴','嘉兴'],
        '江苏':['南京','苏州','泰州','南通'],
        '广东':['广州','深圳','惠州','清远']
    };
    //2、for循获取到省
    for (let key in data){
        //2.1 将省信息做成一个个option标签,并添加到第一个select框中
        //1 创建option框
        let opEle=document.createElement('option')
        //2 设置value
        opEle.value=key
        //3 设置文本
        opEle.innerText=key
        //4 添加到第一个select中
        proEle.appendChild(opEle)
    }
    //3、设置文本域变化事件,使得再更换省的时候可以更新市
    proEle.onchange=function (){
        //先清空市select中所有的option
        cityEle.innerHTML=''
        //先获取到用户选择的省
        let getPro=proEle.value
        //获取对应的市信息
        let getCity=data[getPro]
        //添加默认选择标签
        let oppEle=document.createElement('option')
        oppEle.innerText='-请选择-'
        oppEle.setAttribute('selected','selected')
        oppEle.setAttribute('disabled','disabled')
        cityEle.appendChild(oppEle)
        //for循环所有的市,渲染到第二个select中
        for(let i=0;i<getCity.length;i++){
            //拿到市名
            let city=getCity[i]
            //创建标签
            let opEle=document.createElement('option')
            //设置文本
            opEle.innerText=city
            //设置值
            opEle.value=city
            //添加到第二个select中
            cityEle.appendChild(opEle)
        }
    }

</script>
</body>
</html>

jQuery

jQuery 是一个快速、小巧且功能强大的 JavaScript 库。它通过简化 JavaScript 编程,使开发人员能够更轻松地操作 HTML 文档、处理事件、执行动画效果以及处理 AJAX 请求等。

jQuery基本语法:

jQuery(选择器).action()
可以简写:jQuery()===$()

基本选择器:

1. 元素选择器:使用元素名称选择 HTML 元素。例如:`$("p")` 会选择所有的 `<p>` 元素。

2. ID 选择器:使用元素的 ID 属性选择指定的元素。例如:`$("#myElement")` 会选择具有 ID 属性为 "myElement" 的元素。

3. 类选择器:使用元素的类名选择指定的元素。例如:`$(".myClass")` 会选择具有类名为 "myClass" 的元素。

4.属性选择器:使用元素的属性选择指定的元素。例如:$("[name='myName']") 会选择具有 name 属性值为 "myName" 的元素。

通过jQuery选择器获取到的对象都是jQuery对象,如需使用JavaScript代码对其进行操作的话,则需要转换成标签对象。

$("#myElement")[0]	//转成标签对象

同样,标签对象也可转成jQuery对象:

$(document.getElementByid('#myElement')) //转成jQuery对象

组合分组嵌套选择器:

jQuery也支持组合选择器:

$('div#d1')	//选择具有 id 属性为 "d1" 的 <div> 元素。
$('div.c1')	//选择具有 class 属性为 "c1" 的 <div> 元素。
... ...
$('div span')	//后代选择器,用于选择 <div> 元素内的所有 <span> 元素。
$('div>span')	//子元素选择器,用于选择 <div> 元素的直接子元素中的 <span> 元素。
$('div+span')	//相邻元素选择器,用于选择紧接在 <div> 元素后面的第一个 <span> 元素。
$('div~span')	//兄弟元素选择器,用于选择所有在 <div> 元素后出现的同级别的 <span> 元素。

基本筛选器:

在jQuery中,筛选器可以根据不同的条件来选择匹配的元素,从而实现对元素的精确控制和操作。

jQuery提供了许多基本筛选器,用于选择匹配指定条件的元素。以下是一些常用的基本筛选器:

- `:first`:选择第一个匹配的元素。

- `:last`:选择最后一个匹配的元素。

- `:even`:选择索引为偶数的元素(从0开始计数)。

- `:odd`:选择索引为奇数的元素(从0开始计数)。

- `:eq(index)`:选择索引为指定值的元素。

- `:gt(index)`:选择索引大于指定值的元素。

- `:lt(index)`:选择索引小于指定值的元素。

- `:header`:选择所有标题元素(如`<h1>`、`<h2>`等)。

- `:not(selector)`:选择不匹配给定选择器的元素。

- `:has(selector)`:选择包含匹配给定选择器的元素。

- `:contains(text)`:选择包含指定文本内容的元素。

$('ul li') #ul下面的所有li子标签
$('ul li:first') #ul下面的第一个li子标签 $('ul li').first()
$('ul li:last') #ul下面的最后一个li子标签 $('ul li').last()
$('ul li:eq(2)') #放索引,根据索引取值
$('ul li:even') #偶数索引,0包含在内
$('ul li:odd') #奇数索引
$('ul li:gt(2)') #大于索引
$('ul li:lt(2)') #小于索引
$('div:has("p")') #选举出包含一个标签,或多个标签在内的标签(父级)
$('ul li:not("#d1")') # 移除所有满足条件的标签

这些基本筛选器可以与其他选择器和方法组合使用,以便更精确地选择和操作网页中的元素。

表单筛选器:

特殊情况

筛选器方法:

知道大家有没有这样的需求,用户自定义页面布局,把布局的html标签代码保存到数据库中,预览的时候根据保存的值,把标签代码渲染到页面中,当然普通JS / jQuery肯定没问题,下面小编介绍如何用Vue来实现。

在VUE中,把标准html标签输出到网页中利用v-html可以实现,但是自定义组件的标签是无法正确输出我们想要的结果,下面我介绍一种方法来实现这种需求。

  1. 首先创建一个自定义的组件,包含一个输入参数;
<template>
    <div>
        测试组件 {{ dataId }}
    </div>
</template>
<script>
export default {
    props: {
        dataId: {
            type: String,
            default: ''
        }
    }
}
  1. 建核心渲染标签组件,并把刚刚创建的自定义组件引入进去;
<script>
import Vue from "vue";
// 1.引入组件
var testCom=()=> import("./test-com");

export default {
  props: {
    html: {
      type: String,
      required: true
    }
  },

  render(h) {
    if (!this.html) {
      return false;
    }
    const com=Vue.extend({
      components: {
        "test-com": testCom
      },
      template: this.html
    });
    return h(com);
  }
};
</script>
  1. 创建测试组件测试
<template>
    <div>
        <comrender :html="htmltemplate" />
    </div>
</template>
<script>
import comrender from './render'

export default {
  components: {
    comrender: comrender
  },
  data() {
    return {
        htmltemplate: '<test-com dataId="001"></test-com>'
    }
  }
};
</script>

大功告成(可自行测试,如果想要完整源码可以私信我哦)!目前自定义组件可以通过字符串来渲染,并且可以传递简单的一个字符串参数。但是如果想要传递一个对象参数,目前是不行的,如果有此需求等待小编下次更新!