Vue常见问题

99 阅读4分钟

计算属性methods的区别

  • 计算属性的特性就是,只要其依赖值不变,那么就不会重新计算,因为它会有一个缓存,第一次调用结束后,它会将结果进行一次缓存,当再次调用时,会直接取缓存中的值。
  • methods中的方法每调用一次就会执行一次。
computed:{
    reverseMsg:function(){
        return this.message.split("").reverse().join("")
    }
}
methods:{
    reverseMessage:function(){
        return this.message.split("").reverse().join("")
    }
}
//第一种,js表达式,会计算三次
<p>{{message.split("").reverse().join("")}}<p>
<p>{{message.split("").reverse().join("")}}<p>
<p>{{message.split("").reverse().join("")}}<p>
//第二种,计算属性,计算一次
<p>{{reverseMsg}}<p>
<p>{{reverseMsg}}<p>
<p>{{reverseMsg}}<p>
//第三种,methods中的方法,计算三次
<p>{{reverseMessage()}}<p>
<p>{{reverseMessage()}}<p>
<p>{{reverseMessage()}}<p>

v-showv-if的区别

  • v-show始终会被渲染并保存在DOM中,它只是简单的切换元素的display属性。
  • v-if当后面为false,相应的元素及子元素不会被渲染,控制DOM元素的创建销毁
  • v-show适合频繁切换状态时使用,而v-if适合运行时很少使用的时候使用
  • v-if设置在div标签时,会显示一个div空标签,而子元素不会被显示,当设置在template上时,template不会显示。v-show不支持设置在template上。

v-for为什么要加key

为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素。

  • 首先我们先创建一个列表和一个按钮,按钮用于增加列表项,注意此时我们没有添加key属性。

image.png

  • 在页面中显示是这样的,点击后也成功在列表项前添加成功。

image.png image.png

  • 此时我们先将某一项选中后,再次点击增加,此时添加之后出现了一个问题,就是原本选中的是李四,而增加后选中的变为了张三,是因为它找不到它对应的列表项。

image.png image.png

  • key的作用就是添加一个唯一的标识,这时就应该提到一下diff算法,虚拟DOMdiff算法会对比操作之前和操作之后的同一层的节点。当在同一层有很多相似的节点时,例如li标签,此时diff算法会进行如下操作:

  • 我们此时想在BC之间插入F

image.png

  • diff算法会将C的位置更新成FD的位置更新成CE的位置更新成D,然后在末尾添加一个E

image.png

  • 但是这种方法很没有效率并且很消耗性能,此时我们就需要key值给它们添加一个唯一标识,也可以理解为起一个名字,有key值后,就如第二个图一样,aabb,在cd中间添加一个z。所以添加上key值方便我们快速找到节点,减少渲染次数,提升渲染性能。

image.png

v-model原理

v-model本质上是两个操作,第一个是通过v-bind绑定一个value属性,第二个是通过v-on给当前元素添加一个input事件。

  • 首先我们先给input标签添加一个动态的value属性,此时可以实现修改msg时输入框的内容上改变。
<input type="text" :value="msg" >
  • 其次我们给input标签绑定一个input事件,使得输入框内容改变时,修改msg的值,这样就实现了数据的双向绑定。
<input type="text" :value="msg" @input="changeMsg" >

组件中data为什么一定是一个函数,并且要返回一个对象

我们知道,函数是会有局部作用域的,它每一次执行这个函数返回出的对象都是一个全新的对象,而我们这个组件,又是个可复用的,也就是说复用多次的话,如果共享的是同一个数据的话,就会造成数据污染。

  • 首先创建好一个content组件,组件里data内设置一个msg。

image.png

  • 我们在另一个组件里调用三次,会显示如下结果。

image.png image.png

  • 在content组件中设置一个修改msg的点击事件,点击第一个按钮,显示如下结果。

image.png image.png

  • 可以看到只改变了第一个组件的msg值,因为每次调用content组建返回出的对象都是一个新的对象互不干扰。我们修改一下父组件,使其直接返回一个对象。

image.png image.png

  • 可以看到所有组件的msg值都发生了改变,因为我们直接返回了一个对象,三次调用组件调用的都是同一个对象,所以其中一个改变,所有的值都会改变。所以这就是data要是一个函数并且要返回一个对象的原因。

$router$route

  • $router是一个全局的路由容器,里面包含着很多的路由对象;$route$router容器中状态是active的那个路由对象。
  • $route是当前活跃的路由对象,可以获取当前页面的pathparamsqueryname等信息。
  • $router可以调用pushforwardgo等方法,对页面进行操作。

vite/vue-cli通过proxy解决跨域问题

例如当从localhost:3000访问https://i.maoyan.com/api/mmdb/movie/v3/list/hot.json?ct=%E5%8C%97%E4%BA%AC&ci=1&channelId=4时,由于违反了同源策略造成跨域问题,所以我们可以配置一个代理服务器来请求到数据后返回。

vite

具体操作方法在vite.config.js中的export default中添加server字段。

export default defineConfig({
    server:{//中转服务器
        proxy:{//通过代理解决跨域
            //https://i.maoyan.com
            '/path':{
                target:'https://i.maoyan.com',//替换的服务端地址
                changeOrigin:true,//开启代理,允许跨域
                rewrite:path=>path.replace(/^\/path/,'')//设置重写的路径
            }
        }
    }
})

此时只需要访问时修改下路径即可。

axios.get('/path/api/mmdb/movie/v3/list/hot.json?ct=%E5%8C%97%E4%BA%AC&ci=1&channelId=4').then((res)=>{
    console.log(res)
})

vue-cli

实现方法与vite类似,只不过某些字段名不同。

module.exports = {
    devServer: {
        // 代理配置
        proxy: {
            // 这里的api 表示如果我们的请求地址有/api的时候,就出触发代理机制
            // localhost:8888/api/abc  => 代理给另一个服务器
            // 本地的前端  =》 本地的后端  =》 代理我们向另一个服务器发请求 (行得通)
            // 本地的前端  =》 另外一个服务器发请求 (跨域 行不通)
            '/api': {
                target: 'www.baidu.com', // 我们要代理的地址
                changeOrigin: true, // 是否跨域 需要设置此值为true 才可以让本地服务代理我们发出请求
                // 路径重写
                pathRewrite: {
                    // 重新路由  localhost:8888/api/login  => www.baidu.com/api/login
                    '^/api','' // 假设我们想把 localhost:8888/api/login 变成www.baidu.com/login 就需要这么做 
                }
            },
        }
    }
}

v-forv-if为什么不建议在一起使用

  • vue2中,v-for的优先级是高于v-if,把它们放在一起,输出的渲染函数中可以看出会先执行循环再判断条件,哪怕我们只渲染列表中一小部分元素,也得在每次重渲染的时候遍历整个列表,这会比较浪费;
  • vue3中则完全相反,v-if的优先级高于v-for,所以v-if执行时,它调用的变量还不存在,就会导致异常。
  • 建议如果有这种需求时,先使用计算属性对数据进行过滤,再使用v-for进行循环遍历。

vue中如何使用自定义指令?

  • 指令:v-bindv-ifv-forv-model...不同的指令实现不同的功能。
  • 局部注册:直接在对应的组件下的options下定义属性directives,与data同级。
<script>
export default {
    data(){
        return {}
    }
    directives(){// 自定义指令,不需要写v-。
        focus:{
            inserted:function(el){// inserted表示被绑定元素插入父节点是调用,bind(第一次绑定时调用)、update...
                // el表示指令所绑定的元素
                // binding:对象,属性name:指令名,value:指令绑定值...
                el.focus()// 获取焦点
            }
        }
    }
}
</script>

<input type="text" v-focus><!-- 使用时需要写v-。 --!>
  • 全局注册:在main.js中定义Vue.directive
// Vue.directive("指令的名字", "对象数据也可以是一个指令函数")
Vue.directive("focus", {
    inserted:function(el){// inserted表示被绑定元素插入父节点是调用,bind(第一次绑定时调用)、update...
        // el表示指令所绑定的元素
        // binding:对象,属性name:指令名,value:指令绑定值...
        el.focus()// 获取焦点
    }
})

vue中什么是$nextTick?

理解:是将回调函数延迟在下一次dom更新数据之后调用

<template>
    <div>
        <div ref="msg">{{ msg }}</div>
        <p>{{ msg1 }}</p>
        <button @click="changeMsg">改变msg</button>
    </div>
</template>

<script>
    export default {
        data() {
            msg:"helloworld",
            msg1:""
        }
        methods: {
            changeMsg: function(){
                this.msg = "你好世界"
                this.msg1 = this.$refs.msg.innerHTML
            }
        }
    }
</script>

如果直接向上方这么写的话,在点击按钮后,msg1显示的仍是helloworld,因为vue是异步渲染的框架数据更新之后,dom是不会立刻渲染的。

image.png

此时有两种解决方式,一种是使用setTimeOut,另一种是使用this.$nextTick$nextTick会在dom渲染之后被触发,用来获取最新的dom节点。

methods: {
    changeMsg: function(){
        this.msg = "你好世界"
        setTimeOut(() => {
            this.msg1 = this.$refs.msg.innerHTML
        })
    }
}
methods: {
    changeMsg: function(){
        this.msg = "你好世界"
        this.$nextTick(() => {
            this.msg1 = this.$refs.msg.innerHTML
        })
    }
}

image.png

可以看到此时msg1显示的是你好世界

使用场景:

  1. 在生命周期钩子created中进行dom操作,一定要放到$nextTick函数中执行。
  2. 在数据变化后要执行某个操作,而这个操作需要使用随数据变化而变化的dom结构时,这个操作需要放到$nextTick中。